summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2022-06-22 09:53:13 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2022-06-22 10:23:17 +0000
commitc5dbcb143405a38088d78b4b760d64aaff5157ab (patch)
treeb37edca540b35f898e212bebfa6ded0806988122
parent774f54339e5db91f785733232d3950366db65d07 (diff)
downloadqtwebengine-chromium-c5dbcb143405a38088d78b4b760d64aaff5157ab.tar.gz
BASELINE: Update Chromium to 102.0.5005.137
Change-Id: I162cdc7f56760218868e000a4c8ea92573344036 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--chromium/DEPS8
-rw-r--r--chromium/base/bind_internal.h17
-rw-r--r--chromium/base/bind_unittest.cc28
-rw-r--r--chromium/base/bind_unittest.nc31
-rw-r--r--chromium/base/memory/raw_ptr.h31
-rw-r--r--chromium/base/memory/raw_scoped_refptr_mismatch_checker.h5
-rwxr-xr-xchromium/build/fuchsia/update_sdk.py8
-rwxr-xr-xchromium/build/mac_toolchain.py5
-rw-r--r--chromium/build/util/LASTCHANGE2
-rw-r--r--chromium/build/util/LASTCHANGE.committime2
-rw-r--r--chromium/cc/paint/paint_op_reader.cc4
-rw-r--r--chromium/cc/paint/paint_op_reader.h3
-rw-r--r--chromium/chrome/VERSION2
-rw-r--r--chromium/chrome/app/resources/chromium_strings_es-419.xtb2
-rw-r--r--chromium/chrome/app/resources/chromium_strings_eu.xtb4
-rw-r--r--chromium/chrome/app/resources/chromium_strings_fr.xtb2
-rw-r--r--chromium/chrome/app/resources/chromium_strings_mr.xtb8
-rw-r--r--chromium/chrome/app/resources/chromium_strings_no.xtb2
-rw-r--r--chromium/chrome/app/resources/chromium_strings_sr-Latn.xtb2
-rw-r--r--chromium/chrome/app/resources/chromium_strings_sr.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_af.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_ar.xtb25
-rw-r--r--chromium/chrome/app/resources/generated_resources_bs.xtb12
-rw-r--r--chromium/chrome/app/resources/generated_resources_ca.xtb6
-rw-r--r--chromium/chrome/app/resources/generated_resources_da.xtb20
-rw-r--r--chromium/chrome/app/resources/generated_resources_en-GB.xtb130
-rw-r--r--chromium/chrome/app/resources/generated_resources_es-419.xtb10
-rw-r--r--chromium/chrome/app/resources/generated_resources_es.xtb14
-rw-r--r--chromium/chrome/app/resources/generated_resources_eu.xtb12
-rw-r--r--chromium/chrome/app/resources/generated_resources_fa.xtb48
-rw-r--r--chromium/chrome/app/resources/generated_resources_fr-CA.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_fr.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_gl.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_hi.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_hr.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_id.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_is.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_it.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_iw.xtb18
-rw-r--r--chromium/chrome/app/resources/generated_resources_ja.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_kk.xtb6
-rw-r--r--chromium/chrome/app/resources/generated_resources_kn.xtb18
-rw-r--r--chromium/chrome/app/resources/generated_resources_ko.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_ky.xtb24
-rw-r--r--chromium/chrome/app/resources/generated_resources_mk.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_ml.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_mn.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_mr.xtb56
-rw-r--r--chromium/chrome/app/resources/generated_resources_my.xtb8
-rw-r--r--chromium/chrome/app/resources/generated_resources_ne.xtb8
-rw-r--r--chromium/chrome/app/resources/generated_resources_nl.xtb20
-rw-r--r--chromium/chrome/app/resources/generated_resources_no.xtb10
-rw-r--r--chromium/chrome/app/resources/generated_resources_pa.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_pl.xtb10
-rw-r--r--chromium/chrome/app/resources/generated_resources_pt-BR.xtb16
-rw-r--r--chromium/chrome/app/resources/generated_resources_pt-PT.xtb14
-rw-r--r--chromium/chrome/app/resources/generated_resources_ro.xtb8
-rw-r--r--chromium/chrome/app/resources/generated_resources_ru.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_sk.xtb6
-rw-r--r--chromium/chrome/app/resources/generated_resources_sl.xtb6
-rw-r--r--chromium/chrome/app/resources/generated_resources_sr-Latn.xtb10
-rw-r--r--chromium/chrome/app/resources/generated_resources_sr.xtb10
-rw-r--r--chromium/chrome/app/resources/generated_resources_te.xtb20
-rw-r--r--chromium/chrome/app/resources/generated_resources_th.xtb4
-rw-r--r--chromium/chrome/app/resources/generated_resources_uk.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_ur.xtb2
-rw-r--r--chromium/chrome/app/resources/generated_resources_vi.xtb8
-rw-r--r--chromium/chrome/app/resources/generated_resources_zh-CN.xtb4
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_ar.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_es.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_eu.xtb4
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_fr.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_gl.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_mr.xtb8
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_no.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_sr-Latn.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_sr.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_tr.xtb2
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_vi.xtb4
-rw-r--r--chromium/chrome/app/resources/google_chrome_strings_zh-CN.xtb2
-rw-r--r--chromium/chrome/browser/media/webrtc/region_capture_browsertest.cc131
-rw-r--r--chromium/chrome/browser/resources/print_preview/ui/destination_dialog.ts7
-rw-r--r--chromium/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts7
-rw-r--r--chromium/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html13
-rw-r--r--chromium/chrome/browser/signin/dice_web_signin_interceptor.cc9
-rw-r--r--chromium/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc53
-rw-r--r--chromium/chrome/browser/signin/signin_features.cc6
-rw-r--r--chromium/chrome/browser/signin/signin_features.h2
-rw-r--r--chromium/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc11
-rw-r--r--chromium/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler_unittest.cc26
-rw-r--r--chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc15
-rw-r--r--chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h4
-rw-r--r--chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc8
-rw-r--r--chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h15
-rw-r--r--chromium/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc61
-rw-r--r--chromium/components/browser_ui/strings/android/translations/browser_ui_strings_as.xtb2
-rw-r--r--chromium/components/browser_ui/strings/android/translations/browser_ui_strings_mr.xtb10
-rw-r--r--chromium/components/browser_ui/strings/android/translations/browser_ui_strings_pt-BR.xtb4
-rw-r--r--chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr-Latn.xtb2
-rw-r--r--chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr.xtb2
-rw-r--r--chromium/components/optimization_guide/core/optimization_guide_constants.cc7
-rw-r--r--chromium/components/optimization_guide/core/optimization_guide_constants.h11
-rw-r--r--chromium/components/permissions/android/translations/permissions_android_strings_sr-Latn.xtb2
-rw-r--r--chromium/components/permissions/android/translations/permissions_android_strings_sr.xtb2
-rw-r--r--chromium/components/policy/COMMON_METADATA5
-rw-r--r--chromium/components/policy/DIR_METADATA2
-rw-r--r--chromium/components/policy/ENTERPRISE_POLICY_OWNERS14
-rw-r--r--chromium/components/policy/OWNERS13
-rw-r--r--chromium/components/policy/android/DEPS3
-rw-r--r--chromium/components/policy/android/java_templates/PolicySwitches.java.tmpl16
-rw-r--r--chromium/components/policy/android/junit/src/org/chromium/components/policy/AbstractAppRestrictionsProviderTest.java211
-rw-r--r--chromium/components/policy/android/junit/src/org/chromium/components/policy/CombinedPolicyProviderTest.java222
-rw-r--r--chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheProviderTest.java70
-rw-r--r--chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java268
-rw-r--r--chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyConverterTest.java79
-rw-r--r--chromium/components/policy/content/DEPS17
-rw-r--r--chromium/components/policy/content/policy_blocklist_navigation_throttle.cc116
-rw-r--r--chromium/components/policy/content/policy_blocklist_navigation_throttle.h55
-rw-r--r--chromium/components/policy/content/policy_blocklist_navigation_throttle_unittest.cc448
-rw-r--r--chromium/components/policy/content/policy_blocklist_service.cc58
-rw-r--r--chromium/components/policy/content/policy_blocklist_service.h58
-rw-r--r--chromium/components/policy/content/safe_search_service.cc109
-rw-r--r--chromium/components/policy/content/safe_search_service.h71
-rw-r--r--chromium/components/policy/content/safe_sites_navigation_throttle.cc111
-rw-r--r--chromium/components/policy/content/safe_sites_navigation_throttle.h83
-rw-r--r--chromium/components/policy/core/DEPS11
-rw-r--r--chromium/components/policy/core/browser/DEPS18
-rw-r--r--chromium/components/policy/core/browser/android/policy_cache_updater_android.cc65
-rw-r--r--chromium/components/policy/core/browser/android/policy_cache_updater_android.h41
-rw-r--r--chromium/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc195
-rw-r--r--chromium/components/policy/core/browser/browser_policy_connector.cc209
-rw-r--r--chromium/components/policy/core/browser/browser_policy_connector.h108
-rw-r--r--chromium/components/policy/core/browser/browser_policy_connector_base.cc171
-rw-r--r--chromium/components/policy/core/browser/browser_policy_connector_base.h128
-rw-r--r--chromium/components/policy/core/browser/browser_policy_connector_unittest.cc49
-rw-r--r--chromium/components/policy/core/browser/cloud/message_util.cc158
-rw-r--r--chromium/components/policy/core/browser/cloud/message_util.h34
-rw-r--r--chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.cc350
-rw-r--r--chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.h225
-rw-r--r--chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.cc81
-rw-r--r--chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.h58
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler.cc730
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler.h531
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler_list.cc142
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler_list.h97
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler_list_unittest.cc206
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler_parameters.h22
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_handler_unittest.cc1092
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_pref_store.cc162
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_pref_store.h92
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_pref_store_test.cc57
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_pref_store_test.h51
-rw-r--r--chromium/components/policy/core/browser/configuration_policy_pref_store_unittest.cc209
-rw-r--r--chromium/components/policy/core/browser/policy_conversions.cc258
-rw-r--r--chromium/components/policy/core/browser/policy_conversions.h139
-rw-r--r--chromium/components/policy/core/browser/policy_conversions_client.cc448
-rw-r--r--chromium/components/policy/core/browser/policy_conversions_client.h190
-rw-r--r--chromium/components/policy/core/browser/policy_conversions_client_unittest.cc99
-rw-r--r--chromium/components/policy/core/browser/policy_error_map.cc301
-rw-r--r--chromium/components/policy/core/browser/policy_error_map.h130
-rw-r--r--chromium/components/policy/core/browser/policy_error_map_unittest.cc40
-rw-r--r--chromium/components/policy/core/browser/policy_pref_mapping_test.cc697
-rw-r--r--chromium/components/policy/core/browser/policy_pref_mapping_test.h40
-rw-r--r--chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.cc223
-rw-r--r--chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h97
-rw-r--r--chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher_unittest.cc156
-rw-r--r--chromium/components/policy/core/browser/url_allowlist_policy_handler.cc104
-rw-r--r--chromium/components/policy/core/browser/url_allowlist_policy_handler.h37
-rw-r--r--chromium/components/policy/core/browser/url_allowlist_policy_handler_unittest.cc178
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_manager.cc311
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_manager.h146
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_manager_unittest.cc649
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_policy_handler.cc142
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_policy_handler.h44
-rw-r--r--chromium/components/policy/core/browser/url_blocklist_policy_handler_unittest.cc317
-rw-r--r--chromium/components/policy/core/browser/url_scheme_list_policy_handler.cc93
-rw-r--r--chromium/components/policy/core/browser/url_scheme_list_policy_handler.h41
-rw-r--r--chromium/components/policy/core/browser/url_scheme_list_policy_handler_unittest.cc232
-rw-r--r--chromium/components/policy/core/browser/webui/json_generation.cc66
-rw-r--r--chromium/components/policy/core/browser/webui/json_generation.h78
-rw-r--r--chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc94
-rw-r--r--chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h56
-rw-r--r--chromium/components/policy/core/browser/webui/policy_status_provider.cc183
-rw-r--r--chromium/components/policy/core/browser/webui/policy_status_provider.h69
-rw-r--r--chromium/components/policy/core/common/DEPS18
-rw-r--r--chromium/components/policy/core/common/android/android_combined_policy_provider.cc73
-rw-r--r--chromium/components/policy/core/common/android/android_combined_policy_provider.h66
-rw-r--r--chromium/components/policy/core/common/android/android_combined_policy_provider_unittest.cc91
-rw-r--r--chromium/components/policy/core/common/android/policy_converter.cc208
-rw-r--r--chromium/components/policy/core/common/android/policy_converter.h95
-rw-r--r--chromium/components/policy/core/common/android/policy_converter_unittest.cc185
-rw-r--r--chromium/components/policy/core/common/android/policy_map_android.cc117
-rw-r--r--chromium/components/policy/core/common/android/policy_map_android.h79
-rw-r--r--chromium/components/policy/core/common/android/policy_map_android_unittest.cc105
-rw-r--r--chromium/components/policy/core/common/android/policy_service_android.cc75
-rw-r--r--chromium/components/policy/core/common/android/policy_service_android.h69
-rw-r--r--chromium/components/policy/core/common/android/policy_service_android_unittest.cc153
-rw-r--r--chromium/components/policy/core/common/async_policy_loader.cc205
-rw-r--r--chromium/components/policy/core/common/async_policy_loader.h159
-rw-r--r--chromium/components/policy/core/common/async_policy_provider.cc130
-rw-r--r--chromium/components/policy/core/common/async_policy_provider.h82
-rw-r--r--chromium/components/policy/core/common/async_policy_provider_unittest.cc229
-rw-r--r--chromium/components/policy/core/common/chrome_schema.cc19
-rw-r--r--chromium/components/policy/core/common/chrome_schema.h20
-rw-r--r--chromium/components/policy/core/common/cloud/DEPS4
-rw-r--r--chromium/components/policy/core/common/cloud/OWNERS3
-rw-r--r--chromium/components/policy/core/common/cloud/affiliation.cc18
-rw-r--r--chromium/components/policy/core/common/cloud/affiliation.h24
-rw-r--r--chromium/components/policy/core/common/cloud/affiliation_unittest.cc60
-rw-r--r--chromium/components/policy/core/common/cloud/chrome_browser_cloud_management_metrics.h22
-rw-r--r--chromium/components/policy/core/common/cloud/client_data_delegate.h31
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_external_data_manager.cc61
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_external_data_manager.h92
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_external_data_store.cc79
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_external_data_store.h78
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_external_data_store_unittest.cc206
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_client.cc1723
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_client.h912
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.cc212
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.h104
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_client_unittest.cc3012
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_constants.cc151
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_constants.h196
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_core.cc132
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_core.h149
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_core_unittest.cc151
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_manager.cc174
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_manager.h119
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_manager_unittest.cc356
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.cc447
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h189
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler_unittest.cc599
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_service.cc281
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_service.h165
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_service_unittest.cc332
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_store.cc125
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_store.h232
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_util.cc249
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_util.h60
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_validator.cc659
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_validator.h452
-rw-r--r--chromium/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc533
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_service.cc548
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_service.h198
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_service_observer.h34
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc52
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc687
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_store.cc466
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_store.h188
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc675
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_updater.cc119
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_updater.h67
-rw-r--r--chromium/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc522
-rw-r--r--chromium/components/policy/core/common/cloud/device_management_service.cc712
-rw-r--r--chromium/components/policy/core/common/cloud/device_management_service.h404
-rw-r--r--chromium/components/policy/core/common/cloud/device_management_service_unittest.cc1222
-rw-r--r--chromium/components/policy/core/common/cloud/dm_auth.cc52
-rw-r--r--chromium/components/policy/core/common/cloud/dm_auth.h91
-rw-r--r--chromium/components/policy/core/common/cloud/dm_token.cc46
-rw-r--r--chromium/components/policy/core/common/cloud/dm_token.h60
-rw-r--r--chromium/components/policy/core/common/cloud/dmserver_job_configurations.cc313
-rw-r--r--chromium/components/policy/core/common/cloud/dmserver_job_configurations.h117
-rw-r--r--chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc82
-rw-r--r--chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h109
-rw-r--r--chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc532
-rw-r--r--chromium/components/policy/core/common/cloud/enterprise_metrics.cc152
-rw-r--r--chromium/components/policy/core/common/cloud/enterprise_metrics.h278
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_fetcher.cc295
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_fetcher.h118
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_fetcher_unittest.cc449
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_updater.cc426
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_updater.h132
-rw-r--r--chromium/components/policy/core/common/cloud/external_policy_data_updater_unittest.cc863
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc128
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h68
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc66
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc249
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h89
-rw-r--r--chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc431
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.cc26
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.h43
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_client.cc58
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_client.h224
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_service.cc32
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_service.h44
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_store.cc19
-rw-r--r--chromium/components/policy/core/common/cloud/mock_cloud_policy_store.h48
-rw-r--r--chromium/components/policy/core/common/cloud/mock_device_management_service.cc294
-rw-r--r--chromium/components/policy/core/common/cloud/mock_device_management_service.h186
-rw-r--r--chromium/components/policy/core/common/cloud/mock_signing_service.cc46
-rw-r--r--chromium/components/policy/core/common/cloud/mock_signing_service.h48
-rw-r--r--chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc19
-rw-r--r--chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.h35
-rw-r--r--chromium/components/policy/core/common/cloud/policy_invalidation_scope.h21
-rw-r--r--chromium/components/policy/core/common/cloud/policy_value_validator.h41
-rw-r--r--chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc142
-rw-r--r--chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.h93
-rw-r--r--chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc332
-rw-r--r--chromium/components/policy/core/common/cloud/reporting_job_configuration_base.cc303
-rw-r--r--chromium/components/policy/core/common/cloud/reporting_job_configuration_base.h168
-rw-r--r--chromium/components/policy/core/common/cloud/resource_cache.cc315
-rw-r--r--chromium/components/policy/core/common/cloud/resource_cache.h145
-rw-r--r--chromium/components/policy/core/common/cloud/resource_cache_unittest.cc255
-rw-r--r--chromium/components/policy/core/common/cloud/signing_service.h32
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_manager.cc173
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_manager.h112
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc99
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_store.cc441
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_store.h204
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.cc78
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.h63
-rw-r--r--chromium/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc518
-rw-r--r--chromium/components/policy/core/common/cloud/user_info_fetcher.cc121
-rw-r--r--chromium/components/policy/core/common/cloud/user_info_fetcher.h69
-rw-r--r--chromium/components/policy/core/common/cloud/user_info_fetcher_unittest.cc95
-rw-r--r--chromium/components/policy/core/common/command_line_policy_provider.cc66
-rw-r--r--chromium/components/policy/core/common/command_line_policy_provider.h49
-rw-r--r--chromium/components/policy/core/common/command_line_policy_provider_unittest.cc87
-rw-r--r--chromium/components/policy/core/common/config_dir_policy_loader.cc242
-rw-r--r--chromium/components/policy/core/common/config_dir_policy_loader.h73
-rw-r--r--chromium/components/policy/core/common/config_dir_policy_loader_unittest.cc284
-rw-r--r--chromium/components/policy/core/common/configuration_policy_provider.cc91
-rw-r--r--chromium/components/policy/core/common/configuration_policy_provider.h117
-rw-r--r--chromium/components/policy/core/common/configuration_policy_provider_test.cc423
-rw-r--r--chromium/components/policy/core/common/configuration_policy_provider_test.h162
-rw-r--r--chromium/components/policy/core/common/default_chrome_apps_migrator.cc133
-rw-r--r--chromium/components/policy/core/common/default_chrome_apps_migrator.h68
-rw-r--r--chromium/components/policy/core/common/default_chrome_apps_migrator_unittest.cc240
-rw-r--r--chromium/components/policy/core/common/external_data_fetcher.cc53
-rw-r--r--chromium/components/policy/core/common/external_data_fetcher.h61
-rw-r--r--chromium/components/policy/core/common/external_data_manager.h40
-rw-r--r--chromium/components/policy/core/common/fake_async_policy_loader.cc37
-rw-r--r--chromium/components/policy/core/common/fake_async_policy_loader.h53
-rw-r--r--chromium/components/policy/core/common/features.cc40
-rw-r--r--chromium/components/policy/core/common/features.h50
-rw-r--r--chromium/components/policy/core/common/generate_policy_source_unittest.cc305
-rw-r--r--chromium/components/policy/core/common/json_schema_constants.cc31
-rw-r--r--chromium/components/policy/core/common/json_schema_constants.h35
-rw-r--r--chromium/components/policy/core/common/legacy_chrome_policy_migrator.cc34
-rw-r--r--chromium/components/policy/core/common/legacy_chrome_policy_migrator.h40
-rw-r--r--chromium/components/policy/core/common/legacy_chrome_policy_migrator_unittest.cc124
-rw-r--r--chromium/components/policy/core/common/mac_util.cc96
-rw-r--r--chromium/components/policy/core/common/mac_util.h33
-rw-r--r--chromium/components/policy/core/common/mac_util_unittest.cc62
-rw-r--r--chromium/components/policy/core/common/management/management_service.cc203
-rw-r--r--chromium/components/policy/core/common/management/management_service.h157
-rw-r--r--chromium/components/policy/core/common/management/management_service.md103
-rw-r--r--chromium/components/policy/core/common/management/management_service_unittest.cc168
-rw-r--r--chromium/components/policy/core/common/management/platform_management_service.cc104
-rw-r--r--chromium/components/policy/core/common/management/platform_management_service.h60
-rw-r--r--chromium/components/policy/core/common/management/platform_management_status_provider_mac.cc53
-rw-r--r--chromium/components/policy/core/common/management/platform_management_status_provider_mac.h41
-rw-r--r--chromium/components/policy/core/common/management/platform_management_status_provider_win.cc44
-rw-r--r--chromium/components/policy/core/common/management/platform_management_status_provider_win.h66
-rw-r--r--chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.cc28
-rw-r--r--chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.h39
-rw-r--r--chromium/components/policy/core/common/mock_configuration_policy_provider.cc69
-rw-r--r--chromium/components/policy/core/common/mock_configuration_policy_provider.h86
-rw-r--r--chromium/components/policy/core/common/mock_policy_service.cc23
-rw-r--r--chromium/components/policy/core/common/mock_policy_service.h59
-rw-r--r--chromium/components/policy/core/common/policy_bundle.cc106
-rw-r--r--chromium/components/policy/core/common/policy_bundle.h71
-rw-r--r--chromium/components/policy/core/common/policy_bundle_unittest.cc278
-rw-r--r--chromium/components/policy/core/common/policy_details.h51
-rw-r--r--chromium/components/policy/core/common/policy_load_status.cc42
-rw-r--r--chromium/components/policy/core/common/policy_load_status.h76
-rw-r--r--chromium/components/policy/core/common/policy_loader_command_line.cc48
-rw-r--r--chromium/components/policy/core/common/policy_loader_command_line.h37
-rw-r--r--chromium/components/policy/core/common/policy_loader_command_line_unittest.cc96
-rw-r--r--chromium/components/policy/core/common/policy_loader_common.cc184
-rw-r--r--chromium/components/policy/core/common/policy_loader_common.h20
-rw-r--r--chromium/components/policy/core/common/policy_loader_common_unittest.cc132
-rw-r--r--chromium/components/policy/core/common/policy_loader_ios.h64
-rw-r--r--chromium/components/policy/core/common/policy_loader_ios.mm175
-rw-r--r--chromium/components/policy/core/common/policy_loader_ios_constants.h13
-rw-r--r--chromium/components/policy/core/common/policy_loader_ios_constants.mm8
-rw-r--r--chromium/components/policy/core/common/policy_loader_ios_unittest.mm192
-rw-r--r--chromium/components/policy/core/common/policy_loader_lacros.cc221
-rw-r--r--chromium/components/policy/core/common/policy_loader_lacros.h92
-rw-r--r--chromium/components/policy/core/common/policy_loader_lacros_unittest.cc286
-rw-r--r--chromium/components/policy/core/common/policy_loader_mac.h94
-rw-r--r--chromium/components/policy/core/common/policy_loader_mac.mm254
-rw-r--r--chromium/components/policy/core/common/policy_loader_mac_unittest.cc201
-rw-r--r--chromium/components/policy/core/common/policy_loader_win.cc419
-rw-r--r--chromium/components/policy/core/common/policy_loader_win.h82
-rw-r--r--chromium/components/policy/core/common/policy_loader_win_unittest.cc744
-rw-r--r--chromium/components/policy/core/common/policy_map.cc662
-rw-r--r--chromium/components/policy/core/common/policy_map.h371
-rw-r--r--chromium/components/policy/core/common/policy_map_unittest.cc1807
-rw-r--r--chromium/components/policy/core/common/policy_merger.cc337
-rw-r--r--chromium/components/policy/core/common/policy_merger.h125
-rw-r--r--chromium/components/policy/core/common/policy_migrator.cc65
-rw-r--r--chromium/components/policy/core/common/policy_migrator.h60
-rw-r--r--chromium/components/policy/core/common/policy_namespace.cc43
-rw-r--r--chromium/components/policy/core/common/policy_namespace.h65
-rw-r--r--chromium/components/policy/core/common/policy_pref_names.cc114
-rw-r--r--chromium/components/policy/core/common/policy_pref_names.h49
-rw-r--r--chromium/components/policy/core/common/policy_proto_decoders.cc213
-rw-r--r--chromium/components/policy/core/common/policy_proto_decoders.h45
-rw-r--r--chromium/components/policy/core/common/policy_proto_decoders_unittest.cc259
-rw-r--r--chromium/components/policy/core/common/policy_scheduler.cc75
-rw-r--r--chromium/components/policy/core/common/policy_scheduler.h102
-rw-r--r--chromium/components/policy/core/common/policy_scheduler_unittest.cc133
-rw-r--r--chromium/components/policy/core/common/policy_service.cc44
-rw-r--r--chromium/components/policy/core/common/policy_service.h176
-rw-r--r--chromium/components/policy/core/common/policy_service_impl.cc479
-rw-r--r--chromium/components/policy/core/common/policy_service_impl.h189
-rw-r--r--chromium/components/policy/core/common/policy_service_impl_unittest.cc2248
-rw-r--r--chromium/components/policy/core/common/policy_service_stub.cc34
-rw-r--r--chromium/components/policy/core/common/policy_service_stub.h42
-rw-r--r--chromium/components/policy/core/common/policy_statistics_collector.cc128
-rw-r--r--chromium/components/policy/core/common/policy_statistics_collector.h78
-rw-r--r--chromium/components/policy/core/common/policy_statistics_collector_unittest.cc234
-rw-r--r--chromium/components/policy/core/common/policy_switches.cc34
-rw-r--r--chromium/components/policy/core/common/policy_switches.h28
-rw-r--r--chromium/components/policy/core/common/policy_test_utils.cc190
-rw-r--r--chromium/components/policy/core/common/policy_test_utils.h72
-rw-r--r--chromium/components/policy/core/common/policy_types.h114
-rw-r--r--chromium/components/policy/core/common/preferences_mac.cc19
-rw-r--r--chromium/components/policy/core/common/preferences_mac.h33
-rw-r--r--chromium/components/policy/core/common/preferences_mock_mac.cc47
-rw-r--r--chromium/components/policy/core/common/preferences_mock_mac.h33
-rw-r--r--chromium/components/policy/core/common/preg_parser.cc408
-rw-r--r--chromium/components/policy/core/common/preg_parser.h55
-rw-r--r--chromium/components/policy/core/common/preg_parser_fuzzer.cc46
-rw-r--r--chromium/components/policy/core/common/preg_parser_unittest.cc188
-rw-r--r--chromium/components/policy/core/common/proxy_policy_provider.cc72
-rw-r--r--chromium/components/policy/core/common/proxy_policy_provider.h72
-rw-r--r--chromium/components/policy/core/common/proxy_policy_provider_unittest.cc97
-rw-r--r--chromium/components/policy/core/common/proxy_settings_constants.cc11
-rw-r--r--chromium/components/policy/core/common/proxy_settings_constants.h15
-rw-r--r--chromium/components/policy/core/common/registry_dict.cc361
-rw-r--r--chromium/components/policy/core/common/registry_dict.h102
-rw-r--r--chromium/components/policy/core/common/registry_dict_unittest.cc390
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_command_job.cc167
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_command_job.h184
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_factory.cc12
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_factory.h32
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_queue.cc102
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_queue.h91
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_queue_unittest.cc338
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_service.cc418
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_service.h183
-rw-r--r--chromium/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc864
-rw-r--r--chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.cc79
-rw-r--r--chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h46
-rw-r--r--chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.cc188
-rw-r--r--chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h143
-rw-r--r--chromium/components/policy/core/common/schema.cc1655
-rw-r--r--chromium/components/policy/core/common/schema.h259
-rw-r--r--chromium/components/policy/core/common/schema_fuzzer.cc85
-rw-r--r--chromium/components/policy/core/common/schema_internal.h150
-rw-r--r--chromium/components/policy/core/common/schema_map.cc129
-rw-r--r--chromium/components/policy/core/common/schema_map.h72
-rw-r--r--chromium/components/policy/core/common/schema_map_unittest.cc385
-rw-r--r--chromium/components/policy/core/common/schema_registry.cc279
-rw-r--r--chromium/components/policy/core/common/schema_registry.h170
-rw-r--r--chromium/components/policy/core/common/schema_registry_tracking_policy_provider.cc108
-rw-r--r--chromium/components/policy/core/common/schema_registry_tracking_policy_provider.h95
-rw-r--r--chromium/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc240
-rw-r--r--chromium/components/policy/core/common/schema_registry_unittest.cc342
-rw-r--r--chromium/components/policy/core/common/schema_unittest.cc1592
-rw-r--r--chromium/components/policy/core/common/values_util.cc28
-rw-r--r--chromium/components/policy/core/common/values_util.h23
-rw-r--r--chromium/components/policy/core/common/values_util_unittest.cc57
-rw-r--r--chromium/components/policy/policy_export.h34
-rw-r--r--chromium/components/policy/proto/README.md36
-rw-r--r--chromium/components/policy/proto/chrome_device_policy.proto1873
-rw-r--r--chromium/components/policy/proto/chrome_extension_policy.proto26
-rw-r--r--chromium/components/policy/proto/device_management_backend.proto4394
-rw-r--r--chromium/components/policy/proto/install_attributes.proto20
-rw-r--r--chromium/components/policy/proto/policy_common_definitions.proto50
-rw-r--r--chromium/components/policy/proto/policy_proto_export.h48
-rw-r--r--chromium/components/policy/proto/policy_signing_key.proto26
-rw-r--r--chromium/components/policy/proto/secure_connect.proto18
-rw-r--r--chromium/components/policy/test_support/.style.yapf4
-rw-r--r--chromium/components/policy/test_support/DEPS16
-rw-r--r--chromium/components/policy/test_support/bootstrap_deps16
-rw-r--r--chromium/components/policy/test_support/client_storage.cc112
-rw-r--r--chromium/components/policy/test_support/client_storage.h81
-rw-r--r--chromium/components/policy/test_support/client_storage_unittest.cc84
-rw-r--r--chromium/components/policy/test_support/embedded_policy_test_server.cc215
-rw-r--r--chromium/components/policy/test_support/embedded_policy_test_server.h114
-rw-r--r--chromium/components/policy/test_support/embedded_policy_test_server_test_base.cc181
-rw-r--r--chromium/components/policy/test_support/embedded_policy_test_server_test_base.h91
-rw-r--r--chromium/components/policy/test_support/embedded_policy_test_server_unittest.cc141
-rw-r--r--chromium/components/policy/test_support/failing_request_handler.cc34
-rw-r--r--chromium/components/policy/test_support/failing_request_handler.h36
-rw-r--r--chromium/components/policy/test_support/failing_request_handler_unittest.cc47
-rw-r--r--chromium/components/policy/test_support/policy_storage.cc128
-rw-r--r--chromium/components/policy/test_support/policy_storage.h200
-rw-r--r--chromium/components/policy/test_support/policy_storage_unittest.cc34
-rw-r--r--chromium/components/policy/test_support/request_handler_for_api_authorization.cc44
-rw-r--r--chromium/components/policy/test_support/request_handler_for_api_authorization.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_api_authorization_unittest.cc52
-rw-r--r--chromium/components/policy/test_support/request_handler_for_auto_enrollment.cc83
-rw-r--r--chromium/components/policy/test_support/request_handler_for_auto_enrollment.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_auto_enrollment_unittest.cc148
-rw-r--r--chromium/components/policy/test_support/request_handler_for_check_android_management.cc56
-rw-r--r--chromium/components/policy/test_support/request_handler_for_check_android_management.h37
-rw-r--r--chromium/components/policy/test_support/request_handler_for_check_android_management_unittest.cc66
-rw-r--r--chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.cc47
-rw-r--r--chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_chrome_desktop_report_unittest.cc49
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update.cc45
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.cc55
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.h33
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission_unittest.cc79
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_attribute_update_unittest.cc56
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.cc58
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state_unittest.cc77
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_state_retrieval.cc62
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_state_retrieval.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_device_state_retrieval_unittest.cc75
-rw-r--r--chromium/components/policy/test_support/request_handler_for_policy.cc246
-rw-r--r--chromium/components/policy/test_support/request_handler_for_policy.h60
-rw-r--r--chromium/components/policy/test_support/request_handler_for_policy_unittest.cc435
-rw-r--r--chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.cc94
-rw-r--r--chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.h37
-rw-r--r--chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment_unittest.cc154
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_browser.cc83
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_browser.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_browser_unittest.cc144
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_cert_based.cc66
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_cert_based.h33
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_cert_based_unittest.cc123
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_device_and_user.cc195
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_device_and_user.h42
-rw-r--r--chromium/components/policy/test_support/request_handler_for_register_device_and_user_unittest.cc244
-rw-r--r--chromium/components/policy/test_support/request_handler_for_remote_commands.cc42
-rw-r--r--chromium/components/policy/test_support/request_handler_for_remote_commands.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_remote_commands_unittest.cc48
-rw-r--r--chromium/components/policy/test_support/request_handler_for_status_upload.cc43
-rw-r--r--chromium/components/policy/test_support/request_handler_for_status_upload.h32
-rw-r--r--chromium/components/policy/test_support/request_handler_for_status_upload_unittest.cc49
-rw-r--r--chromium/components/policy/test_support/request_handler_for_unregister.cc48
-rw-r--r--chromium/components/policy/test_support/request_handler_for_unregister.h31
-rw-r--r--chromium/components/policy/test_support/request_handler_for_unregister_unittest.cc73
-rw-r--r--chromium/components/policy/test_support/signature_provider.cc217
-rw-r--r--chromium/components/policy/test_support/signature_provider.h100
-rw-r--r--chromium/components/policy/test_support/signature_provider_unittest.cc65
-rw-r--r--chromium/components/policy/test_support/test_server_helpers.cc101
-rw-r--r--chromium/components/policy/test_support/test_server_helpers.h69
-rw-r--r--chromium/components/policy/tools/.style.yapf6
-rw-r--r--chromium/components/policy/tools/OWNERS9
-rw-r--r--chromium/components/policy/tools/PRESUBMIT.py47
-rwxr-xr-xchromium/components/policy/tools/generate_extension_admx.py404
-rwxr-xr-xchromium/components/policy/tools/generate_policy_source.py1897
-rwxr-xr-xchromium/components/policy/tools/generate_policy_source_test.py473
-rw-r--r--chromium/components/policy/tools/generate_policy_source_test_data.py937
-rwxr-xr-xchromium/components/policy/tools/make_policy_zip.py55
-rw-r--r--chromium/components/policy/tools/schema_validator.py541
-rwxr-xr-xchromium/components/policy/tools/syntax_check_policy_template_json.py2021
-rwxr-xr-xchromium/components/policy/tools/template_writers/PRESUBMIT.py25
-rwxr-xr-xchromium/components/policy/tools/template_writers/__init__.py8
-rwxr-xr-xchromium/components/policy/tools/template_writers/policy_template_generator.py321
-rwxr-xr-xchromium/components/policy/tools/template_writers/policy_template_generator_unittest.py654
-rwxr-xr-xchromium/components/policy/tools/template_writers/template_formatter.py274
-rwxr-xr-xchromium/components/policy/tools/template_writers/test_suite_all.py65
-rwxr-xr-xchromium/components/policy/tools/template_writers/writer_configuration.py133
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/__init__.py8
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/adm_writer.py314
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py1470
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/adml_writer.py266
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py521
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/admx_writer.py500
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py700
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/android_policy_writer.py109
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py104
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py33
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py109
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py38
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py151
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py96
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/doc_writer.py883
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py1835
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/google_adml_writer.py36
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py24
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/google_admx_writer.py36
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py24
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py128
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py202
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py434
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/jamf_writer.py239
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py383
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/json_writer.py93
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/json_writer_unittest.py460
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/mock_writer.py29
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/plist_helper.py13
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/plist_strings_writer.py78
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py402
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/plist_writer.py156
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py732
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/reg_writer.py108
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py428
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/template_writer.py468
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/template_writer_unittest.py287
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/writer_unittest_common.py41
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py90
-rwxr-xr-xchromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py37
-rw-r--r--chromium/components/strings/components_strings_af.xtb2
-rw-r--r--chromium/components/strings/components_strings_ar.xtb3
-rw-r--r--chromium/components/strings/components_strings_as.xtb2
-rw-r--r--chromium/components/strings/components_strings_da.xtb2
-rw-r--r--chromium/components/strings/components_strings_de.xtb2
-rw-r--r--chromium/components/strings/components_strings_en-GB.xtb32
-rw-r--r--chromium/components/strings/components_strings_eu.xtb8
-rw-r--r--chromium/components/strings/components_strings_fa.xtb26
-rw-r--r--chromium/components/strings/components_strings_iw.xtb2
-rw-r--r--chromium/components/strings/components_strings_kn.xtb8
-rw-r--r--chromium/components/strings/components_strings_ky.xtb14
-rw-r--r--chromium/components/strings/components_strings_mr.xtb14
-rw-r--r--chromium/components/strings/components_strings_ne.xtb2
-rw-r--r--chromium/components/strings/components_strings_no.xtb10
-rw-r--r--chromium/components/strings/components_strings_pt-PT.xtb18
-rw-r--r--chromium/components/strings/components_strings_sr-Latn.xtb2
-rw-r--r--chromium/components/strings/components_strings_sr.xtb2
-rw-r--r--chromium/components/strings/components_strings_te.xtb4
-rw-r--r--chromium/components/strings/components_strings_vi.xtb4
-rw-r--r--chromium/components/variations/service/variations_field_trial_creator.cc3
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_context_impl.cc56
-rw-r--r--chromium/content/browser/indexed_db/indexed_db_context_impl.h10
-rw-r--r--chromium/content/browser/loader/navigation_url_loader_impl.cc5
-rw-r--r--chromium/content/browser/network_service_instance_impl.cc33
-rw-r--r--chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc44
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.cc25
-rw-r--r--chromium/content/browser/renderer_host/media/video_capture_manager.h9
-rw-r--r--chromium/content/public/common/content_features.cc2
-rw-r--r--chromium/content/renderer/media/media_factory.cc6
-rw-r--r--chromium/content/renderer/media/render_media_client.cc81
-rw-r--r--chromium/content/renderer/media/render_media_client.h12
-rw-r--r--chromium/content/shell/DEPS57
-rw-r--r--chromium/content/shell/DIR_METADATA3
-rw-r--r--chromium/content/shell/OWNERS4
-rw-r--r--chromium/content/shell/app/DEPS11
-rw-r--r--chromium/content/shell/app/app-Info.plist32
-rw-r--r--chromium/content/shell/app/app.icnsbin0 -> 55080 bytes
-rw-r--r--chromium/content/shell/app/framework-Info.plist18
-rw-r--r--chromium/content/shell/app/helper-Info.plist28
-rw-r--r--chromium/content/shell/app/paths_mac.h30
-rw-r--r--chromium/content/shell/app/paths_mac.mm96
-rw-r--r--chromium/content/shell/app/resource.h41
-rw-r--r--chromium/content/shell/app/shell.rc146
-rw-r--r--chromium/content/shell/app/shell_content_main.cc29
-rw-r--r--chromium/content/shell/app/shell_content_main.h18
-rw-r--r--chromium/content/shell/app/shell_crash_reporter_client.cc95
-rw-r--r--chromium/content/shell/app/shell_crash_reporter_client.h61
-rw-r--r--chromium/content/shell/app/shell_main.cc46
-rw-r--r--chromium/content/shell/app/shell_main_delegate.cc381
-rw-r--r--chromium/content/shell/app/shell_main_delegate.h77
-rw-r--r--chromium/content/shell/app/shell_main_delegate_mac.h22
-rw-r--r--chromium/content/shell/app/shell_main_delegate_mac.mm72
-rw-r--r--chromium/content/shell/app/shell_main_mac.cc102
-rw-r--r--chromium/content/shell/fuchsia/DIR_METADATA2
-rw-r--r--chromium/content/shell/fuchsia/OWNERS4
-rw-r--r--chromium/content/shell/fuchsia/content_shell.cmx42
-rw-r--r--chromium/content/shell/gpu/DEPS4
-rw-r--r--chromium/content/shell/gpu/shell_content_gpu_client.cc26
-rw-r--r--chromium/content/shell/gpu/shell_content_gpu_client.h31
-rw-r--r--chromium/content/shell/renderer/DEPS4
-rw-r--r--chromium/content/shell/renderer/shell_content_renderer_client.cc204
-rw-r--r--chromium/content/shell/renderer/shell_content_renderer_client.h59
-rw-r--r--chromium/content/shell/renderer/shell_render_frame_observer.cc32
-rw-r--r--chromium/content/shell/renderer/shell_render_frame_observer.h28
-rw-r--r--chromium/content/shell/resources/README.txt26
-rw-r--r--chromium/content/shell/resources/brokenCanvas.pngbin0 -> 289 bytes
-rw-r--r--chromium/content/shell/resources/shell_devtools_discovery_page.html54
-rw-r--r--chromium/content/shell/tools/DEPS4
-rwxr-xr-xchromium/content/shell/tools/breakpad_integration_test.py331
-rw-r--r--chromium/content/shell/utility/DEPS5
-rw-r--r--chromium/content/shell/utility/shell_content_utility_client.cc166
-rw-r--r--chromium/content/shell/utility/shell_content_utility_client.h38
-rw-r--r--chromium/device/bluetooth/bluetooth_adapter_unittest.cc3
-rw-r--r--chromium/device/bluetooth/strings/bluetooth_strings_kn.xtb4
-rw-r--r--chromium/fuchsia/engine/renderer/web_engine_audio_renderer.cc9
-rw-r--r--chromium/fuchsia/engine/renderer/web_engine_audio_renderer.h7
-rw-r--r--chromium/fuchsia/engine/renderer/web_engine_audio_renderer_test.cc9
-rw-r--r--chromium/gpu/config/gpu_lists_version.h2
-rw-r--r--chromium/infra/config/generated/builders/ci/Android Release (Nexus 5X)/properties.json53
-rw-r--r--chromium/infra/config/generated/builders/ci/Android x86 Builder (dbg)/properties.json49
-rw-r--r--chromium/infra/config/generated/builders/ci/Cast Android (dbg)/properties.json52
-rw-r--r--chromium/infra/config/generated/builders/ci/Cast Linux Debug/properties.json47
-rw-r--r--chromium/infra/config/generated/builders/ci/Cast Linux/properties.json47
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Builder/properties.json121
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (Intel HD 630)/properties.json79
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (NVIDIA)/properties.json79
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Builder/properties.json118
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (AMD)/properties.json76
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (Intel)/properties.json76
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Builder/properties.json118
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (Intel HD 630)/properties.json76
-rw-r--r--chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (NVIDIA)/properties.json76
-rw-r--r--chromium/infra/config/generated/builders/ci/Linux Builder (Wayland)/properties.json87
-rw-r--r--chromium/infra/config/generated/builders/ci/Linux TSan Builder/properties.json85
-rw-r--r--chromium/infra/config/generated/builders/ci/Linux TSan Tests/properties.json78
-rw-r--r--chromium/infra/config/generated/builders/ci/Linux Tests (Wayland)/properties.json80
-rw-r--r--chromium/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json53
-rw-r--r--chromium/infra/config/generated/builders/ci/android-marshmallow-arm64-rel/properties.json53
-rw-r--r--chromium/infra/config/generated/builders/ci/android-marshmallow-x86-rel/properties.json53
-rw-r--r--chromium/infra/config/generated/builders/ci/android-pie-arm64-rel/properties.json52
-rw-r--r--chromium/infra/config/generated/builders/ci/lacros-amd64-generic-rel/properties.json56
-rw-r--r--chromium/infra/config/generated/builders/ci/linux-chromeos-dbg/properties.json52
-rw-r--r--chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel-compilator/properties.json84
-rw-r--r--chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel/properties.json84
-rw-r--r--chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel-compilator/properties.json50
-rw-r--r--chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel/properties.json50
-rw-r--r--chromium/infra/config/generated/builders/try/android-pie-arm64-rel-compilator/properties.json49
-rw-r--r--chromium/infra/config/generated/builders/try/android-pie-arm64-rel/properties.json49
-rw-r--r--chromium/infra/config/generated/builders/try/android_compile_x86_dbg/properties.json44
-rw-r--r--chromium/infra/config/generated/builders/try/android_cronet/properties.json48
-rw-r--r--chromium/infra/config/generated/builders/try/cast_shell_android/properties.json46
-rw-r--r--chromium/infra/config/generated/builders/try/cast_shell_linux/properties.json41
-rw-r--r--chromium/infra/config/generated/builders/try/cast_shell_linux_dbg/properties.json41
-rw-r--r--chromium/infra/config/generated/builders/try/dawn-linux-x64-deps-rel/properties.json115
-rw-r--r--chromium/infra/config/generated/builders/try/dawn-mac-x64-deps-rel/properties.json112
-rw-r--r--chromium/infra/config/generated/builders/try/dawn-win10-x64-deps-rel/properties.json112
-rw-r--r--chromium/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json50
-rw-r--r--chromium/infra/config/generated/builders/try/linux-chromeos-compile-dbg/properties.json43
-rw-r--r--chromium/infra/config/generated/builders/try/linux-chromeos-dbg/properties.json42
-rw-r--r--chromium/infra/config/generated/builders/try/linux-wayland-rel/properties.json84
-rw-r--r--chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng-compilator/properties.json82
-rw-r--r--chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/properties.json82
-rw-r--r--chromium/infra/config/generated/luci/cr-buildbucket.cfg116
-rw-r--r--chromium/infra/config/lib/args.star2
-rw-r--r--chromium/infra/config/lib/builders.star25
-rw-r--r--chromium/infra/config/lib/ci.star4
-rw-r--r--chromium/infra/config/lib/consoles.star11
-rw-r--r--chromium/infra/config/outages/outages.star2
-rw-r--r--chromium/infra/config/subprojects/chromium/ci.star3
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.android.star132
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.chromiumos.star44
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.dawn.star153
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.gpu.star23
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.linux.star71
-rw-r--r--chromium/infra/config/subprojects/chromium/ci/chromium.memory.star35
-rw-r--r--chromium/infra/config/subprojects/chromium/gpu.try.star3
-rw-r--r--chromium/infra/config/subprojects/chromium/try/tryserver.chromium.android.star36
-rw-r--r--chromium/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star19
-rw-r--r--chromium/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star15
-rw-r--r--chromium/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star30
-rw-r--r--chromium/infra/config/subprojects/flakiness/flakiness.star3
-rw-r--r--chromium/infra/config/subprojects/goma/goma.star3
-rw-r--r--chromium/infra/config/subprojects/reclient/reclient.star3
-rw-r--r--chromium/ios/chrome/browser/ui/settings/password/BUILD.gn1
-rw-r--r--chromium/media/base/supported_types.cc1
-rw-r--r--chromium/media/fuchsia/common/vmo_buffer_writer_queue.cc10
-rw-r--r--chromium/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc19
-rw-r--r--chromium/net/http/transport_security_state_static.json.gzbin1353766 -> 1353766 bytes
-rw-r--r--chromium/services/network/public/cpp/features.cc6
-rw-r--r--chromium/services/network/public/cpp/features.h3
-rw-r--r--chromium/services/network/url_loader.cc3
-rw-r--r--chromium/services/network/url_loader_unittest.cc3
-rw-r--r--chromium/storage/browser/quota/quota_manager_proxy.cc90
-rw-r--r--chromium/third_party/angle/src/libANGLE/Context.cpp1
-rw-r--r--chromium/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp11
-rw-r--r--chromium/third_party/angle/src/libANGLE/validationEGL.cpp2
-rw-r--r--chromium/third_party/angle/src/libGLESv2/egl_stubs.cpp19
-rw-r--r--chromium/third_party/blink/common/features.cc2
-rw-r--r--chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_fa.xtb2
-rw-r--r--chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_pt-PT.xtb2
-rw-r--r--chromium/third_party/blink/public/strings/translations/blink_strings_fa.xtb4
-rw-r--r--chromium/third_party/blink/public/strings/translations/blink_strings_id.xtb2
-rw-r--r--chromium/third_party/blink/public/strings/translations/blink_strings_kn.xtb2
-rw-r--r--chromium/third_party/blink/renderer/core/css/css_property_value_set.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc48
-rw-r--r--chromium/third_party/blink/renderer/core/css/style_engine.cc5
-rw-r--r--chromium/third_party/blink/renderer/core/frame/local_frame_view.cc3
-rw-r--r--chromium/third_party/blink/renderer/core/layout/layout_box.cc6
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc6
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h11
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc34
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc4
-rw-r--r--chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h8
-rw-r--r--chromium/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc3
-rw-r--r--chromium/third_party/crashpad/README.chromium4
-rw-r--r--chromium/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc17
-rw-r--r--chromium/third_party/crashpad/crashpad/snapshot/BUILD.gn1
-rw-r--r--chromium/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc38
-rw-r--r--chromium/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_reader.cc6
-rw-r--r--chromium/third_party/node/node_modules.tar.gz.sha12
-rw-r--r--chromium/third_party/node/node_modules/@types/d3-drag/index.d.ts406
-rwxr-xr-xchromium/third_party/node/node_modules/@types/d3-force/index.d.ts1249
-rw-r--r--chromium/third_party/node/node_modules/@types/d3-scale-chromatic/index.d.ts525
-rw-r--r--chromium/third_party/node/node_modules/@types/d3-selection/index.d.ts1256
-rwxr-xr-xchromium/third_party/node/node_modules/@types/d3-transition/index.d.ts643
-rw-r--r--chromium/third_party/node/node_modules/@types/d3/index.d.ts27
-rw-r--r--chromium/third_party/node/node_modules/@types/trusted-types/index.d.ts75
-rw-r--r--chromium/third_party/webrtc/modules/desktop_capture/desktop_frame.cc20
-rw-r--r--chromium/tools/metrics/histograms/enums.xml1
-rw-r--r--chromium/ui/base/ime/win/tsf_text_store.cc28
-rw-r--r--chromium/ui/base/ime/win/tsf_text_store.h5
-rw-r--r--chromium/ui/events/gestures/gesture_recognizer_impl.cc9
-rw-r--r--chromium/ui/gfx/x/window_cache.cc4
-rw-r--r--chromium/ui/gfx/x/window_cache_unittest.cc15
-rw-r--r--chromium/ui/ozone/platform/wayland/host/wayland_screen.cc10
-rw-r--r--chromium/ui/ozone/platform/wayland/host/wayland_surface.cc32
-rw-r--r--chromium/ui/ozone/platform/wayland/host/wayland_window.cc16
-rw-r--r--chromium/ui/ozone/platform/wayland/host/wayland_window.h9
-rw-r--r--chromium/ui/strings/translations/ax_strings_te.xtb2
-rw-r--r--chromium/ui/strings/translations/ui_strings_fa.xtb2
-rw-r--r--chromium/v8/include/v8-version.h2
-rw-r--r--chromium/v8/src/builtins/arm/builtins-arm.cc23
-rw-r--r--chromium/v8/src/builtins/arm64/builtins-arm64.cc81
-rw-r--r--chromium/v8/src/builtins/ia32/builtins-ia32.cc27
-rw-r--r--chromium/v8/src/builtins/loong64/builtins-loong64.cc73
-rw-r--r--chromium/v8/src/builtins/mips/builtins-mips.cc68
-rw-r--r--chromium/v8/src/builtins/mips64/builtins-mips64.cc76
-rw-r--r--chromium/v8/src/builtins/ppc/builtins-ppc.cc30
-rw-r--r--chromium/v8/src/builtins/riscv64/builtins-riscv64.cc67
-rw-r--r--chromium/v8/src/builtins/s390/builtins-s390.cc28
-rw-r--r--chromium/v8/src/builtins/x64/builtins-x64.cc22
-rw-r--r--chromium/v8/src/compiler/pipeline.cc5
-rw-r--r--chromium/v8/src/execution/riscv64/frame-constants-riscv64.h5
-rw-r--r--chromium/v8/src/heap/mark-compact.cc5
-rw-r--r--chromium/v8/src/ic/ic.cc17
-rw-r--r--chromium/v8/src/objects/code-kind.h4
-rw-r--r--chromium/v8/src/objects/js-objects.cc49
-rw-r--r--chromium/v8/src/objects/js-objects.h5
-rw-r--r--chromium/v8/src/objects/js-weak-refs-inl.h21
-rw-r--r--chromium/v8/src/objects/js-weak-refs.h8
-rw-r--r--chromium/v8/src/objects/lookup.cc4
-rw-r--r--chromium/v8/src/objects/objects.cc2
-rw-r--r--chromium/v8/src/profiler/profile-generator.cc7
-rw-r--r--chromium/v8/src/profiler/profile-generator.h2
-rw-r--r--chromium/v8/src/runtime/runtime-object.cc43
-rw-r--r--chromium/v8/src/runtime/runtime-wasm.cc9
-rw-r--r--chromium/v8/src/runtime/runtime.h7
-rw-r--r--chromium/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h12
-rw-r--r--chromium/v8/src/wasm/wasm-linkage.h2
831 files changed, 115512 insertions, 1068 deletions
diff --git a/chromium/DEPS b/chromium/DEPS
index e2c2dcc06bc..e83cb63fa4b 100644
--- a/chromium/DEPS
+++ b/chromium/DEPS
@@ -257,11 +257,11 @@ vars = {
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling V8
# and whatever else without interference from each other.
- 'v8_revision': '87c27db79e6a35a6bdedcbfe732f978812bf6ced',
+ 'v8_revision': 'd0882b3dff76a9fc86bbf93381df1585d58f4f57',
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling ANGLE
# and whatever else without interference from each other.
- 'angle_revision': '6661eb4900dae62cbe9af5023f9c1e7105798b50',
+ 'angle_revision': '9768648fffc94a434a7d400a2542ce3706224417',
# Three lines of non-changing comments so that
# the commit queue can handle CLs rolling SwiftShader
# and whatever else without interference from each other.
@@ -1731,7 +1731,7 @@ deps = {
Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '958d732db02c2a70bcf4a2b0986f09318db4adfb',
'src/third_party/webrtc':
- Var('webrtc_git') + '/src.git' + '@' + '6ff73180ad01aca444c9856f91148eb2b948ce63',
+ Var('webrtc_git') + '/src.git' + '@' + 'd4f4b84f50e4cf82a89580f8a01846a25dda6ba2',
'src/third_party/libgifcodec':
Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'),
@@ -1801,7 +1801,7 @@ deps = {
Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'),
'src-internal': {
- 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7c969d3f5b910b97ec3711acc9cef2d1adfa6b9e',
+ 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6d651b2438a579c5982225a3ba1fd7a3c2fc27dd',
'condition': 'checkout_src_internal',
},
diff --git a/chromium/base/bind_internal.h b/chromium/base/bind_internal.h
index 60607efadb3..1b06e54b65a 100644
--- a/chromium/base/bind_internal.h
+++ b/chromium/base/bind_internal.h
@@ -859,8 +859,8 @@ bool QueryCancellationTraits(const BindStateBase* base,
template <typename Functor, typename Receiver, typename... Unused>
std::enable_if_t<
!(MakeFunctorTraits<Functor>::is_method &&
- std::is_pointer_v<std::decay_t<Receiver>> &&
- IsRefCountedType<std::remove_pointer_t<std::decay_t<Receiver>>>::value)>
+ IsPointerV<std::decay_t<Receiver>> &&
+ IsRefCountedType<RemovePointerT<std::decay_t<Receiver>>>::value)>
BanUnconstructedRefCountedReceiver(const Receiver& receiver, Unused&&...) {}
template <typename Functor>
@@ -870,8 +870,8 @@ void BanUnconstructedRefCountedReceiver() {}
template <typename Functor, typename Receiver, typename... Unused>
std::enable_if_t<
MakeFunctorTraits<Functor>::is_method &&
- std::is_pointer_v<std::decay_t<Receiver>> &&
- IsRefCountedType<std::remove_pointer_t<std::decay_t<Receiver>>>::value>
+ IsPointerV<std::decay_t<Receiver>> &&
+ IsRefCountedType<RemovePointerT<std::decay_t<Receiver>>>::value>
BanUnconstructedRefCountedReceiver(const Receiver& receiver, Unused&&...) {
DCHECK(receiver);
@@ -1006,19 +1006,20 @@ struct MakeBindStateTypeImpl<true, Functor, Receiver, BoundArgs...> {
static_assert(!std::is_array_v<std::remove_reference_t<Receiver>>,
"First bound argument to a method cannot be an array.");
static_assert(
- !std::is_pointer_v<DecayedReceiver> ||
- IsRefCountedType<std::remove_pointer_t<DecayedReceiver>>::value,
+ !IsPointerV<DecayedReceiver> ||
+ IsRefCountedType<RemovePointerT<DecayedReceiver>>::value,
"Receivers may not be raw pointers. If using a raw pointer here is safe"
" and has no lifetime concerns, use base::Unretained() and document why"
" it's safe.");
+
static_assert(!HasRefCountedTypeAsRawPtr<std::decay_t<BoundArgs>...>::value,
"A parameter is a refcounted type and needs scoped_refptr.");
public:
using Type = BindState<
std::decay_t<Functor>,
- std::conditional_t<std::is_pointer_v<DecayedReceiver>,
- scoped_refptr<std::remove_pointer_t<DecayedReceiver>>,
+ std::conditional_t<IsPointerV<DecayedReceiver>,
+ scoped_refptr<RemovePointerT<DecayedReceiver>>,
DecayedReceiver>,
MakeStorageType<BoundArgs>...>;
};
diff --git a/chromium/base/bind_unittest.cc b/chromium/base/bind_unittest.cc
index a5f681fe53b..6844b6796d9 100644
--- a/chromium/base/bind_unittest.cc
+++ b/chromium/base/bind_unittest.cc
@@ -1169,6 +1169,28 @@ TYPED_TEST(BindVariantsTest, UniquePtrReceiver) {
TypeParam::Bind(&NoRef::VoidMethod0, std::move(no_ref)).Run();
}
+TYPED_TEST(BindVariantsTest, ImplicitRefPtrReceiver) {
+ StrictMock<HasRef> has_ref;
+ EXPECT_CALL(has_ref, AddRef()).Times(1);
+ EXPECT_CALL(has_ref, Release()).Times(1);
+ EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillRepeatedly(Return(true));
+
+ HasRef* ptr = &has_ref;
+ auto ptr_cb = TypeParam::Bind(&HasRef::HasAtLeastOneRef, ptr);
+ EXPECT_EQ(1, std::move(ptr_cb).Run());
+}
+
+TYPED_TEST(BindVariantsTest, RawPtrReceiver) {
+ StrictMock<HasRef> has_ref;
+ EXPECT_CALL(has_ref, AddRef()).Times(1);
+ EXPECT_CALL(has_ref, Release()).Times(1);
+ EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillRepeatedly(Return(true));
+
+ raw_ptr<HasRef> rawptr(&has_ref);
+ auto rawptr_cb = TypeParam::Bind(&HasRef::HasAtLeastOneRef, rawptr);
+ EXPECT_EQ(1, std::move(rawptr_cb).Run());
+}
+
// Tests for Passed() wrapper support:
// - Passed() can be constructed from a pointer to scoper.
// - Passed() can be constructed from a scoper rvalue.
@@ -1751,6 +1773,12 @@ TEST(BindDeathTest, BanFirstOwnerOfRefCountedType) {
EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillOnce(Return(false));
base::BindOnce(&HasRef::VoidMethod0, &has_ref);
});
+
+ EXPECT_DCHECK_DEATH({
+ raw_ptr<HasRef> rawptr(&has_ref);
+ EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillOnce(Return(false));
+ base::BindOnce(&HasRef::VoidMethod0, rawptr);
+ });
}
} // namespace
diff --git a/chromium/base/bind_unittest.nc b/chromium/base/bind_unittest.nc
index 20b0e0ba2ce..29807298ca3 100644
--- a/chromium/base/bind_unittest.nc
+++ b/chromium/base/bind_unittest.nc
@@ -93,7 +93,7 @@ void WontCompile() {
method_to_const_cb.Run();
}
-#elif defined(NCTEST_METHOD_BIND_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!std::is_pointer_v<base::NoRef *> || IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained() and document why it's safe.\""]
+#elif defined(NCTEST_METHOD_BIND_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!IsPointerV<base::NoRef \*> \|\| IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained\(\) and document why it's safe.\""]
// Method bound to non-refcounted object.
@@ -106,7 +106,7 @@ void WontCompile() {
no_ref_cb.Run();
}
-#elif defined(NCTEST_CONST_METHOD_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!std::is_pointer_v<base::NoRef *> || IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained() and document why it's safe.\""]
+#elif defined(NCTEST_CONST_METHOD_BIND_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!IsPointerV<base::NoRef \*> \|\| IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained\(\) and document why it's safe.\""]
// Const Method bound to non-refcounted object.
//
@@ -118,6 +118,33 @@ void WontCompile() {
no_ref_const_cb.Run();
}
+#elif defined(NCTEST_METHOD_BIND_RAW_PTR_RECEIVER_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!IsPointerV<base::raw_ptr<base::NoRef, [^>]+>> \|\| IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained\(\) and document why it's safe.\""]
+
+
+// Method bound to non-refcounted object.
+//
+// We require refcounts unless you have Unretained().
+void WontCompile() {
+ NoRef no_ref;
+ raw_ptr<NoRef> rawptr(&no_ref);
+ RepeatingCallback<void()> no_ref_cb =
+ BindRepeating(&NoRef::VoidMethod0, rawptr);
+ no_ref_cb.Run();
+}
+
+#elif defined(NCTEST_CONST_METHOD_BIND_RAW_PTR_RECEIVER_NEEDS_REFCOUNTED_OBJECT) // [r"fatal error: static_assert failed due to requirement '!IsPointerV<base::raw_ptr<base::NoRef, [^>]+>> \|\| IsRefCountedType<base::NoRef, void>::value' \"Receivers may not be raw pointers. If using a raw pointer here is safe and has no lifetime concerns, use base::Unretained\(\) and document why it's safe.\""]
+
+// Const Method bound to non-refcounted object.
+//
+// We require refcounts unless you have Unretained().
+void WontCompile() {
+ NoRef no_ref;
+ raw_ptr<NoRef> rawptr(&no_ref);
+ RepeatingCallback<void()> no_ref_const_cb =
+ BindRepeating(&NoRef::VoidConstMethod0, rawptr);
+ no_ref_const_cb.Run();
+}
+
#elif defined(NCTEST_CONST_POINTER) // [r"static_assert failed.+?BindArgument<0>::ForwardedAs<.+?>::ToParamWithType<.+?>::kCanBeForwardedToBoundFunctor.+?Type mismatch between bound argument and bound functor's parameter\."]
// Const argument used with non-const pointer parameter of same type.
//
diff --git a/chromium/base/memory/raw_ptr.h b/chromium/base/memory/raw_ptr.h
index 4d978e97986..639713cd619 100644
--- a/chromium/base/memory/raw_ptr.h
+++ b/chromium/base/memory/raw_ptr.h
@@ -1051,6 +1051,37 @@ ALWAYS_INLINE bool operator>=(const raw_ptr<U, I>& lhs,
return lhs.GetForComparison() >= rhs.GetForComparison();
}
+// Template helpers for working with T* or raw_ptr<T>.
+template <typename T>
+struct IsPointer : std::false_type {};
+
+template <typename T>
+struct IsPointer<T*> : std::true_type {};
+
+template <typename T, typename I>
+struct IsPointer<raw_ptr<T, I>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool IsPointerV = IsPointer<T>::value;
+
+template <typename T>
+struct RemovePointer {
+ using type = T;
+};
+
+template <typename T>
+struct RemovePointer<T*> {
+ using type = T;
+};
+
+template <typename T, typename I>
+struct RemovePointer<raw_ptr<T, I>> {
+ using type = T;
+};
+
+template <typename T>
+using RemovePointerT = typename RemovePointer<T>::type;
+
} // namespace base
using base::raw_ptr;
diff --git a/chromium/base/memory/raw_scoped_refptr_mismatch_checker.h b/chromium/base/memory/raw_scoped_refptr_mismatch_checker.h
index 9e50458ec98..7afae066fa3 100644
--- a/chromium/base/memory/raw_scoped_refptr_mismatch_checker.h
+++ b/chromium/base/memory/raw_scoped_refptr_mismatch_checker.h
@@ -7,6 +7,7 @@
#include <type_traits>
+#include "base/memory/raw_ptr.h"
#include "base/template_util.h"
// It is dangerous to post a task with a T* argument where T is a subtype of
@@ -35,8 +36,8 @@ struct IsRefCountedType<T,
// pointer type and are convertible to a RefCounted(Base|ThreadSafeBase) type.
template <typename T>
struct NeedsScopedRefptrButGetsRawPtr
- : conjunction<std::is_pointer<T>,
- IsRefCountedType<std::remove_pointer_t<T>>> {
+ : conjunction<base::IsPointer<T>,
+ IsRefCountedType<base::RemovePointerT<T>>> {
static_assert(!std::is_reference<T>::value,
"NeedsScopedRefptrButGetsRawPtr requires non-reference type.");
};
diff --git a/chromium/build/fuchsia/update_sdk.py b/chromium/build/fuchsia/update_sdk.py
index 6bdb9d8f921..28a87275483 100755
--- a/chromium/build/fuchsia/update_sdk.py
+++ b/chromium/build/fuchsia/update_sdk.py
@@ -21,6 +21,7 @@ from common import GetHostOsFromPlatform, GetHostArchFromPlatform, \
sys.path.append(os.path.join(DIR_SOURCE_ROOT, 'build'))
import find_depot_tools
+LICENSE_FILE = 'LICENSE'
SDK_SIGNATURE_FILE = '.hash'
SDK_TARBALL_PATH_TEMPLATE = (
'gs://{bucket}/development/{sdk_hash}/sdk/{platform}-amd64/gn.tar.gz')
@@ -154,9 +155,14 @@ def main():
return 1
signature_filename = os.path.join(SDK_ROOT, SDK_SIGNATURE_FILE)
+
+ # crbug.com/1325179: Need to check LICENSE file existence due to transition
+ # to using CIPD to download the SDK.
+ license_filename = os.path.join(SDK_ROOT, LICENSE_FILE)
+
current_signature = (open(signature_filename, 'r').read().strip()
if os.path.exists(signature_filename) else '')
- if current_signature != sdk_hash:
+ if current_signature != sdk_hash or not os.path.exists(license_filename):
logging.info('Downloading GN SDK %s...' % sdk_hash)
MakeCleanDirectory(SDK_ROOT)
diff --git a/chromium/build/mac_toolchain.py b/chromium/build/mac_toolchain.py
index e44a6463605..d5ca0a9e1b6 100755
--- a/chromium/build/mac_toolchain.py
+++ b/chromium/build/mac_toolchain.py
@@ -159,11 +159,10 @@ def InstallXcodeBinaries():
return 0
# Use puppet's sudoers script to accept the license if its available.
- license_accept_script = '/usr/local/bin/xcode_accept_license.py'
+ license_accept_script = '/usr/local/bin/xcode_accept_license.sh'
if os.path.exists(license_accept_script):
args = [
- 'sudo', license_accept_script, '--xcode-version', cipd_xcode_version,
- '--license-version', cipd_license_version
+ 'sudo', license_accept_script, cipd_xcode_version, cipd_license_version
]
subprocess.check_call(args)
return 0
diff --git a/chromium/build/util/LASTCHANGE b/chromium/build/util/LASTCHANGE
index 2545e893b3b..6b00a313a05 100644
--- a/chromium/build/util/LASTCHANGE
+++ b/chromium/build/util/LASTCHANGE
@@ -1,2 +1,2 @@
-LASTCHANGE=76de02bc398043843909a8da1c2bc69ad3d2e5bf-refs/branch-heads/5005@{#782}
+LASTCHANGE=d40c1c345c6c905254498a9622b8cd89297dd0f2-refs/branch-heads/5005@{#1191}
LASTCHANGE_YEAR=2022
diff --git a/chromium/build/util/LASTCHANGE.committime b/chromium/build/util/LASTCHANGE.committime
index 65bd1c76a78..1cebc058cea 100644
--- a/chromium/build/util/LASTCHANGE.committime
+++ b/chromium/build/util/LASTCHANGE.committime
@@ -1 +1 @@
-1652745632 \ No newline at end of file
+1655859640 \ No newline at end of file
diff --git a/chromium/cc/paint/paint_op_reader.cc b/chromium/cc/paint/paint_op_reader.cc
index 006aad39f4f..39584797d3b 100644
--- a/chromium/cc/paint/paint_op_reader.cc
+++ b/chromium/cc/paint/paint_op_reader.cc
@@ -332,6 +332,10 @@ void PaintOpReader::Read(PaintImage* image) {
SkImageInfo image_info =
SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType);
+ if (pixel_size < image_info.computeMinByteSize()) {
+ SetInvalid(DeserializationError::kInsufficientPixelData);
+ return;
+ }
const volatile void* pixel_data = ExtractReadableMemory(pixel_size);
if (!valid_)
return;
diff --git a/chromium/cc/paint/paint_op_reader.h b/chromium/cc/paint/paint_op_reader.h
index 61ea287e5b9..c5b5e6e1e08 100644
--- a/chromium/cc/paint/paint_op_reader.h
+++ b/chromium/cc/paint/paint_op_reader.h
@@ -183,8 +183,9 @@ class CC_PAINT_EXPORT PaintOpReader {
kSharedImageProviderNoAccess = 50,
kSharedImageProviderSkImageCreationFailed = 51,
kZeroSkColorFilterBytes = 52,
+ kInsufficientPixelData = 53,
- kMaxValue = kZeroSkColorFilterBytes,
+ kMaxValue = kInsufficientPixelData
};
template <typename T>
diff --git a/chromium/chrome/VERSION b/chromium/chrome/VERSION
index df1b15e9fb9..96ae4bfab48 100644
--- a/chromium/chrome/VERSION
+++ b/chromium/chrome/VERSION
@@ -1,4 +1,4 @@
MAJOR=102
MINOR=0
BUILD=5005
-PATCH=57
+PATCH=137
diff --git a/chromium/chrome/app/resources/chromium_strings_es-419.xtb b/chromium/chrome/app/resources/chromium_strings_es-419.xtb
index bc2f18d8661..ecf03214ec1 100644
--- a/chromium/chrome/app/resources/chromium_strings_es-419.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_es-419.xtb
@@ -278,7 +278,7 @@ Es posible que se apliquen a esta cuenta los permisos que otorgaste a sitios web
<translation id="7975919845073681630">Como esta es una instalación secundaria de Chromium, no puede establecerse como tu navegador predeterminado.</translation>
<translation id="7982481011030453202">Si no ves una opción de configuración en esta página, consulta la <ph name="LINK_BEGIN" />
configuración de Chromium OS<ph name="LINK_END" /></translation>
-<translation id="7997934263947464652">Extensiones, apps y temas de fuentes desconocidas que pueden dañar tu dispositivo. Chrome recomienda instalarlas únicamente desde <ph name="IDS_EXTENSION_WEB_STORE_TITLE" /></translation>
+<translation id="7997934263947464652">Las extensiones, apps y temas de fuentes desconocidas pueden dañar tu dispositivo. Chromium recomienda instalarlas únicamente desde <ph name="IDS_EXTENSION_WEB_STORE_TITLE" /></translation>
<translation id="8013436988911883588">Una vez que Chromium tenga acceso, los sitios web podrán solicitarte acceso.</translation>
<translation id="8045118833343856403">No se accedió a ningún perfil de Chromium con las siguientes cuentas. Si quieres usar una cuenta en otro perfil, primero quita ese perfil.</translation>
<translation id="8105840573057009683">Chromium necesita permiso de acceso a la ubicación para este sitio.</translation>
diff --git a/chromium/chrome/app/resources/chromium_strings_eu.xtb b/chromium/chrome/app/resources/chromium_strings_eu.xtb
index fc75a7b9b96..0ed40618610 100644
--- a/chromium/chrome/app/resources/chromium_strings_eu.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_eu.xtb
@@ -88,7 +88,7 @@ Baliteke webguneei eta aplikazioei emandako baimenak kontu honi aplikatzea. Goog
<translation id="3350761136195634146">Badago kontu honen Chromium-eko profil bat</translation>
<translation id="3387527074123400161">Chromium OS</translation>
<translation id="3406848076815591792">Lehendik dagoen Chromium-eko profil batera aldatu nahi duzu?</translation>
-<translation id="3412460710772753638">Gailuko Pasahitz-kudeatzailea aplikazioan</translation>
+<translation id="3412460710772753638">Gailuko Pasahitz-kudeatzailea zerbitzuan</translation>
<translation id="3430503420100763906">Chromium-eko profilekin, Chromium-en dituzun gauza guztiak bereiz ditzakezu. Sortu profilak lagunentzat eta familiako kideentzat, edo banandu gauza pertsonalak eta lanekoak.</translation>
<translation id="347328004046849135">Arriskuan dagoen pasahitz batekin hasten baduzu saioa, jakinarazi egingo dizu Chromium-ek</translation>
<translation id="3474745554856756813">Gailuko <ph name="ITEMS_COUNT" /> elementu ezabatuko dira. Geroago datuak eskuratu nahi izanez gero, hasi saioa Chromium-en <ph name="USER_EMAIL" /> gisa.</translation>
@@ -135,7 +135,7 @@ Baliteke webguneei eta aplikazioei emandako baimenak kontu honi aplikatzea. Goog
<translation id="4544142686420020088">Chromium ez da eguneratu arazoren bat izan delako. <ph name="BEGIN_LINK" />Konpondu Chromium eguneratzeko arazoak eta huts egindako eguneratzeak.<ph name="END_LINK" /></translation>
<translation id="454579500955453258">Chromium-eko beste profil batean egin nahi duzu aurrera?</translation>
<translation id="4567424176335768812"><ph name="USER_EMAIL_ADDRESS" /> gisa hasi duzu saioa. Laster-markak, historia eta bestelako ezarpenak atzi ditzakezu saioa hasita daukaten gailu guztien bidez.</translation>
-<translation id="4594305310729380060">Gailuko Pasahitz-kudeatzailea aplikazioan</translation>
+<translation id="4594305310729380060">Gailuko Pasahitz-kudeatzailea zerbitzuan</translation>
<translation id="459535195905078186">Chromium aplikazioak</translation>
<translation id="4613863813562375431">Chromium OS-ren bertsioa</translation>
<translation id="4621240073146040695">Ia amaitu dugu! Eguneratzen amaitzeko, berrabiarazi Chromium.</translation>
diff --git a/chromium/chrome/app/resources/chromium_strings_fr.xtb b/chromium/chrome/app/resources/chromium_strings_fr.xtb
index 54eefb16044..99247fb586f 100644
--- a/chromium/chrome/app/resources/chromium_strings_fr.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_fr.xtb
@@ -14,7 +14,7 @@ Certaines fonctionnalités ne seront peut-être pas disponibles, et les modifica
<translation id="1414495520565016063">Vous êtes connecté à Chromium.</translation>
<translation id="1524282610922162960">Partager un onglet Chromium</translation>
<translation id="1553461853655228091">Chromium a besoin de votre autorisation pour accéder à votre appareil photo et créer un plan 3D de votre environnement</translation>
-<translation id="1602421994560205104">Chromium a bloqué ce fichier. Impossible de vérifier si ce fichier est sûr, car il est trop volumineux. Réessayez avec des fichiers de 50 Mo maximum</translation>
+<translation id="1602421994560205104">Chromium a bloqué ce fichier, car il est trop volumineux pour être analysé. Réessayez avec des fichiers de 50 Mo maximum.</translation>
<translation id="1607715478322902680">{COUNT,plural, =0{À la demande de votre administrateur, vous devez relancer Chromium pour installer une mise à jour}=1{À la demande de votre administrateur, vous devez relancer Chromium pour installer une mise à jour. Sachez que la fenêtre de navigation privée ne sera pas rouverte.}one{À la demande de votre administrateur, vous devez relancer Chromium pour installer une mise à jour. Sachez que la fenêtre de navigation privée (#) ne sera pas rouverte.}other{À la demande de votre administrateur, vous devez relancer Chromium pour installer une mise à jour. Sachez que les # fenêtres de navigation privée ne seront pas rouvertes.}}</translation>
<translation id="1625909126243026060">Examinez les paramètres clés de confidentialité et de sécurité dans Chromium</translation>
<translation id="1632539827495546968">Si vous souhaitez utiliser ce compte une seule fois, vous pouvez utiliser le <ph name="GUEST_LINK_BEGIN" />mode Invité<ph name="GUEST_LINK_END" /> dans le navigateur Chromium. Pour ajouter le compte d'une autre personne, <ph name="LINK_BEGIN" />ajoutez un nouvel utilisateur<ph name="LINK_END" /> à votre <ph name="DEVICE_TYPE" />.
diff --git a/chromium/chrome/app/resources/chromium_strings_mr.xtb b/chromium/chrome/app/resources/chromium_strings_mr.xtb
index 3ddba812b59..e8c6c81eebc 100644
--- a/chromium/chrome/app/resources/chromium_strings_mr.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_mr.xtb
@@ -186,7 +186,7 @@
<translation id="608189560609172163">साइन इन करणà¥à¤¯à¤¾à¤¤ à¤à¤°à¤° आलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium तà¥à¤®à¤šà¤¾ डेटा सिंक करू शकले नाही.</translation>
<translation id="6096348254544841612">Chromium कसà¥à¤Ÿà¤®à¤¾à¤‡à¤ करा आणि नियंतà¥à¤°à¤¿à¤¤ करा. अपडेट उपलबà¥à¤§ आहे.</translation>
<translation id="6120345080069858279">Chromium हा पासवरà¥à¤¡ तà¥à¤®à¤šà¥à¤¯à¤¾ Google खातà¥à¤¯à¤¾à¤®à¤§à¥à¤¯à¥‡ सेवà¥à¤¹ करेल. तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ पासवरà¥à¤¡ लकà¥à¤·à¤¾à¤¤ ठेवावा लागणार नाही.</translation>
-<translation id="6129621093834146363"><ph name="FILE_NAME" /> धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ते अवरोधित केले आहे.</translation>
+<translation id="6129621093834146363"><ph name="FILE_NAME" /> धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="6134968993075716475">सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ बà¥à¤°à¤¾à¤‰à¤à¤¿à¤‚ग बंद आहे. Chromium ते सà¥à¤°à¥‚ करणà¥à¤¯à¤¾à¤šà¥€ शिफारस करते.</translation>
<translation id="6145820983052037069">तà¥à¤®à¥à¤¹à¥€ Chromium पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤¦à¤°à¤®à¥à¤¯à¤¾à¤¨ येथे सà¥à¤µà¤¿à¤š करू शकता</translation>
<translation id="6183079672144801177">तà¥à¤®à¥à¤¹à¥€ तà¥à¤®à¤šà¥à¤¯à¤¾ <ph name="TARGET_DEVICE_NAME" /> वर Chromium मधà¥à¤¯à¥‡ साइन इन केले असलà¥à¤¯à¤¾à¤šà¥€ खातà¥à¤°à¥€ करा आणि तà¥à¤¯à¤¾à¤¨à¤‚तर पà¥à¤¨à¥à¤¹à¤¾ पाठवून पहा.</translation>
@@ -199,7 +199,7 @@
<translation id="6268381023930128611">Chromium मधून साइन आउट करायचे?</translation>
<translation id="6281746429495226318">तà¥à¤®à¤šà¥€ Chromium पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² कसà¥à¤Ÿà¤®à¤¾à¤‡à¤ करा</translation>
<translation id="6290827346642914212">तà¥à¤®à¤šà¥à¤¯à¤¾ Chromium पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤²à¤¾ नाव दà¥à¤¯à¤¾</translation>
-<translation id="6295779123002464101"><ph name="FILE_NAME" /> धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ते अवरोधित केले आहे.</translation>
+<translation id="6295779123002464101"><ph name="FILE_NAME" /> धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="6309712487085796862">Chromium तà¥à¤®à¤šà¤¾ कॅमेरा वापरत आहे.</translation>
<translation id="632355235628111698">ही फाइल धोकादायक असलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡, Chromium ने ती बà¥à¤²à¥‰à¤• केली आहे</translation>
<translation id="6333502561965082103">Chromium वर दà¥à¤¸à¤°à¥‡ ऑपरेशन पà¥à¤°à¤—तीपथावर आहे. कृपया नंतर पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
@@ -290,11 +290,11 @@ Chromium तà¥à¤®à¤šà¥€ सेटिंगà¥à¤œ रिकवà¥à¤¹à¤° करà¥
<translation id="8045118833343856403">पà¥à¤¢à¥€à¤² खातà¥à¤¯à¤¾à¤‚नी कोणतà¥à¤¯à¤¾à¤¹à¥€ Chromium पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤®à¤§à¥à¤¯à¥‡ साइन इन केलेले नाही. तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ दà¥à¤¸à¤±à¥à¤¯à¤¾ पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤®à¤§à¥à¤¯à¥‡ à¤à¤–ादे खाते वापरायचे असलà¥à¤¯à¤¾à¤¸, सरà¥à¤µà¤ªà¥à¤°à¤¥à¤® ती पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² काढून टाका.</translation>
<translation id="8105840573057009683">Chromium ला या साइटसाठी सà¥à¤¥à¤¾à¤¨à¤¾à¤¶à¥€ संबंधित परवानगी आवशà¥à¤¯à¤• आहे</translation>
<translation id="8133124826068723441">तà¥à¤®à¤šà¥à¤¯à¤¾ डोमेनसाठी सिंक करणे उपलबà¥à¤§ नसलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡, ChromiumOS ला तà¥à¤®à¤šà¤¾ डेटा सिंक करता आला नाही.</translation>
-<translation id="81770708095080097">ही फाईल धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने अवरोधित केली आहे.</translation>
+<translation id="81770708095080097">ही फाइल धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="8248265253516264921">इमेजचे उपयोगी वरà¥à¤£à¤¨ नसलà¥à¤¯à¤¾à¤¸, Chromium तà¥à¤®à¤šà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ ते देणà¥à¤¯à¤¾à¤šà¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करेल. वरà¥à¤£à¤¨à¥‡ तयार करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, इमेज Google ला पाठवलà¥à¤¯à¤¾ जातात. तà¥à¤®à¥à¤¹à¥€ हे सेटिंगà¥à¤œà¤®à¤§à¥à¤¯à¥‡ कधीही बंद करू शकता.</translation>
<translation id="8266560134891435528">तà¥à¤®à¥à¤¹à¥€ साइन इन केलेले नसलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium तà¥à¤®à¤šà¥‡ पासवरà¥à¤¡ तपासू शकत नाही</translation>
<translation id="8276522524898344151">Chromium पासवरà¥à¤¡ कॉपी करणà¥à¤¯à¤¾à¤šà¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करत आहे.</translation>
-<translation id="8290862415967981663">ही फाईल कदाचित धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ती अवरोधित केली आहे.</translation>
+<translation id="8290862415967981663">ही फाइल कदाचित धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chromium ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="8330519371938183845">तà¥à¤®à¤šà¥à¤¯à¤¾ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° Chromium सिंक आणि परà¥à¤¸à¤¨à¤²à¤¾à¤‡à¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ साइन इन करा</translation>
<translation id="8340674089072921962"><ph name="USER_EMAIL_ADDRESS" /> पूरà¥à¤µà¥€ Chromium वापरत होते</translation>
<translation id="8357820681460164151">तà¥à¤®à¤šà¥à¤¯à¤¾ Chromium बà¥à¤°à¤¾à¤‰à¤à¤°à¤µà¤°à¥€à¤² आशय तà¥à¤®à¤šà¥à¤¯à¤¾ सरà¥à¤µ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° अâ€à¥…कà¥à¤¸à¥‡à¤¸ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ साइन इन करा, तà¥à¤¯à¤¾à¤¨à¤‚तर सिंक सà¥à¤°à¥‚ करा</translation>
diff --git a/chromium/chrome/app/resources/chromium_strings_no.xtb b/chromium/chrome/app/resources/chromium_strings_no.xtb
index 54159b6cb38..2c87eb737f7 100644
--- a/chromium/chrome/app/resources/chromium_strings_no.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_no.xtb
@@ -328,7 +328,7 @@ Chromium kan ikke gjenopprette innstillingen dine.</translation>
<translation id="9089354809943900324">Chromium er utdatert</translation>
<translation id="9093206154853821181">{0,plural, =1{Chromium startes på nytt om en time}other{Chromium startes på nytt om # timer}}</translation>
<translation id="91086099826398415">Ã…pne linken i en ny &amp;fane i Chromium</translation>
-<translation id="911206726377975832">Vil du slette all nettleserdata også?</translation>
+<translation id="911206726377975832">Vil du slette all nettlesingsdata også?</translation>
<translation id="9144490074902256427">Underveis i prøveprosjektene kan du se og fjerne interesseemnene som nettsteder bruker for å vise deg annonser. Chromium anslår interessene dine basert på den nylige nettleserloggen din.</translation>
<translation id="9158494823179993217">Systemadministratoren din har konfigurert Chromium til å åpne en annen nettleser når du går til <ph name="TARGET_URL_HOSTNAME" />.</translation>
<translation id="9185526690718004400">Start på nytt for å oppdatere &amp;Chromium</translation>
diff --git a/chromium/chrome/app/resources/chromium_strings_sr-Latn.xtb b/chromium/chrome/app/resources/chromium_strings_sr-Latn.xtb
index e34845f11c9..a517d512d86 100644
--- a/chromium/chrome/app/resources/chromium_strings_sr-Latn.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_sr-Latn.xtb
@@ -44,7 +44,7 @@ Dozvole koje ste vecÌ dali veb-sajtovima i aplikacijama mogu da važe za ovaj n
<translation id="2313870531055795960">Proverava URL-ove sa listom nebezbednih sajtova koji se Äuvaju u Chromium-u. Ako neki sajt pokuÅ¡a da ukrade vaÅ¡u lozinku ili probate da preuzmete Å¡tetnu datoteku, Chromium može da poÅ¡alje URL-ove, ukljuÄujucÌi delove sadržaja stranice, u Bezbedno pregledanje.</translation>
<translation id="2343156876103232566">Da biste poslali broj sa ovog uređaja na Android telefon, prijavite se u Chromium na oba uređaja.</translation>
<translation id="2347108572062610441">Ovaj dodatak je promenio stranicu koja se prikazuje kada pokrenete Chromium.</translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="2384373936468275798">Chromium OS ne može da sinhronizuje podatke zato što su podaci za prijavljivanje na nalog zastareli.</translation>
<translation id="2396765026452590966">Dodatak „<ph name="EXTENSION_NAME" />“ je promenio stranicu koja se prikazuje kada pokrenete Chromium.</translation>
<translation id="2401032172288869980">Chromium-u su potrebne dozvole za kameru i mikrofon za ovaj sajt</translation>
diff --git a/chromium/chrome/app/resources/chromium_strings_sr.xtb b/chromium/chrome/app/resources/chromium_strings_sr.xtb
index 509252d175e..b9261110d8f 100644
--- a/chromium/chrome/app/resources/chromium_strings_sr.xtb
+++ b/chromium/chrome/app/resources/chromium_strings_sr.xtb
@@ -44,7 +44,7 @@
<translation id="2313870531055795960">Проверава URL-ове Ñа лиÑтом небезбедних Ñајтова који Ñе чувају у Chromium-у. Ðко неки Ñајт покуша да украде вашу лозинку или пробате да преузмете штетну датотеку, Chromium може да пошаље URL-ове, укључујући делове Ñадржаја Ñтранице, у Безбедно прегледање.</translation>
<translation id="2343156876103232566">Да биÑте поÑлали број Ñа овог уређаја на Android телефон, пријавите Ñе у Chromium на оба уређаја.</translation>
<translation id="2347108572062610441">Овај додатак је променио Ñтраницу која Ñе приказује када покренете Chromium.</translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="2384373936468275798">Chromium ОС не може да Ñинхронизује податке зато што Ñу подаци за пријављивање на налог заÑтарели.</translation>
<translation id="2396765026452590966">Додатак „<ph name="EXTENSION_NAME" />“ је променио Ñтраницу која Ñе приказује када покренете Chromium.</translation>
<translation id="2401032172288869980">Chromium-у Ñу потребне дозволе за камеру и микрофон за овај Ñајт</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_af.xtb b/chromium/chrome/app/resources/generated_resources_af.xtb
index 67a73c16b30..2031e92b953 100644
--- a/chromium/chrome/app/resources/generated_resources_af.xtb
+++ b/chromium/chrome/app/resources/generated_resources_af.xtb
@@ -2376,7 +2376,7 @@ en Ctrl+Alt+Verlaag Helderheid om uit te zoem</translation>
<translation id="3259723213051400722">Probeer weer.</translation>
<translation id="3261268979727295785">Vir ouer kinders kan jy ouerkontroles byvoeg nadat jy die opstelling voltooi het. Jy kan inligting oor ouerkontroles in die Verken-program kry.</translation>
<translation id="3262986719682892278">Te groot</translation>
-<translation id="3264544094376351444">Sans-serif lettertipe</translation>
+<translation id="3264544094376351444">Sans Serif lettertipe</translation>
<translation id="3264582393905923483">Konteks</translation>
<translation id="3265118321284789528">Wag tans vir ouergoedkeuring</translation>
<translation id="3265459715026181080">Maak venster toe</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ar.xtb b/chromium/chrome/app/resources/generated_resources_ar.xtb
index cc089e6dc4e..4eec1516e8e 100644
--- a/chromium/chrome/app/resources/generated_resources_ar.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ar.xtb
@@ -529,7 +529,8 @@
<translation id="1515163294334130951">تشغيل</translation>
<translation id="1521442365706402292">تنظيم الشهادات</translation>
<translation id="1521774566618522728">نشط اليوم</translation>
-<translation id="152234381334907219">المواقع التي لن تحÙظ كلمات المرور أبدًا</translation>
+<translation id="152234381334907219">المواقع الإلكترونية التي لن تحÙظ كلمات المرور أبدًا
+</translation>
<translation id="1523279371236772909">تم الاطّلاع عليه ÙÙŠ الشهر الماضي.</translation>
<translation id="1523978563989812243">محرّكات "تحويل النص إلى كلام"</translation>
<translation id="1524430321211440688">لوحة المÙاتيح</translation>
@@ -1130,7 +1131,7 @@
<translation id="2082187087049518845">إضاÙØ© علامة التبويب إلى مجموعة جديدة</translation>
<translation id="2082510809738716738">اختيار لون المظهر</translation>
<translation id="208586643495776849">ÙŠÙرجى إعادة المحاولة</translation>
-<translation id="208634871997892083">â€Ø§Ù„شبكة الاÙتراضية الخاصة (VPN) قيد التشغيل دائمًا</translation>
+<translation id="208634871997892083">â€Ø´Ø¨ÙƒØ© اÙتراضية خاصة (VPN) قيد التشغيل دائمًا</translation>
<translation id="2087822576218954668">الطباعة: <ph name="PRINT_NAME" /></translation>
<translation id="2088092308059522196">لن ÙŠÙتاح التسجيل إلا بعد تثبيت نظام التشغيل <ph name="DEVICE_OS" />.</translation>
<translation id="208928984520943006">للوصول إلى الشاشة الرئيسية ÙÙŠ أي وقت، مرّر سريعًا بإصبعك من أسÙÙ„ الشاشة إلى أعلاها.</translation>
@@ -1168,7 +1169,7 @@
<translation id="2105809836724866556">تم إخÙاء <ph name="MODULE_TITLE" />.</translation>
<translation id="2108349519800154983">{COUNT,plural, =1{رقم هات٠واحد}zero{# رقم هاتÙ}two{رقما هاتÙ}few{# أرقام هاتÙ}many{# رقم هاتÙ}other{# رقم هاتÙ}}</translation>
<translation id="211144231511833662">محو الأنواع</translation>
-<translation id="2111670510994270194">إضاÙØ© علامة تبويب جديدة يمينًا</translation>
+<translation id="2111670510994270194">إضاÙØ© علامة تبويب جديدة على اليمين</translation>
<translation id="2112554630428445878">مرحبًا، <ph name="USERNAME" /></translation>
<translation id="21133533946938348">تثبيت علامة التبويب</translation>
<translation id="2113479184312716848">Ùتح &amp;ملÙ...</translation>
@@ -1544,7 +1545,7 @@
<translation id="244231003699905658">العنوان غير صالح ÙŠÙرجى التحقّق من العنوان وإعادة المحاولة.</translation>
<translation id="2442916515643169563">ظل النص</translation>
<translation id="2443487764245141020">قد تحتاج المواقع الإلكترونية أيضًا إلى التعرّÙ٠على جهازك باستخدام أحد المعرّÙات.</translation>
-<translation id="244475495405467108">إغلاق علامات التبويب على اليمين</translation>
+<translation id="244475495405467108">إغلاق علامات التبويب على اليسار</translation>
<translation id="2445081178310039857">دليل الجذر للإضاÙØ© مطلوب.</translation>
<translation id="2445484935443597917">إنشاء مل٠شخصي جديد</translation>
<translation id="244641233057214044">ذو صلة بعملية البحث</translation>
@@ -1799,7 +1800,7 @@
<translation id="2687407218262674387">â€Ø¨Ù†ÙˆØ¯ خدمة Google</translation>
<translation id="2687621393791886981">اسألني لاحقًا</translation>
<translation id="2688196195245426394">حدث خطأ عند تسجيل الجهاز مع الخادم: <ph name="CLIENT_ERROR" />.</translation>
-<translation id="2688734475209947648">â€Ù„Ù† تضطر إلى تذكّÙر كلمة المرور هذه. سيتم Ø­Ùظ كلمة المرور ÙÙŠ "مدير كلمات المرور ÙÙŠ Google" على عنوان البريد الإلكتروني <ph name="ACCOUNT" />.</translation>
+<translation id="2688734475209947648">â€Ù„Ù† تضطر إلى تذكّÙر كلمة المرور هذه. سيتم Ø­Ùظ كلمة المرور ÙÙŠ "مدير كلمات المرور ÙÙŠ Google" ضمن الحساب <ph name="ACCOUNT" />.</translation>
<translation id="2690024944919328218">عرض خيارات اللغة</translation>
<translation id="2691385045260836588">الطراز</translation>
<translation id="2691440343905273290">تغيير إعدادات الإدخال</translation>
@@ -2300,7 +2301,7 @@
<translation id="3164329792803560526">مشاركة علامة التبويب هذه مع <ph name="APP_NAME" /></translation>
<translation id="3165390001037658081">قد يحظر بعض مشغلي شبكات الجوّال هذه الميزة.</translation>
<translation id="3170072451822350649">يمكنك أيضًا تخطي تسجيل الدخول Ùˆ<ph name="LINK_START" />التصÙØ­ كضيÙ<ph name="LINK_END" />.</translation>
-<translation id="31774765611822736">إضاÙØ© علامة تبويب جديدة على اليمين</translation>
+<translation id="31774765611822736">إضاÙØ© علامة تبويب جديدة على اليسار</translation>
<translation id="3177909033752230686">لغة الصÙحة:</translation>
<translation id="3179982752812949580">خط النص</translation>
<translation id="3181954750937456830">التصÙÙ‘ÙØ­ الآمن (يحميك ويحمي جهازك من المواقع الإلكترونية الضارة)</translation>
@@ -2834,7 +2835,7 @@
<translation id="3714195043138862580">ÙˆÙضÙعَ هذا الجهاز التجريبي ÙÙŠ الحالة "غير متوÙÙ‘Ùر".</translation>
<translation id="3719826155360621982">الصÙحة الرئيسية</translation>
<translation id="372062398998492895">â€Ø¥Ø¹Ø¯Ø§Ø¯Ø§Øª طابعات CUPS</translation>
-<translation id="3721119614952978349">â€Ø¹Ù„اقتك مع Google</translation>
+<translation id="3721119614952978349">â€Ø£Ù†Øª ÙˆGoogle</translation>
<translation id="3722108462506185496">حدث خطأ أثناء بدء تشغيل خدمة الآلة الاÙتراضية. ÙŠÙرجى إعادة المحاولة.</translation>
<translation id="3727144509609414201">â€Ø´Ø¨ÙƒØ§Øª Wi-Fi المتاحة</translation>
<translation id="3727187387656390258">Ùحص النواÙØ° المنبثقة</translation>
@@ -3163,7 +3164,7 @@
<translation id="4021279097213088397">–</translation>
<translation id="402184264550408568">â€Ø¨Ø±ÙˆØªÙˆÙƒÙˆÙ„ (TCP)</translation>
<translation id="4021909830315618592">نسخ تÙاصيل الإصدار</translation>
-<translation id="4021941025609472374">إغلاق علامات التبويب على اليمين</translation>
+<translation id="4021941025609472374">إغلاق علامات التبويب على اليسار</translation>
<translation id="4022426551683927403">Ø¥&amp;ضاÙØ© إلى القاموس</translation>
<translation id="4023146161712577481">جار٠تحديد تكوين الجهاز.</translation>
<translation id="4025039777635956441">كتم صوت الموقع الإلكتروني المحدد</translation>
@@ -3867,7 +3868,7 @@
<translation id="4742970037960872810">إزالة التمييز</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />التعرّ٠على كيÙية تحديث التطبيقات<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{إعادة صوت موقع إلكتروني واحد}zero{إعادة صوت المواقع الإلكترونية}two{إعادة صوت موقعيّ٠الويب}few{إعادة صوت المواقع الإلكترونية}many{إعادة صوت المواقع الإلكترونية}other{إعادة صوت المواقع الإلكترونية}}</translation>
-<translation id="474609389162964566">â€Ø§Ù„وصول إلى "مساعد Google" من خلال قول الكلمة المÙتاح Ok Google</translation>
+<translation id="474609389162964566">â€Ø§Ù„وصول إلى "مساعد Google" من خلال قول Ok Google</translation>
<translation id="4746351372139058112">الرسائل</translation>
<translation id="4748783296226936791">â€ØªØªÙ‘صل المواقع الإلكترونية عادةً بأجهزة HID لتتيح الميزات التي تستخدم لوحات المÙاتيح غير العادية ووحدات التحكّم ÙÙŠ الألعاب وغيرها من الأجهزة.</translation>
<translation id="4750185073185658673">â€Ø¹Ù„يك الانتقال إلى هاتÙÙƒ لمراجعة بعض الأذونات الإضاÙية. تأكَّد من تÙعيل البلوتوث ÙˆWi-Fi على هاتÙÙƒ.</translation>
@@ -4156,7 +4157,7 @@
<translation id="5051836348807686060">لا تتوÙر ميزة التدقيق الإملائي للغات التي اخترتها.</translation>
<translation id="5052499409147950210">تعديل موقع إلكتروني</translation>
<translation id="505347685865235222">مجموعة لم تتم تسميتها - <ph name="GROUP_CONTENT_STRING" /></translation>
-<translation id="5053962746715621840">â€Ø§Ø¨Ø­Ø« ÙÙŠ الصور عبر "عدسة Google"</translation>
+<translation id="5053962746715621840">â€Ø§Ù„بحث باستخدام الصورة بواسطة "عدسة Google"</translation>
<translation id="5054374119096692193">â€Ø§Ù„اطّلاع على كل خيارات البطاقة ÙÙŠ القسم <ph name="BEGIN_LINK" />تخصيص Chrome<ph name="END_LINK" /></translation>
<translation id="5056950756634735043">جار٠الاتصال بالحاوية</translation>
<translation id="5057110919553308744">عند النقر على "الإضاÙØ©"</translation>
@@ -4971,7 +4972,7 @@
<translation id="5870155679953074650">الأخطاء الجسيمة</translation>
<translation id="5875534259258494936">تم إنهاء مشاركة الشاشة</translation>
<translation id="5876576639916258720">يتم الآن التنÙيذ...</translation>
-<translation id="5876851302954717356">إضاÙØ© علامة تبويب جديدة يمينًا</translation>
+<translation id="5876851302954717356">إضاÙØ© علامة تبويب جديدة على اليمين</translation>
<translation id="5877064549588274448">تم تغيير القناة. يمكنك إعادة تشغيل الجهاز لتدخل التغييرات حيز التنÙيذ.</translation>
<translation id="5877584842898320529">الطابعة المÙختارة غير متاحة أو لم يتم تثبيتها بشكل٠صحيح. <ph name="BR" /> ÙŠÙرجى التحقّÙÙ‚ من الطابعة أو تجربة اختيار طابعة أخرى.</translation>
<translation id="5882919346125742463">الشبكات المعروÙØ©</translation>
@@ -6716,7 +6717,7 @@
<translation id="7622966771025050155">التبديل إلى علامة التبويب التي يتم تسجيلها</translation>
<translation id="7624337243375417909">â€Ù…Ùتاح caps lock متوقÙ</translation>
<translation id="7625568159987162309">عرض الأذونات والبيانات المÙخزَّنة على المواقع</translation>
-<translation id="7625823789272218216">إضاÙØ© علامة تبويب جديدة على اليمين</translation>
+<translation id="7625823789272218216">إضاÙØ© علامة تبويب جديدة على اليسار</translation>
<translation id="7626032353295482388">â€Ù…رحبًا بك ÙÙŠ Chromeâ€</translation>
<translation id="7628201176665550262">معدّل إعادة التحميل</translation>
<translation id="7629827748548208700">علامة التبويب: <ph name="TAB_NAME" /></translation>
diff --git a/chromium/chrome/app/resources/generated_resources_bs.xtb b/chromium/chrome/app/resources/generated_resources_bs.xtb
index 1166d29f6c7..fe27694bd42 100644
--- a/chromium/chrome/app/resources/generated_resources_bs.xtb
+++ b/chromium/chrome/app/resources/generated_resources_bs.xtb
@@ -517,7 +517,7 @@ Odobrenja koja ste već dali aplikacijama se mogu primjenjivati na ovaj raÄun.
<translation id="1506061864768559482">PretraživaÄ</translation>
<translation id="1507170440449692343">Ovoj stranici je blokiran pristup vašoj kameri.</translation>
<translation id="1507246803636407672">&amp;Odbaci</translation>
-<translation id="1507884455975553832">Prenosite aplikacije za slanje poruka</translation>
+<translation id="1507884455975553832">Prenosite aplikacije za razmjenu poruka</translation>
<translation id="1509163368529404530">&amp;Vrati grupu</translation>
<translation id="1509281256533087115">Pristupiti bilo kojem uređaju <ph name="DEVICE_NAME_AND_VENDOR" /> putem USB-a</translation>
<translation id="1510238584712386396">PokretaÄ</translation>
@@ -724,7 +724,7 @@ da li proksi server funkcionira. Ako smatrate da ne trebate koristiti
<translation id="1682548588986054654">&amp;Novi anoniman prozor</translation>
<translation id="1682867089915960590">UkljuÄiti navigaciju kursorom?</translation>
<translation id="1684279041537802716">Boja za isticanje</translation>
-<translation id="168511795252678620">Nedavne fotografije i aplikacije za slanje poruka</translation>
+<translation id="168511795252678620">Nedavne fotografije i aplikacije za razmjenu poruka</translation>
<translation id="1686550358074589746">Omogući pisanje klizanjem prstom</translation>
<translation id="168715261339224929">Da biste imali svoje oznake na svim svojim ureÄ‘ajima, ukljuÄite sinhronizaciju.</translation>
<translation id="1688867105868176567">Obrisati podatke web lokacije?</translation>
@@ -6276,7 +6276,7 @@ Domena <ph name="DOMAIN" /> zahtijeva da pametna kartica ostane umetnuta.</trans
<translation id="7152478047064750137">Za ovu ekstenziju nisu potrebna posebna odobrenja</translation>
<translation id="7154130902455071009">Promijenite poÄetnu stranicu na: <ph name="START_PAGE" /></translation>
<translation id="7155171745945906037">Postojeća fotografija sa kamere ili fajl</translation>
-<translation id="7155352398126583949">Obavještenja i aplikacije za slanje poruka</translation>
+<translation id="7155352398126583949">Obavještenja i aplikacije za razmjenu poruka</translation>
<translation id="7163202347044721291">Potvrđivanje koda za aktivaciju...</translation>
<translation id="716640248772308851">Ekstenzija "<ph name="EXTENSION" />" može Äitati fajlove za slike, videozapise i zvuk u oznaÄenim lokacijama.</translation>
<translation id="7167486101654761064">&amp;Uvijek otvori ovu vrstu datoteka</translation>
@@ -7585,7 +7585,7 @@ Fajl kljuÄa: <ph name="KEY_FILE" />
<translation id="8439506636278576865">Ponudi prijevod stranica na ovom jeziku</translation>
<translation id="8440630305826533614">Linux aplikacije</translation>
<translation id="8443795068008423036">Provjerite je li uređaj ažuriran i pokušajte ponovo</translation>
-<translation id="8445046761938045900">Nedavne fotografije, obavještenja i aplikacije za slanje poruka</translation>
+<translation id="8445046761938045900">Nedavne fotografije, obavještenja i aplikacije za razmjenu poruka</translation>
<translation id="8446884382197647889">Saznajte više</translation>
<translation id="8447409163267621480">UkljuÄite Ctrl ili Alt</translation>
<translation id="844850004779619592">Nisu pronađene komande</translation>
@@ -7607,7 +7607,7 @@ Fajl kljuÄa: <ph name="KEY_FILE" />
<translation id="846374874681391779">Traka preuzetih fajlova</translation>
<translation id="8463955938112983119">Dodatak <ph name="PLUGIN_NAME" /> je onemogućen.</translation>
<translation id="8464132254133862871">Ovaj korisniÄki raÄun ne ispunjava uslove za ovu uslugu.</translation>
-<translation id="8464735509712879725">Za prostorije sa slabim svjetlom. Također optimizirano za vijek trajanja baterije.</translation>
+<translation id="8464735509712879725">Za prostorije sa slabim svjetlom. Također optimizirano prema vijeku trajanja baterije.</translation>
<translation id="8465252176946159372">Nije važeće</translation>
<translation id="8465444703385715657">Za pokretanje dodatka <ph name="PLUGIN_NAME" /> je potrebno vaše odobrenje</translation>
<translation id="8466417995783206254">Ova kartica reproducira videozapis u naÄinu rada slike u slici.</translation>
@@ -8250,7 +8250,7 @@ Domena <ph name="DOMAIN" /> zahtijeva da pametna kartica ostane umetnuta.}}</tra
<translation id="9108674852930645435">Istražite šta je novo na uređaju <ph name="DEVICE_TYPE" /></translation>
<translation id="9108808586816295166">Moguće je da sigurnosni DNS neće uvijek biti dostupan</translation>
<translation id="9109122242323516435">Da oslobodite prostor, izbrišite fajlove iz pohrane uređaja.</translation>
-<translation id="9109283579179481106">Povežite se na mobilnu mrežu</translation>
+<translation id="9109283579179481106">Povežite se s mobilnom mrežom</translation>
<translation id="9111102763498581341">OtkljuÄaj</translation>
<translation id="9111305600911828693">Licenca nije postavljena</translation>
<translation id="9111395131601239814"><ph name="NETWORKDEVICE" />: <ph name="STATUS" /></translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ca.xtb b/chromium/chrome/app/resources/generated_resources_ca.xtb
index 60957f5d731..cc790fea63b 100644
--- a/chromium/chrome/app/resources/generated_resources_ca.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ca.xtb
@@ -322,7 +322,7 @@
<translation id="1317637799698924700">La base de connexió funcionarà en un mode compatible amb els connectors USB tipus C.</translation>
<translation id="1319983966058170660">Botó Enrere de la subpàgina <ph name="SUBPAGE_TITLE" /></translation>
<translation id="1322046419516468189">Consulta i gestiona les contrasenyes desades al <ph name="SAVED_PASSWORDS_STORE" /></translation>
-<translation id="1324106254079708331">Protegeix els Comptes personals de Google de qualsevol usuari que corri el risc de rebre atacs dirigits</translation>
+<translation id="1324106254079708331">Protegeix els Comptes de Google personals de qualsevol usuari que corri el risc de rebre atacs dirigits</translation>
<translation id="1326317727527857210">Inicia la sessió a Chrome per accedir a les pestanyes dels altres dispositius que tinguis.</translation>
<translation id="1327272175893960498">Tiquets de Kerberos</translation>
<translation id="1327495825214193325">Per activar la depuració d'ADB, cal reiniciar el dispositiu <ph name="DEVICE_TYPE" />. Per desactivar-la, cal restablir-ne la configuració de fàbrica.</translation>
@@ -2920,7 +2920,7 @@ i Ctrl+Alt+disminueix la brillantor per reduir.</translation>
<translation id="3800828618615365228">Condicions addicionals de Google Chrome i Chrome OS</translation>
<translation id="3802486193901166966">Aquesta extensió no requereix cap permís especial i no té accés a cap lloc web addicional</translation>
<translation id="380329542618494757">Nom</translation>
-<translation id="3803345858388753269">Qualitat del vídeo</translation>
+<translation id="3803345858388753269">Qualitat de vídeo</translation>
<translation id="380408572480438692">L'activació de la recollida de dades de rendiment ajudarà Google a millorar el sistema amb el temps. No s'enviarà cap dada fins que no empleneu un informe de suggeriments (Alt-Maj-I) i les dades de rendiment. Podeu tornar a aquesta pantalla per desactivar la recopilació en qualsevol moment.</translation>
<translation id="3807249107536149332"><ph name="EXTENSION_NAME" /> (amb identificador d'extensió "<ph name="EXTENSION_ID" />") no es permet a les pantalles d'inici de sessió.</translation>
<translation id="3807747707162121253">&amp;Cancel·la</translation>
@@ -7084,7 +7084,7 @@ Prem un interruptor o una tecla assignats per suprimir una assignació.</transla
<translation id="7961015016161918242">Mai</translation>
<translation id="7963001036288347286">Acceleració del ratolí tàctil</translation>
<translation id="7963608432878156675">Altres dispositius poden veure aquest nom per a les connexions Bluetooth i a la xarxa</translation>
-<translation id="7963826112438303517">L'Assistent utilitza aquestes gravacions i les sol·licituds de veu per crear i actualitzar el teu model de veu, que només es desa als dispositius en què has activat Voice Match. Consulta l'activitat de veu o torna a entrenar el model a Configuració de l'Assistent.</translation>
+<translation id="7963826112438303517">L'Assistent utilitza aquestes gravacions i les sol·licituds de veu per crear i actualitzar el teu model de veu, que només es desa als dispositius en què has activat Voice Match. Consulta l'activitat de veu o torna a entrenar el model a la configuració de l'Assistent.</translation>
<translation id="7966241909927244760">C&amp;opia l'adreça de la imatge</translation>
<translation id="7966571622054096916">{COUNT,plural, =1{1 element a la llista d'adreces d'interès}other{{COUNT} elements a la llista d'adreces d'interès}}</translation>
<translation id="7968072247663421402">Opcions de proveïdors</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_da.xtb b/chromium/chrome/app/resources/generated_resources_da.xtb
index 5436aa891a5..5e3aaa5db75 100644
--- a/chromium/chrome/app/resources/generated_resources_da.xtb
+++ b/chromium/chrome/app/resources/generated_resources_da.xtb
@@ -400,7 +400,7 @@
<translation id="1397594434718759194">Da du er logget ind i Chrome på disse enheder, kan du bruge dem som sikkerhedsnøgler.</translation>
<translation id="1398853756734560583">Maksimér</translation>
<translation id="139911022479327130">Lås din telefon op, og bekræft din identitet</translation>
-<translation id="1401308693935339022">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge denne enheds lokation. Google kan jævnligt indsamle lokationsdata og bruge disse data på en anonym måde for at forbedre lokationernes nøjagtighed og placeringsbaserede tjenester.</translation>
+<translation id="1401308693935339022">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge denne enheds lokation. Google kan jævnligt indsamle lokationsdata og bruge disse data på en anonym måde for at forbedre lokationernes nøjagtighed og lokationsbaserede tjenester.</translation>
<translation id="1403222014593521787">Der kunne ikke oprettes forbindelse til proxyserveren</translation>
<translation id="1405779994569073824">GÃ¥et ned.</translation>
<translation id="1406500794671479665">Bekræfter...</translation>
@@ -2101,7 +2101,7 @@ og tryk på Ctrl+Alt+lysstyrke ned for at zoome ud.</translation>
<translation id="2947605845283690091">Webbrowsing bør være hurtigt. Brug et øjeblik på at <ph name="BEGIN_LINK" />tjekke dine udvidelser<ph name="END_LINK" /> nu.</translation>
<translation id="2948300991547862301">GÃ¥ til <ph name="PAGE_TITLE" /></translation>
<translation id="29488703364906173">En hurtig, enkel og sikker webbrowser, der er udviklet til det moderne internet.</translation>
-<translation id="2949289451367477459">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge enhedens placering. Google kan med jævne mellemrum indsamle lokationsdata og bruge oplysningerne anonymt til at gøre placeringen mere nøjagtig og forbedre placeringsbaserede tjenester. <ph name="BEGIN_LINK1" />Få flere oplysninger<ph name="END_LINK1" /></translation>
+<translation id="2949289451367477459">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge enhedens placering. Google kan med jævne mellemrum indsamle lokationsdata og bruge oplysningerne anonymt til at gøre lokationen mere nøjagtig og forbedre lokationsbaserede tjenester. <ph name="BEGIN_LINK1" />Få flere oplysninger<ph name="END_LINK1" /></translation>
<translation id="2950666755714083615">Tilmeld mig</translation>
<translation id="2953019166882260872">Opret forbindelse til din telefon via et kabel</translation>
<translation id="2956070239128776395">Sektionen er indlejret i en gruppe: <ph name="ERROR_LINE" /></translation>
@@ -3387,7 +3387,7 @@ Vil du starte <ph name="CONTROL_PANEL_APPLET_NAME" />?</translation>
<translation id="424963718355121712">Apps, der styrer et website, kan kun downloades fra det pågældende website</translation>
<translation id="4250229828105606438">Screenshot</translation>
<translation id="4250680216510889253">Nej</translation>
-<translation id="4251377547188244181">Tilmelding af terminal- og signeringsenhed</translation>
+<translation id="4251377547188244181">Tilmelding af enhed til terminal- og signeringstilstand</translation>
<translation id="4252035718262427477">Webside, enkelt fil (Web Bundle)</translation>
<translation id="4252899949534773101">Bluetooth er deaktiveret</translation>
<translation id="4252996741873942488"><ph name="WINDOW_TITLE" /> – faneindholdet deles</translation>
@@ -4228,7 +4228,7 @@ Vil du starte <ph name="CONTROL_PANEL_APPLET_NAME" />?</translation>
<translation id="5111646998522066203">Afslut inkognito</translation>
<translation id="5111692334209731439">&amp;Bogmærkeadministrator</translation>
<translation id="5112577000029535889">&amp;Udviklerværktøjer</translation>
-<translation id="511313294362309725">Slå Hurtig sammenkobling til</translation>
+<translation id="511313294362309725">Slå Hurtig parring til</translation>
<translation id="5113739826273394829">Hvis du klikker på dette ikon, låses denne <ph name="DEVICE_TYPE" /> manuelt. Næste gang skal du indtaster adgangskoden for at få adgang.</translation>
<translation id="51143538739122961">Indsæt din sikkerhedsnøgle, og tryk på den</translation>
<translation id="5115309401544567011">Slut din <ph name="DEVICE_TYPE" /> til en strømkilde.</translation>
@@ -5185,7 +5185,7 @@ Flere kontakter kan tildeles denne handling.</translation>
<translation id="6073903501322152803">Tilføj hjælpefunktioner</translation>
<translation id="6075731018162044558">Ups! Systemet kunne ikke hente et langfristet API-adgangstoken til denne enhed.</translation>
<translation id="6075907793831890935">Udveksle data med enheden ved navn <ph name="HOSTNAME" /></translation>
-<translation id="6076175485108489240">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge enhedens placering. Google kan med jævne mellemrum indsamle lokationsdata og bruge oplysningerne anonymt til at gøre placeringen mere nøjagtig og forbedre placeringsbaserede tjenester. <ph name="BEGIN_LINK1" />Få flere oplysninger<ph name="END_LINK1" /></translation>
+<translation id="6076175485108489240">Brug placering. Tillad, at apps og tjenester med placeringstilladelse kan bruge enhedens placering. Google kan med jævne mellemrum indsamle lokationsdata og bruge oplysningerne anonymt til at gøre lokationen mere nøjagtig og forbedre lokationsbaserede tjenester. <ph name="BEGIN_LINK1" />Få flere oplysninger<ph name="END_LINK1" /></translation>
<translation id="6076491747490570887">Kølig grå</translation>
<translation id="6077131872140550515">Fjern fra foretrukne</translation>
<translation id="6077189836672154517">Tips og opdateringer om <ph name="DEVICE_TYPE" /></translation>
@@ -5765,7 +5765,7 @@ Flere kontakter kan tildeles denne handling.</translation>
<translation id="6644512095122093795">FÃ¥ tilbudt at gemme adgangskoder</translation>
<translation id="6644513150317163574">Webadresseformatet er ugyldigt. Serveren skal angives som hostname, når der anvendes SSO-godkendelse.</translation>
<translation id="6644846457769259194">Opdaterer din enhed (<ph name="PROGRESS_PERCENT" />)</translation>
-<translation id="6646476869708241165">Slå Hurtig sammenkobling fra</translation>
+<translation id="6646476869708241165">Slå Hurtig parring fra</translation>
<translation id="6647228709620733774">Webadresse for tilbagekaldelse af Netscape-certifikatautoritet</translation>
<translation id="6647441008198474441">De websites, du besøger, sendes til Google for at forudsige, hvilke websites du kunne have lyst til at besøge som det næste</translation>
<translation id="6648911618876616409">En vigtig opdatering er klar til at blive installeret. Log ind for at komme godt i gang.</translation>
@@ -5882,7 +5882,7 @@ Flere kontakter kan tildeles denne handling.</translation>
<translation id="6770602306803890733">Forbedrer din og alle andres sikkerhed på nettet</translation>
<translation id="6771503742377376720">Er en certificeringsautoritet</translation>
<translation id="6775163072363532304">Tilgængelige enheder vises her.</translation>
-<translation id="6776729248872343918">Aktivér Hurtig sammenkobling</translation>
+<translation id="6776729248872343918">Aktivér Hurtig parring</translation>
<translation id="6777817260680419853">Omdirigeringen blev blokeret</translation>
<translation id="6778737459546443941">Din forælder har ikke godkendt det endnu</translation>
<translation id="6779092717724412415">Vælg en tekst, og højreklik på den for at oprette en markering som denne.</translation>
@@ -6508,7 +6508,7 @@ Flere kontakter kan tildeles denne handling.</translation>
<translation id="7404065585741198296">Din telefon med et USB-kabel</translation>
<translation id="7405938989981604410">{NUM_HOURS,plural, =1{Sikkerhedstjekket blev udført for 1 time siden}one{Sikkerhedstjekket blev udført for {NUM_HOURS} time siden}other{Sikkerhedstjekket blev udført for {NUM_HOURS} timer siden}}</translation>
<translation id="740624631517654988">Pop op-vindue blokeret</translation>
-<translation id="7407430846095439694">Importér, og tilknyt</translation>
+<translation id="7407430846095439694">Importér og tilknyt</translation>
<translation id="7407504355934009739">Folk blokerer som regel notifikationer fra dette website</translation>
<translation id="740810853557944681">Tilføj en printerserver</translation>
<translation id="7409549334477097887">Ekstra stor</translation>
@@ -8153,7 +8153,7 @@ Opbevar din nøglefil på et sikkert sted. Du skal bruge den til at oprette nye
<translation id="901668144954885282">Sikkerhedskopiér til Google Drev</translation>
<translation id="9018218886431812662">Installationen er fuldført</translation>
<translation id="9019062154811256702">Læse og redigere indstillinger for autofyld</translation>
-<translation id="9019894137004772119">Brug placering. Tillad, at apps og tjenester med adgangstilladelse til lokation kan bruge din enheds lokation. Google kan jævnligt indsamle lokationsdata og bruge disse data på en anonym måde for at forbedre lokationernes nøjagtighed og placeringsbaserede tjenester.</translation>
+<translation id="9019894137004772119">Brug placering. Tillad, at apps og tjenester med adgangstilladelse til lokation kan bruge din enheds lokation. Google kan jævnligt indsamle lokationsdata og bruge disse data på en anonym måde for at forbedre lokationernes nøjagtighed og lokationsbaserede tjenester.</translation>
<translation id="9019956081903586892">Ordbogen til stavekontrollen kunne ikke downloades</translation>
<translation id="9020300839812600209">Angiv en webadresse for at se, hvad Emulator til ældre websites ville gøre ved den.</translation>
<translation id="9020362265352758658">4x</translation>
@@ -8175,7 +8175,7 @@ Opbevar din nøglefil på et sikkert sted. Du skal bruge den til at oprette nye
<translation id="9030855135435061269">"<ph name="PLUGIN_NAME" />" understøttes ikke længere</translation>
<translation id="9031549947500880805">Sikkerhedskopiér i Google Drev. Gendan nemt dine data, eller skift enhed når som helst. Din backup omfatter appdata.</translation>
<translation id="9031811691986152304">prøv igen</translation>
-<translation id="9032097289595078011">Deaktiver Hurtig sammenkobling</translation>
+<translation id="9032097289595078011">Deaktiver Hurtig parring</translation>
<translation id="9033765790910064284">Fortsæt alligevel</translation>
<translation id="9033857511263905942">&amp;Indsæt</translation>
<translation id="903480517321259405">Angiv pinkoden igen</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_en-GB.xtb b/chromium/chrome/app/resources/generated_resources_en-GB.xtb
index 226e0ab3ab4..750571d685d 100644
--- a/chromium/chrome/app/resources/generated_resources_en-GB.xtb
+++ b/chromium/chrome/app/resources/generated_resources_en-GB.xtb
@@ -51,7 +51,7 @@
<translation id="1049795001945932310">&amp;Language settings</translation>
<translation id="1050693411695664090">Poor</translation>
<translation id="1054048317165655285">Complete setup on your phone</translation>
-<translation id="1054153489933238809">Open original image in new tab</translation>
+<translation id="1054153489933238809">Open Original &amp;Image in New Tab</translation>
<translation id="1055274863771110134">{NUM_WEEKS,plural, =1{Update <ph name="DEVICE_TYPE" /> within 1 week}other{Update <ph name="DEVICE_TYPE" /> within {NUM_WEEKS} weeks}}</translation>
<translation id="1056898198331236512">Warning</translation>
<translation id="1058262162121953039">PUK</translation>
@@ -85,7 +85,7 @@
<translation id="1076818208934827215">Microsoft Internet Explorer</translation>
<translation id="1076882167394279216">Couldn’t download spell check dictionary for <ph name="LANGUAGE" />. Try again.</translation>
<translation id="1079766198702302550">Always block camera access</translation>
-<translation id="1081956462909987459">{NUM_TABS,plural, =1{<ph name="GROUP_TITLE" /> – 1 tab}other{<ph name="GROUP_TITLE" /> – # tabs}}</translation>
+<translation id="1081956462909987459">{NUM_TABS,plural, =1{<ph name="GROUP_TITLE" /> – 1 Tab}other{<ph name="GROUP_TITLE" /> – # Tabs}}</translation>
<translation id="1082214733466244292">Your administrator has blocked some functionality for this device</translation>
<translation id="1082398631555931481"><ph name="THIRD_PARTY_TOOL_NAME" /> wants to restore your Chrome settings to their original defaults. This will reset your homepage, new tab page and search engine, disable your extensions and unpin all tabs. It will also clear other temporary and cached data, such as cookies, content and site data.</translation>
<translation id="1084096383128641877">Removing this password will not delete your account on <ph name="DOMAIN" />. Change your password or delete your account on <ph name="DOMAIN_LINK" /> to keep it safe from others.</translation>
@@ -95,7 +95,7 @@
<translation id="1087965115100412394">Don't allow sites to connect to MIDI devices</translation>
<translation id="1088654056000736875">Chrome is removing harmful software from your computer…</translation>
<translation id="1088659085457112967">Enter Reader Mode</translation>
-<translation id="1090126737595388931">No background apps running</translation>
+<translation id="1090126737595388931">No Background Apps Running</translation>
<translation id="1090541560108055381">Before pairing, make sure that this code is the same on both devices</translation>
<translation id="1091767800771861448">Press ESCAPE to skip (Non-official builds only).</translation>
<translation id="1093457606523402488">Visible Networks:</translation>
@@ -176,7 +176,7 @@
<translation id="1163931534039071049">&amp;View frame source</translation>
<translation id="1164891049599601209">Entered on deceptive site</translation>
<translation id="1165039591588034296">Error</translation>
-<translation id="1166212789817575481">Close tabs to the right</translation>
+<translation id="1166212789817575481">Close Tabs to the Right</translation>
<translation id="1166583374608765787">Review name update</translation>
<translation id="1166596238782048887"><ph name="TAB_TITLE" /> belongs to desk <ph name="DESK_TITLE" /></translation>
<translation id="1168020859489941584">Opening in <ph name="TIME_REMAINING" />...</translation>
@@ -388,7 +388,7 @@
<translation id="1388253969141979417">Allowed to use your microphone</translation>
<translation id="1388728792929436380"><ph name="DEVICE_TYPE" /> will restart when updates are complete.</translation>
<translation id="139013308650923562">Allowed to use fonts installed on your device</translation>
-<translation id="1390548061267426325">Open as a standard tab</translation>
+<translation id="1390548061267426325">Open as a Standard Tab</translation>
<translation id="1390907927270446471"><ph name="PROFILE_USERNAME" /> is not authorised to print to <ph name="PRINTER_NAME" />. Please contact your administrator.</translation>
<translation id="1393283411312835250">Sun and clouds</translation>
<translation id="1395730723686586365">Updater started</translation>
@@ -481,7 +481,7 @@ Permissions that you've already given to apps may apply to this account. You can
<translation id="1465827627707997754">Pizza slice</translation>
<translation id="1467432559032391204">Left</translation>
<translation id="1468571364034902819">Can't use this profile</translation>
-<translation id="1470084204649225129">{NUM_TABS,plural, =1{Add tab to new group}other{Add tabs to new group}}</translation>
+<translation id="1470084204649225129">{NUM_TABS,plural, =1{Add Tab to New Group}other{Add Tabs to New Group}}</translation>
<translation id="1470350905258700113">Use this device</translation>
<translation id="1470946456740188591">To turn caret browsing on or off, use the shortcut Ctrl+Search+7</translation>
<translation id="1472675084647422956">Show more</translation>
@@ -534,7 +534,7 @@ Permissions that you've already given to apps may apply to this account. You can
<translation id="1523279371236772909">Viewed in the past month</translation>
<translation id="1523978563989812243">Text-to-speech engines</translation>
<translation id="1524430321211440688">Keyboard</translation>
-<translation id="1524563461097350801">No, thanks</translation>
+<translation id="1524563461097350801">No, Thanks</translation>
<translation id="1525740877599838384">Use only Wi-Fi to determine location</translation>
<translation id="152629053603783244">Restart Linux</translation>
<translation id="1526335046150927198">Enable touchpad scroll acceleration</translation>
@@ -602,7 +602,7 @@ Permissions that you've already given to apps may apply to this account. You can
<translation id="1587275751631642843">&amp;JavaScript console</translation>
<translation id="1587907146729660231">Touch the power button with your finger</translation>
<translation id="1588438908519853928">Normal</translation>
-<translation id="1588870296199743671">Open link with...</translation>
+<translation id="1588870296199743671">Open Link With...</translation>
<translation id="1588919647604819635">Right-click card</translation>
<translation id="1589055389569595240">Show spelling and grammar</translation>
<translation id="1591679663873027990">Give Parallels Desktop permission to access USB devices. Parallels Desktop won't remember a USB device after it's removed.</translation>
@@ -720,7 +720,7 @@ Permissions that you've already given to apps may apply to this account. You can
<translation id="1680849702532889074">An error occurred during installation of your Linux application.</translation>
<translation id="16815041330799488">Do not allow sites to see text and images copied to the clipboard</translation>
<translation id="1682548588986054654">New Incognito Window</translation>
-<translation id="1682867089915960590">Turn on caret browsing?</translation>
+<translation id="1682867089915960590">Turn on Caret Browsing?</translation>
<translation id="1684279041537802716">Accent colour</translation>
<translation id="168511795252678620">Recent photos and messaging apps</translation>
<translation id="1686550358074589746">Enable glide typing</translation>
@@ -889,7 +889,7 @@ Permissions that you've already given to apps may apply to this account. You can
<translation id="1826516787628120939">Checking</translation>
<translation id="1827738518074806965">Art gallery</translation>
<translation id="1828378091493947763">This plug-in is not supported on this device</translation>
-<translation id="1828879788654007962">{COUNT,plural, =0{&amp;Open all}=1{&amp;Open bookmark}other{&amp;Open all ({COUNT})}}</translation>
+<translation id="1828879788654007962">{COUNT,plural, =0{&amp;Open All}=1{&amp;Open Bookmark}other{&amp;Open All ({COUNT})}}</translation>
<translation id="1828901632669367785">Print Using System Dialogue...</translation>
<translation id="1829129547161959350">Penguin</translation>
<translation id="1829192082282182671">Zoom &amp;Out</translation>
@@ -1251,7 +1251,7 @@ You can manage this account’s settings by installing the Family Link app on yo
<translation id="2169062631698640254">Sign in anyway</translation>
<translation id="2173302385160625112">Check your Internet connection</translation>
<translation id="2173801458090845390">Add requisition ID to this device</translation>
-<translation id="2175384018164129879">&amp;Manage search engines and Site Search</translation>
+<translation id="2175384018164129879">&amp;Manage Search Engines and Site Search</translation>
<translation id="2175607476662778685">Quick launch bar</translation>
<translation id="217576141146192373">Unable to add printer. Please check your printer's configuration and try again.</translation>
<translation id="2175927920773552910">QR code</translation>
@@ -1462,7 +1462,7 @@ You can manage this account’s settings by installing the Family Link app on yo
<translation id="2340239562261172947"><ph name="FILE_NAME" /> can't be downloaded securely</translation>
<translation id="2342180549977909852">Your child can use a number (PIN) instead of a password to unlock this device. To set a PIN later, go to Settings.</translation>
<translation id="2342740338116612727">Bookmarks added</translation>
-<translation id="2343747224442182863">Focus this tab</translation>
+<translation id="2343747224442182863">Focus This Tab</translation>
<translation id="2344032937402519675">Couldn’t connect with the server. Check your network connection and try again. If you're still having trouble, try restarting your Chromebook.</translation>
<translation id="2345723121311404059">1 page to <ph name="PRINTER_NAME" /></translation>
<translation id="23463457491630512">For example, if you visit a site to buy shoes for a marathon, the site might define your interest as running marathons. Later, if you visit a different site to register for a race, that site can show you an ad for running shoes based on your interests.</translation>
@@ -1759,7 +1759,7 @@ You can manage this account’s settings by installing the Family Link app on yo
<translation id="2635094637295383009">Twitter</translation>
<translation id="2635276683026132559">Signing</translation>
<translation id="2636625531157955190">Chrome cannot access the image.</translation>
-<translation id="2637313651144986786">Search tabs…</translation>
+<translation id="2637313651144986786">Search Tabs…</translation>
<translation id="2637400434494156704">Incorrect PIN. You have one attempt remaining.</translation>
<translation id="2638286699381354126">Update...</translation>
<translation id="2638662041295312666">Sign-in image</translation>
@@ -2005,7 +2005,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="2864601841139725659">Set your profile picture</translation>
<translation id="2865919525181940183">Screenshot of programs that are currently on the screen</translation>
<translation id="286674810810214575">Checking power sources…</translation>
-<translation id="2867768963760577682">Open as pinned tab</translation>
+<translation id="2867768963760577682">Open as Pinned Tab</translation>
<translation id="2868746137289129307">This extension is outdated and disabled by enterprise policy. It might become enabled automatically when a newer version is available.</translation>
<translation id="2870560284913253234">Site</translation>
<translation id="2870909136778269686">Updating...</translation>
@@ -2023,7 +2023,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="2878782256107578644">Scan in progress. Open now?</translation>
<translation id="2878889940310164513">Add mobile…</translation>
<translation id="288042212351694283">Access your Universal 2nd Factor devices</translation>
-<translation id="2881076733170862447">When you click the extension</translation>
+<translation id="2881076733170862447">When You Click the Extension</translation>
<translation id="2882943222317434580"><ph name="IDS_SHORT_PRODUCT_NAME" /> will restart and reset momentarily</translation>
<translation id="2885378588091291677">Task Manager</translation>
<translation id="2885729872133513017">A problem occurred when decoding server response.</translation>
@@ -2284,7 +2284,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3139925690611372679">Default yellow avatar</translation>
<translation id="3141318088920353606">Listening...</translation>
<translation id="3141917231319778873">The given request is not supported to: "<ph name="DEVICE_NAME" />".</translation>
-<translation id="3142562627629111859">New group</translation>
+<translation id="3142562627629111859">New Group</translation>
<translation id="3143515551205905069">Cancel sync</translation>
<translation id="3143754809889689516">Play from the beginning</translation>
<translation id="3144647712221361880">Open link as</translation>
@@ -2313,7 +2313,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3165390001037658081">Some operators might block this feature.</translation>
<translation id="3170072451822350649">You may also skip signing in and <ph name="LINK_START" />browse as Guest<ph name="LINK_END" />.</translation>
<translation id="31774765611822736">New tab to the left</translation>
-<translation id="3177909033752230686">Page language:</translation>
+<translation id="3177909033752230686">Page Language:</translation>
<translation id="3179982752812949580">Text font</translation>
<translation id="3181954750937456830">Safe Browsing (protects you and your device from dangerous sites)</translation>
<translation id="3182749001423093222">Spell check</translation>
@@ -2811,7 +2811,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3688507211863392146">Write to files and folders that you open in the application</translation>
<translation id="3688526734140524629">Change channel</translation>
<translation id="3688578402379768763">Up-to-date</translation>
-<translation id="3688794912214798596">Change languages…</translation>
+<translation id="3688794912214798596">Change Languages…</translation>
<translation id="3690369331356918524">Warns you if passwords are exposed in a data breach</translation>
<translation id="3691231116639905343">Keyboard apps</translation>
<translation id="369135240373237088">Sign in again with a school account</translation>
@@ -2836,7 +2836,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3708684582558000260">Don't allow closed sites to finish sending or receiving data</translation>
<translation id="3709244229496787112">The browser was shut down before the download completed.</translation>
<translation id="371174301504454251">To protect your privacy, we auto-delete sites from the list that are older than four weeks. A site that you visit again might appear on the list again. Or you can remove a site if you don’t want that site to ever define interests for you.</translation>
-<translation id="3711931198657368127">Paste and go to <ph name="URL" /></translation>
+<translation id="3711931198657368127">Paste and Go to <ph name="URL" /></translation>
<translation id="3711945201266135623">Found <ph name="NUM_PRINTERS" /> printers from the print server</translation>
<translation id="3712050472459130149">Account update required</translation>
<translation id="3712897371525859903">Save page &amp;as...</translation>
@@ -2994,14 +2994,14 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3854967233147778866">Offer to translate websites in other languages</translation>
<translation id="3854976556788175030">Output tray is full</translation>
<translation id="3855441664322950881">Pack extension</translation>
-<translation id="3855676282923585394">Import bookmarks and settings...</translation>
+<translation id="3855676282923585394">Import Bookmarks and Settings...</translation>
<translation id="3856096718352044181">Please verify that this is a valid provider or try again later</translation>
<translation id="3856800405688283469">Select Time Zone</translation>
<translation id="3857807444929313943">Lift, then touch again</translation>
<translation id="3858860766373142691">Name</translation>
<translation id="3861638017150647085">Username '<ph name="USERNAME" />' is not available</translation>
<translation id="3861977424605124250">Show on startup</translation>
-<translation id="386239283124269513">&amp;Restore group</translation>
+<translation id="386239283124269513">&amp;Restore Group</translation>
<translation id="3865414814144988605">Resolution</translation>
<translation id="3866249974567520381">Description</translation>
<translation id="3867134342671430205">Drag or use arrow keys to move a display</translation>
@@ -3038,7 +3038,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3894123633473837029">Include recent Assistant history via Sherlog. This may include your identity, location and debug info. <ph name="BEGIN_LINK" />Learn more<ph name="END_LINK" /></translation>
<translation id="3894427358181296146">Add folder</translation>
<translation id="3894770151966614831">Move to Google Account?</translation>
-<translation id="3895076768659607631">&amp;Manage search engines…</translation>
+<translation id="3895076768659607631">&amp;Manage Search Engines…</translation>
<translation id="3895090224522145010">Kerberos username</translation>
<translation id="389521680295183045">Sites can ask to know when you're actively using your device</translation>
<translation id="3897298432557662720">{COUNT,plural, =1{an image}other{# images}}</translation>
@@ -3052,7 +3052,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="3905761538810670789">Repair app</translation>
<translation id="3908393983276948098"><ph name="PLUGIN_NAME" /> is out of date</translation>
<translation id="3908501907586732282">Enable extension</translation>
-<translation id="3909701002594999354">Show all controls</translation>
+<translation id="3909701002594999354">Show All &amp;Controls</translation>
<translation id="3909791450649380159">Cu&amp;t</translation>
<translation id="39103738135459590">Activation code</translation>
<translation id="3911824782900911339">New Tab page</translation>
@@ -3392,7 +3392,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4252996741873942488"><ph name="WINDOW_TITLE" /> – Tab content shared</translation>
<translation id="4253168017788158739">Note</translation>
<translation id="4253183225471855471">No network found. Please insert your SIM and reboot your device before trying again.</translation>
-<translation id="4254813446494774748">Translation language:</translation>
+<translation id="4254813446494774748">Translation Language:</translation>
<translation id="425573743389990240">Battery Discharge Rate in Watts (Negative value means battery is charging)</translation>
<translation id="4256316378292851214">Sa&amp;ve Video As...</translation>
<translation id="4258348331913189841">File System</translation>
@@ -3460,7 +3460,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4316850752623536204">Developer Website</translation>
<translation id="4317820549299924617">Verification was not successful</translation>
<translation id="4320177379694898372">No internet connection</translation>
-<translation id="4322394346347055525">Close other tabs</translation>
+<translation id="4322394346347055525">Close Other Tabs</translation>
<translation id="4324577459193912240">File incomplete</translation>
<translation id="4325237902968425115">Uninstalling <ph name="LINUX_APP_NAME" />…</translation>
<translation id="4330191372652740264">Ice water</translation>
@@ -3686,7 +3686,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4546308221697447294">Browse fast with Google Chrome</translation>
<translation id="4546345569117159016">Right button</translation>
<translation id="4546692474302123343">Google Assistant voice input</translation>
-<translation id="4547659257713117923">No tabs from other devices</translation>
+<translation id="4547659257713117923">No Tabs From Other Devices</translation>
<translation id="4547672827276975204">Set automatically</translation>
<translation id="4549791035683739768">Your security key has no fingerprints stored</translation>
<translation id="4550926046134589611">Some supported links will still open in <ph name="APP_NAME" />.</translation>
@@ -3738,7 +3738,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4596295440756783523">You have certificates on file that identify these servers</translation>
<translation id="4598556348158889687">Storage management</translation>
<translation id="4598776695426288251">Wi-Fi available via multiple devices</translation>
-<translation id="4601426376352205922">Mark as unread</translation>
+<translation id="4601426376352205922">Mark as Unread</translation>
<translation id="4602466770786743961">Always allow <ph name="HOST" /> to access your camera and microphone</translation>
<translation id="4606551464649945562">Do not allow sites to create a 3D map of your surroundings or track camera position</translation>
<translation id="4608500690299898628">&amp;Find...</translation>
@@ -3815,7 +3815,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4667027203988048332">What data is used:</translation>
<translation id="46733273239502219">Offline data in installed apps will also be cleared</translation>
<translation id="4673442866648850031">Open stylus tools when the stylus is removed</translation>
-<translation id="4675065861091108046">You previously chose to allow all extensions on <ph name="ORIGIN" /></translation>
+<translation id="4675065861091108046">You Previously Chose To Allow All Extensions On <ph name="ORIGIN" /></translation>
<translation id="4675828034887792601">Create shortcuts for searching sites and manage your search engine</translation>
<translation id="4676595058027112862">Phone Hub, learn more</translation>
<translation id="4677772697204437347">GPU memory</translation>
@@ -4013,7 +4013,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="4880827082731008257">Search history</translation>
<translation id="4881685975363383806">Don't remind me next time</translation>
<translation id="4881695831933465202">Open</translation>
-<translation id="488211015466188466">Follow site</translation>
+<translation id="488211015466188466">Follow Site</translation>
<translation id="4882312758060467256">Has access to this site</translation>
<translation id="4882919381756638075">Sites usually use your microphone for communication features like video chatting</translation>
<translation id="4883436287898674711">All <ph name="WEBSITE_1" /> sites</translation>
@@ -4171,7 +4171,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="5051836348807686060">Spellcheck isn’t supported for the languages that you have selected</translation>
<translation id="5052499409147950210">Edit site</translation>
<translation id="505347685865235222">Unnamed group – <ph name="GROUP_CONTENT_STRING" /></translation>
-<translation id="5053962746715621840">Search image with Google Lens</translation>
+<translation id="5053962746715621840">Search Image with Google Lens</translation>
<translation id="5054374119096692193">See all card options in <ph name="BEGIN_LINK" />Customise Chrome<ph name="END_LINK" /></translation>
<translation id="5056950756634735043">Connecting to the container</translation>
<translation id="5057110919553308744">When you click the extension</translation>
@@ -4511,7 +4511,7 @@ and Ctrl+Alt+Brightness down to zoom out.</translation>
<translation id="5397794290049113714">You</translation>
<translation id="5398497406011404839">Hidden bookmarks</translation>
<translation id="5398572795982417028">Out of bounds page reference, limit is <ph name="MAXIMUM_PAGE" /></translation>
-<translation id="5401426944298678474">Unfollow site</translation>
+<translation id="5401426944298678474">Unfollow Site</translation>
<translation id="5402815541704507626">Download update using mobile data</translation>
<translation id="540296380408672091">Always block cookies on <ph name="HOST" /></translation>
<translation id="5404740137318486384">Press a switch or keyboard key to assign it to '<ph name="ACTION" />'.
@@ -4534,7 +4534,7 @@ You can assign multiple switches to this action.</translation>
<translation id="5425042808445046667">Continue downloading</translation>
<translation id="5425863515030416387">Sign in easily across devices</translation>
<translation id="5427278936122846523">Always Translate</translation>
-<translation id="5427459444770871191">Rotate &amp;clockwise</translation>
+<translation id="5427459444770871191">Rotate &amp;Clockwise</translation>
<translation id="542750953150239272">By continuing, you agree that this device may also automatically download and install updates and apps from Google, your operator and your device's manufacturer, possibly using mobile data. Some of these apps may offer in-app purchases.</translation>
<translation id="5428850089342283580"><ph name="ACCNAME_APP" /> (Update is available)</translation>
<translation id="5429373054983029602">Search your screen with <ph name="VISUAL_SEARCH_PROVIDER" /></translation>
@@ -4592,7 +4592,7 @@ You can assign multiple switches to this action.</translation>
<translation id="5485102783864353244">Add app</translation>
<translation id="5485754497697573575">Restore All Tabs</translation>
<translation id="5486261815000869482">Confirm password</translation>
-<translation id="5486561344817861625">Simulate browser restart</translation>
+<translation id="5486561344817861625">Simulate Browser Restart</translation>
<translation id="5487460042548760727">Rename profile to <ph name="PROFILE_NAME" /></translation>
<translation id="5487521232677179737">Clear data</translation>
<translation id="5488093641312826914">'<ph name="COPIED_ITEM_NAME" />' copied</translation>
@@ -4999,7 +4999,7 @@ You can assign multiple switches to this action.</translation>
<translation id="5891688036610113830">Preferred Wi-Fi networks</translation>
<translation id="5895138241574237353">Restart</translation>
<translation id="5895335062901455404">Your saved preferences and activity will be ready on any Chrome OS Flex device when you sign in with your Google Account. You can choose what to sync in Settings.</translation>
-<translation id="5896436821193322561">Don't allow</translation>
+<translation id="5896436821193322561">Don't Allow</translation>
<translation id="5900302528761731119">Google Profile photo</translation>
<translation id="590036993063074298">Mirroring quality details</translation>
<translation id="5901069264981746702">Your fingerprint data is stored securely and never leaves your <ph name="DEVICE_TYPE" />. <ph name="LINK_BEGIN" />Learn more<ph name="LINK_END" /></translation>
@@ -5009,7 +5009,7 @@ You can assign multiple switches to this action.</translation>
<translation id="5904614460720589786">Couldn't set up <ph name="APP_NAME" /> because of a configuration problem. Please contact your administrator. Error code: <ph name="ERROR_CODE" />.</translation>
<translation id="5906655207909574370">Nearly up to date! Restart your device to finish updating.</translation>
<translation id="5906732635754427568">Data associated with this app will be removed from this device.</translation>
-<translation id="5908474332780919512">Start app when you sign in</translation>
+<translation id="5908474332780919512">Start App When You Sign In</translation>
<translation id="5908695239556627796">Mouse scroll speed</translation>
<translation id="5909379458939060601">Delete this profile and browsing data?</translation>
<translation id="5910363049092958439">Sa&amp;ve Image As...</translation>
@@ -5304,7 +5304,7 @@ You can assign multiple switches to this action.</translation>
<translation id="6198102561359457428">Sign out then sign in again...</translation>
<translation id="6198252989419008588">Change PIN</translation>
<translation id="6200047250927636406">Discard file</translation>
-<translation id="6200151268994853226">Manage extension</translation>
+<translation id="6200151268994853226">Manage Extension</translation>
<translation id="6201608810045805374">Remove this account?</translation>
<translation id="6202304368170870640">You can use your PIN to sign in to or unlock your device.</translation>
<translation id="6206311232642889873">Cop&amp;y Image</translation>
@@ -5316,7 +5316,7 @@ You can assign multiple switches to this action.</translation>
<translation id="6209838773933913227">Component updating</translation>
<translation id="6209908325007204267">Your device includes a Chrome Enterprise Upgrade, but your username is not associated with an enterprise account. Please create an enterprise account by visiting g.co/ChromeEnterpriseAccount on a secondary device.</translation>
<translation id="6210282067670792090">In the address bar, use this keyboard shortcut with shortcuts for search engines and site search</translation>
-<translation id="621172521139737651">{COUNT,plural, =0{Open all in &amp;new tab group}=1{Open in &amp;new tab group}other{Open all ({COUNT}) in &amp;new tab group}}</translation>
+<translation id="621172521139737651">{COUNT,plural, =0{Open All in &amp;New Tab Group}=1{Open in &amp;New Tab Group}other{Open All ({COUNT}) in &amp;New Tab Group}}</translation>
<translation id="6212039847102026977">Show advanced network properties</translation>
<translation id="6212168817037875041">Turn off display</translation>
<translation id="6212752530110374741">Email Link</translation>
@@ -5506,7 +5506,7 @@ You can assign multiple switches to this action.</translation>
<translation id="6387674443318562538">Split vertical</translation>
<translation id="6388429472088318283">Search languages</translation>
<translation id="6388577073199278153">Can't access your mobile account</translation>
-<translation id="6390020764191254941">Move tab to new window</translation>
+<translation id="6390020764191254941">Move Tab to New Window</translation>
<translation id="6393156038355142111">Suggest strong password</translation>
<translation id="6393550101331051049">Allowed to show insecure content</translation>
<translation id="6395423953133416962">Send <ph name="BEGIN_LINK1" />system information<ph name="END_LINK1" /> and <ph name="BEGIN_LINK2" />metrics<ph name="END_LINK2" /></translation>
@@ -5799,7 +5799,7 @@ You can assign multiple switches to this action.</translation>
<translation id="6680442031740878064">Available: <ph name="AVAILABLE_SPACE" /></translation>
<translation id="6680650203439190394">Rate</translation>
<translation id="6681668084120808868">Take photo</translation>
-<translation id="6683087162435654533">Restore all tabs</translation>
+<translation id="6683087162435654533">R&amp;estore All Tabs</translation>
<translation id="6684827949542560880">Downloading the latest update</translation>
<translation id="668599234725812620">Open Google Play</translation>
<translation id="6686490380836145850">Close tabs to the right</translation>
@@ -5938,7 +5938,7 @@ You can assign multiple switches to this action.</translation>
<translation id="6812841287760418429">Keep changes</translation>
<translation id="6813907279658683733">Entire screen</translation>
<translation id="6817174620439930047">Ask when a site wants to use system exclusive messages to access MIDI devices (recommended)</translation>
-<translation id="6818198425579322765">Page language to translate</translation>
+<translation id="6818198425579322765">Page Language to Translate</translation>
<translation id="6818802132960437751">Built-in virus protection</translation>
<translation id="6823174134746916417">Touchpad tap-to-click</translation>
<translation id="6824564591481349393">Copy &amp;Email Address</translation>
@@ -6164,7 +6164,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7025082428878635038">Introducing a new way to navigate with gestures</translation>
<translation id="7025190659207909717">Mobile data service management</translation>
<translation id="7025895441903756761">Security and privacy</translation>
-<translation id="7027258625819743915">{COUNT,plural, =0{Open all in &amp;incognito window}=1{Open in &amp;incognito window}other{Open all ({COUNT}) in &amp;incognito window}}</translation>
+<translation id="7027258625819743915">{COUNT,plural, =0{Open All in &amp;Incognito Window}=1{Open in &amp;Incognito Window}other{Open All ({COUNT}) in &amp;Incognito Window}}</translation>
<translation id="7029307918966275733">Crostini is not installed. Please install Crostini to view credits.</translation>
<translation id="7029809446516969842">Passwords</translation>
<translation id="7030304022046916278">Sends URLs to Safe Browsing to check them</translation>
@@ -6208,7 +6208,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7067396782363924830">Ambient colours</translation>
<translation id="7067725467529581407">Never show this again.</translation>
<translation id="7069811530847688087"><ph name="WEBSITE" /> may require a newer or different kind of security key</translation>
-<translation id="7070484045139057854">This can read and change site data</translation>
+<translation id="7070484045139057854">This Can Read and Change Site Data</translation>
<translation id="7072010813301522126">Shortcut name</translation>
<translation id="7075513071073410194">PKCS #1 MD5 With RSA Encryption</translation>
<translation id="7075625805486468288">Manage HTTPS/SSL certificates and settings</translation>
@@ -6246,7 +6246,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7115361495406486998">No reachable contacts</translation>
<translation id="7117228822971127758">Please try again later</translation>
<translation id="7117247127439884114">Sign in Again...</translation>
-<translation id="711840821796638741">Show managed bookmarks</translation>
+<translation id="711840821796638741">Show Managed Bookmarks</translation>
<translation id="711902386174337313">Read the list of your signed-in devices</translation>
<translation id="711985611146095797">This page allows you to manage your signed-in Google Accounts. <ph name="LINK_BEGIN" />Learn more<ph name="LINK_END" /></translation>
<translation id="7120762240626567834">Chrome Browser and Android traffic will be blocked unless a VPN is connected</translation>
@@ -6287,7 +6287,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7168109975831002660">Minimum font size</translation>
<translation id="7169122689956315694">Turn on notification when devices are nearby</translation>
<translation id="7170236477717446850">Profile picture</translation>
-<translation id="7171000599584840888">Add profile…</translation>
+<translation id="7171000599584840888">Add Profile…</translation>
<translation id="7171259390164035663">Don't enrol</translation>
<translation id="7172470549472604877">{NUM_TABS,plural, =1{Add tab to new group}other{Add tabs to new group}}</translation>
<translation id="7173114856073700355">Open settings</translation>
@@ -6337,11 +6337,11 @@ You can assign multiple switches to this action.</translation>
<translation id="7225179976675429563">Network type missing</translation>
<translation id="7227458944009118910">Apps listed below can handle protocol links as well. Other apps will ask for permission.</translation>
<translation id="7228523857728654909">Screen lock and sign-in</translation>
-<translation id="7230222852462421043">&amp;Restore window</translation>
+<translation id="7230222852462421043">&amp;Restore Window</translation>
<translation id="7230787553283372882">Customise your text size</translation>
<translation id="7231260028442989757">View, dismiss and reply to your phone’s notifications</translation>
<translation id="7232750842195536390">Renaming failed</translation>
-<translation id="723343421145275488">Search images with <ph name="VISUAL_SEARCH_PROVIDER" /></translation>
+<translation id="723343421145275488">Search Images with <ph name="VISUAL_SEARCH_PROVIDER" /></translation>
<translation id="7234010996000898150">Cancelling Linux restore</translation>
<translation id="7235716375204803342">Fetching activities...</translation>
<translation id="7235737137505019098">Your security key does not have enough space for any more accounts.</translation>
@@ -6576,7 +6576,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7470424110735398630">Allowed to see your clipboard</translation>
<translation id="747114903913869239">Error: Unable to decode extension</translation>
<translation id="7471520329163184433">Slower</translation>
-<translation id="7473891865547856676">No, thanks</translation>
+<translation id="7473891865547856676">No, Thanks</translation>
<translation id="747459581954555080">Restore all</translation>
<translation id="747507174130726364">{NUM_DAYS,plural, =1{Immediate return required}other{Return <ph name="DEVICE_TYPE" /> within {NUM_DAYS} days}}</translation>
<translation id="7475671414023905704">Netscape Lost Password URL</translation>
@@ -6632,7 +6632,7 @@ You can assign multiple switches to this action.</translation>
<translation id="7526658513669652747">{NUM_DOWNLOADS,plural, =1{1 more}other{{NUM_DOWNLOADS} more}}</translation>
<translation id="7526989658317409655">Placeholder</translation>
<translation id="7527758104894292229">Update it in your Google Account, <ph name="ACCOUNT" /></translation>
-<translation id="7528224636098571080">Don’t open</translation>
+<translation id="7528224636098571080">Don’t Open</translation>
<translation id="7529411698175791732">Check your Internet connection. If the problem continues, try signing out and signing in again.</translation>
<translation id="7529876053219658589">{0,plural, =1{Close guest}other{Close guest}}</translation>
<translation id="7530016656428373557">Discharge Rate in Watts</translation>
@@ -6742,8 +6742,8 @@ You can assign multiple switches to this action.</translation>
<translation id="7633724038415831385">This is the only time that you’ll wait for an update. On Chromebooks, software updates happen in the background.</translation>
<translation id="7634280112532283638">Spam and fraud reduction</translation>
<translation id="7634566076839829401">Something went wrong. Please try again.</translation>
-<translation id="7635048370253485243">Pinned by your administrator</translation>
-<translation id="7636919061354591437">Install on this device</translation>
+<translation id="7635048370253485243">Pinned by your Administrator</translation>
+<translation id="7636919061354591437">Install on this Device</translation>
<translation id="7637593984496473097">Not enough disk space</translation>
<translation id="7639914187072011620">Failed to fetch the SAML redirect URL from the server</translation>
<translation id="764017888128728"><ph name="PASSWORD_MANAGER_BRAND" /> automatically signs you in to eligible sites with passwords that you saved.</translation>
@@ -7098,12 +7098,12 @@ Press an assigned switch or key to remove assignment.</translation>
<translation id="7957074856830851026">See device information, such as its serial number or asset ID</translation>
<translation id="7957615753207896812">Open keyboard device settings</translation>
<translation id="7959074893852789871">The file contained multiple certificates, some of which were not imported:</translation>
-<translation id="7959665254555683862">New incognito tab</translation>
+<translation id="7959665254555683862">New Incognito &amp;Tab</translation>
<translation id="7961015016161918242">Never</translation>
<translation id="7963001036288347286">Touchpad acceleration</translation>
<translation id="7963608432878156675">This name is visible to other devices for Bluetooth and network connections</translation>
<translation id="7963826112438303517">Your Assistant uses these recordings and your spoken requests to create and update your voice model, which is only stored on devices where you've turned on Voice Match. View or retrain voice activity in Assistant Settings.</translation>
-<translation id="7966241909927244760">C&amp;opy image address</translation>
+<translation id="7966241909927244760">C&amp;opy Image Address</translation>
<translation id="7966571622054096916">{COUNT,plural, =1{1 item in bookmark list}other{{COUNT} items in bookmark list}}</translation>
<translation id="7968072247663421402">Provider options</translation>
<translation id="7968742106503422125">Read and modify data that you copy and paste</translation>
@@ -7319,12 +7319,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="816095449251911490"><ph name="SPEED" /> - <ph name="RECEIVED_AMOUNT" />, <ph name="TIME_REMAINING" /></translation>
<translation id="81610453212785426">With <ph name="BEGIN_LINK" />Privacy Sandbox<ph name="END_LINK" />, Chrome is developing new technologies to safeguard you from cross-site tracking while preserving the open web.</translation>
<translation id="8161293209665121583">Reader mode for web pages</translation>
-<translation id="8162984717805647492">{NUM_TABS,plural, =1{Move tab to new window}other{Move tabs to new window}}</translation>
+<translation id="8162984717805647492">{NUM_TABS,plural, =1{Move Tab to New Window}other{Move Tabs to New Window}}</translation>
<translation id="8165997195302308593">Crostini port forwarding</translation>
<translation id="816704878106051517">{COUNT,plural, =1{a phone number}other{# phone numbers}}</translation>
<translation id="8168071266284693455">Your bookmarks, passwords, history and more are synced on all your devices</translation>
<translation id="8168435359814927499">Content</translation>
-<translation id="8169165065843881617">{NUM_TABS,plural, =1{Add tab to reading list}other{Add tabs to reading list}}</translation>
+<translation id="8169165065843881617">{NUM_TABS,plural, =1{Add Tab to Reading List}other{Add Tabs to Reading List}}</translation>
<translation id="8171334254070436367">Hide all cards</translation>
<translation id="8174047975335711832">Device information</translation>
<translation id="8174876712881364124">Back up to Google Drive. Easily restore data or switch device at any time. This backup includes app data. Backups are uploaded to Google and encrypted using your child's Google Account password. <ph name="BEGIN_LINK1" />Learn More<ph name="END_LINK1" /></translation>
@@ -7401,7 +7401,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8248381369318572865">Access your microphone and analyse your speech</translation>
<translation id="8248887045858762645">Chrome tip</translation>
<translation id="8249048954461686687">OEM folder</translation>
-<translation id="8249615410597138718">Send to your devices</translation>
+<translation id="8249615410597138718">Send to Your Devices</translation>
<translation id="8249672078237421304">Offer to translate pages that aren't in a language you read</translation>
<translation id="8250210000648910632">Out of storage space</translation>
<translation id="8251441930213048644">Refresh now</translation>
@@ -7415,7 +7415,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8260864402787962391">Mouse</translation>
<translation id="8261378640211443080">This extension is not listed in the <ph name="IDS_EXTENSION_WEB_STORE_TITLE" /> and may have been added without your knowledge.</translation>
<translation id="8261506727792406068">Delete</translation>
-<translation id="8263336784344783289">Name this group</translation>
+<translation id="8263336784344783289">Name This Group</translation>
<translation id="8263744495942430914"><ph name="FULLSCREEN_ORIGIN" /> has disabled your mouse cursor.</translation>
<translation id="8264024885325823677">This setting is managed by your administrator.</translation>
<translation id="8264718194193514834">"<ph name="EXTENSION_NAME" />" triggered full screen.</translation>
@@ -7569,7 +7569,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8425213833346101688">Change</translation>
<translation id="8425492902634685834">Pin to task bar</translation>
<translation id="8425768983279799676">You can use your PIN to unlock your device.</translation>
-<translation id="8426111352542548860">Save group</translation>
+<translation id="8426111352542548860">Save Group</translation>
<translation id="8426713856918551002">Enabling</translation>
<translation id="8427213022735114808">Dictation sends your voice to Google to allow voice typing in any text field.</translation>
<translation id="8427292751741042100">embedded on any host</translation>
@@ -7664,13 +7664,13 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8531701051932785007">Enhanced Safe Browsing is off</translation>
<translation id="8534656636775144800">Oops! Something went wrong when trying to join the domain. Please try again.</translation>
<translation id="8535005006684281994">Netscape Certificate Renewal URL</translation>
-<translation id="8536713137312218707">Quick commands</translation>
+<translation id="8536713137312218707">Quick Commands</translation>
<translation id="8536956381488731905">Sound on key-press</translation>
<translation id="8539727552378197395">No (HttpOnly)</translation>
<translation id="8539766201049804895">Upgrade</translation>
<translation id="8540136935098276800">Enter a correctly formatted URL</translation>
<translation id="8540503336857689453">Using a hidden network isn't recommended for security reasons.</translation>
-<translation id="854071720451629801">Mark as read</translation>
+<translation id="854071720451629801">Mark as Read</translation>
<translation id="8540942859441851323">Roaming required by provider</translation>
<translation id="8541462173655894684">Did not find any printers from the print server</translation>
<translation id="8541838361296720865">Press a switch or keyboard key to assign it to '<ph name="ACTION" />'</translation>
@@ -7689,7 +7689,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8557022314818157177">Keep touching your security key until your fingerprint is captured</translation>
<translation id="8557180006508471423">Turn on 'Google Chrome' in Location Services on your Mac</translation>
<translation id="8557856025359704738">Next download is at <ph name="NEXT_DATE_DOWNLOAD" />.</translation>
-<translation id="8560327176991673955">{COUNT,plural, =0{Open all in &amp;new window}=1{Open in &amp;new window}other{Open all ({COUNT}) in &amp;new window}}</translation>
+<translation id="8560327176991673955">{COUNT,plural, =0{Open All in &amp;New Window}=1{Open in &amp;New Window}other{Open All ({COUNT}) in &amp;New Window}}</translation>
<translation id="8561206103590473338">Elephant</translation>
<translation id="8561565784790166472">Proceed with caution</translation>
<translation id="8561853412914299728"><ph name="TAB_TITLE" /> <ph name="EMOJI_PLAYING" /></translation>
@@ -7752,7 +7752,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8630338733867813168">Sleep while charging</translation>
<translation id="8631032106121706562">Petals</translation>
<translation id="863109444997383731">Sites will be blocked from asking to show you notifications. If a site requests notifications, a blocked indicator will appear in the address bar.</translation>
-<translation id="8632104508818855045">You previously chose not to allow any extensions on <ph name="ORIGIN" /></translation>
+<translation id="8632104508818855045">You Previously Chose Not To Allow Any Extensions On <ph name="ORIGIN" /></translation>
<translation id="8633025649649592204">Recent activity</translation>
<translation id="8634348081024879304">You will no longer be able to use your virtual card with Google Pay. <ph name="BEGIN_LINK" />Learn about virtual cards<ph name="END_LINK" /></translation>
<translation id="8635628933471165173">Reloading...</translation>
@@ -7925,7 +7925,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8784626084144195648">Binned Average</translation>
<translation id="8785622406424941542">Stylus</translation>
<translation id="8786824282808281903">When your child sees this icon, a fingerprint can be used for identification or to approve purchases.</translation>
-<translation id="8787575090331305835">{NUM_TABS,plural, =1{Unnamed group – 1 tab}other{Unnamed group – # tabs}}</translation>
+<translation id="8787575090331305835">{NUM_TABS,plural, =1{Unnamed Group – 1 Tab}other{Unnamed Group – # Tabs}}</translation>
<translation id="8791534160414513928">Send a ‘Do Not Track’ request with your browsing traffic</translation>
<translation id="879413103056696865">While the hotspot is on, your <ph name="PHONE_NAME" /> will:</translation>
<translation id="8795916974678578410">New Window</translation>
@@ -7935,7 +7935,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="8803526663383843427">When on</translation>
<translation id="8803953437405899238">Open a new tab with one click</translation>
<translation id="8804419452060773146">Opens in</translation>
-<translation id="8804999695258552249">{NUM_TABS,plural, =1{Move tab to another window}other{Move tabs to another window}}</translation>
+<translation id="8804999695258552249">{NUM_TABS,plural, =1{Move Tab to Another Window}other{Move Tabs to Another Window}}</translation>
<translation id="8805140816472474147">Confirm sync settings to start sync.</translation>
<translation id="8806680466228877631"><ph name="SHORTCUT" /> can reopen accidentally closed tabs</translation>
<translation id="8807632654848257479">Stable</translation>
@@ -8311,7 +8311,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<ph name="LIST_ITEM" />Running Chrome Connectivity Diagnostics
<ph name="END_LIST" /></translation>
<translation id="916607977885256133">Picture in Picture</translation>
-<translation id="9167063903968449027">Show reading list</translation>
+<translation id="9167063903968449027">Show Reading List</translation>
<translation id="9167450455589251456">The profile is not supported</translation>
<translation id="9168436347345867845">Do it later</translation>
<translation id="9169496697824289689">View keyboard shortcuts</translation>
@@ -8410,7 +8410,7 @@ Keep your key file in a safe place. You will need it to create new versions of y
<translation id="971774202801778802">Bookmark URL</translation>
<translation id="972996901592717370">Touch the power button with your finger. Your data is stored securely and never leaves your <ph name="DEVICE_TYPE" />.</translation>
<translation id="973473557718930265">Quit</translation>
-<translation id="975893173032473675">Language to translate into</translation>
+<translation id="975893173032473675">Language to Translate into</translation>
<translation id="976499800099896273">Autocorrect undo dialogue is shown for <ph name="TYPED_WORD" /> corrected to <ph name="CORRECTED_WORD" />. Press up arrow to access, escape to ignore.</translation>
<translation id="978146274692397928">Initial punctuation width is Full</translation>
<translation id="97905529126098460">This window will close after cancellation is complete.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_es-419.xtb b/chromium/chrome/app/resources/generated_resources_es-419.xtb
index c0276e3030f..dd4a406234d 100644
--- a/chromium/chrome/app/resources/generated_resources_es-419.xtb
+++ b/chromium/chrome/app/resources/generated_resources_es-419.xtb
@@ -2860,7 +2860,7 @@ y Ctrl + Alt + Disminuir brillo para alejar.</translation>
<translation id="3755411799582650620">El <ph name="PHONE_NAME" /> ahora también puede desbloquear este dispositivo <ph name="DEVICE_TYPE" />.</translation>
<translation id="375636864092143889">El sitio está usando el micrófono</translation>
<translation id="3756485814916578707">Transmitiendo pantalla</translation>
-<translation id="3756578970075173856">Establece un PIN</translation>
+<translation id="3756578970075173856">Establecer un PIN</translation>
<translation id="3756795331760037744">Permite que Asistente de Google utilice información de la pantalla de <ph name="SUPERVISED_USER_NAME" /> para que le sea útil</translation>
<translation id="3757733214359997190">No se encontraron sitios</translation>
<translation id="375841316537350618">Descargando secuencia de comandos proxy...</translation>
@@ -3860,10 +3860,10 @@ y Ctrl + Alt + Disminuir brillo para alejar.</translation>
<translation id="473936925429402449">Seleccionado, contenido adicional: <ph name="CURRENT_ELEMENT" /> de <ph name="TOTAL_ELEMENTS" /></translation>
<translation id="4739639199548674512">Tickets</translation>
<translation id="4742334355511750246">No puede mostrar imágenes</translation>
-<translation id="4742970037960872810">Quitar el texto destacado</translation>
+<translation id="4742970037960872810">Dejar de destacar</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />Obtener información sobre cómo actualizar las aplicaciones<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{Activar el sonido del sitio}other{Activar el sonido de los sitios}}</translation>
-<translation id="474609389162964566">Accede a tu Asistente mediante "Hey Google"</translation>
+<translation id="474609389162964566">Accede a tu Asistente con "Hey Google"</translation>
<translation id="4746351372139058112">Mensajes</translation>
<translation id="4748783296226936791">Por lo general, los sitios se conectan a dispositivos HID para usarlos en funciones que utilizan teclados poco habituales, controles de juegos y otros dispositivos.</translation>
<translation id="4750185073185658673">Revisa algunos permisos en tu teléfono. Asegúrate de que las conexiones Bluetooth y Wi-Fi del teléfono estén activadas.</translation>
@@ -6134,7 +6134,7 @@ Puedes asignar varios interruptores a esta acción.</translation>
<translation id="7009709314043432820"><ph name="APP_NAME" /> está utilizando la cámara</translation>
<translation id="701080569351381435">Ver código fuente</translation>
<translation id="7014174261166285193">Error de instalación</translation>
-<translation id="7014480873681694324">Quitar el texto destacado</translation>
+<translation id="7014480873681694324">Dejar de destacar</translation>
<translation id="7017004637493394352">Di "OK Google" otra vez.</translation>
<translation id="7017219178341817193">Agregar nueva página</translation>
<translation id="7017354871202642555">No es posible configurar el modo después de que se ha configurado la ventana.</translation>
@@ -7022,7 +7022,7 @@ Presiona un interruptor o tecla que ya esté asignado para quitar la asignación
<translation id="7903925330883316394">Utilidad: <ph name="UTILITY_TYPE" /></translation>
<translation id="7904526211178107182">Haz que los puertos de Linux estén disponibles para otros dispositivos en tu red.</translation>
<translation id="7907837847548254634">Destacar brevemente el objeto enfocado</translation>
-<translation id="7908378463497120834">Lo sentimos, al menos una partición en tu dispositivo de almacenamiento externo no se pudo montar.</translation>
+<translation id="7908378463497120834">Lo sentimos, no se pudo activar al menos una partición en tu dispositivo de almacenamiento externo.</translation>
<translation id="7909324225945368569">Cambia el nombre de tu perfil</translation>
<translation id="7909969815743704077">Descargado en el modo de navegación de incógnito</translation>
<translation id="7909986151924474987">Es posible que no puedas volver a instalar este perfil</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_es.xtb b/chromium/chrome/app/resources/generated_resources_es.xtb
index bb8fc0fbe8f..6d7a3114f54 100644
--- a/chromium/chrome/app/resources/generated_resources_es.xtb
+++ b/chromium/chrome/app/resources/generated_resources_es.xtb
@@ -553,7 +553,7 @@ Los permisos que hayas dado a aplicaciones se pueden aplicar a esta cuenta. Pued
<translation id="1542514202066550870">Esta pestaña está proyectando contenido de realidad virtual a un visor.</translation>
<translation id="1543284117603151572">Importado desde Edge</translation>
<translation id="1544588554445317666">Intenta utilizar un nombre de archivo más corto o guardarlo en otra carpeta.</translation>
-<translation id="1545177026077493356">Modo kiosco automático</translation>
+<translation id="1545177026077493356">Modo Kiosco automático</translation>
<translation id="1545749641540134597">Escanea el código QR</translation>
<translation id="1545775234664667895">El tema <ph name="THEME_NAME" /> se ha instalado.</translation>
<translation id="1546280085599573572">Esta extensión ha cambiado la página que se muestra al hacer clic en el botón de página principal.</translation>
@@ -2344,7 +2344,7 @@ Puedes gestionar la configuración de esta cuenta instalando la aplicación Fami
<translation id="3238192140106069382">Conectando y verificando</translation>
<translation id="3239373508713281971">Se ha quitado el límite de tiempo de <ph name="APP_NAME" /></translation>
<translation id="3241680850019875542">Selecciona el directorio raíz de las extensiones que quieras empaquetar. Para actualizar una extensión, debes seleccionar también el archivo de clave privada que se va a volver a usar.</translation>
-<translation id="3242289508736283383">La aplicación con el atributo del archivo de manifiesto "kiosk_only" se debe instalar en el modo kiosco de ChromeOS</translation>
+<translation id="3242289508736283383">La aplicación con el atributo del archivo de manifiesto "kiosk_only" se debe instalar en el modo Kiosco de ChromeOS</translation>
<translation id="3242665648857227438">Este perfil usa la configuración de proxy de ChromeOS.</translation>
<translation id="3244294424315804309">Seguir silenciando el sonido</translation>
<translation id="324849028894344899"><ph name="WINDOW_TITLE" />: error de red</translation>
@@ -4119,7 +4119,7 @@ Puedes gestionar la configuración de esta cuenta instalando la aplicación Fami
<translation id="5008936837313706385">Nombre de la actividad</translation>
<translation id="5009463889040999939">Cambiando el nombre del perfil. Este proceso puede durar unos minutos.</translation>
<translation id="5010043101506446253">Entidad emisora de certificados</translation>
-<translation id="501057610015570208">La aplicación con el atributo del archivo de manifiesto "kiosk_only" se debe instalar en el modo kiosco de ChromeOS Flex</translation>
+<translation id="501057610015570208">La aplicación con el atributo del archivo de manifiesto "kiosk_only" se debe instalar en el modo Kiosco de ChromeOS Flex</translation>
<translation id="5015344424288992913">Resolviendo proxy...</translation>
<translation id="5016491575926936899">Puedes enviar mensajes de texto desde tu ordenador, compartir tu conexión a Internet, responder a notificaciones de conversaciones y desbloquear tu <ph name="DEVICE_TYPE" /> con el teléfono.<ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />Más información<ph name="LINK_END" /></translation>
<translation id="5017643436812738274">Puedes desplazarte por las páginas con un cursor de texto. Pulsa Ctrl + tecla de búsqueda + 7 para desactivar esta función.</translation>
@@ -5369,7 +5369,7 @@ Exponente público (<ph name="PUBLIC_EXPONENT_NUM_BITS" /> bits):
<translation id="6271780480930459892">Ponte en contacto con tu administrador para obtener la versión más reciente.</translation>
<translation id="6272643420381259437">Se ha producido un error (<ph name="ERROR" />) al descargar el complemento</translation>
<translation id="6273677812470008672">Calidad</translation>
-<translation id="6274202259872570803">Captura de vídeo</translation>
+<translation id="6274202259872570803">Grabación de pantalla</translation>
<translation id="6276210637549544171">El proxy <ph name="PROXY_SERVER" /> requiere un nombre de usuario y una contraseña.</translation>
<translation id="6277105963844135994">Tiempo de espera de red agotado</translation>
<translation id="6277518330158259200">H&amp;acer captura de pantalla</translation>
@@ -6296,7 +6296,7 @@ Exponente público (<ph name="PUBLIC_EXPONENT_NUM_BITS" /> bits):
<translation id="7198503619164954386">Debes usar un dispositivo registrado por una empresa</translation>
<translation id="7199158086730159431">Obtener a&amp;yuda</translation>
<translation id="7200083590239651963">Seleccionar configuración</translation>
-<translation id="720110658997053098">Mantener este dispositivo en modo kiosco de forma permanente</translation>
+<translation id="720110658997053098">Mantener este dispositivo en modo Kiosco de forma permanente</translation>
<translation id="7201118060536064622">Se ha eliminado "<ph name="DELETED_ITEM_NAME" />"</translation>
<translation id="7201420661433230412">Ver archivos</translation>
<translation id="7203150201908454328">Ampliado</translation>
@@ -6367,7 +6367,7 @@ Exponente público (<ph name="PUBLIC_EXPONENT_NUM_BITS" /> bits):
<translation id="7269736181983384521">Uso de datos de Compartir con Nearby</translation>
<translation id="7272674038937250585">No se ha especificado ninguna descripción</translation>
<translation id="7273110280511444812">fecha de última conexión: <ph name="DATE" /></translation>
-<translation id="7273970016743909808">Usarás una Licencia de Kiosco y Señalización, que solo permite que el dispositivo funcione en modo kiosco o de señalización. Si quieres que los usuarios inicien sesión en el dispositivo, vuelve y regístrate usando la Licencia de Chrome Enterprise.</translation>
+<translation id="7273970016743909808">Usarás una Licencia de Kiosco y Señalización, que solo permite que el dispositivo funcione en modo Kiosco o de señalización. Si quieres que los usuarios inicien sesión en el dispositivo, vuelve y regístrate usando la Licencia de Chrome Enterprise.</translation>
<translation id="727441411541283857"><ph name="PERCENTAGE" />% - <ph name="TIME" /> hasta completar la carga</translation>
<translation id="727952162645687754">Error de descarga</translation>
<translation id="7280041992884344566">Chrome no ha podido buscar software dañino</translation>
@@ -8094,7 +8094,7 @@ Guarda tu archivo de clave en un lugar seguro, ya que lo necesitarás para crear
<translation id="89720367119469899">Esc</translation>
<translation id="8972513834460200407">Consulta al administrador de tu red para asegurarte de que el cortafuegos no está bloqueando las descargas procedentes de los servidores de Google.</translation>
<translation id="8973557916016709913">Quitar el nivel de zoom</translation>
-<translation id="8973596347849323817">Personaliza este dispositivo para adaptarlo a tus necesidades. Puedes modificar las funciones de accesibilidad más tarde en Configuración.</translation>
+<translation id="8973596347849323817">Puedes personalizar este dispositivo para adaptarlo a tus necesidades. Estas funciones de accesibilidad se pueden cambiar más tarde en Configuración.</translation>
<translation id="897414447285476047">El archivo de destino está incompleto debido a un problema relacionado con la conexión.</translation>
<translation id="897525204902889653">Servicio de cuarentena</translation>
<translation id="8975396729541388937">Puedes cancelar la suscripción en cualquier momento haciendo clic en el enlace de los correos que recibas.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_eu.xtb b/chromium/chrome/app/resources/generated_resources_eu.xtb
index 9c7b6a46204..3bb13d525fe 100644
--- a/chromium/chrome/app/resources/generated_resources_eu.xtb
+++ b/chromium/chrome/app/resources/generated_resources_eu.xtb
@@ -1797,7 +1797,7 @@ Voice Match-ekin, Google-ren Laguntzailea zerbitzuak <ph name="SUPERVISED_USER_N
<translation id="2687407218262674387">Google-ren Zerbitzu-baldintzak</translation>
<translation id="2687621393791886981">Galdetu geroago</translation>
<translation id="2688196195245426394">Errore bat gertatu da gailua zerbitzariarekin erregistratzean: <ph name="CLIENT_ERROR" />.</translation>
-<translation id="2688734475209947648">Ez duzu pasahitza gogoratu beharko. <ph name="ACCOUNT" /> kontuko Google-ren Pasahitz-kudeatzailea aplikazioan gordeko da.</translation>
+<translation id="2688734475209947648">Ez duzu pasahitza gogoratu beharko. <ph name="ACCOUNT" /> kontuko Google-ren Pasahitz-kudeatzailea zerbitzuan gordeko da.</translation>
<translation id="2690024944919328218">Erakutsi hizkuntza-aukerak</translation>
<translation id="2691385045260836588">Modeloa</translation>
<translation id="2691440343905273290">Aldatu idazketa-metodoaren ezarpenak</translation>
@@ -3954,7 +3954,7 @@ Txartel adimenduna sartuta edukitzea eskatzen du <ph name="DOMAIN" /> domeinuak.
<translation id="4841741146571978176">Beharrezkoa den makina birtual bat ez dago. Aurrera egiteko, konfiguratu <ph name="VM_TYPE" />.</translation>
<translation id="4842976633412754305">Orria autentifikatu gabeko iturburuetako scriptak kargatu nahian dabil.</translation>
<translation id="4844333629810439236">Bestelako teklatuak</translation>
-<translation id="4844633725025837809">Seguruagoa izan dadin, enkriptatu pasahitzak gailuan bertan Google-ren Pasahitz-kudeatzailea atalean gorde aurretik</translation>
+<translation id="4844633725025837809">Seguruagoa izan dadin, enkriptatu pasahitzak gailuan bertan Google-ren Pasahitz-kudeatzailea zerbitzuan gorde aurretik</translation>
<translation id="4846680374085650406">Administratzailearen gomendioa betetzen ari zara ezarpenari dagokionez.</translation>
<translation id="4847902821209177679"><ph name="TOPIC_SOURCE" /> <ph name="TOPIC_SOURCE_DESC" /> hautatuta daukazula, sakatu Sartu <ph name="TOPIC_SOURCE" /> zerbitzuko albumak hautatzeko</translation>
<translation id="4848191975108266266">Google-ren Laguntzailea zerbitzuaren "Hey Google" agindua</translation>
@@ -4278,7 +4278,7 @@ Txartel adimenduna sartuta edukitzea eskatzen du <ph name="DOMAIN" /> domeinuak.
<translation id="5177549709747445269">Mugikorreko datuak erabiltzen ari zara</translation>
<translation id="5178667623289523808">Bilatu aurrekoa</translation>
<translation id="5181140330217080051">Deskargatzen</translation>
-<translation id="5181172023548002891">Google-ren Pasahitz-kudeatzailea atalean (<ph name="ACCOUNT" /> kontuan)</translation>
+<translation id="5181172023548002891">Google-ren Pasahitz-kudeatzailea zerbitzuan (<ph name="ACCOUNT" /> kontuan)</translation>
<translation id="5184063094292164363">&amp;JavaScript kontsola</translation>
<translation id="5184209580557088469">Bada erabiltzaile-izen hori duen zerbitzu-eskaera bat</translation>
<translation id="5184662919967270437">Gailua eguneratzen</translation>
@@ -6765,7 +6765,7 @@ Inoiz Voice Match erabiltzeari utzi nahi badiozu, ken ezazu Laguntzailea zerbitz
<translation id="7665082356120621510">Gorde tamaina</translation>
<translation id="7665369617277396874">Gehitu kontu bat</translation>
<translation id="7668002322287525834">{NUM_WEEKS,plural, =1{Itzuli <ph name="DEVICE_TYPE" /> gailua {NUM_WEEKS} asteko epean}other{Itzuli <ph name="DEVICE_TYPE" /> gailua {NUM_WEEKS} asteko epean}}</translation>
-<translation id="7668423670802040666">Google-ren Pasahitz-kudeatzailea atalean (<ph name="ACCOUNT" /> kontuan)</translation>
+<translation id="7668423670802040666">Google-ren Pasahitz-kudeatzailea zerbitzuan (<ph name="ACCOUNT" /> kontuan)</translation>
<translation id="7669825497510425694">{NUM_ATTEMPTS,plural, =1{PINa okerra da. Saiakera bakarra geratzen zaizu.}other{PINa okerra da. # saiakera geratzen zaizkizu.}}</translation>
<translation id="7670434942695515800">Errendimendurik onena lortzeko, berritu azken bertsiora. Fitxategien babeskopiak egitea gomendatzen dugu. Horrela, ez da daturik galduko bertsio-berritzea osatu ezin bada. Bertsio-berritzen hasi ondoren, Linux itzali egingo da. Aurrera egin aurretik, gorde irekita dauden fitxategiak. <ph name="LINK_START" />Lortu informazio gehiago<ph name="LINK_END" /></translation>
<translation id="7671130400130574146">Erabili sistemaren izenaren barra eta ertzak</translation>
@@ -7159,7 +7159,7 @@ Gorde gakoen fitxategia leku seguru batean. Zure luzapenaren bertsio berriak sor
<translation id="803771048473350947">Fitxategia</translation>
<translation id="8041089156583427627">Bidali oharrak</translation>
<translation id="8042142357103597104">Testuaren opakutasuna</translation>
-<translation id="8042331986490021244">Pasahitzak gailuan bertan enkriptatzen dira Google-ren Pasahitz-kudeatzailea atalean gorde aurretik</translation>
+<translation id="8042331986490021244">Pasahitzak gailuan bertan enkriptatzen dira Google-ren Pasahitz-kudeatzailea zerbitzuan gorde aurretik</translation>
<translation id="8044262338717486897"><ph name="LINUX_APP_NAME" /> aplikazioak ez du erantzuten.</translation>
<translation id="8044899503464538266">Motela</translation>
<translation id="8045253504249021590">Sinkronizazioa gelditu egin da Google-ren Panela atalean.</translation>
@@ -8089,7 +8089,7 @@ Txartel adimenduna sartuta edukitzea eskatzen du <ph name="DOMAIN" /> domeinuak.
<translation id="89720367119469899">Ihes-tekla</translation>
<translation id="8972513834460200407">Galdetu sarearen administratzaileari suebakia Google-ren zerbitzariaren deskargak blokeatzen ari den.</translation>
<translation id="8973557916016709913">Kendu zooma</translation>
-<translation id="8973596347849323817">Doitu gailua zure beharretara. Ezarpenak atalean alda ditzakezu Erabilerraztasun-eginbideak.</translation>
+<translation id="8973596347849323817">Doitu gailua zure beharretara. Ezarpenak atalean alda ditzakezu erabilerraztasun-eginbideak.</translation>
<translation id="897414447285476047">Helmugako fitxategia ez dago osorik konexio-arazo bat izan delako.</translation>
<translation id="897525204902889653">Berrogeialdi-zerbitzua</translation>
<translation id="8975396729541388937">Harpidetza kentzeko, sakatu horretarako esteka jasotzen dituzun mezu elektronikoetan.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_fa.xtb b/chromium/chrome/app/resources/generated_resources_fa.xtb
index 55b05b85646..05d479b3f7e 100644
--- a/chromium/chrome/app/resources/generated_resources_fa.xtb
+++ b/chromium/chrome/app/resources/generated_resources_fa.xtb
@@ -79,7 +79,7 @@
<ph name="BEGIN_PARAGRAPH4" />اگر تنظیم «Ùعالیت وب Ùˆ برنامه» تکمیلی برای «حساب Google» Ùرزندتان روشن باشد، ممکن است داده‌های Ùرزندتان در «حساب Google» او ذخیره شود. در families.google.comØŒ درباره این تنظیمات Ùˆ نحوه تغییر آن‌ها بیشتر بدانید.<ph name="END_PARAGRAPH4" />
<ph name="BEGIN_PARAGRAPH4" />اگر مالک دستگاه «ارسال داده‌های استÙاده Ùˆ عیب‌یابی» را روشن کند، ممکن است گزارش‌های خرابی نیز برای Google بارگذاری شود.<ph name="END_PARAGRAPH3" /></translation>
<translation id="1076176485976385390">پیمایش کردن صÙحه‌ها با نشانگر نوشتار</translation>
-<translation id="1076382954055048850">نمایش جلسه‌های دیگر ارسال محتوا</translation>
+<translation id="1076382954055048850">نمایش جلسه‌های دیگر پخش محتوا</translation>
<translation id="1076698951459398590">Ùعال کردن زمینه</translation>
<translation id="1076766328672150609">Ùرزندتان می‌تواند برای باز کردن Ù‚ÙÙ„ دستگاه از پین استÙاده کند.</translation>
<translation id="1076818208934827215">Microsoft Internet Explorer</translation>
@@ -176,7 +176,7 @@
<translation id="1163931534039071049">&amp;نمای منبع قاب</translation>
<translation id="1164891049599601209">در سایت Ùریب‌دهنده وارد شده است</translation>
<translation id="1165039591588034296">خطا</translation>
-<translation id="1166212789817575481">بستن برگه‌ها به چپ</translation>
+<translation id="1166212789817575481">بستن برگه‌های سمت راست</translation>
<translation id="1166583374608765787">مرور به‌روزرسانی نام</translation>
<translation id="1166596238782048887"><ph name="TAB_TITLE" /> به میزکار <ph name="DESK_TITLE" /> تعلق دارد</translation>
<translation id="1168020859489941584">باز کردن با <ph name="TIME_REMAINING" />...</translation>
@@ -683,7 +683,7 @@
<translation id="1643072738649235303">â€Ø§Ù…ضای X9.62 ECDSA با SHA-1</translation>
<translation id="1643921258693943800">â€Ø¨Ø±Ø§ÛŒ استÙاده از «هم‌رسانی با اطراÙ»، بلوتوث Ùˆ Wi-Fi را روشن کنید</translation>
<translation id="1644574205037202324">سابقه</translation>
-<translation id="1644852018355792105">«کلیدواژه بلوتوث» مربوط به دستگاه <ph name="DEVICE" /> را وارد کنید</translation>
+<translation id="1644852018355792105">«گذرکلید بلوتوث» مربوط به دستگاه <ph name="DEVICE" /> را وارد کنید</translation>
<translation id="1645004815457365098">منبع ناشناخته</translation>
<translation id="1645516838734033527">â€Ø¨Ø±Ø§ÛŒ ایمن نگه‌داشتن <ph name="DEVICE_TYPE" />ØŒ Smart Lock در تلÙنتان Ù‚ÙÙ„ صÙحه لازم دارد.</translation>
<translation id="1646982517418478057">برای رمزگذاری این گواهی، گذرواژه‌ای وارد کنید</translation>
@@ -826,7 +826,7 @@
<translation id="1778457539567749232">علامت‌گذاری به عنوان خوانده نشده</translation>
<translation id="1778991607452011493">ارسال گزارش‌های اشکال‌زدایی (توصیه می‌شود)</translation>
<translation id="1779468444204342338">حداقل</translation>
-<translation id="1779652936965200207">لطÙاً این کلیدواژه را در "<ph name="DEVICE_NAME" />" وارد کنید:</translation>
+<translation id="1779652936965200207">لطÙاً این گذرکلید را در "<ph name="DEVICE_NAME" />" وارد کنید:</translation>
<translation id="177989070088644880">برنامه (<ph name="ANDROID_PACKAGE_NAME" />)</translation>
<translation id="1780152987505130652">بستن گروه</translation>
<translation id="1780273119488802839">وارد کردن نشانک‌ها…</translation>
@@ -1175,7 +1175,7 @@
<translation id="2105809836724866556"><ph name="MODULE_TITLE" /> پنهان شده است</translation>
<translation id="2108349519800154983">{COUNT,plural, =1{شماره تلÙÙ†}one{# شماره تلÙÙ†}other{# شماره تلÙÙ†}}</translation>
<translation id="211144231511833662">پاک کردن نوع</translation>
-<translation id="2111670510994270194">برگه جدید در راست</translation>
+<translation id="2111670510994270194">برگه جدید در سمت راست</translation>
<translation id="2112554630428445878">خوش آمدید، <ph name="USERNAME" /></translation>
<translation id="21133533946938348">کوچک کردن برگه</translation>
<translation id="2113479184312716848">باز کردن &amp;Ùایل...</translation>
@@ -1337,7 +1337,7 @@
<translation id="2263189956353037928">خروج از سیستم و ورود دوباره به آن</translation>
<translation id="2263371730707937087">نرخ بازآوری صÙحه</translation>
<translation id="2263679799334060788">â€Ø¨Ø§Ø²Ø®ÙˆØ±Ø¯ شما به ما Ú©Ù…Ú© می‌کند Google Cast را بهبود ببخشیم Ùˆ بابت ارائه آن از شما سپاس‌گزاریم.
- برای دریاÙت راهنمایی درباره عیب‌یابی مشکلات ارسال محتوا، لطÙاً به
+ برای دریاÙت راهنمایی درباره عیب‌یابی مشکلات پخش محتوا، لطÙاً به
<ph name="BEGIN_LINK" />
مرکز راهنمایی<ph name="END_LINK" /> مراجعه کنید.</translation>
<translation id="22665427234727190">وقتی سایتی می‌خواهد به دستگاه‌های مجهز به بلوتوث دسترسی پیدا کند سؤال شود (توصیه می‌شود)</translation>
@@ -2196,7 +2196,7 @@
<translation id="3046910703532196514">صÙحهٔ وب، کامل</translation>
<translation id="304747341537320566">موتورهای Ú¯Ùتار</translation>
<translation id="3048336643003835855">â€Ø¯Ø³ØªÚ¯Ø§Ù‡â€ŒÙ‡Ø§ÛŒ HID از Ùروشنده <ph name="VENDOR_ID" /></translation>
-<translation id="3048917188684939573">گزارش‌های ارسال محتوا و دستگاه‌ها</translation>
+<translation id="3048917188684939573">گزارش‌های پخش محتوا و دستگاه‌ها</translation>
<translation id="3051250416341590778">اندازه نمایش</translation>
<translation id="3053013834507634016">کاربرد کلید گواهی</translation>
<translation id="3053273573829329829">Ùعال کردن پین کاربر</translation>
@@ -2309,7 +2309,7 @@
<translation id="3164329792803560526">درحال هم‌رسانی این برگه با <ph name="APP_NAME" /></translation>
<translation id="3165390001037658081">برخی شرکت‌های مخابراتی ممکن است این قابلیت را مسدود کنند.</translation>
<translation id="3170072451822350649">همچنین می‌توانید از ورود به سیستم صرÙ‌نظر کرده، <ph name="LINK_START" />به عنوان مهمان مرور کنید<ph name="LINK_END" />.</translation>
-<translation id="31774765611822736">برگه جدید در چپ</translation>
+<translation id="31774765611822736">برگه جدید در سمت چپ</translation>
<translation id="3177909033752230686">زبان صÙحه:</translation>
<translation id="3179982752812949580">قلم نوشتار</translation>
<translation id="3181954750937456830">مرور ایمن (از شما Ùˆ دستگاهتان درمقابل سایت‌های خطرناک محاÙظت می‌کند)</translation>
@@ -2503,7 +2503,7 @@
<translation id="3393582007140394275">محتوای صÙحه ارسال نشد.</translation>
<translation id="3394850431319394743">سایت‌هایی Ú©Ù‡ مجاز هستند از شناسه‌ها برای پخش محتوای محاÙظت‌شده استÙاده کنند</translation>
<translation id="3396744558790608201">â€Ø¨Ø±Ø§ÛŒ جستجوی هر بخشی از سایت Ùˆ دریاÙت اطلاعات بیشتر درباره محتوای بصری Ú©Ù‡ هنگام مرور Ùˆ خرید در وب می‌بینید، کلیک راست کنید Ùˆ «جستجوی تصاویر با «لنز Google»» را انتخاب کنید.</translation>
-<translation id="3396800784455899911">â€Ø¨Ø§ کلیک کردن روی دکمه «پذیرÙتن Ùˆ ادامه»، با پردازش مربوط به این سرویس‌های Google Ú©Ù‡ در بالا توضیح داده شد مواÙقت می‌کنید.</translation>
+<translation id="3396800784455899911">â€Ø¨Ø§ کلیک کردن روی دکمه «پذیرÙتن Ùˆ ادامه دادن»، با پردازش مربوط به این سرویس‌های Google Ú©Ù‡ در بالا توضیح داده شد مواÙقت می‌کنید.</translation>
<translation id="339722927132407568">ثابت می‌ماند</translation>
<translation id="3399432415385675819">اعلان‌ها غیرÙعال خواهند شد</translation>
<translation id="3400390787768057815"><ph name="WIDTH" /> × <ph name="HEIGHT" /> (<ph name="REFRESH_RATE" /> هرتز) - درهم‌باÙته</translation>
@@ -3045,7 +3045,7 @@
<translation id="3900966090527141178">صادر کردن گذرواژه‌ها</translation>
<translation id="3903187154317825986">صÙحه‌کلید داخلی</translation>
<translation id="3904326018476041253">خدمات مکان</translation>
-<translation id="3904849010307028014">براساس تعاملتان با یک سایت (مثلاً مرتباً وارد شدن به سیستم حسابی خاص)، این سایت می‌تواند کد اطمینان برای مرورگرتان صادر کنند. بعداً، اگر سایت‌های دیگری که از آن‌ها بازدید می‌کنید کد اطمینان معتبری را جستجو و پیدا کنند، احتمال اینکه با شما مثل یک انسان (و نه روبات) برخورد کنند بیشتر است.</translation>
+<translation id="3904849010307028014">براساس تعاملتان با یک سایت (مثلاً مرتباً وارد شدن به سیستم حسابی خاص)، این سایت می‌تواند کد اطمینان برای مرورگرتان صادر کنند. بعداً، اگر سایت‌های دیگری که از آن‌ها بازدید می‌کنید کد اطمینان معتبری را جستجو و پیدا کنند، احتمال اینکه با شما مثل یک انسان (و نه ربات) برخورد کنند بیشتر است.</translation>
<translation id="3905761538810670789">تعمیر برنامه</translation>
<translation id="3908393983276948098"><ph name="PLUGIN_NAME" /> قدیمی است</translation>
<translation id="3908501907586732282">Ùعال کردن برنامه اÙزودنی</translation>
@@ -3480,7 +3480,7 @@
<translation id="4348766275249686434">جمع‌آوری خطاها</translation>
<translation id="4349828822184870497">Ù…Ùید</translation>
<translation id="4350230709416545141">دسترسی <ph name="HOST" /> به مکان شما همیشه مسدود شود</translation>
-<translation id="4350782034419308508">Hey Google</translation>
+<translation id="4350782034419308508">Ok Google</translation>
<translation id="4351770750390404505">â€<ph name="BEGIN_PARAGRAPH1" />برای ارائه بهترین تجربه، <ph name="DEVICE_OS" /> داده‌های سخت‌اÙزار مربوط دستگاه‌ها را جمع‌آوری می‌کند Ùˆ آن‌ها را با Google هم‌رسانی می‌کند تا مشخص شود Ú†Ù‡ به‌روزرسانی‌هایی باید ارائه شود. درصورت تمایل، می‌توانید به Google اجازه دهید از این داده‌ها برای اهدا٠دیگری مانند پشتیبانی Ùˆ بهبود بخشیدن به سرویس Ùˆ تجربه <ph name="DEVICE_OS" /> استÙاده کند.<ph name="END_PARAGRAPH1" />
<ph name="BEGIN_PARAGRAPH2" />می‌توانید در این دستگاه به سیستم وارد شوید Ùˆ به بخش CHROMEOSFLEX_HARDWARE_INFO در chrome://system بروید تا این موارد را ببینید: داده‌های ارسال‌شده به Google برای Ùیلتر کردن به‌روزرسانی‌ها Ùˆ همچنین هر نمونه دیگری از انتخاب هم‌رسانی داده با Google.<ph name="END_PARAGRAPH2" />
<ph name="BEGIN_PARAGRAPH3" />برای دریاÙت جزئیات بیشتر درباره داده‌هایی Ú©Ù‡ <ph name="DEVICE_OS" /> ممکن است با Google هم‌رسانی کند Ùˆ نحوه استÙاده از این داده‌ها، از g.co/flex/HWDataCollection بازدید کنید.<ph name="END_PARAGRAPH3" /></translation>
@@ -3494,7 +3494,7 @@
<translation id="4361142739114356624">کلید خصوصی برای این گواهی کارخواه موجود نیست یا نامعتبر است</translation>
<translation id="4361745360460842907">باز کردن به‌عنوان برگه</translation>
<translation id="4362675504017386626"><ph name="ACCOUNT_EMAIL" /> حساب پیش‌Ùرض در <ph name="DEVICE_TYPE" /> شما است</translation>
-<translation id="4363771538994847871">هیچ مقصدی برای ارسال محتوا پیدا نشد. نیاز به راهنمایی دارید؟</translation>
+<translation id="4363771538994847871">هیچ مقصدی برای پخش محتوا پیدا نشد. نیاز به راهنمایی دارید؟</translation>
<translation id="4364327530094270451">خربزه</translation>
<translation id="4364567974334641491"><ph name="APP_NAME" /> در حال اشتراک‌گذاری یک پنجره است.</translation>
<translation id="4364830672918311045">نمایش اعلان‌ها</translation>
@@ -3529,7 +3529,7 @@
<translation id="4400367121200150367">سایت‌هایی که هرگز گذرواژه را ذخیره نمی‌کنند در اینجا نشان داده نخواهند شد</translation>
<translation id="4400632832271803360">برای تغییر عملکرد کلیدهای ردی٠بالا، کلید «راه‌انداز» را نگه‌دارید</translation>
<translation id="4400963414856942668">برای نشانک‌گذاری یک برگه، می‌توانید روی ستاره کلیک کنید</translation>
-<translation id="4401912261345737180">برای ارسال محتوا، بااستÙاده از کد متصل شوید</translation>
+<translation id="4401912261345737180">برای پخش محتوا، بااستÙاده از کد متصل شوید</translation>
<translation id="4402755511846832236">از اینکه سایت‌ها بدانند Ú†Ù‡ زمانی به‌صورت Ùعال از این دستگاه استÙاده می‌کنید جلوگیری می‌شود</translation>
<translation id="4403266582403435904">â€Ù‡Ø±Ø²Ù…ان خواستید به‌آسانی داده‌هایتان را بازیابی کنید یا دستگاه‌هایتان را عوض کنید. داده‌های پشتیبان در Google بارگذاری می‌شوند Ùˆ با گذرواژه «حساب Google» Ùرزندتان رمزگذاری می‌شوند.</translation>
<translation id="4403775189117163360">پوشه دیگری را انتخاب کنید</translation>
@@ -4671,7 +4671,7 @@
<translation id="5554403733534868102">پس از این، نیازی نیست برای به‌روزرسانی‌ها صبر کنید</translation>
<translation id="5554489410841842733">این نماد وقتی قابل رؤیت است Ú©Ù‡ برنامهٔ اÙزودنی بتواند برای صÙحه Ùعلی کار کند.</translation>
<translation id="5554720593229208774">ارائه دهنده مجوز ایمیل</translation>
-<translation id="5555363196923735206">چرخش دوربین</translation>
+<translation id="5555363196923735206">چرخاندن دوربین</translation>
<translation id="5555525474779371165">انتخاب ویژگی Ø­Ùاظتی «مرور ایمن»</translation>
<translation id="5556459405103347317">تازه‌سازی</translation>
<translation id="5558125320634132440">این سایت مسدود شده است زیرا احتمالاً حاوی محتوای بزرگ‌سالان است</translation>
@@ -4983,7 +4983,7 @@
<translation id="5870155679953074650">خطاهای سخت‌اÙزاری</translation>
<translation id="5875534259258494936">هم‌رسانی صÙحه به‌پایان رسید</translation>
<translation id="5876576639916258720">درحال اجرا…</translation>
-<translation id="5876851302954717356">برگه جدید در راست</translation>
+<translation id="5876851302954717356">برگه جدید در سمت راست</translation>
<translation id="5877064549588274448">کانال تغییر کرد. برای اعمال تغییرات دستگاه خود را مجدداً راه‌اندازی کنید.</translation>
<translation id="5877584842898320529">چاپگر انتخابی موجود نیست یا به‌درستی نصب نشده است. <ph name="BR" /> چاپگر خود را بررسی کنید یا چاپگر دیگری را انتخاب کنید.</translation>
<translation id="5882919346125742463">شبکه‌های شناخته‌شده</translation>
@@ -5155,7 +5155,7 @@
<ph name="BEGIN_PARAGRAPH4" />اگر تنظیم «Ùعالیت وب Ùˆ برنامه» تکمیلی برای «حساب Google» Ùرزندتان روشن باشد، ممکن است داده‌های Ùرزندتان در این حساب ذخیره شود. در families.google.comØŒ درباره این تنظیمات Ùˆ نحوه تغییر آن‌ها بیشتر بدانید.<ph name="END_PARAGRAPH4" /></translation>
<translation id="6051811090255711417">سازمان شما این Ùایل را به‌دلیل عدم رعایت خط‌مشی امنیتی مسدود کرد</translation>
<translation id="6052284303005792909">•</translation>
-<translation id="6052488962264772833">برای شروع ارسال محتوا، کد دسترسی را تایپ کنید</translation>
+<translation id="6052488962264772833">برای شروع پخش محتوا، کد دسترسی را تایپ کنید</translation>
<translation id="6052976518993719690">â€Ø§Ø¹ØªØ¨Ø§Ø± گواهی SSL</translation>
<translation id="6053401458108962351">&amp;حذ٠داده‌های مرور...</translation>
<translation id="6054284857788651331">گروه برگه اخیراً بسته‌شده</translation>
@@ -5799,7 +5799,7 @@
<translation id="6683087162435654533">بازیابی همه برگه‌ها</translation>
<translation id="6684827949542560880">درحال بارگیری آخرین به‌روزرسانی</translation>
<translation id="668599234725812620">â€Ø¨Ø§Ø² کردن Google Play</translation>
-<translation id="6686490380836145850">بستن برگه‌ها به چپ</translation>
+<translation id="6686490380836145850">بستن برگه‌های سمت راست</translation>
<translation id="6686665106869989887">برگه به راست منتقل شد</translation>
<translation id="6686817083349815241">ذخیره گذرواژه‌تان</translation>
<translation id="6687079240787935001">پنهان کردن <ph name="MODULE_TITLE" /></translation>
@@ -6459,7 +6459,7 @@
<translation id="7362387053578559123">سایت‌ها می‌توانند برای اتصال به دستگاه‌های بلوتوث درخواست دهند</translation>
<translation id="7363349185727752629">راهنمای گزینه‌های حریم‌خصوصی</translation>
<translation id="7364591875953874521">دسترسی درخواست شده</translation>
-<translation id="7364745943115323529">ارسال محتوا…</translation>
+<translation id="7364745943115323529">پخش محتوا…</translation>
<translation id="7364796246159120393">انتخاب Ùایل</translation>
<translation id="7365076891350562061">اندازه نمایشگر</translation>
<translation id="7366316827772164604">درحال اسکن کردن برای یاÙتن دستگاه‌های اطراÙ…</translation>
@@ -6512,7 +6512,7 @@
<translation id="7409735910987429903">سایت‌ها شاید برای نمایش Ø¢Ú¯Ù‡ÛŒ از بالاپر استÙاده کنند، یا بااستÙاده از هدایت‌ها شما را به وب‌سایت‌هایی هدایت کنند Ú©Ù‡ شاید نخواهید بازدید کنید</translation>
<translation id="7409854300652085600">نشانک‌ها وارد شد.</translation>
<translation id="7410344089573941623">بپرسید Ú©Ù‡ آیا <ph name="HOST" /> می‌خواهد به دوربین Ùˆ میکرÙون شما دسترسی داشته باشد</translation>
-<translation id="7410852728357935715">ارسال محتوا به دستگاه</translation>
+<translation id="7410852728357935715">پخش محتوا به دستگاه</translation>
<translation id="741204030948306876">بله، مواÙقم</translation>
<translation id="7412226954991670867">â€Ø­Ø§Ùظه GPU</translation>
<translation id="7414464185801331860">۱۸ برابر</translation>
@@ -6727,7 +6727,7 @@
<translation id="7622966771025050155">رÙتن به برگه ضبط‌شده</translation>
<translation id="7624337243375417909">â€caps lock غیرÙعال</translation>
<translation id="7625568159987162309">مشاهده مجوزها و داده‌های ذخیره‌شده در همه سایت‌ها</translation>
-<translation id="7625823789272218216">برگه جدید در چپ</translation>
+<translation id="7625823789272218216">برگه جدید در سمت چپ</translation>
<translation id="7626032353295482388">â€Ø¨Ù‡ Chrome خوش آمدید</translation>
<translation id="7628201176665550262">نرخ بازآوری</translation>
<translation id="7629827748548208700">برگه: <ph name="TAB_NAME" /></translation>
@@ -8015,7 +8015,7 @@
<translation id="8872506776304248286">باز کردن در برنامه</translation>
<translation id="8872777911145321141">وقتی سایتی می‌خواهد از دستگاه‌ها Ùˆ داده‌های واقعیت مجازی استÙاده کند سؤال شود (توصیه می‌شود)</translation>
<translation id="8874184842967597500">متصل نیست</translation>
-<translation id="8874341931345877644">ارسال محتوا به دستگاه:</translation>
+<translation id="8874341931345877644">پخش محتوا به دستگاه:</translation>
<translation id="8875520811099717934">â€Ø§Ø±ØªÙ‚ا دادن Linux</translation>
<translation id="8875736897340638404">انتخاب وضعیت نمایان بودن</translation>
<translation id="8876307312329369159">این تنظیم در جلسه نمایشی قابل‌تغییر نیست.</translation>
@@ -8184,7 +8184,7 @@
<translation id="9041692268811217999">دسترسی به Ùایل‌های محلی موجود در دستگاهتان، توسط سرپرست شما غیرÙعال شده است</translation>
<translation id="904224458472510106">این عملیات را نمی‌توان واگرد کرد</translation>
<translation id="9042893549633094279">حریم‌خصوصی و امنیت</translation>
-<translation id="904451693890288097">لطÙاً کلیدواژه را برای "<ph name="DEVICE_NAME" />" وارد کنید:</translation>
+<translation id="904451693890288097">لطÙاً گذرکلید "<ph name="DEVICE_NAME" />" را وارد کنید:</translation>
<translation id="9044646465488564462">اتصال به شبکه انجام نشد: <ph name="DETAILS" /></translation>
<translation id="9045160989383249058">Ùهرست خواندن به پانل کناری جدید منتقل شده است. آن را در اینجا امتحان کنید.</translation>
<translation id="9045430190527754450">â€Ù†Ø´Ø§Ù†ÛŒ وب صÙحه‌ای را Ú©Ù‡ باز می‌کنید به Google ارسال می‌کند</translation>
@@ -8227,7 +8227,7 @@
<translation id="9087949559523851360">اÙزودن کاربر محدودشده</translation>
<translation id="9088234649737575428">خط‌مشی شرکت <ph name="PLUGIN_NAME" /> را مسدود کرده است</translation>
<translation id="9088446193279799727">â€Linux پیکربندی نشد. به اینترنت متصل شوید Ùˆ دوباره امتحان کنید.</translation>
-<translation id="9088917181875854783">لطÙاً تأیید کنید Ú©Ù‡ این کلیدواژه در دستگاه "<ph name="DEVICE_NAME" />" نشان داده می‌شود:</translation>
+<translation id="9088917181875854783">لطÙاً تأیید کنید Ú©Ù‡ این گذرکلید در دستگاه "<ph name="DEVICE_NAME" />" نشان داده می‌شود:</translation>
<translation id="9089416786594320554">روش‌های ورودی</translation>
<translation id="9090044809052745245">نحوه نمایش دستگاه شما به دیگران</translation>
<translation id="9094033019050270033">به‌روزرسانی گذرواژه</translation>
@@ -8391,7 +8391,7 @@
<translation id="957960681186851048">این سایت تلاش کرده است چند Ùایل را به‌طور خودکار بارگیری کند</translation>
<translation id="960987915827980018">حدود ۱ ساعت باقی مانده است</translation>
<translation id="962802172452141067">درخت پوشه نشانک</translation>
-<translation id="964286338916298286">â€Ø³Ø±Ù¾Ø±Ø³Øª IT شما Chrome Goodies را برای دستگاهتان غیرÙعال کرده است.</translation>
+<translation id="964286338916298286">â€Ø³Ø±Ù¾Ø±Ø³Øª Ùناوری اطلاعات شما Chrome Goodies را برای دستگاهتان غیرÙعال کرده است.</translation>
<translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{برنامه}one{برنامه}other{برنامه}}</translation>
<translation id="964790508619473209">تنظیم صÙحه‌نمایش</translation>
<translation id="965211523698323809">ارسال Ùˆ دریاÙت پیامک از <ph name="DEVICE_TYPE" /> شما. <ph name="LINK_BEGIN" />بیشتر بدانید<ph name="LINK_END" /></translation>
diff --git a/chromium/chrome/app/resources/generated_resources_fr-CA.xtb b/chromium/chrome/app/resources/generated_resources_fr-CA.xtb
index da3b39a870d..404223fe6a5 100644
--- a/chromium/chrome/app/resources/generated_resources_fr-CA.xtb
+++ b/chromium/chrome/app/resources/generated_resources_fr-CA.xtb
@@ -555,7 +555,7 @@ Les autorisations que vous avez déjà accordées à des applications peuvent s'
<translation id="1543284117603151572">Importé à partir d'Edge</translation>
<translation id="1544588554445317666">Essayez d'utiliser un nom de fichier plus court ou de l'enregistrer dans un autre dossier</translation>
<translation id="1545177026077493356">Mode kiosque automatique</translation>
-<translation id="1545749641540134597">Scannez le code QR</translation>
+<translation id="1545749641540134597">Numérisez le code QR</translation>
<translation id="1545775234664667895">Thème installé : « <ph name="THEME_NAME" /> »</translation>
<translation id="1546280085599573572">Cette extension a modifié la page qui s'affiche lorsque vous cliquez sur le bouton Accueil.</translation>
<translation id="1546452108651444655"><ph name="CHILD_NAME" /> souhaite installer une extension <ph name="EXTENSION_TYPE" />, qui peut :</translation>
@@ -2649,7 +2649,7 @@ et sur Ctrl+Alt+diminuer la luminosité pour effectuer un zoom arrière.</transl
<translation id="353316712352074340"><ph name="WINDOW_TITLE" /> - Audio désactivé</translation>
<translation id="3537881477201137177">Vous pouvez modifier cela plus tard dans le menu Paramètres</translation>
<translation id="3538066758857505094">Erreur lors de la désinstallation de Linux. Veuillez réessayer.</translation>
-<translation id="354060433403403521">Adaptateur c.a.</translation>
+<translation id="354060433403403521">Adaptateur CA</translation>
<translation id="354068948465830244">Cette extension pourra lire et modifier les données du site</translation>
<translation id="3541823293333232175">Attribué</translation>
<translation id="3543393733900874979">Échec de la mise à jour (erreur : <ph name="ERROR_NUMBER" />)</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_fr.xtb b/chromium/chrome/app/resources/generated_resources_fr.xtb
index 64ad55d8914..19feb27af5a 100644
--- a/chromium/chrome/app/resources/generated_resources_fr.xtb
+++ b/chromium/chrome/app/resources/generated_resources_fr.xtb
@@ -5526,7 +5526,7 @@ Assurez-vous de ne pas dévoiler d'informations sensibles.</translation>
<translation id="6419524191360800346">Une mise à niveau vers Debian 11 (Bullseye) est disponible</translation>
<translation id="6419546358665792306">Charger l'extension non empaquetée</translation>
<translation id="642469772702851743">Le propriétaire de cet appareil (n° de série : <ph name="SERIAL_NUMBER" />) l'a verrouillé.</translation>
-<translation id="6425556984042222041">Débit de la voix de la synthèse vocale</translation>
+<translation id="6425556984042222041">Débit de parole de la synthèse vocale</translation>
<translation id="6426200009596957090">Ouvrir les paramètres ChromeVox</translation>
<translation id="642729974267661262">Non autorisé à lire des sons</translation>
<translation id="6429384232893414837">Erreur de mise à jour</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_gl.xtb b/chromium/chrome/app/resources/generated_resources_gl.xtb
index cb6696652d4..5bdf99d8171 100644
--- a/chromium/chrome/app/resources/generated_resources_gl.xtb
+++ b/chromium/chrome/app/resources/generated_resources_gl.xtb
@@ -1875,7 +1875,7 @@ Se máis tarde decides que prefires que o teu fillo ou filla non utilice Voice M
<translation id="2757338480560142065">Asegúrate de que o contrasinal que gardas coincida co contrasinal de <ph name="WEBSITE" /></translation>
<translation id="2762441749940182211">Bloqueouse a cámara</translation>
<translation id="2764786626780673772">Detalles da VPN</translation>
-<translation id="2765100602267695013">Contacta co teu provedor de telefonía móbil</translation>
+<translation id="2765100602267695013">Contacta co teu operador móbil</translation>
<translation id="2765217105034171413">Pequeno</translation>
<translation id="2766006623206032690">Pe&amp;gar e ir</translation>
<translation id="2766161002040448006">Preguntar a teu pai ou túa nai</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_hi.xtb b/chromium/chrome/app/resources/generated_resources_hi.xtb
index 695b4ca4e95..2f6119a1b1d 100644
--- a/chromium/chrome/app/resources/generated_resources_hi.xtb
+++ b/chromium/chrome/app/resources/generated_resources_hi.xtb
@@ -2275,7 +2275,7 @@
<translation id="3129173833825111527">बायां हाशिया</translation>
<translation id="3129215702932019810">à¤à¤ªà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨ को लॉनà¥à¤š करने में गड़बड़ी हà¥à¤ˆ</translation>
<translation id="3130528281680948470">आपका डिवाइस रीसेट कर दिया जाà¤à¤—ा और सभी उपयोगकरà¥à¤¤à¤¾ खाते और सà¥â€à¤¥à¤¾à¤¨à¥€à¤¯ डेटा हटा दिठजाà¤à¤‚गे. इसे पहले जैसा नहीं किया जा सकता.</translation>
-<translation id="313205617302240621">पासवरà¥à¤¡ भूल गà¤?</translation>
+<translation id="313205617302240621">पासवरà¥à¤¡ याद नहीं है?</translation>
<translation id="3132277757485842847">डिवाइस आपके फ़ोन से बार-बार डिसकनेकà¥à¤Ÿ हो रहा है. पकà¥à¤•à¤¾ करें कि आपका फ़ोन आस-पास है और अनलॉक है. साथ ही, यह भी कि उस पर बà¥à¤²à¥‚टूथ और वाई-फ़ाई चालू हैं.</translation>
<translation id="3132896062549112541">नियम</translation>
<translation id="3132996321662585180">हर रोज़ रीफà¥à¤°à¥‡à¤¶ करें</translation>
@@ -4992,7 +4992,7 @@
<translation id="5883356647197510494"><ph name="PERMISSION_1" />, <ph name="PERMISSION_2" /> को अपने-आप बà¥à¤²à¥‰à¤• किया गया</translation>
<translation id="5884474295213649357">यह टैब किसी USB डिवाइस से कनेकà¥à¤Ÿ है.</translation>
<translation id="5885314688092915589">आपका संगठन इस पà¥à¤°à¥‹à¤«à¤¼à¤¾à¤‡à¤² को मैनेज करेगा</translation>
-<translation id="5886009770935151472">उंगली à¤à¤•</translation>
+<translation id="5886009770935151472">उंगली 1</translation>
<translation id="5888843733007437002">टेंपà¥à¤²à¥‡à¤Ÿ देखा जा रहा है. नेविगेट करने के लिठTab दबाà¤à¤‚.</translation>
<translation id="5889282057229379085">मधà¥à¤¯à¤µà¤°à¥à¤¤à¥€ CA की अधिकतम संखà¥à¤¯à¤¾: <ph name="NUM_INTERMEDIATE_CA" /></translation>
<translation id="5891688036610113830">पसंदीदा वाई-फ़ाई नेटवरà¥à¤•</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_hr.xtb b/chromium/chrome/app/resources/generated_resources_hr.xtb
index b11e723c46f..36c26547481 100644
--- a/chromium/chrome/app/resources/generated_resources_hr.xtb
+++ b/chromium/chrome/app/resources/generated_resources_hr.xtb
@@ -3869,7 +3869,7 @@ i Ctrl + Alt + Smanjivanje svjetline da biste smanjili prikaz.</translation>
<translation id="4742970037960872810">Ukloni isticanje</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />Saznajte kako ažurirati aplikacije<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{UkljuÄi zvuk na web-lokaciji}one{UkljuÄi zvuk na web-lokacijama}few{UkljuÄi zvuk na web-lokacijama}other{UkljuÄi zvuk na web-lokacijama}}</translation>
-<translation id="474609389162964566">Pristupajte Asistentu tako što ćete reći "Hey Google"</translation>
+<translation id="474609389162964566">Pristupite Asistentu tako da kažete Ok Google</translation>
<translation id="4746351372139058112">Poruke</translation>
<translation id="4748783296226936791">Web-lokacije se obiÄno povezuju s HID ureÄ‘ajima radi znaÄajki koje koriste neuobiÄajene tipkovnice, upravljaÄe za igre i druge ureÄ‘aje</translation>
<translation id="4750185073185658673">Pregled daljnjih nekoliko dopuÅ¡tenja nastavite na telefonu. Provjerite jesu li na telefonu ukljuÄeni Bluetooth i Wi-Fi.</translation>
@@ -6491,7 +6491,7 @@ Toj radnji možete dodijeliti viÅ¡e prekidaÄa.</translation>
<translation id="7404065585741198296">Telefonom povezanim USB kabelom</translation>
<translation id="7405938989981604410">{NUM_HOURS,plural, =1{Sigurnosna provjera izvršena je prije sat vremena}one{Sigurnosna provjera izvršena je prije {NUM_HOURS} sata}few{Sigurnosna provjera izvršena je prije {NUM_HOURS} sata}other{Sigurnosna provjera izvršena je prije {NUM_HOURS} sati}}</translation>
<translation id="740624631517654988">SkoÄni prozor blokiran</translation>
-<translation id="7407430846095439694">Uvezi i uveži</translation>
+<translation id="7407430846095439694">Uvezi i poveži</translation>
<translation id="7407504355934009739">Većina korisnika blokira obavijesti s te web-lokacije</translation>
<translation id="740810853557944681">Dodajte poslužitelj za ispis</translation>
<translation id="7409549334477097887">Posebno velik</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_id.xtb b/chromium/chrome/app/resources/generated_resources_id.xtb
index 113187d68f5..004d75c452e 100644
--- a/chromium/chrome/app/resources/generated_resources_id.xtb
+++ b/chromium/chrome/app/resources/generated_resources_id.xtb
@@ -4653,7 +4653,7 @@ Anda dapat menetapkan beberapa tombol akses untuk tindakan ini.</translation>
<translation id="5533001281916885985"><ph name="SITE_NAME" /> ingin</translation>
<translation id="5534304873398226603">Hapus foto atau video</translation>
<translation id="5535941515421698170">Tindakan ini juga menghapus data yang sudah ada dari perangkat ini</translation>
-<translation id="5537725057119320332">Cast</translation>
+<translation id="5537725057119320332">Transmisikan</translation>
<translation id="5539221284352502426">Sandi yang Anda masukkan ditolak oleh server. Kemungkinan alasannya adalah: Sandi terlalu pendek. Sandi harus menyertakan angka atau simbol. Sandi harus berbeda dari sandi sebelumnya.</translation>
<translation id="5541694225089836610">Tindakan dinonaktifkan oleh administrator Anda</translation>
<translation id="5542132724887566711">Profil</translation>
@@ -6946,7 +6946,7 @@ Tekan tombol akses atau tombol keyboard yang telah ditetapkan untuk menghapus pe
<translation id="7807117920154132308">Sepertinya <ph name="SUPERVISED_USER_NAME" /> sudah menyiapkan Asisten Google di perangkat lain. <ph name="SUPERVISED_USER_NAME" /> dapat mengoptimalkan penggunaan Asisten dengan mengaktifkan Konteks layar di perangkat ini.</translation>
<translation id="7807711621188256451">Selalu izinkan <ph name="HOST" /> mengakses kamera Anda</translation>
<translation id="7810202088502699111">Pop-up diblokir di halaman ini.</translation>
-<translation id="781167124805380294">Cast <ph name="FILE_NAME" /></translation>
+<translation id="781167124805380294">Transmisikan <ph name="FILE_NAME" /></translation>
<translation id="7811886112806886172">Bantu tingkatkan fitur dan performa Chrome dan ChromeOS dengan mengirimkan data penggunaan dan diagnostik secara otomatis ke Google. Beberapa data gabungan juga akan membantu aplikasi Android dan partner Google. Jika setelan Aktivitas Web &amp; Aplikasi diaktifkan untuk Akun Google Anda, data Android Anda mungkin akan disimpan ke Akun Google tersebut.</translation>
<translation id="7814458197256864873">&amp;Salin</translation>
<translation id="7815680994978050279">Download berbahaya diblokir</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_is.xtb b/chromium/chrome/app/resources/generated_resources_is.xtb
index 77ca11dc21d..26604c24325 100644
--- a/chromium/chrome/app/resources/generated_resources_is.xtb
+++ b/chromium/chrome/app/resources/generated_resources_is.xtb
@@ -8388,7 +8388,7 @@ Geymdu lykilskrána á öruggum stað. Þú þarft hana til að búa til nýjar
<translation id="947667444780368238"><ph name="ORIGIN" /> getur ekki opnað skrár í þessari möppu vegna þess að hún inniheldur kerfisskrár</translation>
<translation id="950307215746360464">Uppsetningarleiðbeiningar</translation>
<translation id="951991426597076286">Hafna</translation>
-<translation id="952471655966876828">Tækið tengist sjálfkrafa þegar kveikt er á því og verið er að nota það</translation>
+<translation id="952471655966876828">Tækið tengist sjálfkrafa þegar kveikt er á því og það notað</translation>
<translation id="953434574221655299">Mega vita hvenær þú notar tækið þitt</translation>
<translation id="956500788634395331">Tækið er varið gegn viðbótum sem gætu verið hættulegar</translation>
<translation id="957960681186851048">Þetta vefsvæði reyndi að sækja margar skrár sjálfkrafa</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_it.xtb b/chromium/chrome/app/resources/generated_resources_it.xtb
index 91b21778fa2..f02f15ad739 100644
--- a/chromium/chrome/app/resources/generated_resources_it.xtb
+++ b/chromium/chrome/app/resources/generated_resources_it.xtb
@@ -4491,7 +4491,7 @@ e Ctrl + Alt + Riduzione luminosità per diminuire lo zoom.</translation>
<translation id="5392192690789334093">Possono inviare notifiche</translation>
<translation id="5393761864111565424">{COUNT,plural, =1{Link}other{# link}}</translation>
<translation id="5396325212236512832">Accedi automaticamente ai siti e alle app usando le credenziali memorizzate. Se la funzionalità è disattivata, ti verrà chiesta una conferma ogni volta che vuoi accedere a un sito o a un'app.</translation>
-<translation id="5397378439569041789">Registra dispositivi kiosk o insegna</translation>
+<translation id="5397378439569041789">Registra dispositivi kiosk o segnaletica</translation>
<translation id="5397794290049113714">Tu</translation>
<translation id="5398497406011404839">Preferiti nascosti</translation>
<translation id="5398572795982417028">Riferimento pagina oltre i limiti. Il limite è <ph name="MAXIMUM_PAGE" /></translation>
@@ -5569,7 +5569,7 @@ Puoi assegnare più sensori a questa azione.</translation>
<translation id="6466258437571594570">I siti non possono interromperti quando chiedono di poter inviare notifiche</translation>
<translation id="6466988389784393586">&amp;Apri tutti i Preferiti</translation>
<translation id="6467304607960172345">Ottimizza video a schermo intero</translation>
-<translation id="6467377768028664108"><ph name="DEVICE_TYPE" /> potrà poi:</translation>
+<translation id="6467377768028664108">Su <ph name="DEVICE_TYPE" />:</translation>
<translation id="6468485451923838994">Caratteri</translation>
<translation id="6468773105221177474"><ph name="FILE_COUNT" /> file</translation>
<translation id="6469557521904094793">Attiva rete mobile</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_iw.xtb b/chromium/chrome/app/resources/generated_resources_iw.xtb
index 3fa3578d6d0..5a57465d912 100644
--- a/chromium/chrome/app/resources/generated_resources_iw.xtb
+++ b/chromium/chrome/app/resources/generated_resources_iw.xtb
@@ -176,7 +176,7 @@
<translation id="1163931534039071049">&amp;הצגת מקור המסגרת</translation>
<translation id="1164891049599601209">הוזנה ב×תר מטעה</translation>
<translation id="1165039591588034296">שגי××”</translation>
-<translation id="1166212789817575481">סגירת הכרטיסיות משמ×ל</translation>
+<translation id="1166212789817575481">סגירת הכרטיסיות מימין</translation>
<translation id="1166583374608765787">בדיקת עדכון הש×</translation>
<translation id="1166596238782048887">הכרטיסייה <ph name="TAB_TITLE" /> שייכת לשולחן העבודה הווירטו×לי <ph name="DESK_TITLE" /></translation>
<translation id="1168020859489941584">הפתיחה תתבצע בעוד <ph name="TIME_REMAINING" />...</translation>
@@ -1058,7 +1058,7 @@
<translation id="1994173015038366702">כתובת ×תר</translation>
<translation id="1995916364271252349">בדף ×”×–×” ×פשר לשלוט על המידע ש××ª×¨×™× ×™×›×•×œ×™× ×œ×”×©×ª×ž×© בו ולהציג ×ותו (מיקו×, מצלמה, חלונות ×§×•×¤×¦×™× ×•×¢×•×“)</translation>
<translation id="1997616988432401742">×”××™×©×•×¨×™× ×©×œ×š</translation>
-<translation id="1999115740519098545">בעת ההפעלה</translation>
+<translation id="1999115740519098545">×›×©×¤×•×ª×—×™× ×ת הדפדפן</translation>
<translation id="2000419248597011803">â€×©×œ×™×—×” של חלק מקובצי ×”-Cookie ×•×”×—×™×¤×•×©×™× ×ž×©×•×¨×ª כתובת ×”×תר ומתיבת החיפוש ×ל מנוע החיפוש שהוגדר כברירת מחדל</translation>
<translation id="2002109485265116295">זמן ×מת</translation>
<translation id="2003130567827682533">â€×›×“×™ להפעיל × ×ª×•× ×™× ×©×œ '<ph name="NAME" />', יש להתחבר תחילה לרשת Wi-Fi</translation>
@@ -1177,7 +1177,7 @@
<translation id="2105809836724866556">ההסתרה של <ph name="MODULE_TITLE" /> בוצעה</translation>
<translation id="2108349519800154983">{COUNT,plural, =1{מספר טלפון}two{# מספרי טלפון}many{# מספרי טלפון}other{# מספרי טלפון}}</translation>
<translation id="211144231511833662">ניקוי הסוגי×</translation>
-<translation id="2111670510994270194">כרטיסייה חדשה מצד שמ×ל</translation>
+<translation id="2111670510994270194">כרטיסייה חדשה מצד ימין</translation>
<translation id="2112554630428445878">שלו×, <ph name="USERNAME" /></translation>
<translation id="21133533946938348">הצמדת כרטיסייה</translation>
<translation id="2113479184312716848">פתיחת &amp;קובץ...</translation>
@@ -1556,7 +1556,7 @@
<translation id="244231003699905658">כתובת ×œ× ×—×•×§×™×ª. יש לבדוק ×ת הכתובת ולנסות שוב.</translation>
<translation id="2442916515643169563">צללית טקסט</translation>
<translation id="2443487764245141020">ייתכן ×©×’× ×‘××ª×¨×™× ×™×”×™×” צורך לזהות ×ת המכשיר שלך ב×מצעות מזהה</translation>
-<translation id="244475495405467108">סגירת הכרטיסיות מימין</translation>
+<translation id="244475495405467108">סגירת הכרטיסיות משמ×ל</translation>
<translation id="2445081178310039857">ספריית הבסיס של ההרחבה נחוצה.</translation>
<translation id="2445484935443597917">יצירת פרופיל חדש</translation>
<translation id="244641233057214044">קשור לחיפוש שלך</translation>
@@ -2312,7 +2312,7 @@
<translation id="3164329792803560526">המערכת משתפת ×ת הכרטיסייה הזו ×¢× <ph name="APP_NAME" /></translation>
<translation id="3165390001037658081">ייתכן ×©×¡×¤×§×™× ×ž×¡×•×™×ž×™× ×—×•×¡×ž×™× ×ת התכונה הזו.</translation>
<translation id="3170072451822350649">ניתן ×’× ×œ×“×œ×’ על הכניסה ו<ph name="LINK_START" />לגלוש ×›×ורח<ph name="LINK_END" />.</translation>
-<translation id="31774765611822736">כרטיסייה חדשה מצד ימין</translation>
+<translation id="31774765611822736">כרטיסייה חדשה מצד שמ×ל</translation>
<translation id="3177909033752230686">שפת הדף:</translation>
<translation id="3179982752812949580">גופן הטקסט</translation>
<translation id="3181954750937456830">גלישה בטוחה (מגנה עליך ועל המכשיר מפני ××ª×¨×™× ×ž×¡×•×›× ×™×)</translation>
@@ -3177,7 +3177,7 @@
<translation id="4021279097213088397">â€â€“</translation>
<translation id="402184264550408568">(TCP)</translation>
<translation id="4021909830315618592">â€×”עתקת פרטי build</translation>
-<translation id="4021941025609472374">סגירת הכרטיסיות מימין</translation>
+<translation id="4021941025609472374">סגירת הכרטיסיות משמ×ל</translation>
<translation id="4022426551683927403">&amp;הוספה למילון</translation>
<translation id="4023146161712577481">קביעת התצורה של המכשיר.</translation>
<translation id="4025039777635956441">השתקת ×”××ª×¨×™× ×©× ×‘×—×¨×•</translation>
@@ -4986,7 +4986,7 @@
<translation id="5870155679953074650">שגי×ות חמורות</translation>
<translation id="5875534259258494936">שיתוף המסך הסתיי×</translation>
<translation id="5876576639916258720">התכונה פעילה…</translation>
-<translation id="5876851302954717356">כרטיסייה חדשה מצד שמ×ל</translation>
+<translation id="5876851302954717356">כרטיסייה חדשה מצד ימין</translation>
<translation id="5877064549588274448">הערוץ שונה. יש להפעיל מחדש ×ת המכשיר להחלת השינויי×.</translation>
<translation id="5877584842898320529">המדפסת שבחרת ××™× ×” זמינה ×ו ש××™× ×” מותקנת כר×וי. <ph name="BR" /> יש לבדוק ×ת המדפסת ×ו לנסות לבחור מדפסת ×חרת.</translation>
<translation id="5882919346125742463">רשתות מוכרות</translation>
@@ -5802,7 +5802,7 @@
<translation id="6683087162435654533">ש&amp;חזור כל הכרטיסיות</translation>
<translation id="6684827949542560880">המערכת מורידה ×ת העדכון ×”×חרון</translation>
<translation id="668599234725812620">â€×¤×ª×™×—ת Google Play</translation>
-<translation id="6686490380836145850">סגירת הכרטיסיות משמ×ל</translation>
+<translation id="6686490380836145850">סגירת הכרטיסיות מימין</translation>
<translation id="6686665106869989887">הכרטיסייה הועברה שמ×לה</translation>
<translation id="6686817083349815241">שמירת הסיסמה שלך</translation>
<translation id="6687079240787935001">הסתרה של <ph name="MODULE_TITLE" /></translation>
@@ -6730,7 +6730,7 @@
<translation id="7622966771025050155">לכרטיסייה המוקלטת</translation>
<translation id="7624337243375417909">â€caps lock מושבת</translation>
<translation id="7625568159987162309">הצגת הרש×ות ×•× ×ª×•× ×™× ×”×ž××•×—×¡× ×™× ×‘××ª×¨×™× ×©×•× ×™×</translation>
-<translation id="7625823789272218216">כרטיסייה חדשה מצד ימין</translation>
+<translation id="7625823789272218216">כרטיסייה חדשה מצד שמ×ל</translation>
<translation id="7626032353295482388">â€×‘רוך בו×ך ×ל Chrome</translation>
<translation id="7628201176665550262">קצב רענון</translation>
<translation id="7629827748548208700">כרטיסייה: <ph name="TAB_NAME" /></translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ja.xtb b/chromium/chrome/app/resources/generated_resources_ja.xtb
index 00fe96593b4..80881237567 100644
--- a/chromium/chrome/app/resources/generated_resources_ja.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ja.xtb
@@ -2975,7 +2975,7 @@
<translation id="3855676282923585394">ブックマークã¨è¨­å®šã‚’インãƒãƒ¼ãƒˆ...</translation>
<translation id="3856096718352044181">有効ãªãƒ—ロãƒã‚¤ãƒ€ã§ã‚ã‚‹ã“ã¨ã‚’確èªã™ã‚‹ã‹ã€ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„</translation>
<translation id="3856800405688283469">タイムゾーンをé¸æŠž</translation>
-<translation id="3857807444929313943">指を放ã—ã¦ã€ã‚‚ã†ä¸€åº¦ã‚¿ãƒƒãƒ</translation>
+<translation id="3857807444929313943">指を離ã—ã¦ã€ã‚‚ã†ä¸€åº¦ã‚¿ãƒƒãƒ</translation>
<translation id="3858860766373142691">åå‰</translation>
<translation id="3861638017150647085">ユーザーå「<ph name="USERNAME" />ã€ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“</translation>
<translation id="3861977424605124250">起動時ã«è¡¨ç¤ºã™ã‚‹</translation>
@@ -4431,7 +4431,7 @@
<translation id="5339031667684712858">削除ã—ãŸã‚µã‚¤ãƒˆ</translation>
<translation id="5340638867532133571">サイトã«æ”¯æ‰•ã„ãƒãƒ³ãƒ‰ãƒ©ã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã‚’許å¯ã™ã‚‹ï¼ˆæŽ¨å¥¨ï¼‰</translation>
<translation id="5341793073192892252">以下㮠Cookie ãŒãƒ–ロックã•ã‚Œã¾ã—ãŸï¼ˆã‚µãƒ¼ãƒ‰ãƒ‘ーティ㮠Cookie ã¯ä¾‹å¤–ãªãブロックã•ã‚Œã¾ã™ï¼‰</translation>
-<translation id="5342091991439452114">PIN 㯠<ph name="MINIMUM" /> æ¡ä»¥ä¸Šã§ä½œæˆã—ã¦ãã ã•ã„</translation>
+<translation id="5342091991439452114">PIN 㯠<ph name="MINIMUM" /> æ¡ä»¥ä¸Šã§è¨­å®šã—ã¦ãã ã•ã„</translation>
<translation id="5344036115151554031">Linux を復元ã—ã¦ã„ã¾ã™</translation>
<translation id="5344128444027639014"><ph name="BATTERY_PERCENTAGE" />%(å³ï¼‰</translation>
<translation id="5345916423802287046">ログイン時ã«ã‚¢ãƒ—リを開ã</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_kk.xtb b/chromium/chrome/app/resources/generated_resources_kk.xtb
index 65cac933d65..5ed77882085 100644
--- a/chromium/chrome/app/resources/generated_resources_kk.xtb
+++ b/chromium/chrome/app/resources/generated_resources_kk.xtb
@@ -992,7 +992,7 @@
<translation id="1936931585862840749">БаÑып шығарылатын көшірмелер Ñанын көрÑетіңіз (1-<ph name="MAX_COPIES" />).</translation>
<translation id="1937774647013465102"><ph name="ARCHITECTURE_CONTAINER" /> контейнер архитектураÑÑ‹ түрін бұл құрылғымен (<ph name="ARCHITECTURE_DEVICE" />) импорттау мүмкін емеÑ. Бұл контейнерді баÑқа құрылғыда қалпына келтіріп көруіңізге болады немеÑе файлдарға контейнер кеÑкінін Files қолданбаÑынан ашу арқылы кіре алаÑыз.</translation>
<translation id="1938351510777341717">Сыртқы пәрмен</translation>
-<translation id="1940546824932169984">ҚоÑылған құрылғылар</translation>
+<translation id="1940546824932169984">Жалғанған құрылғылар</translation>
<translation id="1941410638996203291">БаÑталу уақыты: <ph name="TIME" /></translation>
<translation id="1941553344801134989">ÐÒ±ÑқаÑÑ‹: <ph name="APP_VERSION" /></translation>
<translation id="1942128823046546853">Барлық веб-Ñайттағы деректі оқу және өзгерту</translation>
@@ -2112,7 +2112,7 @@
<translation id="2988018669686457659">ҚоÑалқы рендеринг құралы</translation>
<translation id="2989123969927553766">Тінтуірдің айналдыруын жылдамдату</translation>
<translation id="2989474696604907455">тіркелмеген</translation>
-<translation id="2989786307324390836">DER кодталған бинарлы, бір Ñертификат</translation>
+<translation id="2989786307324390836">DER кодталған екілік, бір Ñертификат</translation>
<translation id="2989805286512600854">Жаңа қойындыда ашу</translation>
<translation id="2990313168615879645">Ðккаунт қоÑу</translation>
<translation id="2990583317361835189">Сайттардың Ò›Ð¾Ð·Ò“Ð°Ð»Ñ‹Ñ Ð´Ð°Ñ‚Ñ‡Ð¸ÐºÑ‚ÐµÑ€Ñ–Ð½ пайдалануына Ñ€Ò±Ò›Ñат бермеу</translation>
@@ -2481,7 +2481,7 @@
<translation id="3387829698079331264">Құрылғыңызды белÑенді пайдаланатын уақытыңыз туралы білуге Ñ€Ò±Ò›Ñат берілмейтін Ñайттар</translation>
<translation id="3388094447051599208">Ð¨Ñ‹Ò“Ñ‹Ñ Ð½Ð°ÑƒÐ°ÑÑ‹ толуға жақын.</translation>
<translation id="3388788256054548012">Бұл файл шифрланған. ИеÑінен шифрдан шығаруын Ñұраңыз.</translation>
-<translation id="3390013585654699824">Қолданба мәліметтері</translation>
+<translation id="3390013585654699824">Қолданба туралы мәлімет</translation>
<translation id="3391512812407811893">Privacy Sandbox Ñынақ мерзімдері</translation>
<translation id="3393554941209044235">Chrome құжат талдау процеÑÑ–</translation>
<translation id="3393582007140394275">Экранды транÑлÑциÑлау мүмкін емеÑ.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_kn.xtb b/chromium/chrome/app/resources/generated_resources_kn.xtb
index 5eac68593d1..acbf1141e71 100644
--- a/chromium/chrome/app/resources/generated_resources_kn.xtb
+++ b/chromium/chrome/app/resources/generated_resources_kn.xtb
@@ -1260,7 +1260,7 @@
<translation id="2187895286714876935">ಸರà³à²µà²°à³ ಪà³à²°à²®à²¾à²£à²ªà²¤à³à²°à²¦ ಆಮದೠದೋಷ</translation>
<translation id="2187906491731510095">ವಿಸà³à²¤à²°à²£à³†à²—ಳನà³à²¨à³ ಅಪà³â€Œà²¡à³‡à²Ÿà³ ಮಾಡಲಾಗಿದೆ</translation>
<translation id="2188881192257509750"><ph name="APPLICATION" /> ತೆರೆಯಿರಿ</translation>
-<translation id="2189787291884708275">ಟà³à²¯à²¾à²¬à³ ಆಡಿಯೊ ಹಂಚಿಕೊಳà³à²³à²¿</translation>
+<translation id="2189787291884708275">ಟà³à²¯à²¾à²¬à³ ಆಡಿಯೋ ಹಂಚಿಕೊಳà³à²³à²¿</translation>
<translation id="2190069059097339078">ವೈಫೈ ರà³à²œà³à²µà²¾à²¤à³à²—ಳ ಪಡೆಯà³à²µà²¿à²•à³†</translation>
<translation id="219008588003277019">ಸà³à²¥à²³à³€à²¯ ಕà³à²²à³ˆà²‚ಟೠಮಾಡà³à²¯à³‚ಲà³: <ph name="NEXE_NAME" /></translation>
<translation id="2190355936436201913">(ಖಾಲಿ)</translation>
@@ -1408,7 +1408,7 @@
<ph name="BR" />
ಅನನà³à²¯ ವಾಯà³à²¸à³ ಮಾಡೆಲೠಅನà³à²¨à³ ರಚಿಸಲೠAssistant ನಿಮà³à²® ಮಗà³à²µà²¿à²¨ ಧà³à²µà²¨à²¿à²¯ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ತೆಗೆದà³à²•à³Šà²³à³à²³à³à²¤à³à²¤à²¦à³†. ಅದನà³à²¨à³ ಅವರ ಸಾಧನ(ಗಳಲà³à²²à²¿)ದಲà³à²²à²¿ ಮಾತà³à²° ಸಂಗà³à²°à²¹à²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¦à³†. ನಿಮà³à²® ಮಗà³à²µà²¿à²¨ ಧà³à²µà²¨à²¿à²¯à²¨à³à²¨à³ ಉತà³à²¤à²®à²µà²¾à²—ಿ ಗà³à²°à³à²¤à²¿à²¸à²²à³ ನಿಮà³à²® ಮಗà³à²µà²¿à²¨ ವಾಯà³à²¸à³ ಮಾಡೆಲೠಅನà³à²¨à³ ತಾತà³à²•à²¾à²²à²¿à²•à²µà²¾à²—ಿ Google ಗೆ ಕಳà³à²¹à²¿à²¸à²¬à²¹à³à²¦à³.
<ph name="BR" />
- Voice Match ನಿಮà³à²® ಮಗà³à²µà²¿à²—ೆ ಸರಿಹೊಂದà³à²µà³à²¦à²¿à²²à³à²² ಎಂದೠನೀವೠನಂತರ ನಿರà³à²§à²°à²¿à²¸à²¿à²¦à²°à³†, ಅದನà³à²¨à³ ಅವರ Assistant ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿ ತೆಗೆದà³à²¹à²¾à²•à²¿. Voice Match ಸೆಟಪà³â€Œà²¨ ಸಮಯದಲà³à²²à²¿ ನಿಮà³à²® ಮಗೠರೆಕಾರà³à²¡à³ ಮಾಡà³à²µ ಆಡಿಯೊ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ವೀಕà³à²·à²¿à²¸à²²à³ ಅಥವಾ ಅಳಿಸಲà³, ನಿಮà³à²® ಮಗà³à²µà²¿à²¨ ಖಾತೆಯಿಂದ <ph name="VOICE_MATCH_SETTINGS_URL" /> ಗೆ ಹೋಗಿ.
+ Voice Match ನಿಮà³à²® ಮಗà³à²µà²¿à²—ೆ ಸರಿಹೊಂದà³à²µà³à²¦à²¿à²²à³à²² ಎಂದೠನೀವೠನಂತರ ನಿರà³à²§à²°à²¿à²¸à²¿à²¦à²°à³†, ಅದನà³à²¨à³ ಅವರ Assistant ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿ ತೆಗೆದà³à²¹à²¾à²•à²¿. Voice Match ಸೆಟಪà³â€Œà²¨ ಸಮಯದಲà³à²²à²¿ ನಿಮà³à²® ಮಗೠರೆಕಾರà³à²¡à³ ಮಾಡà³à²µ ಆಡಿಯೋ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ವೀಕà³à²·à²¿à²¸à²²à³ ಅಥವಾ ಅಳಿಸಲà³, ನಿಮà³à²® ಮಗà³à²µà²¿à²¨ ಖಾತೆಯಿಂದ <ph name="VOICE_MATCH_SETTINGS_URL" /> ಗೆ ಹೋಗಿ.
<ph name="BR" />
<ph name="FOOTER_MESSAGE" /></translation>
<translation id="2307630946657910723"><ph name="VISUAL_SEARCH_PROVIDER" /> ಬಳಸಿಕೊಂಡೠಪà³à²Ÿà²¦ ಭಾಗವನà³à²¨à³ ಹà³à²¡à³à²•à²¿</translation>
@@ -1720,7 +1720,7 @@
<translation id="2605668923777146443">Better Together ಗೆ ಸಂಬಂಧಿಸಿದ ನಿಮà³à²® ಆಯà³à²•à³†à²—ಳನà³à²¨à³ ನೋಡಲೠ<ph name="LINK_BEGIN" />ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಿಗೆ<ph name="LINK_END" /> ಹೋಗಿ.</translation>
<translation id="2606246518223360146">ಡೇಟಾ ಲಿಂಕೠಮಾಡಿ</translation>
<translation id="2606454609872547359">ಇಲà³à²², ChromeVox ಇಲà³à²²à²¦à³†à²¯à³‡ ಮà³à²‚ದà³à²µà²°à²¿à²¸à²¿</translation>
-<translation id="2606568927909309675">ಇಂಗà³à²²à²¿à²·à³ ಆಡಿಯೊ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಸà³à²µà²¯à²‚ಚಾಲಿತವಾಗಿ ರಚಿಸà³à²¤à³à²¤à²¦à³†. ಆಡಿಯೊ ಮತà³à²¤à³ ಶೀರà³à²·à²¿à²•à³†à²—ಳೠಯಾವಾಗಲೂ ನಿಮà³à²® ಸಾಧನದಲà³à²²à²¿à²¯à³‡ ಉಳಿಯà³à²¤à³à²¤à²µà³†.</translation>
+<translation id="2606568927909309675">ಇಂಗà³à²²à²¿à²·à³ ಆಡಿಯೋ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಸà³à²µà²¯à²‚ಚಾಲಿತವಾಗಿ ರಚಿಸà³à²¤à³à²¤à²¦à³†. ಆಡಿಯೋ ಮತà³à²¤à³ ಶೀರà³à²·à²¿à²•à³†à²—ಳೠಯಾವಾಗಲೂ ನಿಮà³à²® ಸಾಧನದಲà³à²²à²¿à²¯à³‡ ಉಳಿಯà³à²¤à³à²¤à²µà³†.</translation>
<translation id="2607101320794533334">ವಿಷಯ ಸಾರà³à²µà²œà²¨à²¿à²• ಕೀಲಿ ಮಾಹಿತಿ</translation>
<translation id="2609896558069604090">ಶಾರà³à²Ÿà³â€Œà²•à²Ÿà³â€Œà²—ಳನà³à²¨à³ ರಚಿಸಿ...</translation>
<translation id="2609980095400624569">ಕನೆಕà³à²·à²¨à³ ಸà³à²¥à²¾à²ªà²¿à²¸à²²à³ ಸಾಧà³à²¯à²µà²¾à²—ಲಿಲà³à²²</translation>
@@ -1968,7 +1968,7 @@
<translation id="2836269494620652131">ಕà³à²°à³à²¯à²¾à²·à³</translation>
<translation id="2836635946302913370">ಈ ಬಳಕೆದಾರರ ಹೆಸರಿನೊಂದಿಗೆ ಸೈನೠಇನೠಮಾಡà³à²µà³à²¦à²¨à³à²¨à³ ನಿಮà³à²® ನಿರà³à²µà²¾à²¹à²•à²° ಮೂಲಕ ನಿಷà³à²•à³à²°à²¿à²¯à²—ೊಳಿಸಲಾಗಿದೆ.</translation>
<translation id="283669119850230892">ನೆಟà³â€Œà²µà²°à³à²•à³ <ph name="NETWORK_ID" /> ಅನà³à²¨à³ ಬಳಸಲà³, ಮೊದಲೠನಿಮà³à²® ಸಂಪರà³à²•à²µà²¨à³à²¨à³ ಕೆಳಗಿನ ಇಂಟರà³â€Œà²¨à³†à²Ÿà³â€Œà²—ೆ ಸಂಪೂರà³à²£à²—ೊಳಿಸಿ.</translation>
-<translation id="2838379631617906747">ಸà³à²¥à²¾à²ªà²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¿à²¦à³†</translation>
+<translation id="2838379631617906747">ಇನà³â€Œà²¸à³à²Ÿà²¾à²²à³ ಆಗà³à²¤à³à²¤à²¿à²¦à³†</translation>
<translation id="2839032553903800133">ಅಧಿಸೂಚನೆಗಳನà³à²¨à³ ನಿರà³à²¬à²‚ಧಿಸಲಾಗಿದೆ</translation>
<translation id="2841013758207633010">ಸಮಯ</translation>
<translation id="2841837950101800123">ಪೂರೈಕೆದಾರರà³</translation>
@@ -3972,7 +3972,7 @@
<translation id="4849517651082200438">ಇನà³â€Œà²¸à³à²Ÿà²¾à²²à³ ಮಾಡಬೇಡಿ</translation>
<translation id="485053257961878904">ಅಧಿಸೂಚನೆಗಳ ಸಿಂಕೠಮಾಡà³à²µà³à²¦à²¨à³à²¨à³ ಸೆಟಪೠಮಾಡಲೠಸಾಧà³à²¯à²µà²¾à²—ಲಿಲà³à²²</translation>
<translation id="4850886885716139402">ವೀಕà³à²·à²£à³†</translation>
-<translation id="485088796993065002">ಸಂಗೀತ, ವೀಡಿಯೊಗಳೠಮತà³à²¤à³ ಇತರ ಮೀಡಿಯಾಗಳಿಗಾಗಿ ಆಡಿಯೊ ಒದಗಿಸಲೠಸೈಟà³â€Œà²—ಳೠಧà³à²µà²¨à²¿à²¯à²¨à³à²¨à³ ಪà³à²²à³‡ ಮಾಡಬಹà³à²¦à³</translation>
+<translation id="485088796993065002">ಸಂಗೀತ, ವೀಡಿಯೊಗಳೠಮತà³à²¤à³ ಇತರ ಮೀಡಿಯಾಗಳಿಗಾಗಿ ಆಡಿಯೋ ಒದಗಿಸಲೠಸೈಟà³â€Œà²—ಳೠಧà³à²µà²¨à²¿à²¯à²¨à³à²¨à³ ಪà³à²²à³‡ ಮಾಡಬಹà³à²¦à³</translation>
<translation id="4853020600495124913">&amp;ಹೊಸ ವಿಂಡೋನಲà³à²²à²¿ ತೆರೆಯಿರಿ</translation>
<translation id="4854317507773910281">ಅನà³à²®à³‹à²¦à²¨à³† ಪಡೆಯಲà³, ಪೋಷಕರ ಖಾತೆಯನà³à²¨à³ ಆಯà³à²•à³† ಮಾಡಿ</translation>
<translation id="485480310608090163">ಇನà³à²¨à²·à³à²Ÿà³ ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳೠಹಾಗೂ ಅನà³à²®à²¤à²¿à²—ಳà³</translation>
@@ -5112,7 +5112,7 @@
<translation id="6011193465932186973">ಫಿಂಗರà³â€Œà²ªà³à²°à²¿à²‚ಟà³</translation>
<translation id="6011449291337289699">ಸೈಟೠಡೇಟಾವನà³à²¨à³ ತೆರವà³à²—ೊಳಿಸಿ</translation>
<translation id="6011908034087870826"><ph name="DEVICE_NAME" /> ಗೆ ಲಿಂಕà³â€Œ ಅನà³à²¨à³ ಕಳà³à²¹à²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¿à²¦à³†</translation>
-<translation id="6013027779243312217">ನಿಮà³à²® ಆಡಿಯೊ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಪಡೆಯಿರಿ</translation>
+<translation id="6013027779243312217">ನಿಮà³à²® ಆಡಿಯೋ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಪಡೆಯಿರಿ</translation>
<translation id="6015796118275082299">ವರà³à²·</translation>
<translation id="6016178549409952427"><ph name="TOTAL_ELEMENTS" /> ರಲà³à²²à²¿ <ph name="CURRENT_ELEMENT" /> ಹೆಚà³à²šà³à²µà²°à²¿ ವಿಷಯಕà³à²•à³† ನà³à²¯à²¾à²µà²¿à²—ೇಟೠಮಾಡಿ</translation>
<translation id="6016551720757758985">ಹಿಂದಿನ ಆವೃತà³à²¤à²¿à²—ೆ ಹಿಂತಿರà³à²—à³à²µ ಮೂಲಕ ಪವರà³â€Œà²µà²¾à²¶à³â€Œ ಅನà³à²¨à³ ದೃಢೀಕರಿಸಿ</translation>
@@ -5920,7 +5920,7 @@
<ph name="BR" />
ನಿಮà³à²® Assistant ನಿಮà³à²®à²¨à³à²¨à³ ಗà³à²°à³à²¤à²¿à²¸à²²à³ ಮತà³à²¤à³ ಇತರರಿಂದ ನಿಮà³à²®à²¨à³à²¨à³ ಹೊರತà³à²ªà²¡à²¿à²¸à²¿ ಹೇಳಲೠVoice Match ಅನà³à²®à²¤à²¿à²¸à³à²¤à³à²¤à²¦à³†. ನಿಮà³à²® ಧà³à²µà²¨à²¿à²¯ ಅನನà³à²¯ ವಾಯà³à²¸à³ ಮಾಡೆಲೠಅನà³à²¨à³ ರಚಿಸಲೠAssistant ನಿಮà³à²® ಧà³à²µà²¨à²¿à²¯ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ತೆಗೆದà³à²•à³Šà²³à³à²³à³à²¤à³à²¤à²¦à³†, ಇದನà³à²¨à³ ನಿಮà³à²® ಸಾಧನದಲà³à²²à²¿ ಮಾತà³à²° ಸಂಗà³à²°à²¹à²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¦à³†. ನಿಮà³à²® ಧà³à²µà²¨à²¿à²¯à²¨à³à²¨à³ ಉತà³à²¤à²®à²µà²¾à²—ಿ ಗà³à²°à³à²¤à²¿à²¸à²²à³ ನಿಮà³à²® ವಾಯà³à²¸à³ ಮಾಡೆಲೠಅನà³à²¨à³ ತಾತà³à²•à²¾à²²à²¿à²•à²µà²¾à²—ಿ Google ಗೆ ಕಳà³à²¹à²¿à²¸à²¬à²¹à³à²¦à³.
<ph name="BR" />
- ನಂತರ ನೀವೠVoice Match ನಿಮಗಾಗಿ ಅಲà³à²² ಎಂದೠನಿರà³à²§à²°à²¿à²¸à²¿à²¦à²°à³†, Assistant ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿ ನೀವೠಅದನà³à²¨à³ ಸರಳವಾಗಿ ತೆಗೆದà³à²¹à²¾à²•à²¬à²¹à³à²¦à³. Voice Match ಸೆಟಪà³â€Œà²¨ ಸಮಯದಲà³à²²à²¿ ನೀವೠರೆಕಾರà³à²¡à³ ಮಾಡà³à²µ ಆಡಿಯೊ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ವೀಕà³à²·à²¿à²¸à²²à³ ಅಥವಾ ಅಳಿಸಲà³, <ph name="VOICE_MATCH_SETTINGS_URL" /> ಗೆ ಹೋಗಿ.
+ ನಂತರ ನೀವೠVoice Match ನಿಮಗಾಗಿ ಅಲà³à²² ಎಂದೠನಿರà³à²§à²°à²¿à²¸à²¿à²¦à²°à³†, Assistant ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿ ನೀವೠಅದನà³à²¨à³ ಸರಳವಾಗಿ ತೆಗೆದà³à²¹à²¾à²•à²¬à²¹à³à²¦à³. Voice Match ಸೆಟಪà³â€Œà²¨ ಸಮಯದಲà³à²²à²¿ ನೀವೠರೆಕಾರà³à²¡à³ ಮಾಡà³à²µ ಆಡಿಯೋ ಕà³à²²à²¿à²ªà³â€Œà²—ಳನà³à²¨à³ ವೀಕà³à²·à²¿à²¸à²²à³ ಅಥವಾ ಅಳಿಸಲà³, <ph name="VOICE_MATCH_SETTINGS_URL" /> ಗೆ ಹೋಗಿ.
<ph name="BR" />
<ph name="FOOTER_MESSAGE" /></translation>
<translation id="6810613314571580006">ಸಂಗà³à²°à²¹à²¿à²¸à²²à²¾à²¦ ರà³à²œà³à²µà²¾à²¤à³à²—ಳನà³à²¨à³ ಬಳಸಿಕೊಳà³à²³à³à²µ ಮೂಲಕ ವೆಬà³â€Œà²¸à³ˆà²Ÿà³â€Œà²—ಳಿಗೆ ಸà³à²µà²¯à²‚ಚಾಲಿತವಾಗಿ ಸೈನೠಇನೠಮಾಡಿ. ವೈಶಿಷà³à²Ÿà³à²¯à²µà²¨à³à²¨à³ ನಿಷà³à²•à³à²°à²¿à²¯à²—ೊಳಿಸಿದಾಗ, ವೆಬà³â€Œà²¸à³ˆà²Ÿà³â€Œà²—ೆ ಸೈನೠಇನೠಮಾಡà³à²µ ಮೊದಲೠಪà³à²°à²¤à²¿ ಬಾರಿಯೂ ನಿಮಗೆ ದೃಢೀಕರಿಸಲೠಕೇಳಲಾಗà³à²µà³à²¦à³.</translation>
@@ -7249,7 +7249,7 @@
<translation id="8104088837833760645">eSIM ಪà³à²°à³Šà²«à³ˆà²²à³ ಡೌನà³â€Œà²²à³‹à²¡à³ ಮಾಡಿ</translation>
<translation id="8105368624971345109">ಆಫೠಮಾಡà³</translation>
<translation id="8105541061909542455">{NUM_APPS,plural, =1{ಬೆಂಬಲಿಸದ ಆà³à²¯à²ªà³}one{ಬೆಂಬಲಿಸದ ಆà³à²¯à²ªà³â€Œà²—ಳà³}other{ಬೆಂಬಲಿಸದ ಆà³à²¯à²ªà³â€Œà²—ಳà³}}</translation>
-<translation id="8107015733319732394">ನಿಮà³à²® <ph name="DEVICE_TYPE" /> ನಲà³à²²à²¿ Google Play Store ಅನà³à²¨à³ ಸà³à²¥à²¾à²ªà²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¿à²¦à³†. ಇದೠಕೆಲವೠನಿಮಿಷಗಳನà³à²¨à³ ತೆಗೆದà³à²•à³Šà²³à³à²³à²¬à²¹à³à²¦à³.</translation>
+<translation id="8107015733319732394">ನಿಮà³à²® <ph name="DEVICE_TYPE" /> ನಲà³à²²à²¿ Google Play Store ಇನà³â€Œà²¸à³à²Ÿà²¾à²²à³ ಆಗà³à²¤à³à²¤à²¿à²¦à³†. ಇದೠಕೆಲವೠನಿಮಿಷಗಳನà³à²¨à³ ತೆಗೆದà³à²•à³Šà²³à³à²³à²¬à²¹à³à²¦à³.</translation>
<translation id="810728361871746125">ಡಿಸà³â€Œà²ªà³à²²à³‡ ರೆಸಲà³à²¯à³‚ಷನà³</translation>
<translation id="8108526232944491552">{COUNT,plural, =0{ಯಾವà³à²¦à³‡ ಥರà³à²¡à³ ಪಾರà³à²Ÿà²¿ ಕà³à²•à³€à²—ಳಿಲà³à²²}=1{1 ಥರà³à²¡à³ ಪಾರà³à²Ÿà²¿ ಕà³à²•à³€à²¯à²¨à³à²¨à³ ನಿರà³à²¬à²‚ಧಿಸಲಾಗಿದೆ}one{# ಥರà³à²¡à³ ಪಾರà³à²Ÿà²¿ ಕà³à²•à³€à²—ಳನà³à²¨à³ ನಿರà³à²¬à²‚ಧಿಸಲಾಗಿದೆ}other{# ಥರà³à²¡à³ ಪಾರà³à²Ÿà²¿ ಕà³à²•à³€à²—ಳನà³à²¨à³ ನಿರà³à²¬à²‚ಧಿಸಲಾಗಿದೆ}}</translation>
<translation id="8109109153262930486">ಡೀಫಾಲà³à²Ÿà³ ಅವತಾರà³</translation>
@@ -7306,7 +7306,7 @@
<translation id="8158117992543756526">ಈ ಸಾಧನವೠ<ph name="MONTH_AND_YEAR" /> ಅವಧಿಯಲà³à²²à²¿ ಸà³à²µà²¯à²‚ಚಾಲಿತ ಸಾಫà³à²Ÿà³â€Œà²µà³‡à²°à³ ಮತà³à²¤à³ ಭದà³à²°à²¤à³† ಅಪà³â€Œà²¡à³‡à²Ÿà³â€Œà²—ಳನà³à²¨à³ ಸà³à²µà³€à²•à²°à²¿à²¸à³à²µà³à²¦à²¨à³à²¨à³ ನಿಲà³à²²à²¿à²¸à²¿à²¦à³†. <ph name="LINK_BEGIN" />ಇನà³à²¨à²·à³à²Ÿà³ ತಿಳಿಯಿರಿ<ph name="LINK_END" /></translation>
<translation id="8159652640256729753">ಸà³à²•à³à²°à³€à²¨à³ ಬೇರà³à²ªà²¡à²¿à²¸à²¿ ಮತà³à²¤à³ ಸà³à²µà²¿à²šà²¿à²‚ಗೠಡೆಸà³à²•à³â€Œà²—ಳಂತಹ ಕà³à²°à²¿à²¯à³†à²—ಳಿಗಾಗಿ ವೈಬà³à²°à³‡à²·à²¨à³â€Œ ದೃಢೀಕರಣವನà³à²¨à³ ಸà³à²µà³€à²•à²°à²¿à²¸à²¿. <ph name="LINK_BEGIN" />ಇನà³à²¨à²·à³à²Ÿà³ ತಿಳಿಯಿರಿ<ph name="LINK_END" /></translation>
<translation id="816055135686411707">ಸೆಟà³à²Ÿà²¿à²‚ಗೠಪà³à²°à²®à²¾à²£à²ªà²¤à³à²°à²¦ ವಿಶà³à²µà²¾à²¸à²¾à²°à³à²¹à²¦à²²à³à²²à²¿ ದೋಷ</translation>
-<translation id="8160775796528709999">ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿à²°à³à²µ ಲೈವೠಕà³à²¯à²¾à²ªà³à²¶à²¨à³ ಅನà³à²¨à³ ಸಕà³à²°à²¿à²¯à²—ೊಳಿಸà³à²µ ಮೂಲಕ ನಿಮà³à²® ಆಡಿಯೊ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಪಡೆಯಿರಿ</translation>
+<translation id="8160775796528709999">ಸೆಟà³à²Ÿà²¿à²‚ಗà³â€Œà²—ಳಲà³à²²à²¿à²°à³à²µ ಲೈವೠಕà³à²¯à²¾à²ªà³à²¶à²¨à³ ಅನà³à²¨à³ ಸಕà³à²°à²¿à²¯à²—ೊಳಿಸà³à²µ ಮೂಲಕ ನಿಮà³à²® ಆಡಿಯೋ ಮತà³à²¤à³ ವೀಡಿಯೊಗಾಗಿ ಶೀರà³à²·à²¿à²•à³†à²—ಳನà³à²¨à³ ಪಡೆಯಿರಿ</translation>
<translation id="816095449251911490"><ph name="SPEED" /> - <ph name="RECEIVED_AMOUNT" />, <ph name="TIME_REMAINING" /></translation>
<translation id="81610453212785426"><ph name="BEGIN_LINK" />ಪà³à²°à³ˆà²µà³†à²¸à²¿ ಸà³à²¯à²¾à²‚ಡà³â€Œà²¬à²¾à²•à³à²¸à³<ph name="END_LINK" /> ನೊಂದಿಗೆ, ತೆರೆದ ವೆಬೠಅನà³à²¨à³ ಸಂರಕà³à²·à²¿à²¸à³à²µà³à²¦à²° ಜೊತೆಗೆ, ಕà³à²°à²¾à²¸à³-ಸೈಟೠಟà³à²°à³à²¯à²¾à²•à²¿à²‚ಗà³â€Œà²¨à²¿à²‚ದ ನಿಮà³à²®à²¨à³à²¨à³ ರಕà³à²·à²¿à²¸à²²à³ Chrome ಹೊಸ ತಂತà³à²°à²œà³à²žà²¾à²¨à²—ಳನà³à²¨à³ ಅಭಿವೃದà³à²§à²¿à²ªà²¡à²¿à²¸à³à²¤à³à²¤à²¿à²¦à³†.</translation>
<translation id="8161293209665121583">ವೆಬೠಪà³à²Ÿà²—ಳಿಗಾಗಿ ರೀಡರà³â€Œ ಮೋಡà³â€Œ</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ko.xtb b/chromium/chrome/app/resources/generated_resources_ko.xtb
index c7af8a98f87..6e986a09558 100644
--- a/chromium/chrome/app/resources/generated_resources_ko.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ko.xtb
@@ -6053,7 +6053,7 @@ https://support.google.com/chromebook/?p=tpm</translation>
<translation id="6923633482430812883">공유를 마운트하는 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. 연결하려는 íŒŒì¼ ì„œë²„ê°€ SMBv2 ì´ìƒì„ 지ì›í•˜ëŠ”지 확ì¸í•˜ì„¸ìš”.</translation>
<translation id="6925127338315966709">브ë¼ìš°ì €ì— 관리ë˜ëŠ” í”„ë¡œí•„ì„ ì¶”ê°€í•˜ë ¤ê³  합니다. 관리ìžê°€ í”„ë¡œí•„ì„ ì œì–´í•˜ê³  프로필 ë°ì´í„°ì— 액세스할 수 있습니다. ë¶ë§ˆí¬, 방문 기ë¡, 비밀번호 ë° ê¸°íƒ€ ì„¤ì •ì´ ê³„ì •ì— ë™ê¸°í™”ë˜ê³  ì´ë¥¼ 관리ìžê°€ 관리할 수 있습니다.</translation>
<translation id="6929126689972602640">í•™êµ ê³„ì •ì—서는 ìžë…€ 보호 ê¸°ëŠ¥ì´ ì§€ì›ë˜ì§€ 않습니다. 집ì—ì„œ í•™êµ ê³¼ì œë¥¼ 위해 Google í´ëž˜ìŠ¤ë£¸ ë° ë‹¤ë¥¸ 웹사ì´íŠ¸ì— 액세스할 수 있는 í•™êµ ê³„ì •ì„ ì¶”ê°€í•˜ë ¤ë©´ 먼저 ìžë…€ì˜ ê°œì¸ ê³„ì •ìœ¼ë¡œ 로그ì¸í•˜ì„¸ìš”. 설정ì—ì„œ í•™êµ ê³„ì •ì„ ë‚˜ì¤‘ì— ì¶”ê°€í•  수 있습니다.</translation>
-<translation id="6929760895658557216">Ok Google</translation>
+<translation id="6929760895658557216">Hey Google</translation>
<translation id="6930036377490597025">외장 보안 키 ë˜ëŠ” 내장 센서</translation>
<translation id="6930161297841867798">{NUM_EXTENSIONS,plural, =1{확장 프로그램 1ê°œ 거부ë¨}other{확장 프로그램 #ê°œ 거부ë¨}}</translation>
<translation id="6931690462168617033">í´ë¦­ ê°•ë„</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ky.xtb b/chromium/chrome/app/resources/generated_resources_ky.xtb
index b97f4f76736..47ae70643bb 100644
--- a/chromium/chrome/app/resources/generated_resources_ky.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ky.xtb
@@ -2093,7 +2093,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="2942279350258725020">Android жазышуулары</translation>
<translation id="2942560570858569904">Күтүп жатат…</translation>
<translation id="2942581856830209953">Бул баракты ыңгайлаштыруу</translation>
-<translation id="2944060181911631861">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Google'га мүчүлүштүктөрдү аныктоо жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө жөнөтүп, Android'де иштөө тажрыйбаңызды жакшыртууга жардам бериңиз. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK1" />Кеңири маалымат<ph name="END_LINK1" /></translation>
+<translation id="2944060181911631861">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Google'га мүчүлүштүктөрдү аныктоо жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө жөнөтүп, Android'де иштөө тажрыйбаңызды жакшыртууга жардам бериңиз. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK1" />Кеңири маалымат<ph name="END_LINK1" /></translation>
<translation id="2946054015403765210">Файлдарга өтүү</translation>
<translation id="2946119680249604491">Туташуу кошуу</translation>
<translation id="2946640296642327832">Bluetooth иштетүү</translation>
@@ -2532,7 +2532,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="3417836307470882032">24 Ñааттык убакыт</translation>
<translation id="3420501302812554910">Ички коопÑуздук ачкычын баштапкы абалга келтирүү талап кылынат</translation>
<translation id="3421387094817716717">Эллиптикалык ийри Ñызыктын жалпыга ачык ачкычы</translation>
-<translation id="3421672904902642628"><ph name="BEGIN_BOLD" />ЭÑкертүү<ph name="END_BOLD" />: Үнү окшош же үнүңүздү жаздырып алган адам жеке жыйынтыктарыңызды колдонуп же Жардамчыңызга кайрылышы мүмкүн.</translation>
+<translation id="3421672904902642628"><ph name="BEGIN_BOLD" />ЭÑкертүү<ph name="END_BOLD" />: Үнү Ñиздикине окшош же аны жаздырып алган адамга Жардамчыңыз жана ал аткарган нерÑелер жеткиликтүү болушу мүмкүн.</translation>
<translation id="3422291238483866753">Сайт айланаңыздын 3D картаÑын түзгөнү же камераңыздын абалын көргөнү жатканда урукÑат ÑуралÑын (Ñунушталат)</translation>
<translation id="3423463006624419153">"<ph name="PHONE_NAME_1" />" жана "<ph name="PHONE_NAME_2" />" телефондоруңузда:</translation>
<translation id="3423858849633684918"><ph name="PRODUCT_NAME" /> кайра ишке киргизүү</translation>
@@ -2601,7 +2601,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="3473479545200714844">Экран чоңойткуч</translation>
<translation id="3474218480460386727">Жаңы Ñөздөр үчүн 99 же андан аз тамгаларды колдонуңуз</translation>
<translation id="3475843873335999118">КечиреÑиз, манжа изиңиз дагы Ñле тааныла Ñлек. СырÑөзүңүздү киргизиңиз.</translation>
-<translation id="3476303763173086583">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Балаңыздын Android'ди колдонуу тажрыйбаÑын жакшыртууга көмөктөшүп, мүчүлүштүктөрдү издөө жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө Google'га жөнөтүүгө урукÑат бериңиз. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Түзмөктүн ÑÑÑи бул түзмөктөн мүчүлүштүктөрдү аныктоо жана колдонуу дайындарын Google'га жөнөтүү мүмкүнчүлүгүн иштетиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="3476303763173086583">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Балаңыздын Android'ди колдонуу тажрыйбаÑын жакшыртууга көмөктөшүп, мүчүлүштүктөрдү издөө жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө Google'га жөнөтүүгө урукÑат бериңиз. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Түзмөктүн ÑÑÑи бул түзмөктөн мүчүлүштүктөрдү аныктоо жана колдонуу дайындарын Google'га жөнөтүү мүмкүнчүлүгүн иштетиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="347670947055184738">Ой! Тутум бул түзмөк үчүн ÑаÑÑатты ала албай калды.</translation>
<translation id="347785443197175480"><ph name="HOST" /> камераңыз менен микрофонуңузду колдоно берÑин</translation>
<translation id="3479552764303398839">Ðзыр ÑмеÑ</translation>
@@ -2656,7 +2656,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="3526034519184079374">Сайттын дайындары окулбай же өзгөрбөй жатат</translation>
<translation id="3527085408025491307">Куржун</translation>
<translation id="3528498924003805721">КыÑка жол буталары</translation>
-<translation id="3531383404180922673">Телефонуңуздагы Ñоңку Ñүрөттөрдү, медианы жана билдирмелерди көрөÑүз. Телефонуңуздагы жазышуу колдонмолорун алып ойнотоÑуз.</translation>
+<translation id="3531383404180922673">Телефонуңуздагы Ñоңку Ñүрөттөрдү, медиа файлдарды жана билдирмелерди көрөÑүз. Телефонуңуздагы жазышуу колдонмолорун алып ойнотоÑуз.</translation>
<translation id="3532273508346491126">Шайкештирүүнү башкаруу</translation>
<translation id="3532521178906420528">Тармакка туташтырылууда...</translation>
<translation id="353316712352074340"><ph name="WINDOW_TITLE" /> – Ðудионун үнү өчүрүлгөн</translation>
@@ -3355,7 +3355,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="4209251085232852247">Өчүрүлдү</translation>
<translation id="4210048056321123003">Виртуалдык машина жүктөлүп алынууда</translation>
<translation id="421182450098841253">КыÑтармалар тилкеÑин &amp;көрÑÓ©Ñ‚Ò¯Ò¯</translation>
-<translation id="4211851069413100178">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Google'га мүчүлүштүктөрдү аныктоо жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө жөнөтүп, Android'де иштөө тажрыйбаңызды жакшыртууга жардам бериңиз. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Түзмөктүн ÑÑÑи бул түзмөктөн мүчүлүштүктөрдү аныктоо жана колдонуу дайындарын Google'га жөнөтүү мүмкүнчүлүгүн иштетиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="4211851069413100178">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Google'га мүчүлүштүктөрдү аныктоо жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө жөнөтүп, Android'де иштөө тажрыйбаңызды жакшыртууга жардам бериңиз. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Түзмөктүн ÑÑÑи бул түзмөктөн мүчүлүштүктөрдү аныктоо жана колдонуу дайындарын Google'га жөнөтүү мүмкүнчүлүгүн иштетиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="4211904048067111541">Android колдонмолорунда колдонууну токтотуу</translation>
<translation id="42126664696688958">ЭкÑпорттоо</translation>
<translation id="42137655013211669">Бул булакка кирүүгө Ñервер тыюу Ñалган.</translation>
@@ -5295,7 +5295,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="6185132558746749656">Түзмөктүн жайгашкан жери</translation>
<translation id="6188346519670155113">Chrome ÑерепчиÑин шайкештирүү күйүк</translation>
<translation id="6190953336330058278">Phone Hub колдонмолору</translation>
-<translation id="619279033188484792">Телефонуңуздагы Ñоңку Ñүрөттөрдү, медианы жана билдирмелерди <ph name="DEVICE_TYPE" /> түзмөгүңүздөн көрөÑүз</translation>
+<translation id="619279033188484792">Телефонуңуздагы Ñоңку Ñүрөттөрдү, медиа файлдарды жана билдирмелерди <ph name="DEVICE_TYPE" /> түзмөгүңүздөн көрөÑүз</translation>
<translation id="6195005504600220730">Серепчиңиз, OS жана түзмөгүңүз тууралуу маалыматты окуу</translation>
<translation id="6195693561221576702">Түзмөктү оффлайн демо режиминде жөндөөгө болбойт.</translation>
<translation id="6196640612572343990">Үчүнчү жактын кукилери бөгөттөлÑүн</translation>
@@ -5847,7 +5847,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="6725206449694821596">Internet Printing Protocol (IPP)</translation>
<translation id="6725970970008349185">Баракчада чагылдырыла турган талапкерлердин Ñаны</translation>
<translation id="672609503628871915">Эмне жаңылык бар</translation>
-<translation id="67269783048918309">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="67269783048918309">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="6727969043791803658">Туташып турат, батареÑнын деңгÑÑли – <ph name="BATTERY_PERCENTAGE" />%</translation>
<translation id="6731320427842222405">Бир нече мүнөт кетиши мүмкүн</translation>
<translation id="6733620523445262364">"<ph name="BOOKMARK_TITLE" />" түзүлдү.</translation>
@@ -6985,7 +6985,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="7850717413915978159"><ph name="BEGIN_PARAGRAPH1" />ChromeOS түзмөктөрүңүзгө кабарларды автоматтык түрдө жөнөтүүгө урукÑат берÑеңиз, ChromeOS'то кайÑÑ‹ нерÑелерди оңдоп жана жакшыртуу маанилүү Ñкенин биле алабыз. Бул кабарларда ChromeOS'тун бузулууÑу, адатта кайÑÑ‹ функциÑларды жана канча ÑÑтутум колдоноруңуз, Android колдонмоÑундагы мүчүлүштүктөрдү аныктоо жана колдонуу ÑтатиÑтикаÑÑ‹ ÑÑ‹Ñктуу маалымат камтылышы мүмкүн. Ðйрым маалыматты бириктирүү Google колдонмолоруна жана Android'дин иштеп чыгуучулары ÑÑ‹Ñктуу өнөктөштөрүнө да жардам берет.<ph name="END_PARAGRAPH1" />
<ph name="BEGIN_PARAGRAPH2" />Каалаган убакта ChromeOS түзмөгүңүздүн жөндөөлөрүнө өтүп, бул кабарларга урукÑат берип же тыюу Ñала алаÑыз. Эгер Ñиз домен админиÑтратору болÑоңуз, бул параметрди админиÑтратордун конÑолунан өзгөртө алаÑыз.<ph name="END_PARAGRAPH2" />
<ph name="BEGIN_PARAGRAPH3" />Эгер Google аккаунтуңуз үчүн Колдонмолор жана Интернеттеги аракеттер таржымалы күйгүзүлгөн болÑо, Android'деги маалыматыңыз Google аккаунтуңузга Ñакталышы мүмкүн. Маалыматты көрүп, өчүрүп жана аккаунтуңуздун жөндөөлөрүн өзгөртүү үчүн төмөнкү дарекке өтүңүз: account.google.com.<ph name="END_PARAGRAPH3" /></translation>
-<translation id="7851021205959621355"><ph name="BEGIN_BOLD" />ЭÑкертүү<ph name="END_BOLD" />: Үнү окшош же үнүңүздү жаздырып алган адам жеке жыйынтыктарыңызды колдонуп же Жардамчыңызга кайрылышы мүмкүн. БатареÑны үнөмдөө үчүн Жардамчыңыздын жөндөөлөрүнөн бул түзмөк кубат булагына туташып турганда гана "Oкей, Google" күйгөндөй кылып жөндөÑөңүз болот.</translation>
+<translation id="7851021205959621355"><ph name="BEGIN_BOLD" />ЭÑкертүү<ph name="END_BOLD" />: Үнү Ñиздикине окшош же аны жаздырып алган адамга Жардамчыңыз жана ал аткарган нерÑелер жеткиликтүү болушу мүмкүн. БатареÑны үнөмдөө үчүн Жардамчыңыздын жөндөөлөрүнөн бул түзмөк кубат булагына туташып турганда гана "Oкей, Google" күйгөндөй кылып жөндөÑөңүз болот.</translation>
<translation id="7851457902707056880">Кирүү аракети аккаунтунун ÑÑÑи менен гана чектелген. Кайра жүктөп, ÑÑÑинин аккаунт менен кириңиз. Шайман 30 Ñекунддан кийин автоматтык түрдө өчүп кайра жүктөлөт.</translation>
<translation id="7851716364080026749">Камера менен микрофонду колдонуу мүмкүнчүлүгү ар дайым бөгөттөлÑүн.</translation>
<translation id="7851720427268294554">IPP талдоочу</translation>
@@ -7713,7 +7713,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="8591783563402255548">1 Ñекунд</translation>
<translation id="8592141010104017453">Билдирмелер такыр көрүнбөÑүн</translation>
<translation id="859246725979739260">Бул Ñайттын жайгашкан жериңизди көрүү мүмкүнчүлүгү бөгөттөлгөн</translation>
-<translation id="8593121833493516339">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Балаңыздын Android'ди колдонуу тажрыйбаÑын жакшыртууга көмөктөшүп, мүчүлүштүктөрдү издөө жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө Google'га жөнөтүүгө урукÑат бериңиз. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK1" />Кеңири маалымат<ph name="END_LINK1" /></translation>
+<translation id="8593121833493516339">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Балаңыздын Android'ди колдонуу тажрыйбаÑын жакшыртууга көмөктөшүп, мүчүлүштүктөрдү издөө жана түзмөк менен колдонмолорду пайдалануу дайындарын автоматтык түрдө Google'га жөнөтүүгө урукÑат бериңиз. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK1" />Кеңири маалымат<ph name="END_LINK1" /></translation>
<translation id="8594908476761052472">Видео жаздыруу</translation>
<translation id="8596540852772265699">Өзгөчөлөштүрүлгөн файлдар</translation>
<translation id="8597845839771543242">Менчик форматы:</translation>
@@ -7849,7 +7849,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="8719653885894320876"><ph name="PLUGIN_NAME" /> жүктөлүп алынбай калды</translation>
<translation id="8720200012906404956">Мобилдик тармак изделүүдө. <ph name="BEGIN_LINK" />Кеңири маалымат<ph name="END_LINK" /></translation>
<translation id="8720816553731218127">Орнотуу убактыÑынын аттрибуттары үчүн берилген ишке кошуу убакыты аÑктады.</translation>
-<translation id="8722912030556880711">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="8722912030556880711">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="8724405322205516354">Бул Ñүрөтчө көрүнгөндө, манжаңыздын изи менен өздүгүңүздү же кандайдыр бир нерÑени Ñатып алууну ыраÑтайÑыз.</translation>
<translation id="8724409975248965964">Манжа изи кошулду</translation>
<translation id="8724859055372736596">Куржунда &amp;көрÑÓ©Ñ‚Ò¯Ò¯</translation>
@@ -8321,7 +8321,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="9170884462774788842">Chrome'дун ишин өзгөртө турган теманы компьютериңиздеги башка программа кошуп койду.</translation>
<translation id="917350715406657904">Ðта-Ñнең <ph name="APP_NAME" /> колдонмоÑуна койгон чекке жеттиң. Ðны Ñртең <ph name="TIME_LIMIT" /> колдоно алаÑÑ‹Ò£.</translation>
<translation id="9174401638287877180">Колдонуу жана мүчүлүштүктөрдү аныктоо маалыматын жөнөтүү. Балаңыздын Android'ди колдонуу тажрыйбаÑын жакшыртууга көмөктөшүп, мүчүлүштүктөрдү издөө жана түзмөк менен колдонмолорду пайдалануу маалыматын автоматтык түрдө Google'га жөнөтүүгө урукÑат бериңиз. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым маалыматтар тобу Google колдонмолоруна жана Android'дин иштеп чыгуучулары ÑÑ‹Ñктуу өнөктөштөрүнө да жардам берет. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул маалымат анын Google аккаунтуна Ñакталышы мүмкүн.</translation>
-<translation id="9176476835295860688">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="9176476835295860688">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Бул <ph name="BEGIN_LINK1" />жөндөөнү<ph name="END_LINK1" /> түзмөктүн ÑÑÑи иштетет. Эгер кошумча Колдонмолор жана Интернеттеги аракеттериңиздин таржымалынын жөндөөлөрү күйгүзүлгөн болÑо, бул нерÑелер Google аккаунтуңузга Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="9176611096776448349"><ph name="WINDOW_TITLE" /> – Bluetooth түзмөгү туташты</translation>
<translation id="9177949831069307748">ChromeOS Flex түзмөгү тууралуу маалыматты жана түзмөктөгү дайын-даректерди окуу.</translation>
<translation id="9178061802301856367">Ðккаунтка кирүү дайындарын өчүрүү</translation>
@@ -8332,7 +8332,7 @@ Family Link колдонмоÑун түзмөгүңүзгө орнотуп, бу
<translation id="918352324374649435">{COUNT,plural, =1{Колдонмо}other{# колдонмо}}</translation>
<translation id="9186963452600581158">Баланын Google аккаунту менен кирүү</translation>
<translation id="9187967020623675250">Ðчкычтар дал келген жок. <ph name="RESPONSE" /> үчүн каалаган баÑкычты баÑыңыз.</translation>
-<translation id="9188732951356337132">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым дайын-даректердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
+<translation id="9188732951356337132">Түзмөктүн иштеши тууралуу маалыматтарды жөнөтүү. Учурда бул түзмөк мүчүлүштүктөрдү аныктоо маалыматын, түзмөктүн жана колдонмонун иштетилиши жөнүндө дайындарды Google'га автоматтык түрдө жөнөтүп жатат. Бул маалымат балаңыздын өздүгүн аныктоо үчүн колдонулбайт жана тутум менен колдонмонун кыйла туруктуу иштешин камÑыз кылууга жана башка нерÑелерди жакшыртууга көмөктөшөт. Ðйрым нерÑелердин Google'дун өнөктөштөрүнө, миÑалы, Android'ди иштеп чыгуучуларга да кереги тийиши мүмкүн. Эгер кошумча Колдонмолор жана Интернеттеги аракеттер таржымалы балаңыз үчүн күйгүзүлгөн болÑо, бул нерÑелер анын Google аккаунтуна Ñакталышы мүмкүн. <ph name="BEGIN_LINK2" />Кеңири маалымат<ph name="END_LINK2" /></translation>
<translation id="919679265671373777">Телефонуңуздагы Ñоңку Ñүрөттөрдү жана медианы көрөÑүз. Телефонуңуздагы жазышуу колдонмолорун алып ойнотоÑуз.</translation>
<translation id="919686179725692564">Колдонмолоруңуздун камдык көчүрмөÑүн Ñактоо тууралуу кеңири маалымат</translation>
<translation id="9198090666959937775">Android телефонуңузду коопÑуздук ачкычы катары колдонуңуз</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_mk.xtb b/chromium/chrome/app/resources/generated_resources_mk.xtb
index 5d6de87c4b8..a02c380c4e4 100644
--- a/chromium/chrome/app/resources/generated_resources_mk.xtb
+++ b/chromium/chrome/app/resources/generated_resources_mk.xtb
@@ -535,7 +535,7 @@
<translation id="1523279371236772909">Прегледано во изминатиот меÑец</translation>
<translation id="1523978563989812243">ЕкÑтензии за „Од текÑÑ‚ во говор“</translation>
<translation id="1524430321211440688">ТаÑтатура</translation>
-<translation id="1524563461097350801">Ðе, благодарам</translation>
+<translation id="1524563461097350801">Ðе, фала</translation>
<translation id="1525740877599838384">КориÑти Ñамо Wi-Fi за утврдување на локацијата</translation>
<translation id="152629053603783244">РеÑтартирај го Linux</translation>
<translation id="1526335046150927198">Овозможи забрзување на лизгањето на подлогата за допир</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ml.xtb b/chromium/chrome/app/resources/generated_resources_ml.xtb
index 6db50377b60..75308f5d91c 100644
--- a/chromium/chrome/app/resources/generated_resources_ml.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ml.xtb
@@ -7741,7 +7741,7 @@
<translation id="8635628933471165173">റീലോഡൠചെയàµà´¯àµà´¨àµà´¨àµ...</translation>
<translation id="8636284842992792762">വിപàµà´²àµ€à´•à´°à´£à´™àµà´™àµ¾ ഇനിഷàµà´¯à´²àµˆà´¸àµ ചെയàµà´¯àµà´¨àµà´¨àµ...</translation>
<translation id="8636500887554457830">പോപàµà´ªàµ-à´…à´ªàµà´ªàµà´•àµ¾ അയയàµâ€Œà´•àµà´•à´¾à´¨àµ‹ റീഡയറകàµà´±àµà´±àµà´•àµ¾ ഉപയോഗികàµà´•à´¾à´¨àµ‹ സൈറàµà´±àµà´•à´³àµ† à´…à´¨àµà´µà´¦à´¿à´•àµà´•à´°àµà´¤àµ</translation>
-<translation id="8637688295594795546">സിസàµà´±àµà´±à´‚ à´…à´ªàµà´¡àµ‡à´±àµà´±àµ ലഭàµà´¯à´®à´¾à´£àµ. ഡൌണàµâ€à´²àµ‹à´¡àµ ചെയàµà´¯àµà´¨àµà´¨à´¤à´¿à´¨àµ തയàµà´¯à´¾à´±àµ†à´Ÿàµà´•àµà´•àµà´¨àµà´¨àµ...</translation>
+<translation id="8637688295594795546">സിസàµà´±àµà´±à´‚ à´…à´ªàµà´¡àµ‡à´±àµà´±àµ ലഭàµà´¯à´®à´¾à´£àµ. ഡൗണàµâ€à´²àµ‹à´¡àµ ചെയàµà´¯àµà´¨àµà´¨à´¤à´¿à´¨àµ തയàµà´¯à´¾à´±àµ†à´Ÿàµà´•àµà´•àµà´¨àµà´¨àµ...</translation>
<translation id="8639047128869322042">ദോഷകരമായ സോഫàµâ€Œà´±àµà´±àµâ€Œà´µàµ†à´¯àµ¼ ഉണàµà´Ÿàµ‹à´¯àµ†à´¨àµà´¨àµ പരിശോധികàµà´•àµà´¨àµà´¨àµ...</translation>
<translation id="8639635302972078117">ഉപയോഗവàµà´‚ à´ªàµà´°à´¶àµâ€Œà´¨à´¨à´¿àµ¼à´£àµà´£à´¯à´µàµà´®à´¾à´¯à´¿ ബനàµà´§à´ªàµà´ªàµ†à´Ÿàµà´Ÿ ഡാറàµà´± അയയàµà´•àµà´•àµà´•. à´ªàµà´°à´¶àµâ€Œà´¨à´¨à´¿àµ¼à´£àµà´£à´¯à´‚, ഉപകരണം, ആപàµà´ªàµ ഉപയോഗം à´Žà´¨àµà´¨à´¿à´µà´¯àµà´®à´¾à´¯à´¿ ബനàµà´§à´ªàµà´ªàµ†à´Ÿàµà´Ÿ ഡാറàµà´±, à´ˆ ഉപകരണം നിലവിൽ à´¸àµà´µà´¯à´®àµ‡à´µ Google-നൠഅയയàµà´•àµà´•àµà´¨àµà´¨àµà´£àµà´Ÿàµ. നിങàµà´™à´³àµà´Ÿàµ† à´•àµà´Ÿàµà´Ÿà´¿à´¯àµ† തിരിചàµà´šà´±à´¿à´¯à´¾àµ» ഇതൠഉപയോഗികàµà´•à´¿à´²àµà´², സിസàµâ€Œà´±àµà´±à´‚, ആപàµà´ªàµ à´¸àµà´¥à´¿à´°à´¤, മറàµà´±àµ മെചàµà´šà´ªàµà´ªàµ†à´Ÿàµà´¤àµà´¤à´²àµà´•àµ¾ à´Žà´¨àµà´¨à´¿à´µà´¯àµà´•àµà´•àµ സഹായികàµà´•àµà´•à´¯àµà´‚ ചെയàµà´¯àµà´‚. à´šà´¿à´² സംഗàµà´°à´¹ ഡാറàµà´±, Google ആപàµà´ªàµà´•à´³àµ†à´¯àµà´‚ Android ഡെവലപàµà´ªàµ¼à´®à´¾à´°àµ†à´ªàµà´ªàµ‹à´²àµà´³àµà´³ പങàµà´•à´¾à´³à´¿à´•à´³àµ†à´¯àµà´‚ സഹായികàµà´•àµà´•à´¯àµà´‚ ചെയàµà´¯àµà´‚. നിങàµà´™à´³àµà´Ÿàµ† à´•àµà´Ÿàµà´Ÿà´¿à´¯àµà´Ÿàµ† അധിക വെബàµ, ആപàµà´ªàµ ആകàµà´±àµà´±à´¿à´µà´¿à´±àµà´±à´¿ à´•àµà´°à´®àµ€à´•à´°à´£à´‚ ഓണാകàµà´•à´¿à´¯à´¿à´Ÿàµà´Ÿàµà´£àµà´Ÿàµ†à´™àµà´•à´¿àµ½, à´ˆ ഡാറàµà´± അവരàµà´Ÿàµ† Google à´…à´•àµà´•àµ—à´£àµà´Ÿà´¿àµ½ സംരകàµà´·à´¿à´•àµà´•à´ªàµà´ªàµ†à´Ÿàµà´Ÿàµ‡à´•àµà´•à´¾à´‚.</translation>
<translation id="8642900771896232685">2 സെകàµà´•àµ»à´¡àµ</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_mn.xtb b/chromium/chrome/app/resources/generated_resources_mn.xtb
index d78f100c3a8..58836e6a0de 100644
--- a/chromium/chrome/app/resources/generated_resources_mn.xtb
+++ b/chromium/chrome/app/resources/generated_resources_mn.xtb
@@ -690,7 +690,7 @@
<translation id="164936512206786300">Bluetooth төхөөрөмжийг үл хоÑлуулах</translation>
<translation id="1651008383952180276">Та нÑвтрÑÑ… үгÑÑ Ñ…Ð¾Ñ‘Ñ€ удаа зөв оруулах шаардлагатай</translation>
<translation id="1652326691684645429">Ойролцоо хуваалцах онцлогийг идÑвхжүүлÑÑ…</translation>
-<translation id="1656528038316521561">ДÑвÑгÑрийн бүдÑгрÑл</translation>
+<translation id="1656528038316521561">Ðрын дÑвÑгÑрийн тод байдал</translation>
<translation id="1657406563541664238"><ph name="PRODUCT_NAME" />-г Ñайжруулахад дÑмжлÑг үзүүлÑÑ… зорилгоор Ñ…ÑÑ€ÑглÑÑний ÑтатиÑтик болон гÑмтлийн тайланг автоматаар Google-д илгÑÑÑ…</translation>
<translation id="1657937299377480641">БоловÑролын нөөцөд хандахаар дахин нÑвтрÑхийн тулд ÑцÑг ÑÑ…ÑÑÑÑÑ Ñ‚Ð°Ð½Ð´ зөвшөөрөл өгөхийг Ñ…Ò¯ÑÐ½Ñ Ò¯Ò¯</translation>
<translation id="1658424621194652532">Ð­Ð½Ñ Ñ…ÑƒÑƒÐ´Ð°Ñ Ñ‚Ð°Ð½Ñ‹ микрофонд хандаж байна.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_mr.xtb b/chromium/chrome/app/resources/generated_resources_mr.xtb
index ea4b4553ab9..765298e60f1 100644
--- a/chromium/chrome/app/resources/generated_resources_mr.xtb
+++ b/chromium/chrome/app/resources/generated_resources_mr.xtb
@@ -321,7 +321,7 @@
<translation id="1316136264406804862">शोधत आहे...</translation>
<translation id="1316248800168909509"><ph name="DEVICE" /> शी कनेकà¥à¤Ÿ करता आले नाही. पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="1316495628809031177">सिंक थांबवले आहे</translation>
-<translation id="1317637799698924700">तà¥à¤®à¤šà¥‡ डॉकिंग सà¥à¤Ÿà¥‡à¤¶à¤¨ हे USB केबल कंपॅटिबिल मोडमधà¥à¤¯à¥‡ काम करेल.</translation>
+<translation id="1317637799698924700">तà¥à¤®à¤šà¥‡ डॉकिंग सà¥à¤Ÿà¥‡à¤¶à¤¨ हे USB केबल कंपॅटिबल मोडमधà¥à¤¯à¥‡ काम करेल.</translation>
<translation id="1319983966058170660"><ph name="SUBPAGE_TITLE" /> सबपेजचे मागे जा बटण</translation>
<translation id="1322046419516468189">सेवà¥à¤¹ केलेले पासवरà¥à¤¡ तà¥à¤®à¤šà¥à¤¯à¤¾ <ph name="SAVED_PASSWORDS_STORE" /> मधà¥à¤¯à¥‡ पहा आणि वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करा</translation>
<translation id="1324106254079708331">लकà¥à¤·à¥à¤¯à¤¿à¤¤ हलà¥à¤²à¥à¤¯à¤¾à¤‚चा धोका असलेलà¥à¤¯à¤¾ कोणाचà¥à¤¯à¤¾à¤¹à¥€ वैयकà¥à¤¤à¤¿à¤• Google खातà¥à¤¯à¤¾à¤‚चे संरकà¥à¤·à¤£ करते</translation>
@@ -357,7 +357,7 @@
<translation id="1353980523955420967">PPD सापडत नाही. तà¥à¤®à¤šà¥‡ Chromebook ऑनलाइन असलà¥à¤¯à¤¾à¤šà¥€ खातà¥à¤°à¥€ करा आणि पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="1354045473509304750"><ph name="HOST" /> ला तà¥à¤®à¤šà¤¾ कॅमेरा वापरणà¥à¤¯à¤¾à¤šà¥€ आणि हलवणà¥à¤¯à¤¾à¤šà¥€ अनà¥à¤®à¤¤à¥€ देणे सà¥à¤°à¥‚ ठेवा</translation>
<translation id="1355088139103479645">सरà¥à¤µ डेटा हटवायचा का?</translation>
-<translation id="1355466263109342573"><ph name="PLUGIN_NAME" /> अवरोधित केले आहे</translation>
+<translation id="1355466263109342573"><ph name="PLUGIN_NAME" /> बà¥à¤²à¥‰à¤• केले आहे</translation>
<translation id="1358741672408003399">शà¥à¤¦à¥à¤§à¤²à¥‡à¤–न आणि वà¥à¤¯à¤¾à¤•à¤°à¤£</translation>
<translation id="1359923111303110318">तà¥à¤®à¤šà¥‡ डिवà¥à¤¹à¤¾à¤‡à¤¸ Smart Lock वापरून अनलॉक केले जाऊ शकते. अनलॉक करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ Enter दाबा.</translation>
<translation id="1361164813881551742">वà¥à¤¯à¤•à¥à¤¤à¤¿à¤šà¤²à¤¿à¤¤à¤ªà¤£à¥‡ जोडा</translation>
@@ -469,7 +469,7 @@
<translation id="145432137617179457">सà¥à¤ªà¥‡à¤² चेकचà¥à¤¯à¤¾ भाषा</translation>
<translation id="1455119378540982311">विंडोचा आकार पà¥à¤°à¥€à¤¸à¥‡à¤Ÿ करा</translation>
<translation id="1459693405370120464">हवामान</translation>
-<translation id="146000042969587795">ही फà¥à¤°à¥‡à¤® अवरोधित केली होती कारण यात काही असà¥à¤°à¤•à¥à¤·à¤¿à¤¤ आशय आहे.</translation>
+<translation id="146000042969587795">ही फà¥à¤°à¥‡à¤® बà¥à¤²à¥‰à¤• केली होती कारण यात काही असà¥à¤°à¤•à¥à¤·à¤¿à¤¤ आशय आहे.</translation>
<translation id="146219525117638703">ONC सà¥à¤¥à¤¿à¤¤à¥€</translation>
<translation id="146220085323579959">इंटरनेट डिसà¥à¤•à¤¨à¥‡à¤•à¥à¤Ÿ à¤à¤¾à¤²à¥‡. कृपया तà¥à¤®à¤šà¥‡ इंटरनेट कनेकà¥â€à¤¶à¤¨ तपासा आणि पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="1462850958694534228">अपडेट केलेलà¥à¤¯à¤¾ आयकनचे पà¥à¤¨à¤°à¤¾à¤µà¤²à¥‹à¤•à¤¨ करा</translation>
@@ -513,7 +513,7 @@
<translation id="150411034776756821"><ph name="SITE" /> काढून टाका</translation>
<translation id="1504551620756424144">शेअर केलेली फोलà¥à¤¡à¤° Windows मधà¥à¤¯à¥‡ <ph name="BASE_DIR" /> येथे उपलबà¥à¤§ आहेत.</translation>
<translation id="1506061864768559482">शोध इंजीन</translation>
-<translation id="1507170440449692343">हे पृषà¥à¤  आपलà¥à¤¯à¤¾ कॅमेरà¥â€à¤¯à¤¾à¤µà¤° पà¥à¤°à¤µà¥‡à¤¶ करणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न अवरोधित केले गेले आहे.</translation>
+<translation id="1507170440449692343">तà¥à¤®à¤šà¥à¤¯à¤¾ कॅमेराचा अâ€à¥…कà¥à¤¸à¥‡à¤¸ या पेजसाठी बà¥à¤²à¥‰à¤• केला आहे.</translation>
<translation id="1507246803636407672">&amp;टाकून दà¥à¤¯à¤¾</translation>
<translation id="1507884455975553832">मेसेजिंग ॲपà¥à¤¸ सà¥à¤Ÿà¥à¤°à¥€à¤® करा</translation>
<translation id="1509163368529404530">&amp;गट रिसà¥à¤Ÿà¥‹à¤…र करा</translation>
@@ -1101,7 +1101,7 @@
<translation id="2044014337866019681">कृपया सेशन अनलॉक करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¥à¤¹à¥€ <ph name="ACCOUNT" /> ची पडताळणी करत आहात याची खातà¥à¤°à¥€ करा.</translation>
<translation id="204497730941176055">Microsoft Certificate Template Name</translation>
<translation id="2045117674524495717">कीबोरà¥à¤¡ शॉरà¥à¤Ÿà¤•à¤Ÿ मदतकरà¥à¤¤à¤¾</translation>
-<translation id="2045969484888636535">कà¥à¤•à¥€ अवरोधित करणे सà¥à¤°à¥‚ ठेवा</translation>
+<translation id="2045969484888636535">कà¥à¤•à¥€ बà¥à¤²à¥‰à¤• करणे सà¥à¤°à¥‚ ठेवा</translation>
<translation id="204622017488417136">तà¥à¤®à¤šà¥‡ डिवà¥à¤¹à¤¾à¤‡à¤¸ Chrome चà¥à¤¯à¤¾ मागील इंसà¥à¤Ÿà¥‰à¤² केलेलà¥à¤¯à¤¾ आवृतà¥à¤¤à¥€à¤µà¤° परत जाईल. सरà¥à¤µ वापरकरà¥à¤¤à¤¾ खाती आणि सà¥à¤¥à¤¾à¤¨à¤¿à¤• डेटा काढला जाईल. हे पूरà¥à¤µà¤µà¤¤ केले जाऊ शकत नाही.</translation>
<translation id="2046702855113914483">रामेन</translation>
<translation id="2046770133657639077">डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¤¾ EID दाखवा</translation>
@@ -1302,7 +1302,7 @@
<translation id="222447520299472966">किमान à¤à¤• आरà¥à¤Ÿ गॅलरी अलà¥à¤¬à¤® निवडणे आवशà¥à¤¯à¤• आहे</translation>
<translation id="2224551243087462610">फोलà¥à¤¡à¤° नाव संपादित करा</translation>
<translation id="2225864335125757863">तà¥à¤®à¤šà¥‡ खाते सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ ठेवणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ हे पासवरà¥à¤¡ तातà¥à¤•à¤¾à¤³ बदला:</translation>
-<translation id="2226449515541314767">MIDI डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¥‡ पूरà¥à¤£ नियंतà¥à¤°à¤£ असणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न ही साइट अवरोधित केली गेली आहे.</translation>
+<translation id="2226449515541314767">MIDI डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¥‡ पूरà¥à¤£ नियंतà¥à¤°à¤£ असणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न ही साइट बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="2226826835915474236">इनॲकà¥à¤Ÿà¤¿à¤µà¥à¤¹ शॉरà¥à¤Ÿà¤•à¤Ÿ</translation>
<translation id="2226907662744526012">पिन à¤à¤‚टर केलà¥à¤¯à¤¾à¤¨à¤‚तर आपोआप अनलॉक करा</translation>
<translation id="2227179592712503583">सूचना काढून टाका</translation>
@@ -1925,7 +1925,7 @@
<translation id="2792290659606763004">Android ॲपà¥à¤¸ काढून टाकायचे?</translation>
<translation id="2792465461386711506">तà¥à¤®à¤šà¥à¤¯à¤¾ फोनवरून अलीकडील Chrome टॅब पाहणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, Chrome सिंक सà¥à¤°à¥‚ करा</translation>
<translation id="2792697226874849938">निरà¥à¤¬à¤‚धाशी संबंधित इमेज</translation>
-<translation id="2794233252405721443">साइट अवरोधित केली</translation>
+<translation id="2794233252405721443">साइट बà¥à¤²à¥‰à¤• केली</translation>
<translation id="2794522004398861033">eSIM सेट करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ वाय-फायशी किंवा इथरनेटशी कनेकà¥à¤Ÿ करा</translation>
<translation id="2795716239552913152">संबंधित वैशिषà¥à¤Ÿà¥à¤¯à¥‡ किंवा सà¥à¤¥à¤¾à¤¨à¤¿à¤• बातमà¥à¤¯à¤¾ अथवा जवळपासची दà¥à¤•à¤¾à¤¨à¥‡ यांसारखà¥à¤¯à¤¾ माहितीसाठी साइट सामानà¥à¤¯à¤ªà¤£à¥‡ तà¥à¤®à¤šà¥‡ सà¥à¤¥à¤¾à¤¨ वापरतात</translation>
<translation id="2796424461616874739">Authentication timeout while connecting to "<ph name="DEVICE_NAME" />".</translation>
@@ -2309,7 +2309,7 @@
<translation id="3163201441334626963"><ph name="VENDOR_ID" /> विकà¥à¤°à¥‡à¤¤à¥à¤¯à¤¾à¤•à¤¡à¥€à¤² <ph name="PRODUCT_ID" /> अजà¥à¤žà¤¾à¤¤ उतà¥à¤ªà¤¾à¤¦à¤¨</translation>
<translation id="3163511056918491211">तà¥à¤®à¤šà¤¾ डेटा सहजरीतà¥â€à¤¯à¤¾ रिसà¥à¤Ÿà¥‹à¤…र करा किंवा डिवà¥à¤¹à¤¾à¤‡à¤¸ कधीही सà¥à¤µà¤¿à¤š करा. तà¥à¤®à¤šà¥‡ बॅकअप Google वर अपलोड केले जातात आणि तà¥à¤®à¤šà¤¾ Google खाते पासवरà¥à¤¡ वापरून à¤à¤‚कà¥à¤°à¤¿à¤ªà¥à¤Ÿ केले जातात.</translation>
<translation id="3164329792803560526"><ph name="APP_NAME" /> यावर हा टॅब शेअर करत आहे</translation>
-<translation id="3165390001037658081">काही वाहक हे वैशिषà¥à¤Ÿà¥à¤¯ अवरोधित शकतात.</translation>
+<translation id="3165390001037658081">काही वाहक हे वैशिषà¥à¤Ÿà¥à¤¯ बà¥à¤²à¥‰à¤• करू शकतात.</translation>
<translation id="3170072451822350649">तà¥à¤®à¥à¤¹à¥€ साइन इन वगळà¥à¤¨ <ph name="LINK_START" />अतिथी मà¥à¤¹à¤£à¥‚न बà¥à¤°à¤¾à¤‰à¤<ph name="LINK_END" /> देखील करू शकता.</translation>
<translation id="31774765611822736">डावीकडील नवीन टॅब</translation>
<translation id="3177909033752230686">पृषà¥à¤  भाषा:</translation>
@@ -2568,7 +2568,7 @@
<translation id="3446274660183028131">Windows इंसà¥à¤Ÿà¥‰à¤² करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ कृपया Parallels Desktop लाà¤à¤š करा.</translation>
<translation id="344630545793878684">अनेक वेबसाइटवर तà¥à¤®à¤šà¤¾ डेटा वाचा</translation>
<translation id="3446650212859500694">या फाइलमधà¥à¤¯à¥‡ संवेदनशील आशय आहे</translation>
-<translation id="3447644283769633681">सरà¥à¤µ तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ अवरोधित करा</translation>
+<translation id="3447644283769633681">सरà¥à¤µ तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ बà¥à¤²à¥‰à¤• करा</translation>
<translation id="3448492834076427715">खाते अपडेट करा</translation>
<translation id="3449393517661170867">नवीन टॅब असलेलà¥à¤¯à¤¾ विंडोमधà¥à¤¯à¥‡ उघडा</translation>
<translation id="3449839693241009168"><ph name="EXTENSION_NAME" /> कडे कमांड पाठविणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ <ph name="SEARCH_KEY" /> दाबा</translation>
@@ -2882,7 +2882,7 @@
<translation id="3757733214359997190">कोणतà¥à¤¯à¤¾à¤¹à¥€ साइट आढळलà¥à¤¯à¤¾ नाहीत</translation>
<translation id="375841316537350618">पà¥à¤°à¥‰à¤•à¥à¤¸à¥€ सà¥à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ डाउनलोड करत आहे...</translation>
<translation id="3758842566811519622">कà¥à¤•à¥€à¤œ सेट केलà¥à¤¯à¤¾</translation>
-<translation id="3759933321830434300">वेब पृषà¥à¤ à¤¾à¤‚चे भाग अवरोधित करा</translation>
+<translation id="3759933321830434300">वेब पेजचे भाग बà¥à¤²à¥‰à¤• करा</translation>
<translation id="3760460896538743390">&amp;पारà¥à¤¶à¥à¤µà¤­à¥‚मी पृषà¥à¤ à¤¾à¤šà¥‡ निरीकà¥à¤·à¤£ करा</translation>
<translation id="37613671848467444">&amp;गà¥à¤ªà¥à¤¤ विंडोमधà¥à¤¯à¥‡ उघडा</translation>
<translation id="3761556954875533505">साइटला फाइल संपादित करू दà¥à¤¯à¤¾à¤¯à¤šà¥à¤¯à¤¾?</translation>
@@ -3233,7 +3233,7 @@
<translation id="408223403876103285"><ph name="WEBSITE" /> ने तà¥à¤®à¤šà¥à¤¯à¤¾ फोनवर सूचना पाठवली आहे. हे तà¥à¤®à¥à¤¹à¥€à¤š आहात याची खातà¥à¤°à¥€ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, तेथे दिलेलà¥à¤¯à¤¾ पायऱà¥à¤¯à¤¾ फॉलो करा.</translation>
<translation id="4084682180776658562">बà¥à¤•à¤®à¤¾à¤°à¥à¤•</translation>
<translation id="4084835346725913160"><ph name="TAB_NAME" /> बंद करा</translation>
-<translation id="4085298594534903246">या पेजवर JavaScript अवरोधित केलेले होते.</translation>
+<translation id="4085298594534903246">या पेजवर JavaScript बà¥à¤²à¥‰à¤• केलेले होते.</translation>
<translation id="4087089424473531098">à¤à¤•à¥à¤¸à¥à¤Ÿà¥‡à¤‚शन तयार केले: <ph name="EXTENSION_FILE" /></translation>
<translation id="408721682677442104">MIDI डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¥‡ पूरà¥à¤£ नियंतà¥à¤°à¤£ नाकारले</translation>
<translation id="4089235344645910861">सेटिंगà¥à¤œ सेवà¥à¤¹ केलà¥à¤¯à¤¾. सिंक सà¥à¤°à¥‚ केले.</translation>
@@ -3379,7 +3379,7 @@
<translation id="4247901771970415646"><ph name="USERNAME" /> शी सिंक करू शकत नाही</translation>
<translation id="4248098802131000011">डेटा भंग आणि इतर सà¥à¤°à¤•à¥à¤·à¤¿à¤¤à¤¤à¤¾ समसà¥à¤¯à¤¾à¤‚पासून तà¥à¤®à¤šà¥‡ पासवरà¥à¤¡ सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ ठेवा</translation>
<translation id="4249248555939881673">नेटवरà¥à¤• कनेकà¥à¤¶à¤¨à¤šà¥€ पà¥à¤°à¤¤à¥€à¤•à¥à¤·à¤¾ करत आहे...</translation>
-<translation id="4249373718504745892">हे पृषà¥â€à¤  तà¥à¤®à¤šà¤¾ कॅमेरा आणि मायकà¥à¤°à¥‹à¤«à¥‹à¤¨à¤µà¤° पà¥à¤°à¤µà¥‡à¤¶ करणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न अवरोधित केले गेले आहे.</translation>
+<translation id="4249373718504745892">तà¥à¤®à¤šà¤¾ कॅमेरा आणि मायकà¥à¤°à¥‹à¤«à¥‹à¤¨à¤šà¤¾ अâ€à¥…कà¥à¤¸à¥‡à¤¸ या पेजसाठी बà¥à¤²à¥‰à¤• केला आहे.</translation>
<translation id="424963718355121712">परिणाम होतो अशा होसà¥à¤Ÿà¤µà¤°à¥‚न ॲपà¥à¤¸ दिली जाणे आवशà¥à¤¯à¤• आहे</translation>
<translation id="4250229828105606438">सà¥à¤•à¥à¤°à¥€à¤¨à¤¶à¥‰à¤Ÿ</translation>
<translation id="4250680216510889253">नाही</translation>
@@ -3450,7 +3450,7 @@
<translation id="4310139701823742692">फाइलचा फॉरमॅट चà¥à¤•à¥€à¤šà¤¾ आहे. PPD फाइल तपासा आणि पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="431076611119798497">&amp;तपशील</translation>
<translation id="4312701113286993760">{COUNT,plural, =1{à¤à¤• Google खाते}other{<ph name="EXTRA_ACCOUNTS" /> Google खाती}}</translation>
-<translation id="4312866146174492540">अवरोधित करा (डीफॉलà¥à¤Ÿ)</translation>
+<translation id="4312866146174492540">बà¥à¤²à¥‰à¤• करा (डीफॉलà¥à¤Ÿ)</translation>
<translation id="4314497418046265427">तà¥à¤®à¥à¤¹à¥€ तà¥à¤®à¤šà¤¾ फोन तà¥à¤®à¤šà¥à¤¯à¤¾ <ph name="DEVICE_TYPE" /> शी कनेकà¥à¤Ÿ करताना आणखी उतà¥à¤ªà¤¾à¤¦à¤¨à¤•à¥à¤·à¤® वà¥à¤¹à¤¾</translation>
<translation id="4314815835985389558">सिंक वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करा</translation>
<translation id="4315933848520197627">खातà¥à¤¯à¤¾à¤šà¥€ लिंक काढून टाका</translation>
@@ -3480,7 +3480,7 @@
<translation id="4348766275249686434">à¤à¤°à¤° संकलित करा</translation>
<translation id="4349828822184870497">उपयà¥à¤•à¥à¤¤</translation>
<translation id="4350230709416545141">तà¥à¤®à¤šà¥‡ सà¥à¤¥à¤¾à¤¨ अâ€à¥…कà¥à¤¸à¥‡à¤¸ करणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न <ph name="HOST" /> ला नेहमी बà¥à¤²à¥‰à¤• करा</translation>
-<translation id="4350782034419308508">Hey Google</translation>
+<translation id="4350782034419308508">Ok Google</translation>
<translation id="4351770750390404505"><ph name="BEGIN_PARAGRAPH1" />सरà¥à¤µà¥‹à¤¤à¥à¤¤à¤® अनà¥à¤­à¤µ पà¥à¤°à¤µà¤£à¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, <ph name="DEVICE_OS" /> हे डिवà¥à¤¹à¤¾à¤‡à¤¸à¤¬à¤¦à¥à¤¦à¤² हारà¥à¤¡à¤µà¥‡à¤…र डेटा गोळा करते आणि कोणती अपडेट डिलिवà¥à¤¹à¤° केली पाहिजे हे निरà¥à¤§à¤¾à¤°à¤¿à¤¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तो Google सोबत शेअर करते. यावà¥à¤¯à¤¤à¤¿à¤°à¤¿à¤•à¥à¤¤, तà¥à¤®à¥à¤¹à¥€ <ph name="DEVICE_OS" /> मधील अनà¥à¤­à¤µ आणि सेवा यांचà¥à¤¯à¤¾à¤¶à¥€ संबंधित सपोरà¥à¤Ÿ आणि सà¥à¤§à¤¾à¤°à¤£à¤¾ यांसारखà¥à¤¯à¤¾ अतिरिकà¥à¤¤ उदà¥à¤¦à¥‡à¤¶à¤¾à¤‚साठी Google ला हा डेटा वापरणà¥à¤¯à¤¾à¤šà¥€ अनà¥à¤®à¤¤à¥€ देऊ शकता.<ph name="END_PARAGRAPH1" />
<ph name="BEGIN_PARAGRAPH2" />फिलà¥à¤Ÿà¤° करणà¥à¤¯à¤¾à¤šà¥‡ परà¥à¤¯à¤¾à¤¯ अपडेट करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ Google ला पाठवलेला डेटा तसेच असे कोणतेही पà¥à¤°à¤¸à¤‚ग जà¥à¤¯à¤¾à¤¤ तà¥à¤®à¥à¤¹à¥€ Google सोबत डेटा शेअर करणà¥à¤¯à¤¾à¤šà¥‡ निवडले होते हे पाहणà¥à¤¯à¤¾à¤•à¤°à¤¿à¤¤à¤¾ तà¥à¤®à¥à¤¹à¥€ या डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° लॉग इन करू शकता आणि chrome://system मधील CHROMEOSFLEX_HARDWARE_INFO विभागाला भेट देऊ शकता.<ph name="END_PARAGRAPH2" />
<ph name="BEGIN_PARAGRAPH3" /><ph name="DEVICE_OS" /> हे Google सोबत कदाचित शेअर करेल असा डेटा आणि तो कसा वापरला जातो याविषयीचà¥à¤¯à¤¾ अधिक तपशिलांसाठी g.co/flex/HWDataCollection ला भेट दà¥à¤¯à¤¾.<ph name="END_PARAGRAPH3" /></translation>
@@ -4124,7 +4124,7 @@
<translation id="4997086284911172121">इंटरनेट कनेकà¥â€à¤¶à¤¨ नाही.</translation>
<translation id="4998430619171209993">सà¥à¤°à¥‚</translation>
<translation id="4999804342505941663">वà¥à¤¯à¤¤à¥à¤¯à¤¯ आणू नका सà¥à¤°à¥‚ करा</translation>
-<translation id="5000922062037820727">अवरोधित केली (शिफारस केलेले)</translation>
+<translation id="5000922062037820727">बà¥à¤²à¥‰à¤• केले (शिफारस केलेले)</translation>
<translation id="5005498671520578047">पासवरà¥à¤¡ कॉपी करा</translation>
<translation id="5006118752738286774">दोन वरà¥à¤·à¤¾à¤‚पूरà¥à¤µà¥€</translation>
<translation id="5006218871145547804">Crostini Android अâ€à¥…प ADB</translation>
@@ -4510,7 +4510,7 @@
<translation id="5398572795982417028">निषिदà¥à¤§ पृषà¥à¤  संदरà¥à¤­, मरà¥à¤¯à¤¾à¤¦à¤¾ <ph name="MAXIMUM_PAGE" /> आहे</translation>
<translation id="5401426944298678474">साइट अनफॉलो करा</translation>
<translation id="5402815541704507626">मोबाइल डेटा वापरून अपडेट डाउनलोड करा</translation>
-<translation id="540296380408672091">कà¥à¤•à¥€à¤œ नेहमी <ph name="HOST" /> वर अवरोधित करा</translation>
+<translation id="540296380408672091">कà¥à¤•à¥€à¤œ नेहमी <ph name="HOST" /> वर बà¥à¤²à¥‰à¤• करा</translation>
<translation id="5404740137318486384">“<ph name="ACTION" />†साठी सà¥à¤µà¤¿à¤š किंवा कीबोरà¥à¤¡ की असाइन करणà¥à¤¯à¤¾à¤•à¤°à¤¿à¤¤à¤¾, ती दाबा.
तà¥à¤®à¥à¤¹à¥€ या कृतीसाठी à¤à¤•à¤¾à¤¹à¥‚न अधिक सà¥à¤µà¤¿à¤š असाइन करू शकता.</translation>
<translation id="540495485885201800">मागचà¥à¤¯à¤¾à¤¶à¥€ बदला</translation>
@@ -4792,7 +4792,7 @@
<translation id="567643736130151854">सरà¥à¤µ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° तà¥à¤®à¤šà¥‡ बà¥à¤•à¤®à¤¾à¤°à¥à¤•, पासवरà¥à¤¡ आणि बरेच काही मिळविणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ साइन इन करा आणि सिंक सà¥à¤°à¥‚ करा</translation>
<translation id="567740581294087470">तà¥à¤®à¥à¤¹à¥€ कोणतà¥à¤¯à¤¾ पà¥à¤°à¤•à¤¾à¤°à¤šà¤¾ फीडबॅक देत आहात?</translation>
<translation id="5677503058916217575">पृषà¥à¤  भाषा:</translation>
-<translation id="5677928146339483299">अवरोधित</translation>
+<translation id="5677928146339483299">बà¥à¤²à¥‰à¤• केले</translation>
<translation id="5678550637669481956"><ph name="VOLUME_NAME" /> ला रीड आणि राइट ॲकà¥à¤¸à¥‡à¤¸ मंजूर केला आहे.</translation>
<translation id="5678821117681811450"><ph name="WEB_DRIVE" /> ला पाठवत आहे</translation>
<translation id="5678955352098267522"><ph name="WEBSITE_1" /> वर तà¥à¤®à¤šà¤¾ डेटा वाचा</translation>
@@ -5296,7 +5296,7 @@
<translation id="619279033188484792">तà¥à¤®à¤šà¥à¤¯à¤¾ <ph name="DEVICE_TYPE" /> वर तà¥à¤®à¤šà¥à¤¯à¤¾ फोनमधील अलीकडील फोटो, मीडिया आणि सूचना पहा</translation>
<translation id="6195005504600220730">तà¥à¤®à¤šà¥‡ बà¥à¤°à¤¾à¤‰à¤à¤°, OS आणि डिवà¥à¤¹à¤¾à¤‡à¤¸ यांबदà¥à¤¦à¤² माहिती वाचा</translation>
<translation id="6195693561221576702">हे डिवà¥à¤¹à¤¾à¤‡à¤¸ ऑफलाइन डेमो मोडमधà¥à¤¯à¥‡ सेट केले जाऊ शकत नाही.</translation>
-<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ अवरोधित करा</translation>
+<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ बà¥à¤²à¥‰à¤• करा</translation>
<translation id="6196854373336333322">"<ph name="EXTENSION_NAME" />" à¤à¤•à¥à¤¸à¥à¤Ÿà¥‡à¤‚शनने तà¥à¤®à¤šà¥à¤¯à¤¾ पà¥à¤°à¥‰à¤•à¥à¤¸à¥€ सेटिंगà¥à¤œà¤šà¥‡ नियंतà¥à¤°à¤£ घेतले आहे, याचा अरà¥à¤¥ हे तà¥à¤®à¥à¤¹à¥€ ऑनलाइन करता ती कोणतीही गोषà¥à¤Ÿ बदलू शकते, खंडित करू शकते किंवा चोरून à¤à¤•à¥‚ शकते. हा बदल का à¤à¤¾à¤²à¤¾, याबदà¥à¤¦à¤² तà¥à¤®à¥à¤¹à¥€ खातà¥à¤°à¥€ नसलà¥à¤¯à¤¾à¤¸, तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ कदाचित तो नको आहे.</translation>
<translation id="6197128521826316819">या पेजसाठी QR कोड तयार करा</translation>
<translation id="6198102561359457428">साइन आउट करा नंतर पà¥à¤¨à¥à¤¹à¤¾ साइन इन करा...</translation>
@@ -5572,7 +5572,7 @@
<translation id="6453921811609336127">पà¥à¤¢à¥€à¤² इनपà¥à¤Ÿ पदà¥à¤§à¤¤à¥€à¤µà¤° सà¥à¤µà¤¿à¤š करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, <ph name="BEGIN_SHORTCUT" /><ph name="BEGIN_CTRL" />Ctrl<ph name="END_CTRL" /><ph name="SEPARATOR1" /><ph name="BEGIN_SHIFT" />Shift<ph name="END_SHIFT" /><ph name="SEPARATOR2" /><ph name="BEGIN_SPACE" />Space<ph name="END_SPACE" /><ph name="END_SHORTCUT" /> दाबा</translation>
<translation id="6455264371803474013">विशिषà¥à¤Ÿ साइटवर</translation>
<translation id="6455894534188563617">नवीन फोलà¥â€à¤¡à¤°</translation>
-<translation id="645705751491738698">JavaScript अवरोधित करणे सà¥à¤°à¥‚ ठेवा</translation>
+<translation id="645705751491738698">JavaScript बà¥à¤²à¥‰à¤• करणे सà¥à¤°à¥‚ ठेवा</translation>
<translation id="6458347417133445570">सरà¥à¤µ कà¥à¤•à¥€à¤‚ना अनà¥à¤®à¤¤à¥€ देणà¥à¤¯à¤¾à¤¸à¤‚बंधित तपशील दाखवा</translation>
<translation id="6458701200018867744">अपलोड करता आले नाही (<ph name="WEBRTC_LOG_UPLOAD_TIME" />).</translation>
<translation id="6459488832681039634">शोधणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ निवड वापरा</translation>
@@ -5742,7 +5742,7 @@
<translation id="6619801788773578757">कियोसà¥à¤• ॲपà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨ जोडा</translation>
<translation id="6619990499523117484">आपलà¥à¤¯à¤¾ पिन ची पà¥à¤·à¥à¤Ÿà¥€ करा</translation>
<translation id="6620254580880484313">कंटेनरचे नाव</translation>
-<translation id="6622980291894852883">इमेज अवरोधित करणे सà¥à¤°à¥‚ ठेवा</translation>
+<translation id="6622980291894852883">इमेज बà¥à¤²à¥‰à¤• करणे सà¥à¤°à¥‚ ठेवा</translation>
<translation id="6624036901798307345">पà¥à¤°à¤¤à¥à¤¯à¥‡à¤• टॅबचे थंबनेल दाखवणारी नवीन टॅब सà¥à¤Ÿà¥à¤°à¤¿à¤ª उघडणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ टॅबलेट मोडमधà¥à¤¯à¥‡, टॅब काउंटर टूलबार बटणवर टॅप करा.</translation>
<translation id="6624535038674360844"><ph name="FILE_NAME" /> मधà¥à¤¯à¥‡ संवेदनशील किंवा धोकादायक आशय आहे. तिचà¥à¤¯à¤¾ मालकाला ती दà¥à¤°à¥à¤¸à¥à¤¤ करणà¥à¤¯à¤¾à¤¸ सांगा.</translation>
<translation id="6624687053722465643">गोडवा</translation>
@@ -6002,7 +6002,7 @@
<translation id="6876155724392614295">सायकल</translation>
<translation id="6876469544038980967">उपयà¥à¤•à¥à¤¤ नाही</translation>
<translation id="6878422606530379992">सेनà¥à¤¸à¤°à¤¨à¤¾ अनà¥à¤®à¤¤à¥€ आहे</translation>
-<translation id="6880587130513028875">या पृषà¥à¤ à¤¾à¤µà¤°à¥€à¤² इमेज अवरोधित केलेलà¥à¤¯à¤¾ होतà¥à¤¯à¤¾.</translation>
+<translation id="6880587130513028875">या पेजवरील इमेज बà¥à¤²à¥‰à¤• केलेलà¥à¤¯à¤¾ होतà¥à¤¯à¤¾.</translation>
<translation id="688312408602122936">Steam दà¥à¤µà¤¾à¤°à¥‡ इंसà¥à¤Ÿà¥‰à¤² केलेले सरà¥à¤µ गेम आणि अâ€à¥…पà¥à¤¸à¤¦à¥‡à¤–ील या डिवà¥à¤¹à¤¾à¤‡à¤¸à¤®à¤§à¥‚न काढून टाकली जातील</translation>
<translation id="6883319974225028188">अरेरे! डिवà¥à¤¹à¤¾à¤‡à¤¸ कॉंफिगरेशन सेवà¥à¤¹ करणà¥à¤¯à¤¾à¤¤ सिसà¥à¤Ÿà¤® अयशसà¥à¤µà¥€ à¤à¤¾à¤²à¥‡.</translation>
<translation id="6884474387073389421">तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ तà¥à¤®à¥à¤¹à¥€ निवडलेला साइन इन डेटा नकà¥à¤•à¥€ हटवायचा आहे का?</translation>
@@ -6185,7 +6185,7 @@
<translation id="7048457618657122233"><ph name="SHARE_TARGET" /> वर लिंक शेअर करा</translation>
<translation id="7049293980323620022">फाईल ठेवायची?</translation>
<translation id="7052237160939977163">कारà¥à¤¯à¤ªà¥à¤°à¤¦à¤°à¥à¤¶à¤¨ टà¥à¤°à¥‡à¤¸ डेटा पाठवा</translation>
-<translation id="7053983685419859001">अवरोधित करा</translation>
+<translation id="7053983685419859001">बà¥à¤²à¥‰à¤• करा</translation>
<translation id="7055152154916055070">रीडिरेकà¥à¤Ÿ बà¥à¤²à¥‰à¤• केले:</translation>
<translation id="7055451306017383754">à¤à¤• अâ€à¥…पà¥à¤²à¤¿à¤•à¥‡à¤¶à¤¨ हे फोलà¥à¤¡à¤° वापरत असलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ ते अनशेअर करता आले नाही. पà¥à¤¢à¥‡ Parallels Desktop बंद केलà¥à¤¯à¤¾à¤µà¤° फोलà¥à¤¡à¤° अनशेअर केले जाईल.</translation>
<translation id="7056418393177503237">{0,plural, =1{गà¥à¤ªà¥à¤¤}other{# गà¥à¤ªà¥à¤¤ विंडो उघडà¥à¤¯à¤¾ आहेत}}</translation>
@@ -6504,7 +6504,7 @@
<translation id="7403642243184989645">सà¥à¤°à¥‹à¤¤ डाउनलोड करत आहे</translation>
<translation id="7404065585741198296">USB केबल असलेला तà¥à¤®à¤šà¤¾ फोन</translation>
<translation id="7405938989981604410">{NUM_HOURS,plural, =1{सà¥à¤°à¤•à¥à¤·à¤¾ तपासणी à¤à¤•à¤¾ तासापूरà¥à¤µà¥€ रन केली गेली}other{सà¥à¤°à¤•à¥à¤·à¤¾ तपासणी {NUM_HOURS} तासांपूरà¥à¤µà¥€ रन केली गेली}}</translation>
-<translation id="740624631517654988">पॉप-अप अवरोधित</translation>
+<translation id="740624631517654988">पॉप-अप बà¥à¤²à¥‰à¤• केलेले होते</translation>
<translation id="7407430846095439694">इंपोरà¥à¤Ÿ करा आणि पà¥à¤°à¤¤à¤¿à¤¬à¤¦à¥à¤§ करा</translation>
<translation id="7407504355934009739">बहà¥à¤¤à¥‡à¤• लोक या साइटवरून येणारà¥â€à¤¯à¤¾ सूचना बà¥à¤²à¥‰à¤• करतात</translation>
<translation id="740810853557944681">à¤à¤–ादा पà¥à¤°à¤¿à¤‚ट सरà¥à¤µà¥à¤¹à¤° जोडा</translation>
@@ -6941,7 +6941,7 @@
<translation id="7807067443225230855">शोध आणि साहायà¥à¤¯à¤•</translation>
<translation id="7807117920154132308"><ph name="SUPERVISED_USER_NAME" /> ने आधीच दà¥à¤¸à¤°à¥â€à¤¯à¤¾ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° Google Assistant सेट केले आहे असे दिसते. या डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤° सà¥à¤•à¥à¤°à¥€à¤¨ संदरà¥à¤­ सà¥à¤°à¥‚ करून <ph name="SUPERVISED_USER_NAME" /> हे Assistant चा पà¥à¤°à¥‡à¤ªà¥‚र वापर करू शकतात.</translation>
<translation id="7807711621188256451">तà¥à¤®à¤šà¤¾ कॅमेरा ॲकà¥à¤¸à¥‡à¤¸ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ <ph name="HOST" /> ला नेहमी परवानगी दà¥à¤¯à¤¾</translation>
-<translation id="7810202088502699111">या पेजवर पॉप-अप अवरोधित केलेले होते.</translation>
+<translation id="7810202088502699111">या पेजवर पॉप-अप बà¥à¤²à¥‰à¤• केलेले होते.</translation>
<translation id="781167124805380294"><ph name="FILE_NAME" /> कासà¥à¤Ÿ करा</translation>
<translation id="7811886112806886172">Google ला निदान आणि वापर डेटा आपोआप पाठवून Chrome व ChromeOS वैशिषà¥à¤Ÿà¥à¤¯à¥‡ आणि परफॉरà¥à¤®à¤¨à¥à¤¸ यांमधà¥à¤¯à¥‡ सà¥à¤§à¤¾à¤°à¤£à¤¾ करणà¥à¤¯à¤¾à¤¤ मदत करा. काही à¤à¤•à¤¤à¥à¤°à¤¿à¤¤ डेटा Android अâ€à¥…पà¥à¤¸ आणि Google भागीदारांनादेखील मदत करेल. तà¥à¤®à¤šà¥à¤¯à¤¾ Google खाते साठी वेब आणि अâ€à¥…प अâ€à¥…कà¥à¤Ÿà¤¿à¤µà¥à¤¹à¤¿à¤Ÿà¥€ सेटिंग सà¥à¤°à¥‚ असलà¥à¤¯à¤¾à¤¸, तà¥à¤®à¤šà¤¾ Android डेटा तà¥à¤®à¤šà¥à¤¯à¤¾ Google खाते मधà¥à¤¯à¥‡ सेवà¥à¤¹ केला जाऊ शकतो.</translation>
<translation id="7814458197256864873">&amp;कॉपी करा</translation>
@@ -7365,7 +7365,7 @@
<translation id="8213449224684199188">फोटो मोड टाकला</translation>
<translation id="8214489666383623925">फाइल उघडा...</translation>
<translation id="8215129063232901118">तà¥à¤®à¤šà¥à¤¯à¤¾ <ph name="DEVICE_TYPE" /> वरून तà¥à¤®à¤šà¥à¤¯à¤¾ फोनचà¥à¤¯à¤¾ कà¥à¤·à¤®à¤¤à¤¾ अâ€à¥…कà¥à¤¸à¥‡à¤¸ करा</translation>
-<translation id="8217399928341212914">à¤à¤•à¤¾à¤§à¤¿à¤• फायलींचे सà¥à¤µà¤¯à¤‚चलित डाउनलोड अवरोधित करणे सà¥à¤°à¥‚ ठेवा</translation>
+<translation id="8217399928341212914">à¤à¤•à¤¾à¤§à¤¿à¤• फाइलचे ऑटोमॅटिक डाउनलोड बà¥à¤²à¥‰à¤• करणे सà¥à¤°à¥‚ ठेवा</translation>
<translation id="8221491193165283816">तà¥à¤®à¥à¤¹à¥€ सहसा सूचना बà¥à¤²à¥‰à¤• करता. या साइटला तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ सूचित करू देणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, येथे कà¥à¤²à¤¿à¤• करा.</translation>
<translation id="822347941086490485">HID डिवà¥à¤¹à¤¾à¤‡à¤¸ शोधत आहे…</translation>
<translation id="8225046344534779393">इंटरनेट कनेकà¥à¤¶à¤¨ तपासा</translation>
@@ -7468,7 +7468,7 @@
<translation id="8317671367883557781">नेटवरà¥à¤• कनेकà¥à¤¶à¤¨ जोडा</translation>
<translation id="8319414634934645341">विसà¥à¤¤à¤¾à¤°à¤¿à¤¤ की वापर</translation>
<translation id="8321837372750396788">हे <ph name="DEVICE_TYPE" /> <ph name="MANAGER" /> दà¥à¤µà¤¾à¤°à¥‡ वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ केले जाईल.</translation>
-<translation id="8322814362483282060">या पृषà¥à¤ à¤¾à¤²à¤¾ आपलà¥à¤¯à¤¾ मायकà¥à¤°à¥‹à¤«à¥‹à¤¨à¤µà¤° पà¥à¤°à¤µà¥‡à¤¶ करणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न अवरोधित केले गेले आहे.</translation>
+<translation id="8322814362483282060">तà¥à¤®à¤šà¥à¤¯à¤¾ मायकà¥à¤°à¥‹à¤«à¥‹à¤¨à¤šà¤¾ अâ€à¥…कà¥à¤¸à¥‡à¤¸ या पेजसाठी बà¥à¤²à¥‰à¤• केला आहे.</translation>
<translation id="8323167517179506834">URL टाइप करा</translation>
<translation id="8323317289166663449">तà¥à¤®à¤šà¥à¤¯à¤¾ कॉंपà¥à¤¯à¥à¤Ÿà¤°à¤µà¤°à¥€à¤² आणि सरà¥à¤µ वेबसाइटवरील तà¥à¤®à¤šà¤¾ सरà¥à¤µ डेटा वाचू व बदलू शकते</translation>
<translation id="8324784016256120271">वेगवेगळà¥à¤¯à¤¾ साइटवरील तà¥à¤®à¤šà¥€ बà¥à¤°à¤¾à¤‰à¤à¤¿à¤‚ग ॲकà¥à¤Ÿà¤¿à¤µà¥à¤¹à¤¿à¤Ÿà¥€ पाहणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ साइट कà¥à¤•à¥€ वापरू शकतात, उदाहरणारà¥à¤¥, जाहिराती परà¥à¤¸à¤¨à¤²à¤¾à¤‡à¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€</translation>
@@ -7786,7 +7786,7 @@
<translation id="8662811608048051533">तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ बहà¥à¤¤à¤¾à¤‚श साइटवरून साइन आउट करते.</translation>
<translation id="8662911384982557515">तà¥à¤®à¤šà¥‡ होम पेज यावर बदला: <ph name="HOME_PAGE" /></translation>
<translation id="8662978096466608964">Chrome वॉलपेपर सेट करू शकत नाही.</translation>
-<translation id="8663099077749055505"><ph name="HOST" /> वरील à¤à¤•à¤¾à¤§à¤¿à¤• सà¥à¤µà¤¯à¤‚चलित डाउनलोड नेहमी अवरोधित करा</translation>
+<translation id="8663099077749055505"><ph name="HOST" /> वरील à¤à¤•à¤¾à¤§à¤¿à¤• ऑटोमॅटिक डाउनलोड नेहमी बà¥à¤²à¥‰à¤• करा</translation>
<translation id="8664389313780386848">पृषà¥à¤  सà¥à¤°à¥‹à¤¤ &amp;पहा</translation>
<translation id="8665110742939124773">तà¥à¤®à¥à¤¹à¥€ चà¥à¤•à¥€à¤šà¤¾ अâ€à¥…कà¥à¤¸à¥‡à¤¸ कोड à¤à¤‚टर केला आहे. पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="8665180165765946056">बॅकअप पूरà¥à¤£ à¤à¤¾à¤²à¤¾</translation>
@@ -8225,7 +8225,7 @@
<translation id="9085256200913095638">निवडलेला टॅब डà¥à¤ªà¥à¤²à¤¿à¤•à¥‡à¤Ÿ करा</translation>
<translation id="9085776959277692427"><ph name="LANGUAGE" /> निवडलेली नाही. निवडणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ सरà¥à¤š आणि सà¥à¤ªà¥‡à¤¸ दाबा.</translation>
<translation id="9087949559523851360">पà¥à¤°à¤¤à¤¿à¤¬à¤‚धित वापरकरà¥à¤¤à¤¾ जोडा</translation>
-<translation id="9088234649737575428">संसà¥â€à¤¥à¥‡à¤šà¥à¤¯à¤¾ धोरणादà¥à¤µà¤¾à¤°à¥‡ <ph name="PLUGIN_NAME" /> अवरोधित केले आहे</translation>
+<translation id="9088234649737575428">संसà¥â€à¤¥à¥‡à¤šà¥à¤¯à¤¾ धोरणादà¥à¤µà¤¾à¤°à¥‡ <ph name="PLUGIN_NAME" /> बà¥à¤²à¥‰à¤• केले आहे</translation>
<translation id="9088446193279799727">Linux कॉंफिगर करता आले नाही. इंटरनेटशी कनेकà¥à¤Ÿ करा आणि पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="9088917181875854783">कृपया ही पासकी "<ph name="DEVICE_NAME" />" वर दरà¥à¤¶à¤µà¤¿à¤²à¥à¤¯à¤¾à¤šà¥€ पà¥à¤·à¥à¤Ÿà¥€ करा:</translation>
<translation id="9089416786594320554">इनपà¥à¤Ÿ पदà¥à¤§à¤¤à¥€</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_my.xtb b/chromium/chrome/app/resources/generated_resources_my.xtb
index b1131c8701e..9cc7e20c5ab 100644
--- a/chromium/chrome/app/resources/generated_resources_my.xtb
+++ b/chromium/chrome/app/resources/generated_resources_my.xtb
@@ -2341,7 +2341,7 @@
<translation id="3210736980143419785">ဒေါင်းလုဒ်လုပ်á€á€¼á€„်း အပြီးသá€á€ºáမရပါ</translation>
<translation id="321084946921799184">အá€á€«á€”ှင့် အဖြူ</translation>
<translation id="321356136776075234">စက်ပစ္စည်း OU (ဥပမာ OU=ChromebooksáŠDC=exampleáŠDC=com)</translation>
-<translation id="3214531106883826119"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် <ph name="SUPERVISED_USER_NAME" /> á ကိုယ်ပိုင်သီးသန့် ရလဒ်များကို á€á€„်သုံးနိုင်သည်á‹</translation>
+<translation id="3214531106883826119"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် <ph name="SUPERVISED_USER_NAME" /> á ကိုယ်ရေးကိုယ်á€á€¬ ရလဒ်များသို့ á€á€„်á€á€½á€„့်ရသွားနိုင်ပါသည်á‹</translation>
<translation id="3217843140356091325">ဖြá€á€ºá€œá€™á€ºá€¸á€œá€„့်á€á€º ပြုလုပ်လိုပါသလားá‹</translation>
<translation id="321834671654278338">Linux ပရိုဂရမ်ကို ဖယ်ရှားသည့်စနစ်</translation>
<translation id="3220943972464248773">သင့်စကားá€á€¾á€€á€ºá€™á€»á€¬á€¸á€€á€­á€¯ စင့်á€á€ºá€œá€¯á€•á€ºá€›á€”် သင်ဖြစ်ကြောင်း အá€á€Šá€ºá€•á€¼á€¯á€•á€«</translation>
@@ -2530,7 +2530,7 @@
<translation id="3417836307470882032">စစ်á€á€•á€ºá€žá€¯á€¶á€¸ အá€á€»á€­á€”်</translation>
<translation id="3420501302812554910">အá€á€½á€„်းပိုင်း လုံá€á€¼á€¯á€¶á€›á€±á€¸á€€á€®á€¸á€€á€­á€¯ ပြင်ဆင်သá€á€ºá€™á€¾á€á€ºá€›á€”် လိုအပ်သည်</translation>
<translation id="3421387094817716717">Elliptic Curve အများသုံးကီး</translation>
-<translation id="3421672904902642628"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံဖမ်းá€á€¼á€„်းဖြင့် သင်áကိုယ်ပိုင်သီးသန့် ရလဒ်များ (သို့) သင့် Assistant ကို သုံးá€á€½á€„့်ရနိုင်ပါသည်á‹</translation>
+<translation id="3421672904902642628"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် သင်áကိုယ်ပိုင်ရေးကိုယ်á€á€¬ ရလဒ်များ (သို့) သင့် Assistant သို့ á€á€„်á€á€½á€„့်ရသွားနိုင်ပါသည်á‹</translation>
<translation id="3422291238483866753">á€á€˜á€ºá€†á€­á€¯á€€á€ºá€€ သင့်ပá€á€ºá€á€”်းကျင်á 3D မြေပုံဆွဲá€á€¼á€„်း သို့မဟုá€á€º ကင်မရာအနေအထား á€á€¼á€±á€›á€¬á€á€¶á€á€¼á€„်းá€á€­á€¯á€· ပြုလုပ်လိုသည့်အá€á€« မေးမြန်းရန် (အကြံပြုထားသည်)</translation>
<translation id="3423463006624419153">သင်á '<ph name="PHONE_NAME_1" />' နှင့် '<ph name="PHONE_NAME_2" />' ပေါ်á€á€½á€„်−</translation>
<translation id="3423858849633684918"><ph name="PRODUCT_NAME" /> ကိုကျေးဇူးပြုá ပြန်လည်စá€á€„်ပါ</translation>
@@ -5167,7 +5167,7 @@
<translation id="6059276912018042191">လá€á€ºá€á€œá€±á€¬ Chrome á€á€˜á€ºá€™á€»á€¬á€¸</translation>
<translation id="6059652578941944813">လက်မှá€á€º အဆင့်ဆင့်</translation>
<translation id="6063284707309177505">QR ကုဒ် ပြုလုပ်ရန်</translation>
-<translation id="6063847492705284550"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် <ph name="SUPERVISED_USER_NAME" /> á ကိုယ်ပိုင်သီးသန့် ရလဒ်များကို á€á€„်သုံးနိုင်သည်ዠဘက်ထရီá€á€»á€½á€±á€á€¬á€›á€”် ဤစက်ကို ပါá€á€« ရင်းမြစ်နှင့် á€á€»á€­á€á€ºá€†á€€á€ºá€‘ားသည့်အá€á€«á€á€½á€„်သာ “Ok Google†ကို ဖွင့်ရန်<ph name="SUPERVISED_USER_NAME" /> á Assistant ဆက်á€á€„်များá€á€½á€„် ရွေးနိုင်သည်á‹</translation>
+<translation id="6063847492705284550"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် <ph name="SUPERVISED_USER_NAME" /> á ကိုယ်ရေးကိုယ်á€á€¬ ရလဒ်များသို့ á€á€„်á€á€½á€„့်ရသွားနိုင်ပါသည်ዠဘက်ထရီá€á€»á€½á€±á€á€¬á€›á€”် ဤစက်ကို ပါá€á€«á€”ှင့် á€á€»á€­á€á€ºá€†á€€á€ºá€‘ားသည့်အá€á€«á€á€½á€„်သာ “Ok Google†ကို ဖွင့်ရန်<ph name="SUPERVISED_USER_NAME" /> á Assistant ဆက်á€á€„်များá€á€½á€„် ရွေးနိုင်သည်á‹</translation>
<translation id="6064217302520318294">ဖန်သားပြင်လော့á€á€º</translation>
<translation id="6065289257230303064">လက်မှá€á€º အကြောင်းအရာ ဒါရိုက်ထရီ အá€á€»á€„်းလက္á€á€á€¬á€™á€»á€¬á€¸</translation>
<translation id="6066794465984119824">ပုံဟက်ရှ်ကုဒ်ကို သá€á€ºá€™á€¾á€á€ºá€™á€‘ားပါ</translation>
@@ -6980,7 +6980,7 @@
<translation id="7850717413915978159"><ph name="BEGIN_PARAGRAPH1" />သင့် ChromeOS စက်များအား အလိုအလျောက် အစီရင်á€á€¶á€…ာများ ပို့á€á€½á€„့်ပြုá€á€¼á€„်းဖြင့် ChromeOS á ပြင်ဆင်ရမည့်အပိုင်းနှင့် ပိုမိုကောင်းမွန်အောင် ပြုလုပ်ရမည့်အပိုင်းá€á€­á€¯á€·á€€á€­á€¯ ဦးစားပေးလုပ်ဆောင်နိုင်ရန် ကျွန်ုပ်á€á€­á€¯á€·á€¡á€¬á€¸ အကူအညီပေးပါသည်ዠဤအစီရင်á€á€¶á€…ာများá€á€½á€„် ChromeOS ရပ်á€á€”့်သွားá€á€»á€­á€”်አသင်အသုံးပြုသော á€á€”်ဆောင်မှုများနှင့် ပုံမှန်သင်အသုံးပြုသော မှá€á€ºá€‰á€¬á€á€ºá€•á€™á€¬á€á€”ှင့် Android အက်ပ် အမှားရှာဖွေမှုနှင့် သုံးစွဲမှုဒေá€á€¬á€€á€²á€·á€žá€­á€¯á€· အá€á€»á€€á€ºá€™á€»á€¬á€¸ ပါá€á€„်နိုင်သည်ዠပေါင်းစည်းထားသည့် ဒေá€á€¬á€¡á€á€»á€­á€¯á€·á€žá€Šá€ºá€œá€Šá€ºá€¸ Google app များနှင့် Android ဆော့ဖ်á€á€²á€›á€±á€¸á€žá€°á€™á€»á€¬á€¸á€€á€²á€·á€žá€­á€¯á€· ပါá€á€”ာများကို ကူညီပေးပါမည်á‹<ph name="END_PARAGRAPH1" />
<ph name="BEGIN_PARAGRAPH2" />သင့် ChromeOS စက် ဆက်á€á€„်များá€á€½á€„် ဤအစီရင်á€á€¶á€…ာများ á€á€½á€„့်ပြုá€á€¼á€„်းကို အá€á€»á€­á€”်မရွေး စá€á€„်နိုင်ပါသည် သို့မဟုá€á€º ရပ်á€á€”့်နိုင်ပါသည်ዠသင်သည် ဒိုမိန်း စီမံá€á€”့်á€á€½á€²á€žá€°á€–ြစ်ပါက ဤဆက်á€á€„်ကို စီမံá€á€”့်á€á€½á€²á€žá€° ကွန်ဆိုးလ်á€á€½á€„် ပြောင်းနိုင်ပါသည်á‹<ph name="END_PARAGRAPH2" />
<ph name="BEGIN_PARAGRAPH3" />သင့် Google Account အá€á€½á€€á€º ‘á€á€˜á€ºá€”ှင့်အက်ပ်လုပ်ဆောင်á€á€»á€€á€ºâ€™ ဆက်á€á€„်ကို ဖွင့်ထားပါက Android ဒေá€á€¬á€€á€­á€¯ Google Account á€á€½á€„် သိမ်းထားနိုင်ပါသည်ዠသင့်ဒေá€á€¬á€™á€»á€¬á€¸ ကြည့်á€á€¼á€„်းአဖျက်á€á€¼á€„်းနှင့် အကောင့်ဆက်á€á€„်များ ပြောင်းá€á€¼á€„်းá€á€­á€¯á€·á€€á€­á€¯ account.google.com á€á€½á€„် လုပ်ဆောင်နိုင်သည်á‹<ph name="END_PARAGRAPH3" /></translation>
-<translation id="7851021205959621355"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံဖမ်းá€á€¼á€„်းဖြင့် သင်áကိုယ်ပိုင်သီးသန့် ရလဒ်များ (သို့) သင့် Assistant ကို သုံးá€á€½á€„့်ရနိုင်ပါသည်ዠဘက်ထရီá€á€»á€½á€±á€á€¬á€›á€”် ဤစက်ကို ပါá€á€« ရင်းမြစ်နှင့် á€á€»á€­á€á€ºá€†á€€á€ºá€‘ားသည့်အá€á€«á€á€½á€„်သာ “Ok Google†ကို ဖွင့်ရန် သင့် Assistant ဆက်á€á€„်များá€á€½á€„် ရွေးနိုင်သည်á‹</translation>
+<translation id="7851021205959621355"><ph name="BEGIN_BOLD" />မှá€á€ºá€á€»á€€á€º-<ph name="END_BOLD" /> ဆင်á€á€°á€žá€±á€¬á€¡á€žá€¶ (သို့) အသံသွင်းá€á€»á€€á€ºá€–ြင့် သင်áကိုယ်ရေးကိုယ်á€á€¬ ရလဒ်များ (သို့) သင့် Assistant သို့ á€á€„်á€á€½á€„့်ရသွားနိုင်ပါသည်ዠဘက်ထရီá€á€»á€½á€±á€á€¬á€›á€”် ဤစက်ကို ပါá€á€«á€”ှင့် á€á€»á€­á€á€ºá€†á€€á€ºá€‘ားသည့်အá€á€«á€á€½á€„်သာ “Ok Google†ကို ဖွင့်ရန် Assistant ဆက်á€á€„်များá€á€½á€„် ရွေးနိုင်သည်á‹</translation>
<translation id="7851457902707056880">လက်မှá€á€ºá€‘ိုး á€á€„်မှုကို အကောင့် ပိုင်ရှင် အá€á€½á€€á€ºá€žá€¬ ကန့်သá€á€ºá€‘ားသည်ዠစက်သည် စက္ကန့် áƒá€ အá€á€½á€„်းမှာ အလိုအလျောက် ပြန်ဖွင့်လိမ့်မည်á‹</translation>
<translation id="7851716364080026749">ကင်မရာနှင့် မိုက်á€á€›á€­á€¯á€–ုန်း အသုံးပြုမှုကို အမြဲá€á€™á€ºá€¸ ပိá€á€ºá€†á€­á€¯á€·á€›á€”်</translation>
<translation id="7851720427268294554">IPP ပါဆာ</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ne.xtb b/chromium/chrome/app/resources/generated_resources_ne.xtb
index da03496008c..3b97345b722 100644
--- a/chromium/chrome/app/resources/generated_resources_ne.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ne.xtb
@@ -230,7 +230,7 @@
<translation id="122082903575839559">पà¥à¤°à¤®à¤¾à¤£à¤ªà¤¤à¥à¤° हसà¥à¤¤à¤¾à¤•à¥à¤·à¤° अलà¥à¤—ोरिदम</translation>
<translation id="1221024147024329929">PKCS #1 MD2 सà¤à¤— RSA गà¥à¤ªà¥à¤¤à¤²à¥‡à¤–न</translation>
<translation id="1221825588892235038">चयन मातà¥à¤°</translation>
-<translation id="1223484782328004593"><ph name="APP_NAME" /> पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ इजाजतपतà¥à¤° चाहिनà¥à¤›</translation>
+<translation id="1223484782328004593"><ph name="APP_NAME" /> पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ लाइसेनà¥à¤¸ चाहिनà¥à¤›</translation>
<translation id="1223853788495130632">तपाइà¤à¤•à¥‹ पà¥à¤°à¤¶à¤¾à¤¸à¤•à¤²à¥‡ यस सेटिङकोल लागि à¤à¤• तोकिà¤à¤•à¥‹ परिणाम सिफारिस गरà¥à¤›à¥¤</translation>
<translation id="1225177025209879837">अनà¥à¤°à¥‹à¤§à¤²à¤¾à¤ˆ पà¥à¤°à¤¶à¥‹à¤§à¤¨ गरà¥à¤¦à¥ˆ...</translation>
<translation id="1227507814927581609">"<ph name="DEVICE_NAME" />" मा जडान गरà¥à¤¦à¤¾ पà¥à¤°à¤®à¤¾à¤£à¥€à¤•à¤°à¤£ असफल भयो।</translation>
@@ -345,7 +345,7 @@
<translation id="134589511016534552">मिडिया टà¥à¤¯à¤¾à¤¬à¤¹à¤°à¥‚ "खà¥à¤²à¤¾ रहेका टà¥à¤¯à¤¾à¤¬à¤¹à¤°à¥‚" खणà¥à¤¡à¤®à¤¾ पनि देखाइनà¥à¤›</translation>
<translation id="1346630054604077329">पà¥à¤·à¥à¤Ÿà¤¿ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ र रिसà¥à¤Ÿà¤¾à¤°à¥à¤Ÿ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥</translation>
<translation id="1346748346194534595">दायाà¤</translation>
-<translation id="1347256498747320987">अपडेट र à¤à¤ª इनà¥à¤¸à¥à¤Ÿà¤² गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥â€Œà¥¤ जारी राखेर, तपाईं यो यनà¥à¤¤à¥à¤°à¤²à¥‡ समà¥à¤­à¤µà¤¤à¤ƒ सेलà¥à¤²à¤° डेटाको पà¥à¤°à¤¯à¥‹à¤— गरी Google, तपाईंको सेवा पà¥à¤°à¤¦à¤¾à¤¯à¤• र तपाईंको डिभाइसका निरà¥à¤®à¤¾à¤¤à¤¾à¤¹à¤°à¥‚का अपडेट र à¤à¤ª सà¥à¤µà¤¤à¤ƒ डाउनलोड गरी सà¥à¤¥à¤¾à¤ªà¤¨à¤¾ गरà¥à¤¨ सकà¥à¤¨à¥‡ कà¥à¤°à¤¾à¤®à¤¾ सहमत हà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤›à¥¤ यीमधà¥à¤¯à¥‡ केही à¤à¤ªà¤¹à¤°à¥‚ले अनà¥à¤ªà¥à¤°à¤¯à¥‹à¤—भितà¥à¤° हà¥à¤¨à¥‡ किनमेलहरू पà¥à¤°à¤¸à¥à¤¤à¤¾à¤µ गरà¥à¤¨ सकà¥à¤›à¤¨à¥à¥¤ <ph name="BEGIN_LINK1" />थप जानà¥à¤¨à¥à¤¹à¥‹à¤¸à¥<ph name="END_LINK1" /></translation>
+<translation id="1347256498747320987">अपडेट र à¤à¤ª इनà¥à¤¸à¥à¤Ÿà¤² गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥â€Œà¥¤ जारी राखà¥à¤¨à¥à¤­à¤¯à¥‹ भने तपाईं यो डिभाइसले समà¥à¤­à¤µà¤¤à¤ƒ मोबाइल डेटाको पà¥à¤°à¤¯à¥‹à¤— गरी Google, तपाईंको सेवा पà¥à¤°à¤¦à¤¾à¤¯à¤• र तपाईंको डिभाइसका उतà¥à¤ªà¤¾à¤¦à¤•à¤•à¤¾ अपडेट र à¤à¤ª सà¥à¤µà¤¤à¤ƒ डाउनलोड गरी इनà¥à¤¸à¥à¤Ÿà¤² गरà¥à¤¨ सकà¥à¤¨à¥‡ कà¥à¤°à¤¾à¤®à¤¾ सहमत हà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› भनà¥à¤¨à¥‡ अरà¥à¤¥à¥¤ यीमधà¥à¤¯à¥‡ केही à¤à¤ªà¤¹à¤°à¥‚ले à¤à¤ªà¤­à¤¿à¤¤à¥à¤° हà¥à¤¨à¥‡ किनमेलहरू पà¥à¤°à¤¸à¥à¤¤à¤¾à¤µ गरà¥à¤¨ सकà¥à¤›à¤¨à¥à¥¤ <ph name="BEGIN_LINK1" />थप जानà¥à¤¨à¥à¤¹à¥‹à¤¸à¥<ph name="END_LINK1" /></translation>
<translation id="1347512539447549782">Linux भणà¥à¤¡à¤¾à¤°à¤£</translation>
<translation id="1347975661240122359">बà¥à¤¯à¤¾à¤Ÿà¥à¤°à¥€à¤•à¥‹ सà¥à¤¤à¤° <ph name="BATTERY_LEVEL" />% पà¥à¤—ेपछि अदà¥à¤¯à¤¾à¤µà¤§à¤¿à¤• सà¥à¤°à¥ हà¥à¤¨à¥‡à¤›à¥¤</translation>
<translation id="1353275871123211385">à¤à¤ª अनà¥à¤®à¥‹à¤¦à¤¨ गरà¥à¤¨à¥‡ तथा यनà¥à¤¤à¥à¤° चलाà¤à¤° बिताउने समय सीमा तोकà¥à¤¨à¥‡ जसà¥à¤¤à¤¾ अभिभावकीय नियनà¥à¤¤à¥à¤°à¤£ सà¥à¤µà¤¿à¤§à¤¾à¤¹à¤°à¥‚ पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ बचà¥à¤šà¤¾à¤¸à¤à¤— अनिवारà¥à¤¯ रूपमा कà¥à¤¨à¥ˆ अभिभावकले सà¥à¤ªà¤°à¤¿à¤µà¥‡à¤•à¥à¤·à¤£ गरà¥à¤¨à¥‡ Google खाता हà¥à¤¨à¥ परà¥à¤›à¥¤ तपाईं पछि Google Classroom जसà¥à¤¤à¤¾ संयनà¥à¤¤à¥à¤° पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨à¤•à¤¾ निमà¥à¤¤à¤¿ विदà¥à¤¯à¤¾à¤²à¤¯à¤•à¥‹ खाता थपà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤›à¥¤</translation>
@@ -1082,7 +1082,7 @@
तपाईं आफà¥à¤¨à¥‹ यनà¥à¤¤à¥à¤°à¤®à¤¾ Family Link à¤à¤ª सà¥à¤¥à¤¾à¤ªà¤¨à¤¾ गरेर यो खाताको सेटिङ वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¿à¤¤ गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤›à¥¤ हामीले इमेलमारà¥à¤«à¤¤ तपाईंलाई निरà¥à¤¦à¥‡à¤¶à¤¨à¤¹à¤°à¥‚ पठाà¤à¤•à¤¾ छौà¤à¥¤</translation>
<translation id="2039464276165755892">कसैले चियो गरिरहेको छ भनà¥à¤¨à¥‡ कà¥à¤°à¤¾ पतà¥à¤¤à¤¾ लागेमा सूचना लà¥à¤•à¤¾à¤‡à¤¯à¥‹à¤¸à¥</translation>
<translation id="2040460856718599782">ओहो! तपाईं पà¥à¤°à¤®à¤¾à¤£à¥€à¤•à¤°à¤£ गरà¥à¤¨ पà¥à¤°à¤¯à¤¾à¤¸ गरà¥à¤¦à¤¾ केही गलà¥à¤¤à¥€ भयो। आफà¥à¤¨à¥‹ साइन इन पà¥à¤°à¤®à¤¾à¤£à¤¹à¤°à¥‚ डबल जाà¤à¤š गरी फेरि पà¥à¤°à¤¯à¤¾à¤¸ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥à¥¤</translation>
-<translation id="2040894699575719559">लोकेसनसमà¥à¤¬à¤¨à¥à¤§à¥€ जानकारी हेरà¥à¤¨ रोक लगाइà¤à¤•à¥‹ छ</translation>
+<translation id="2040894699575719559">लोकेसन हेरà¥à¤¨ रोक लगाइà¤à¤•à¥‹ छ</translation>
<translation id="2042279886444479655">सकà¥à¤°à¤¿à¤¯ पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤¹à¤°à¥‚</translation>
<translation id="2044014337866019681">कृपया तपाईं यो सतà¥à¤° अनलक गरà¥à¤¨ <ph name="ACCOUNT" /> पà¥à¤·à¥à¤Ÿà¤¿ गरà¥à¤¦à¥ˆ हà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤› भनà¥à¤¨à¥‡ कà¥à¤°à¤¾ सà¥à¤¨à¤¿à¤¶à¥à¤šà¤¿à¤¤ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥à¥¤</translation>
<translation id="204497730941176055">Microsoft पà¥à¤°à¤®à¤¾à¤£à¤ªà¤¤à¥à¤° टà¥à¤¯à¤¾à¤®à¥à¤ªà¥à¤²à¥‡à¤Ÿ नाम</translation>
@@ -4069,7 +4069,7 @@
<translation id="495170559598752135">कारà¥à¤¯à¤¹à¤°à¥‚</translation>
<translation id="4953808748584563296">डिफलà¥à¤Ÿ सà¥à¤¨à¥à¤¤à¤²à¤¾ रङà¥à¤—को अवतार</translation>
<translation id="4955710816792587366">आफà¥à¤¨à¥‹ PIN छनौट गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥</translation>
-<translation id="4959262764292427323">तपाईं जà¥à¤¨à¤¸à¥à¤•à¥ˆ बेला पासवरà¥à¤¡ पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ भनà¥à¤¨à¤¾à¤•à¤¾ लागि तिनलाई तपाईंको Google खातामा सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ गरिनà¥à¤›</translation>
+<translation id="4959262764292427323">तपाईं जà¥à¤¨à¤¸à¥à¤•à¥ˆ बेला पासवरà¥à¤¡ पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ सकà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ भनà¥à¤¨à¤¾à¤•à¤¾ लागि तिनलाई तपाईंको Google खातामा सेभ गरिनà¥à¤›</translation>
<translation id="496027654926814138">हà¥à¤¯à¤¾à¤•à¤°à¤¹à¤°à¥‚ <ph name="FILE_NAME" /> मारà¥à¤«à¤¤ तपाईंको वà¥à¤¯à¤•à¥à¤¤à¤¿à¤—त जानकारी चोरी गरà¥à¤¨ सकà¥à¤›à¤¨à¥à¥¤</translation>
<translation id="4960294539892203357"><ph name="WINDOW_TITLE" /> - <ph name="PROFILE_NAME" /></translation>
<translation id="4961318399572185831">सà¥à¤•à¥à¤°à¤¿à¤¨ cast गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_nl.xtb b/chromium/chrome/app/resources/generated_resources_nl.xtb
index 32c17baab1e..51d184860f6 100644
--- a/chromium/chrome/app/resources/generated_resources_nl.xtb
+++ b/chromium/chrome/app/resources/generated_resources_nl.xtb
@@ -347,7 +347,7 @@
<translation id="134589511016534552">Mediatabbladen worden ook getoond in het gedeelte Geopende tabbladen</translation>
<translation id="1346630054604077329">Bevestigen en opnieuw opstarten</translation>
<translation id="1346748346194534595">Rechts</translation>
-<translation id="1347256498747320987">Updates en apps installeren. Als je doorgaat, ga je ermee akkoord dat je apparaat ook automatisch updates en apps van Google, je provider en de fabrikant kan downloaden en installeren. Hiervoor worden mogelijk mobiele data gebruikt. Sommige van deze apps kunnen in-app-aankopen aanbieden. <ph name="BEGIN_LINK1" />Meer informatie<ph name="END_LINK1" /></translation>
+<translation id="1347256498747320987">Updates en apps installeren. Als je doorgaat, ga je ermee akkoord dat je apparaat ook automatisch updates en apps van Google, je provider en de fabrikant kan downloaden en installeren. Hiervoor worden mogelijk mobiele data gebruikt. Sommige van deze apps kunnen in-app aankopen aanbieden. <ph name="BEGIN_LINK1" />Meer informatie<ph name="END_LINK1" /></translation>
<translation id="1347512539447549782">Linux-opslag</translation>
<translation id="1347975661240122359">De update wordt gestart wanneer de batterij voor <ph name="BATTERY_LEVEL" />% vol is.</translation>
<translation id="1353275871123211385">Als je opties voor ouderlijk toezicht, zoals app-goedkeuring en schermtijdlimieten, wilt gebruiken, moet je kind een Google-account hebben dat wordt beheerd door een ouder. Je kunt later een schoolaccount toevoegen voor tools zoals Google Classroom.</translation>
@@ -853,7 +853,7 @@ Rechten die je al hebt gegeven aan apps, kunnen van toepassing zijn op dit accou
<translation id="1807246157184219062">Licht</translation>
<translation id="1809483812148634490">Apps die je hebt gedownload via Google Play worden van deze Chromebook verwijderd.
<ph name="LINE_BREAKS1" />
- De content die je hebt gekocht, zoals films, tv-programma's, muziek, boeken of andere in-app-aankopen, worden mogelijk ook verwijderd.
+ De content die je hebt gekocht, zoals films, tv-programma's, muziek, boeken of andere in-app aankopen, worden mogelijk ook verwijderd.
<ph name="LINE_BREAKS2" />
Dit is niet van invloed op apps of content op andere apparaten.</translation>
<translation id="1809734401532861917">Mijn bookmarks, geschiedenis, wachtwoorden en andere instellingen toevoegen aan <ph name="USER_EMAIL_ADDRESS" /></translation>
@@ -1400,7 +1400,7 @@ Via Voice Match kan de Google Assistent de stem van <ph name="SUPERVISED_USER_NA
<ph name="BR" />
De Assistent neemt clips van de stem van je kind op om een uniek spraakmodel te maken, dat alleen op de apparaten van je kind wordt opgeslagen. Het spraakmodel van je kind kan tijdelijk naar Google worden gestuurd zodat de stem van je kind beter kan worden herkend.
<ph name="BR" />
-Als je later besluit dat je niet wilt dat je kind Voice Match gebruikt, verwijder je de functie via de instellingen voor de Assistent op het apparaat van je kind. Als je de audioclips die je kind opneemt tijdens het instellen van Voice Match wilt bekijken of verwijderen, ga je vanuit het account van je kind naar <ph name="VOICE_MATCH_SETTINGS_URL" />.
+Als je later besluit dat je niet wilt dat je kind Voice Match gebruikt, verwijder je de functie via de instellingen voor de Assistent op het apparaat van je kind. Als je de audiofragmenten die je kind opneemt tijdens het instellen van Voice Match wilt bekijken of verwijderen, ga je vanuit het account van je kind naar <ph name="VOICE_MATCH_SETTINGS_URL" />.
<ph name="BR" />
<ph name="FOOTER_MESSAGE" /></translation>
<translation id="2307630946657910723">Deel van de pagina zoeken met <ph name="VISUAL_SEARCH_PROVIDER" /></translation>
@@ -2885,7 +2885,7 @@ Foutcode: <ph name="ERROR_CODE" />.</translation>
<translation id="3772046291955677288">Ik heb de <ph name="BEGIN_LINK1" />Servicevoorwaarden van Google<ph name="END_LINK1" /> en de <ph name="BEGIN_LINK2" />Aanvullende servicevoorwaarden van Chrome en Chrome OS<ph name="END_LINK2" /> gelezen en ga ermee akkoord.</translation>
<translation id="3774166835015494435">Recente foto's en meldingen</translation>
<translation id="3775432569830822555">SSL-servercertificaat</translation>
-<translation id="3775705724665058594">Verzenden naar je apparaten</translation>
+<translation id="3775705724665058594">Naar je apparaten sturen</translation>
<translation id="3776508619697147021">Sites kunnen vragen of ze automatisch meerdere bestanden mogen downloaden</translation>
<translation id="3776796446459804932">Deze extensie schendt het beleid voor de Chrome Web Store.</translation>
<translation id="3777483481409781352">Kan mobiel apparaat niet activeren</translation>
@@ -4516,7 +4516,7 @@ Je kunt meerdere schakelaars toewijzen aan deze actie.</translation>
<translation id="5425863515030416387">Gemakkelijk inloggen bij apparaten</translation>
<translation id="5427278936122846523">Altijd vertalen</translation>
<translation id="5427459444770871191">Rechtsom &amp;draaien</translation>
-<translation id="542750953150239272">Als je doorgaat, ga je ermee akkoord dat dit apparaat ook automatisch updates of apps van Google, je provider of de fabrikant van je apparaat kan downloaden en installeren, en dat hiervoor mogelijk mobiele data worden gebruikt. Sommige van deze apps kunnen in-app-aankopen aanbieden.</translation>
+<translation id="542750953150239272">Als je doorgaat, ga je ermee akkoord dat dit apparaat ook automatisch updates of apps van Google, je provider of de fabrikant van je apparaat kan downloaden en installeren, en dat hiervoor mogelijk mobiele data worden gebruikt. Sommige van deze apps kunnen in-app aankopen aanbieden.</translation>
<translation id="5428850089342283580"><ph name="ACCNAME_APP" /> (Update is beschikbaar)</translation>
<translation id="5429373054983029602">Zoeken op je scherm met <ph name="VISUAL_SEARCH_PROVIDER" /></translation>
<translation id="542948651837270806">Er moet een update voor de Trusted Platform Module-firmware worden geïnstalleerd. Zie <ph name="TPM_FIRMWARE_UPDATE_LINK" /></translation>
@@ -4562,7 +4562,7 @@ Je kunt meerdere schakelaars toewijzen aan deze actie.</translation>
<translation id="5471768120198416576">Hallo. Ik ben je stem voor tekst-naar-spraak.</translation>
<translation id="5472627187093107397">Wachtwoorden voor deze site opslaan</translation>
<translation id="5473075389972733037">IBM</translation>
-<translation id="5473099001878321374">Als je verdergaat, ga je ermee akkoord dat dit apparaat ook automatische updates en apps van Google, de provider van je kind en de fabrikant van dit apparaat kan downloaden en installeren, waarbij mogelijk mobiele data worden gebruikt. Sommige van deze apps kunnen in-app-aankopen aanbieden.</translation>
+<translation id="5473099001878321374">Als je verdergaat, ga je ermee akkoord dat dit apparaat ook automatische updates en apps van Google, de provider van je kind en de fabrikant van dit apparaat kan downloaden en installeren, waarbij mogelijk mobiele data worden gebruikt. Sommige van deze apps kunnen in-app aankopen aanbieden.</translation>
<translation id="5473156705047072749">{NUM_CHARACTERS,plural, =1{De pincode moet ten minste 1 teken bevatten}other{De pincode moet ten minste # tekens bevatten}}</translation>
<translation id="5474859849784484111"><ph name="MANAGER" /> vereist dat je nu verbinding maakt met wifi en een update downloadt. Je kunt de update ook downloaden via een verbinding met datalimiet (er kunnen kosten van toepassing zijn).</translation>
<translation id="5481273127572794904">Geen toestemming om automatisch meerdere bestanden te downloaden</translation>
@@ -4930,7 +4930,7 @@ Je kunt meerdere schakelaars toewijzen aan deze actie.</translation>
<translation id="5843706793424741864">Fahrenheit</translation>
<translation id="5844574845205796324">Nieuwe content voorstellen om te bekijken</translation>
<translation id="5846200638699387931">Syntaxisfout in relatie: <ph name="ERROR_LINE" /></translation>
-<translation id="5846807460505171493">Installeer updates en apps. Als je doorgaat, ga je ermee akkoord dat je apparaat ook automatisch updates en apps van Google, je provider en de fabrikant kan downloaden en installeren. Hiervoor worden mogelijk mobiele data gebruikt. Sommige van deze apps kunnen in-app-aankopen aanbieden.</translation>
+<translation id="5846807460505171493">Installeer updates en apps. Als je doorgaat, ga je ermee akkoord dat je apparaat ook automatisch updates en apps van Google, je provider en de fabrikant kan downloaden en installeren. Hiervoor worden mogelijk mobiele data gebruikt. Sommige van deze apps kunnen in-app aankopen aanbieden.</translation>
<translation id="5849212445710944278">Al toegevoegd</translation>
<translation id="5851868085455377790">Uitgever</translation>
<translation id="5852112051279473187">Oeps! Er is iets misgegaan bij de aanmelding van dit apparaat. Probeer het opnieuw of neem contact op met een medewerker van het supportteam.</translation>
@@ -5903,7 +5903,7 @@ Als <ph name="SUPERVISED_USER_NAME" /> hulp nodig heeft bij het lezen, laat je k
<ph name="BR" />
Via Voice Match kan de Assistent je herkennen en onderscheiden van anderen. De Assistent neemt clips van je stem op om een uniek spraakmodel te maken, dat alleen op jouw apparaten wordt opgeslagen. Je spraakmodel kan tijdelijk naar Google worden gestuurd zodat je stem beter kan worden herkend.
<ph name="BR" />
-Als je later besluit dat je Voice Match niet wilt gebruiken, verwijder je dit gewoon in de instellingen voor de Assistent. Je kunt de audioclips die je opneemt tijdens het instellen van Voice Match bekijken of verwijderen via <ph name="VOICE_MATCH_SETTINGS_URL" />.
+Als je later besluit dat je Voice Match niet wilt gebruiken, verwijder je dit gewoon in de instellingen voor de Assistent. Je kunt de audiofragmenten die je opneemt tijdens het instellen van Voice Match bekijken of verwijderen via <ph name="VOICE_MATCH_SETTINGS_URL" />.
<ph name="BR" />
<ph name="FOOTER_MESSAGE" /></translation>
<translation id="6810613314571580006">Log automatisch in bij websites met de opgeslagen inloggegevens. Als de functie uitstaat, moet je je identiteit bevestigen voordat je inlogt op een website.</translation>
@@ -6361,7 +6361,7 @@ Als je later besluit dat je Voice Match niet wilt gebruiken, verwijder je dit ge
<translation id="7269736181983384521">Datagebruik van Dichtbij delen</translation>
<translation id="7272674038937250585">Geen beschrijving opgegeven</translation>
<translation id="7273110280511444812">laatst aangesloten op <ph name="DATE" /></translation>
-<translation id="7273970016743909808">Je gebruikt een upgrade voor kiosks en digitale borden waardoor het apparaat alleen de kioskmodus of modus voor digitale borden kan gebruiken. Als je wilt dat gebruikers kunnen inloggen op het apparaat, ga je terug en schrijf je het in via Chrome Enterprise-upgrade.</translation>
+<translation id="7273970016743909808">Je gebruikt een upgrade voor kiosks en digitale borden waardoor het apparaat alleen de kioskmodus of modus voor digitale borden kan gebruiken. Als je wilt dat gebruikers kunnen inloggen op het apparaat, ga je terug en schrijf je het in via Chrome Enterprise Upgrade.</translation>
<translation id="727441411541283857"><ph name="PERCENTAGE" />% - <ph name="TIME" /> tot volledig opgeladen</translation>
<translation id="727952162645687754">Downloadfout</translation>
<translation id="7280041992884344566">Er is een fout opgetreden terwijl Chrome naar schadelijke software zocht</translation>
@@ -7375,7 +7375,7 @@ Bewaar je sleutelbestand op een veilige plaats. Je hebt het bestand nodig om nie
<translation id="8248381369318572865">Toegang krijgen tot je microfoon en je spraak analyseren</translation>
<translation id="8248887045858762645">Chrome-tip</translation>
<translation id="8249048954461686687">OEM-map</translation>
-<translation id="8249615410597138718">Verzenden naar je apparaten</translation>
+<translation id="8249615410597138718">Naar je apparaten sturen</translation>
<translation id="8249672078237421304">Aanbieden om pagina's te vertalen die in een voor jou onbekende taal zijn</translation>
<translation id="8250210000648910632">Geen opslagruimte</translation>
<translation id="8251441930213048644">Nu vernieuwen</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_no.xtb b/chromium/chrome/app/resources/generated_resources_no.xtb
index 0d10527fa22..5315653a991 100644
--- a/chromium/chrome/app/resources/generated_resources_no.xtb
+++ b/chromium/chrome/app/resources/generated_resources_no.xtb
@@ -187,7 +187,7 @@
<translation id="1173894706177603556">Gi nytt navn</translation>
<translation id="1174073918202301297">Snarveien er lagt til</translation>
<translation id="1174366174291287894">Tilkoblingen din er alltid sikker så lenge Chrome ikke forteller deg noe annet</translation>
-<translation id="117445914942805388">For å slette nettleserdata på alle synkroniserte enheter og Google-kontoen din <ph name="BEGIN_LINK" />går du til innstillingene for synkronisering<ph name="END_LINK" />.</translation>
+<translation id="117445914942805388">For å slette nettlesingsdata på alle synkroniserte enheter og Google-kontoen din <ph name="BEGIN_LINK" />går du til innstillingene for synkronisering<ph name="END_LINK" />.</translation>
<translation id="1175364870820465910">&amp;Skriv ut...</translation>
<translation id="1176471985365269981">Nettsteder som ikke har lov til å redigere filer eller mapper på enheten</translation>
<translation id="1177863135347784049">Tilpasset</translation>
@@ -200,7 +200,7 @@
<translation id="1187722533808055681">Inaktive oppvåkninger</translation>
<translation id="1188807932851744811">Loggen er ikke lastet opp.</translation>
<translation id="11901918071949011">{NUM_FILES,plural, =1{Tilgang til en fil som er lagret på datamaskinen din}other{Tilgang til # filer som er lagret på datamaskinen din}}</translation>
-<translation id="119092896208640858">For å slette nettleserdata på kun denne enheten, men beholde dataene på Google-kontoen, må du <ph name="BEGIN_LINK" />logge ut<ph name="END_LINK" />.</translation>
+<translation id="119092896208640858">For å slette nettlesingsdata på kun denne enheten, men beholde dataene på Google-kontoen, må du <ph name="BEGIN_LINK" />logge ut<ph name="END_LINK" />.</translation>
<translation id="1192706927100816598">{0,plural, =1{Du blir logget av automatisk om # sekund.
<ph name="DOMAIN" /> krever at smartkortet blir stående i.}other{Du blir logget av automatisk om # sekunder.
<ph name="DOMAIN" /> krever at smartkortet blir stående i.}}</translation>
@@ -1599,7 +1599,7 @@ Du kan administrere innstillingene for denne kontoen ved å installere Family Li
<translation id="2480868415629598489">endre data du kopierer og limer inn</translation>
<translation id="2482878487686419369">Varsler</translation>
<translation id="2482895651873876648">Fanen er flyttet til gruppen <ph name="GROUP_NAME" /> – <ph name="GROUP_CONTENTS" /></translation>
-<translation id="2484959914739448251">For å slette nettleserdata på alle synkroniserte enheter og Google-kontoen din må du <ph name="BEGIN_LINK" />skrive inn passordfrasen din<ph name="END_LINK" />.</translation>
+<translation id="2484959914739448251">For å slette nettlesingsdata på alle synkroniserte enheter og Google-kontoen din må du <ph name="BEGIN_LINK" />skrive inn passordfrasen din<ph name="END_LINK" />.</translation>
<translation id="2485394160472549611">Toppvalg for deg</translation>
<translation id="2485422356828889247">Avinstaller</translation>
<translation id="2485681265915754872">Vilkår for bruk av Google Play</translation>
@@ -4101,7 +4101,7 @@ og Ctrl + Alt + lysstyrke ned for å zoome ut.</translation>
<translation id="4977882548591990850"><ph name="CHARACTER_COUNT" />/<ph name="CHARACTER_LIMIT" /></translation>
<translation id="4977942889532008999">Bekreft tilgangen</translation>
<translation id="4980805016576257426">Denne utvidelsen inneholder skadelig programvare.</translation>
-<translation id="4981449534399733132">For å slette nettleserdata på alle synkroniserte enheter og Google-kontoen din må du <ph name="BEGIN_LINK" />logge på<ph name="END_LINK" />.</translation>
+<translation id="4981449534399733132">For å slette nettlesingsdata på alle synkroniserte enheter og Google-kontoen din må du <ph name="BEGIN_LINK" />logge på<ph name="END_LINK" />.</translation>
<translation id="4982236238228587209">Enhetsprogramvare</translation>
<translation id="4985248278475639481">Om personlig tilpasning av annonser</translation>
<translation id="4986728572522335985">Dette fører til at alle dataene på sikkerhetsnøkkelen, inkludert PIN-koden, blir slettet</translation>
@@ -7152,7 +7152,7 @@ Oppbevar nøkkelfilen på et trygt sted. Du får bruk for den når du skal oppre
<translation id="8023133589013344428">Administrer språk i ChromeOS Flex-innstillingene</translation>
<translation id="8023801379949507775">Oppdater utvidelser nå</translation>
<translation id="8025151549289123443">Låseskjerm og pålogging</translation>
-<translation id="8026334261755873520">Slett nettleserdata</translation>
+<translation id="8026334261755873520">Slett nettlesingsdata</translation>
<translation id="8028060951694135607">Microsoft nøkkelgjenoppretting</translation>
<translation id="8028803902702117856">Laster ned <ph name="SIZE" />, <ph name="FILE_NAME" /></translation>
<translation id="8028993641010258682">Størrelse</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_pa.xtb b/chromium/chrome/app/resources/generated_resources_pa.xtb
index 21f1d1cc35a..03459c0fb49 100644
--- a/chromium/chrome/app/resources/generated_resources_pa.xtb
+++ b/chromium/chrome/app/resources/generated_resources_pa.xtb
@@ -3113,7 +3113,7 @@
<translation id="3955896417885489542">ਸੈੱਟਅੱਪ ਤੋਂ ਬਾਅਦ Google Play ਵਿਕਲਪਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ</translation>
<translation id="3957079323242030166">ਬੈਕਅੱਪ ਡਾਟੇ ਨੂੰ ਤà©à¨¹à¨¾à¨¡à©‡ 'ਡਰਾਈਵ' ਸਟੋਰੇਜ ਕੋਟੇ ਵਿੱਚ ਨਹੀਂ ਗਿਣਿਆ ਜਾਵੇਗਾ।</translation>
<translation id="3957149833646341246">{NUM_APPS,plural, =1{ਤà©à¨¹à¨¾à¨¡à©€ 1 à¨à¨ª ਹà©à¨£ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।}one{ਤà©à¨¹à¨¾à¨¡à©€ # à¨à¨ª ਹà©à¨£ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।}other{ਤà©à¨¹à¨¾à¨¡à©€à¨†à¨‚ # à¨à¨ªà¨¾à¨‚ ਹà©à¨£ ਸਮਰਥਿਤ ਨਹੀਂ ਹਨ।}}</translation>
-<translation id="3957844511978444971">Google ਸੇਵਾਵਾਂ ਦੀਆਂ ਇਹਨਾਂ ਸੈਟਿੰਗਾਂ ਦੀ ਆਪਣੀ ਚੋਣ ਦੀ ਤਸਦੀਕ ਕਰਨ ਲਈ "ਸਵੀਕਾਰ ਕਰੋ" 'ਤੇ ਟੈਪ ਕਰੋ।</translation>
+<translation id="3957844511978444971">Google ਸੇਵਾਵਾਂ ਦੀਆਂ ਇਨà©à¨¹à¨¾à¨‚ ਸੈਟਿੰਗਾਂ ਦੀ ਆਪਣੀ ਚੋਣ ਦੀ ਤਸਦੀਕ ਕਰਨ ਲਈ "ਸਵੀਕਾਰ ਕਰੋ" 'ਤੇ ਟੈਪ ਕਰੋ।</translation>
<translation id="3958088479270651626">ਬà©à©±à¨•à¨®à¨¾à¨°à¨• ਅਤੇ ਸੈਟਿੰਗਾਂ ਆਯਾਤ ਕਰੋ</translation>
<translation id="3960566196862329469">ONC</translation>
<translation id="3962119236270174787">ਉਨà©à¨¹à¨¾à¨‚ ਵੈੱਬਸਾਈਟਾਂ, ਡਾਊਨਲੋਡਾਂ ਅਤੇ à¨à¨•à¨¸à¨Ÿà©ˆà¨‚ਸ਼ਨਾਂ ਤੋਂ ਮਿਆਰੀ ਸà©à¨°à©±à¨–ਿਆ ਜਿਨà©à¨¹à¨¾à¨‚ ਨੂੰ ਖਤਰਨਾਕ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_pl.xtb b/chromium/chrome/app/resources/generated_resources_pl.xtb
index 2f3c438a07b..66dac88408b 100644
--- a/chromium/chrome/app/resources/generated_resources_pl.xtb
+++ b/chromium/chrome/app/resources/generated_resources_pl.xtb
@@ -293,7 +293,7 @@ Domena <ph name="DOMAIN" /> wymaga, by karta inteligentna pozostała w gnieźdz
<translation id="1285320974508926690">Nigdy nie tłumacz tej witryny</translation>
<translation id="1285484354230578868">Przechowywanie danych na koncie Dysku Google</translation>
<translation id="1285625592773741684">Bieżące ustawienie transmisji danych to Mobilna transmisja danych</translation>
-<translation id="1288037062697528143">Podświetlenie nocne włączy się automatycznie o zachodzie słońca</translation>
+<translation id="1288037062697528143">Podświetlenie nocne będzie włączać się automatycznie o zachodzie słońca</translation>
<translation id="1288300545283011870">Właściwości syntezy mowy</translation>
<translation id="1289619947962767206">Ta opcja nie jest już obsługiwana. Do prezentowania kart używaj <ph name="GOOGLE_MEET" />.</translation>
<translation id="1291119821938122630">Warunki korzystania z usługi <ph name="MANAGER" /></translation>
@@ -2171,7 +2171,7 @@ Domena <ph name="DOMAIN" /> wymaga, by karta inteligentna pozostała w gnieźdz
<translation id="304747341537320566">Mechanizmy syntezy mowy</translation>
<translation id="3048336643003835855">UrzÄ…dzenie HID firmy <ph name="VENDOR_ID" /></translation>
<translation id="3048917188684939573">Dzienniki przesyłania i urządzenia</translation>
-<translation id="3051250416341590778">Rozmiar interfejsu</translation>
+<translation id="3051250416341590778">Rozmiar wyświetlacza</translation>
<translation id="3053013834507634016">Użycie klucza certyfikatu</translation>
<translation id="3053273573829329829">Włącz kod PIN użytkownika</translation>
<translation id="3054766768827382232">Po wyłączeniu tej opcji urządzenia peryferyjne mogą działać lepiej, ale Twoje dane osobowe będą mogły dostać się w niepowołane ręce.</translation>
@@ -5925,7 +5925,7 @@ Domena <ph name="DOMAIN" /> wymaga, by karta inteligentna pozostała w gnieźdz
<translation id="6833479554815567477">Karta została usunięta z grupy <ph name="GROUP_NAME" /> – <ph name="GROUP_CONTENTS" /></translation>
<translation id="683373380308365518">Przejdź na inteligentną i bezpieczną przeglądarkę</translation>
<translation id="6833996806551876956">Okres próbny piaskownicy prywatności</translation>
-<translation id="6834652994408928492">Tryb ciemny włączy się automatycznie o zachodzie słońca</translation>
+<translation id="6834652994408928492">Tryb ciemny będzie włączać się automatycznie o zachodzie słońca</translation>
<translation id="6835762382653651563">Aby zaktualizować urządzenie <ph name="DEVICE_TYPE" />, połącz się z internetem.</translation>
<translation id="6839225236531462745">BÅ‚Ä…d usuwania certyfikatu</translation>
<translation id="6839916869147598086">Logowanie wyglÄ…da teraz inaczej</translation>
@@ -6237,7 +6237,7 @@ Domena <ph name="DOMAIN" /> wymaga, by karta inteligentna pozostała w gnieźdz
<translation id="7136694880210472378">Ustaw jako domyślną</translation>
<translation id="7136993520339022828">Wystąpił błąd. Spróbuj jeszcze raz, wybierając inne obrazy.</translation>
<translation id="7137472406312798422">Witryny mogą prosić o zgodę na korzystanie z informacji o Twoich ekranach, aby mogły otwierać i rozmieszczać okna</translation>
-<translation id="7138515695467025690">Wyłączony / Włączy się automatycznie o zachodzie słońca</translation>
+<translation id="7138515695467025690">Wyłączony / Będzie włączać się automatycznie o zachodzie słońca</translation>
<translation id="7138678301420049075">Inne</translation>
<translation id="7139627972753429585"><ph name="APP_NAME" /> używa Twojego mikrofonu</translation>
<translation id="7141105143012495934">Nie udało się zalogować, bo nie można pobrać informacji o koncie. Skontaktuj się z administratorem lub spróbuj ponownie.</translation>
@@ -6679,7 +6679,7 @@ Domena <ph name="DOMAIN" /> wymaga, by karta inteligentna pozostała w gnieźdz
<translation id="7605594153474022051">Synchronizacja nie działa</translation>
<translation id="7606248551867844312">Potwierdź przycinanie</translation>
<translation id="7606560865764296217">Wstrzymaj animacjÄ™</translation>
-<translation id="7606992457248886637">Urzędy</translation>
+<translation id="7606992457248886637">Wystawcy</translation>
<translation id="7607002721634913082">Wstrzymano</translation>
<translation id="7608810328871051088">Ustawienia Androida</translation>
<translation id="7609148976235050828">Połącz się z internetem i spróbuj ponownie.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_pt-BR.xtb b/chromium/chrome/app/resources/generated_resources_pt-BR.xtb
index 99bed0614f2..92f7521c06a 100644
--- a/chromium/chrome/app/resources/generated_resources_pt-BR.xtb
+++ b/chromium/chrome/app/resources/generated_resources_pt-BR.xtb
@@ -1518,7 +1518,7 @@ Para gerenciar as configurações dessa conta, instale o app Family Link no seu
<translation id="2392369802118427583">Ativar</translation>
<translation id="2393136602862631930">Configure o <ph name="APP_NAME" /> no seu Chromebook</translation>
<translation id="2393313392064891208">Conteúdo dos Termos do Google ChromeOS Flex</translation>
-<translation id="2395616325548404795">Seu <ph name="DEVICE_TYPE" /> foi registrado para o gerenciamento empresarial, mas as informações de localização e recurso não foram enviadas. Forneça essas informações manualmente no Admin Console do dispositivo.</translation>
+<translation id="2395616325548404795">Seu <ph name="DEVICE_TYPE" /> foi registrado para o gerenciamento corporativo, mas as informações de localização e recurso não foram enviadas. Forneça essas informações manualmente no Admin Console do dispositivo.</translation>
<translation id="2396783860772170191">Insira o PIN de quatro dígitos (0000-9999)</translation>
<translation id="2398546389094871088">O Powerwash do dispositivo não removerá seus perfis de eSIM. Para remover esses perfis manualmente, acesse <ph name="LINK_BEGIN" />Configurações do celular<ph name="LINK_END" />.</translation>
<translation id="2399699884460174994">Notificações ativadas</translation>
@@ -1954,7 +1954,7 @@ e Ctrl + Alt + Diminuir brilho para diminuí-lo.</translation>
<translation id="2811205483104563968">Contas</translation>
<translation id="2811564570599779918">Redução de spam e fraudes</translation>
<translation id="2812049959647166806">O Thunderbolt não é compatível</translation>
-<translation id="2813094189969465044">Controle dos pais</translation>
+<translation id="2813094189969465044">Controle da família</translation>
<translation id="281390819046738856">Não foi possível assinar a solicitação.</translation>
<translation id="2814489978934728345">Parar de carregar esta página</translation>
<translation id="281504910091592009">Ver e gerenciar as senhas salvas na sua <ph name="BEGIN_LINK" />Conta do Google<ph name="END_LINK" /></translation>
@@ -3035,7 +3035,7 @@ e Ctrl + Alt + Diminuir brilho para diminuí-lo.</translation>
<translation id="389313931326656921">Atribuir interruptor para "Próxima"</translation>
<translation id="3893295674388762059">Para limpar os dados, feche todas as janelas anônimas</translation>
<translation id="3893536212201235195">Ler e alterar as configurações de acessibilidade</translation>
-<translation id="3893630138897523026">ChromeVox (feedback falado)</translation>
+<translation id="3893630138897523026">ChromeVox (resposta falada)</translation>
<translation id="3893764153531140319"><ph name="DOWNLOADED_SIZE" />/<ph name="DOWNLOAD_SIZE" /></translation>
<translation id="3894123633473837029">Incluir o histórico recente do Assistente via Sherlog. Isso pode conter sua identidade, seu local e suas informações de depuração. <ph name="BEGIN_LINK" />Saiba mais<ph name="END_LINK" /></translation>
<translation id="3894427358181296146">Adicionar pasta</translation>
@@ -3068,7 +3068,7 @@ e Ctrl + Alt + Diminuir brilho para diminuí-lo.</translation>
<translation id="3919229493046408863">Desativar notificação quando os dispositivos estiverem por perto</translation>
<translation id="3919798653937160644">As páginas abertas nesta janela não aparecerão no histórico do navegador nem deixarão outros rastros no computador, como cookies, depois que todas as janelas de visitante forem fechadas. No entanto, todos os downloads serão preservados.</translation>
<translation id="3920504717067627103">Diretivas de certificação</translation>
-<translation id="392089482157167418">Ativar ChromeVox (feedback falado)</translation>
+<translation id="392089482157167418">Ativar ChromeVox (resposta falada)</translation>
<translation id="3920909973552939961">Bloquear a instalação de gerenciadores de pagamento</translation>
<translation id="3922823422695198027">Outros apps estão configurados para abrir os mesmos links que <ph name="APP_NAME" />. Esta ação impedirá que <ph name="APP_NAME_2" />, <ph name="APP_NAME_3" /> e <ph name="APP_NAME_4" /> abram links compatíveis.</translation>
<translation id="3923184630988645767">Uso de dados</translation>
@@ -3602,7 +3602,7 @@ e Ctrl + Alt + Diminuir brilho para diminuí-lo.</translation>
<translation id="4470957202018033307">Preferências de armazenamento externo</translation>
<translation id="4471354919263203780">Fazendo o download dos arquivos de reconhecimento de fala… <ph name="PERCENT" />%</translation>
<translation id="447252321002412580">Ajude a melhorar os recursos e o desempenho do Chrome</translation>
-<translation id="4472575034687746823">Primeiros passos</translation>
+<translation id="4472575034687746823">Vamos começar</translation>
<translation id="4474155171896946103">Adicionar todas as guias aos favoritos...</translation>
<translation id="4475552974751346499">Pesquisar downloads</translation>
<translation id="4475830133618397783">Escolha quais senhas serão movidas. Você poderá acessá-las sempre que sua conta estiver conectada.</translation>
@@ -6098,7 +6098,7 @@ Não exponha nenhuma informação confidencial.</translation>
<translation id="6970856801391541997">Imprimir páginas específicas</translation>
<translation id="6970861306198150268">Salve a senha atual para este site</translation>
<translation id="6972180789171089114">Ãudio/vídeo</translation>
-<translation id="6972754398087986839">Primeiros passos</translation>
+<translation id="6972754398087986839">Vamos começar</translation>
<translation id="6972887130317925583">A senha comprometida foi modificada. Verifique suas senhas a qualquer momento nas <ph name="SETTINGS" />.</translation>
<translation id="697312151395002334">Permitir o envio de pop-ups e o uso de redirecionamentos</translation>
<translation id="6973611239564315524">O upgrade para o Debian 10 (Buster) está disponível</translation>
@@ -6615,7 +6615,7 @@ Não exponha nenhuma informação confidencial.</translation>
<translation id="7506093026325926984">Essa senha será salva neste dispositivo</translation>
<translation id="7506130076368211615">Configurar nova rede</translation>
<translation id="7506242536428928412">Defina um novo PIN para usar sua nova chave de segurança</translation>
-<translation id="7506541170099744506">Seu <ph name="DEVICE_TYPE" /> foi inscrito para o gerenciamento empresarial.</translation>
+<translation id="7506541170099744506">O registro do dispositivo <ph name="DEVICE_TYPE" /> no gerenciamento corporativo foi concluído.</translation>
<translation id="7507207699631365376">Consulte a <ph name="BEGIN_LINK" />política de privacidade<ph name="END_LINK" /> deste provedor</translation>
<translation id="7509097596023256288">Configurando o gerenciamento</translation>
<translation id="7509246181739783082">Verificar sua identidade</translation>
@@ -8433,7 +8433,7 @@ Mantenha a sua chave de arquivo em um local seguro. Você precisará dela para c
<translation id="988978206646512040">A senha longa não pode ficar em branco</translation>
<translation id="992032470292211616">Extensões, aplicativos e temas podem danificar seu dispositivo. Você quer mesmo continuar?</translation>
<translation id="992256792861109788">Rosa</translation>
-<translation id="992592832486024913">Desativar ChromeVox (feedback falado)</translation>
+<translation id="992592832486024913">Desativar ChromeVox (resposta falada)</translation>
<translation id="992778845837390402">O backup do Linux está em andamento</translation>
<translation id="993540765962421562">Instalação em andamento</translation>
<translation id="994289308992179865">&amp;Repetir</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_pt-PT.xtb b/chromium/chrome/app/resources/generated_resources_pt-PT.xtb
index fc8622ec1dd..b2d1427a5dd 100644
--- a/chromium/chrome/app/resources/generated_resources_pt-PT.xtb
+++ b/chromium/chrome/app/resources/generated_resources_pt-PT.xtb
@@ -657,7 +657,7 @@ As autorizações que já concedeu às apps podem aplicar-se a esta conta. Pode
<translation id="1627408615528139100">Já transferido</translation>
<translation id="1628948239858170093">Pretende analisar o ficheiro antes de abrir?</translation>
<translation id="1629314197035607094">A palavra-passe expirou.</translation>
-<translation id="1629451755632656601">Pretende permitir que a Google encontre descontos personalizados nos seus carrinhos?</translation>
+<translation id="1629451755632656601">Permitir que a Google encontre descontos personalizados nos seus carrinhos?</translation>
<translation id="163072119192489970">Com autorização para concluir o envio e a receção de dados</translation>
<translation id="1630768113285622200">Reiniciar e continuar</translation>
<translation id="1632082166874334883">Palavra-passe armazenada na sua Conta Google.</translation>
@@ -1358,7 +1358,7 @@ Pode gerir as definições desta conta ao instalar a aplicação Family Link no
<translation id="229182044471402145">Não foi encontrado qualquer tipo de letra correspondente.</translation>
<translation id="2292848386125228270">Inicie o <ph name="PRODUCT_NAME" /> como um utilizador normal. Se precisar de executar com acesso máximo para programação, volte a executar com a sinalização --no-sandbox.</translation>
<translation id="2294081976975808113">Privacidade do ecrã</translation>
-<translation id="2294358108254308676">Pretende instalar o <ph name="PRODUCT_NAME" />?</translation>
+<translation id="2294358108254308676">Instalar o <ph name="PRODUCT_NAME" />?</translation>
<translation id="229477815107578534">Reveja as definições</translation>
<translation id="2295864384543949385"><ph name="NUM_RESULTS" /> resultados</translation>
<translation id="2296022312651137376">O domínio <ph name="DOMAIN_NAME" /> requer que o dispositivo esteja online ao iniciar sessão em <ph name="EMAIL" />.</translation>
@@ -1944,7 +1944,7 @@ e Ctrl + Alt + Diminuir o brilho para diminuir o zoom.</translation>
<translation id="281504910091592009">Veja e faça a gestão das palavras-passe guardadas na sua <ph name="BEGIN_LINK" />Conta Google<ph name="END_LINK" />.</translation>
<translation id="2815693974042551705">Pasta de marcadores</translation>
<translation id="2816319641769218778">Para guardar palavras-passe na sua Conta Google, ative a sincronização.</translation>
-<translation id="2816628817680324566">Pretende permitir que este site veja a sua chave de segurança?</translation>
+<translation id="2816628817680324566">Permitir que este site veja a sua chave de segurança?</translation>
<translation id="2818476747334107629">Detalhes da impressora</translation>
<translation id="2819167288942847344">Utilize predefinições para janelas redimensionáveis, de telemóveis ou de tablets para impedir o funcionamento incorreto da app</translation>
<translation id="2820957248982571256">A analisar...</translation>
@@ -2201,7 +2201,7 @@ Pretende pará-lo?</translation>
<translation id="3064871050034234884">Os sites podem reproduzir som</translation>
<translation id="3065041951436100775">Comentários acerca de separador desativado.</translation>
<translation id="3065522099314259755">Latência de repetição do teclado</translation>
-<translation id="3067198179881736288">Pretende instalar a aplicação?</translation>
+<translation id="3067198179881736288">Instalar a aplicação?</translation>
<translation id="3067198360141518313">Executar este plug-in</translation>
<translation id="3071624960923923138">Pode clicar aqui para abrir um novo separador</translation>
<translation id="3072775339180057696">Permitir que o site veja o ficheiro <ph name="FILE_NAME" />?</translation>
@@ -2405,7 +2405,7 @@ Pretende pará-lo?</translation>
<translation id="33022249435934718">Identificadores de GDI</translation>
<translation id="3302388252085547855">Introduza a justificação…</translation>
<translation id="3303260552072730022">Uma extensão acionou o ecrã inteiro.</translation>
-<translation id="3303795387212510132">Pretende permitir que a app abra os links <ph name="PROTOCOL_SCHEME" />?</translation>
+<translation id="3303795387212510132">Permitir que a app abra os links <ph name="PROTOCOL_SCHEME" />?</translation>
<translation id="3303818374450886607">Cópias</translation>
<translation id="3303855915957856445">Não foram encontrados resultados da pesquisa</translation>
<translation id="3304212451103136496"><ph name="DISCOUNT_AMOUNT" /> de desconto</translation>
@@ -6876,7 +6876,7 @@ Prima uma tecla ou um interruptor atribuído para remover a atribuição.</trans
<translation id="7764857504908700767">Quando as avaliações estão ativadas, a redução de spam e fraudes depende de símbolos fidedignos para ajudar os sites a combater fraudes e distinguir bots de pessoas.</translation>
<translation id="7765158879357617694">Mover</translation>
<translation id="7765507180157272835">É necessário Bluetooth e Wi-Fi</translation>
-<translation id="7766082757934713382">Ajuda a reduzir a utilização de dados de rede ao colocar em pausa as atualizações automáticas do sistema e de apps.</translation>
+<translation id="7766082757934713382">Ajuda a reduzir a utilização de dados de rede ao pausar as atualizações automáticas do sistema e de apps.</translation>
<translation id="7766807826975222231">Faça uma visita guiada</translation>
<translation id="7766838926148951335">Aceitar autorizações</translation>
<translation id="7767554953520855281">Os detalhes estão ocultos durante a partilha do ecrã</translation>
@@ -7233,7 +7233,7 @@ Mantenha o seu ficheiro de chave num local seguro, pois irá precisar dele para
<translation id="80974698889265265">Os PINs não coincidem</translation>
<translation id="809792523045608178"><ph name="IDS_SHORT_PRODUCT_NAME" /> está a utilizar definições do proxy de uma extensão</translation>
<translation id="8097959162767603171">Primeiro, o seu administrador tem de aceitar os Termos de Utilização na lista de dispositivos do Chrome na Consola do administrador.</translation>
-<translation id="8098156986344908134">Pretende instalar o <ph name="DEVICE_OS" /> e apagar o disco rígido?</translation>
+<translation id="8098156986344908134">Instalar o <ph name="DEVICE_OS" /> e apagar o disco rígido?</translation>
<translation id="8098616321286360457">É necessária uma ligação de rede</translation>
<translation id="810068641062493918"><ph name="LANGUAGE" /> selecionado. Prima Pesquisar e a tecla de espaço para desmarcar.</translation>
<translation id="8100972288595615768">Pretende limpar os dados e as autorizações do site <ph name="SITE_NAME" />?</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ro.xtb b/chromium/chrome/app/resources/generated_resources_ro.xtb
index 9328f925b78..d3157960007 100644
--- a/chromium/chrome/app/resources/generated_resources_ro.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ro.xtb
@@ -444,7 +444,7 @@
<translation id="1428770807407000502">Dezactivezi sincronizarea?</translation>
<translation id="1429300045468813835">Totul a fost șters</translation>
<translation id="1430915738399379752">Printează</translation>
-<translation id="1431188203598586230">Actualizare de software finală</translation>
+<translation id="1431188203598586230">Actualizarea finală a software-ului</translation>
<translation id="1432581352905426595">Gestionați motoarele de căutare</translation>
<translation id="1434696352799406980">Astfel, pagina de pornire, pagina Filă nouă, motorul de căutare și filele fixate vor fi resetate. De asemenea, vor fi dezactivate toate extensiile, iar datele temporare vor fi șterse, cum ar fi cookie-urile. Marcajele, istoricul și parolele salvate nu vor fi șterse.</translation>
<translation id="1434886155212424586">Pagina de pornire este pagina Filă nouă</translation>
@@ -2591,7 +2591,7 @@ Mesaj de la server: <ph name="SERVER_MSG" /></translation>
<translation id="347670947055184738">Hopa! Sistemul nu a putut prelua politica pentru dispozitivul tău.</translation>
<translation id="347785443197175480">Permiteți în continuare accesul <ph name="HOST" /> la camera și microfonul dvs.</translation>
<translation id="3479552764303398839">Nu acum</translation>
-<translation id="3479685872808224578">Nu s-a putut șterge serverul de imprimare. Verifică adresa și încearcă din nou.</translation>
+<translation id="3479685872808224578">Nu s-a putut detecta serverul de printare. Verifică adresa și încearcă din nou.</translation>
<translation id="3480612136143976912">Personalizează dimensiunea și stilul Subtitrărilor live. Unele aplicații și site-uri vor folosi această setare.</translation>
<translation id="3480827850068960424">S-au găsit <ph name="NUM" /> file</translation>
<translation id="3481268647794498892">Se deschide în <ph name="ALTERNATIVE_BROWSER_NAME" /> în <ph name="COUNTDOWN_SECONDS" /> secunde</translation>
@@ -5507,7 +5507,7 @@ Poți atribui mai multe comutatoare acestei acțiuni.</translation>
<translation id="6406708970972405507">Setări – <ph name="SECTION_TITLE" /></translation>
<translation id="6408118934673775994">Citește și modifică datele de pe <ph name="WEBSITE_1" />, <ph name="WEBSITE_2" /> și <ph name="WEBSITE_3" /></translation>
<translation id="6410257289063177456">Fișiere imagine</translation>
-<translation id="6410328738210026208">Schimbați canalul și porniți Powerwash</translation>
+<translation id="6410328738210026208">Schimbați canalul și folosiți Powerwash</translation>
<translation id="6410390304316730527">Navigarea sigură te protejează împotriva atacatorilor care ar putea să te păcălească să faci lucruri periculoase, cum ar fi să instalezi software rău intenționat sau să dezvălui informații cu caracter personal, cum ar fi parole, numere de telefon sau carduri de credit. Dacă o dezactivezi, ai grijă atunci când navighezi pe site-uri nefamiliare sau nelegitime.</translation>
<translation id="6410668567036790476">Adaugă motorul de căutare</translation>
<translation id="6412293788397766100">Înainte să pleci...</translation>
@@ -8184,7 +8184,7 @@ Păstrează fișierul cu cheia într-un loc sigur. Acesta va fi necesar la crear
<translation id="9055636786322918818">Aplică criptarea RC4. Folosirea acestei opțiuni îți mărește riscul, deoarece suitele de codificare RC4 sunt nesecurizate.</translation>
<translation id="9056810968620647706">Nu s-a găsit nicio potrivire.</translation>
<translation id="9057007989365783744"><ph name="SUPERVISED_USER_NAME" /> vrea să acceseze următorul conținut:</translation>
-<translation id="9057354806206861646">Actualizează programul</translation>
+<translation id="9057354806206861646">Programarea actualizărilor</translation>
<translation id="9062468308252555888">14x</translation>
<translation id="9063208415146866933">Eroare de la linia <ph name="ERROR_LINE_START" /> la linia <ph name="ERROR_LINE_END" /></translation>
<translation id="9064275926664971810">Activează completarea automată pentru a completa formularele cu un singur clic</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ru.xtb b/chromium/chrome/app/resources/generated_resources_ru.xtb
index 3ef8920dfc2..0585aa2462b 100644
--- a/chromium/chrome/app/resources/generated_resources_ru.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ru.xtb
@@ -3099,7 +3099,7 @@
<translation id="3955896417885489542">ПоÑмотреть параметры Google Play поÑле наÑтройки</translation>
<translation id="3957079323242030166">Резервные копии не занимают меÑта на Google ДиÑке.</translation>
<translation id="3957149833646341246">{NUM_APPS,plural, =1{Одно из приложений больше не поддерживаетÑÑ.}one{# приложение больше не поддерживаетÑÑ.}few{# Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ не поддерживаютÑÑ.}many{# приложений больше не поддерживаютÑÑ.}other{# Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ не поддерживаютÑÑ.}}</translation>
-<translation id="3957844511978444971">Ðажмите "ПринÑÑ‚ÑŒ", чтобы подтвердить правильноÑÑ‚ÑŒ наÑтроек, выбранных в ÑервиÑах Google.</translation>
+<translation id="3957844511978444971">Ðажмите "ПринÑÑ‚ÑŒ", чтобы подтвердить выбор наÑтроек Ð´Ð»Ñ ÑервиÑов Google.</translation>
<translation id="3958088479270651626">Импорт закладок и наÑтроек</translation>
<translation id="3960566196862329469">ONC</translation>
<translation id="3962119236270174787">Ð¡Ñ‚Ð°Ð½Ð´Ð°Ñ€Ñ‚Ð½Ð°Ñ Ð·Ð°Ñ‰Ð¸Ñ‚Ð° от опаÑных Ñайтов, раÑширений и Ñкачанных файлов.</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_sk.xtb b/chromium/chrome/app/resources/generated_resources_sk.xtb
index 128b478cf6a..d608d754309 100644
--- a/chromium/chrome/app/resources/generated_resources_sk.xtb
+++ b/chromium/chrome/app/resources/generated_resources_sk.xtb
@@ -340,7 +340,7 @@
<translation id="1338631221631423366">Páruje sa…</translation>
<translation id="1338802252451106843"><ph name="ORIGIN" /> chcete otvoriť túto aplikáciu.</translation>
<translation id="1338950911836659113">Prebieha odstraňovanie...</translation>
-<translation id="1339009753652684748">Aktivujte svojho Asistenta vyslovením výrazu „Hey Google“. Ak chcete Å¡etriÅ¥ batériu, vyberte Zapnutý (odporúÄané). Váš Asistent bude reagovaÅ¥ iba vtedy, keÄ bude zariadenie pripojené do zásuvky alebo sa bude nabíjaÅ¥.</translation>
+<translation id="1339009753652684748">Aktivujte svojho Asistenta vyslovením výrazu „Ok, Google“. Ak chcete Å¡etriÅ¥ batériu, vyberte Zapnutý (odporúÄané). Váš Asistent bude reagovaÅ¥ iba vtedy, keÄ bude zariadenie pripojené do zásuvky alebo sa bude nabíjaÅ¥.</translation>
<translation id="13392265090583506">A11y</translation>
<translation id="1340527397989195812">Zálohovať médiá zo zariadenia pomocou aplikácie Súbory</translation>
<translation id="1341988552785875222">Aktuálnu tapetu nastavila aplikácia <ph name="APP_NAME" />. Je možné, že pred výberom inej tapety bude nutné aplikáciu <ph name="APP_NAME" /> odinštalovať.</translation>
@@ -3868,7 +3868,7 @@ stlaÄením klávesov Ctrl + Alt + zníženie jasu zobrazenie oddialite.</tr
<translation id="4742970037960872810">Odstrániť zvýraznenie</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />Ako aktualizovať aplikácie<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{Zapnúť zvuk webu}few{Zapnúť zvuk webov}many{Zapnúť zvuk webov}other{Zapnúť zvuk webov}}</translation>
-<translation id="474609389162964566">Aktivujte svojho Asistenta príkazom „Hey Google“</translation>
+<translation id="474609389162964566">Aktivujte svojho Asistenta príkazom „Ok, Google“</translation>
<translation id="4746351372139058112">Správy</translation>
<translation id="4748783296226936791">Weby sa zvyÄajne pripájajú k zariadeniam HID, aby mohli poskytovaÅ¥ funkcie používajúce nezvyÄajné klávesnice, herné ovládaÄe a iné zariadenia</translation>
<translation id="4750185073185658673">Skontrolujte vo svojom telefóne niekoľko Äalších povolení. Nezabudnite maÅ¥ zapnuté rozhranie Bluetooth aj sieÅ¥ Wi‑Fi.</translation>
@@ -5691,7 +5691,7 @@ Tejto akcii môžete prideliÅ¥ viacero prepínaÄov.</translation>
<translation id="6583328141350416497">PokraÄovaÅ¥ v sÅ¥ahovaní</translation>
<translation id="6584878029876017575">Podpisovanie s neobmedzenou platnosÅ¥ou spoloÄnosti Microsoft</translation>
<translation id="6586099239452884121">Hosťovské prehliadanie</translation>
-<translation id="6586213706115310390">Aktivujte svojho Asistenta vyslovením výrazu „Hey Google“.</translation>
+<translation id="6586213706115310390">Aktivujte svojho Asistenta vyslovením výrazu „Ok, Google“.</translation>
<translation id="6586451623538375658">ZameniÅ¥ primárne tlaÄidlo myÅ¡i</translation>
<translation id="6587958707401001932">Vybrať predvolené nastavenie</translation>
<translation id="6588043302623806746">PoužívaÅ¥ zabezpeÄené DNS</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_sl.xtb b/chromium/chrome/app/resources/generated_resources_sl.xtb
index 6200183bc9f..025f3dee267 100644
--- a/chromium/chrome/app/resources/generated_resources_sl.xtb
+++ b/chromium/chrome/app/resources/generated_resources_sl.xtb
@@ -3486,7 +3486,7 @@ Domena <ph name="DOMAIN" /> zahteva, da je pametna kartica vstavljena.</translat
<translation id="4348766275249686434">Zbiranje napak</translation>
<translation id="4349828822184870497">Uporabno</translation>
<translation id="4350230709416545141">Gostitelju <ph name="HOST" /> vedno prepreÄi dostop do lokacije</translation>
-<translation id="4350782034419308508">Hey Google</translation>
+<translation id="4350782034419308508">OK Google</translation>
<translation id="4351770750390404505"><ph name="BEGIN_PARAGRAPH1" />Zaradi omogoÄanja najboljÅ¡e izkuÅ¡nje operacijski sistem <ph name="DEVICE_OS" /> zbira podatke o strojni opremi naprav in jih deli z Googlom zaradi doloÄanja, katere posodobitve naj vam zagotovi. Izbirno lahko dovolite Googlu, da te podatke uporablja za dodatne namene, kot so podpora in izboljÅ¡anje izkuÅ¡nje sistema <ph name="DEVICE_OS" /> ter njegovih storitev.<ph name="END_PARAGRAPH1" />
<ph name="BEGIN_PARAGRAPH2" />Prijavite se lahko v to napravo in obiÅ¡Äete razdelek CHROMEOSFLEX_HARDWARE_INFO na chrome://system, kjer si ogledate podatke, poslane Googlu zaradi filtriranja posodobitev, in druge primere, v katerih ste izbrali deljenje podatkov z Googlom.<ph name="END_PARAGRAPH2" />
<ph name="BEGIN_PARAGRAPH3" />ÄŒe želite veÄ podrobnosti o podatkih, ki jih <ph name="DEVICE_OS" /> morda deli z Googlom in kako se ti uporabljajo, obiÅ¡Äite g.co/flex/HWDataCollection.<ph name="END_PARAGRAPH3" /></translation>
@@ -6512,7 +6512,7 @@ Domena <ph name="DOMAIN" /> zahteva, da je pametna kartica vstavljena.</translat
<translation id="7404065585741198296">Vaš telefon s kablom USB</translation>
<translation id="7405938989981604410">{NUM_HOURS,plural, =1{Varnostno preverjanje je bilo izvedeno pred 1 uro}one{Varnostno preverjanje je bilo izvedeno pred {NUM_HOURS} uro}two{Varnostno preverjanje je bilo izvedeno pred {NUM_HOURS} urama}few{Varnostno preverjanje je bilo izvedeno pred {NUM_HOURS} urami}other{Varnostno preverjanje je bilo izvedeno pred {NUM_HOURS} urami}}</translation>
<translation id="740624631517654988">Pojavno okno je blokirano</translation>
-<translation id="7407430846095439694">Uvoz in vezava</translation>
+<translation id="7407430846095439694">Uvozi in poveži</translation>
<translation id="7407504355934009739">VeÄina ljudi blokira obvestila s tega spletnega mesta</translation>
<translation id="740810853557944681">Dodajanje tiskalnega strežnika</translation>
<translation id="7409549334477097887">Zelo veliko</translation>
@@ -6818,7 +6818,7 @@ Domena <ph name="DOMAIN" /> zahteva, da je pametna kartica vstavljena.</translat
<translation id="7690853182226561458">Dodaj &amp;mapo...</translation>
<translation id="7691073721729883399">Funkcije »cryptohome« za aplikacijo za kiosk ni bilo mogoÄe vpeti.</translation>
<translation id="7691077781194517083">Tega varnostnega kljuÄa ni mogoÄe ponastaviti. Napaka <ph name="ERROR_CODE" />.</translation>
-<translation id="7691163173018300413">»Hey Google«</translation>
+<translation id="7691163173018300413">»OK Google«</translation>
<translation id="7691698019618282776">Nadgradnja Crostinija</translation>
<translation id="7694246789328885917">Orodje oznaÄevalnika</translation>
<translation id="7696063401938172191">V telefonu »<ph name="PHONE_NAME" />«:</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_sr-Latn.xtb b/chromium/chrome/app/resources/generated_resources_sr-Latn.xtb
index 4d4cd942f11..567e0056d9f 100644
--- a/chromium/chrome/app/resources/generated_resources_sr-Latn.xtb
+++ b/chromium/chrome/app/resources/generated_resources_sr-Latn.xtb
@@ -1470,7 +1470,7 @@ Možete da upravljate podešavanjima ovog naloga ako instalirate aplikaciju Fami
<translation id="2359071692152028734">Linux aplikacije mogu da prestanu da reaguju.</translation>
<translation id="2359345697448000899">Upravljajte dodacima klikom na stavku Dodaci u meniju Alatke.</translation>
<translation id="2359556993567737338">Povežite Bluetooth uređaj</translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="2361100938102002520">Dodajete profil kojim se upravlja u ovaj pregledaÄ. Administrator ima kontrolu nad profilom i može da pristupa njegovim podacima.</translation>
<translation id="236117173274098341">Optimizuj</translation>
<translation id="2361340419970998028">Povratne informacije se Å¡alju...</translation>
@@ -3865,7 +3865,7 @@ a Ctrl+Alt+taster za smanjivanje osvetljenosti da biste umanjili prikaz.</transl
<translation id="4742970037960872810">Ukloni isticanje</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />Saznajte kako da ažurirate aplikacije<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{UkljuÄi zvuk sajta}one{UkljuÄi zvuk sajtova}few{UkljuÄi zvuk sajtova}other{UkljuÄi zvuk sajtova}}</translation>
-<translation id="474609389162964566">Pristupajte PomocÌniku pomocÌu fraze „Hej Google“</translation>
+<translation id="474609389162964566">Pristupajte PomocÌniku frazom „Hej Google“</translation>
<translation id="4746351372139058112">Poruke</translation>
<translation id="4748783296226936791">Sajtovi se obiÄno povezuju sa HID ureÄ‘ajima za potrebe funkcija koje koriste neuobiÄajene tastature, kontrolere za igre i druge ureÄ‘aje</translation>
<translation id="4750185073185658673">Idite na telefon da biste pregledali joÅ¡ nekoliko dozvola. Uverite se da su Bluetooth i WiFi telefona ukljuÄeni.</translation>
@@ -3957,7 +3957,7 @@ a Ctrl+Alt+taster za smanjivanje osvetljenosti da biste umanjili prikaz.</transl
<translation id="4844633725025837809">Radi dodatne zaÅ¡tite Å¡ifrujte lozinke na ureÄ‘aju pre nego Å¡to se saÄuvaju u Google menadžeru lozinki</translation>
<translation id="4846680374085650406">Poštujete preporuku administratora za ovo podešavanje.</translation>
<translation id="4847902821209177679">Izabrali ste uslugu <ph name="TOPIC_SOURCE" /> <ph name="TOPIC_SOURCE_DESC" />, pritisnite Enter da biste izabrali albume usluge <ph name="TOPIC_SOURCE" /></translation>
-<translation id="4848191975108266266">Komanda „Ok Google“ za Google pomocÌnik</translation>
+<translation id="4848191975108266266">Komanda „Hej Google“ za Google pomocÌnik</translation>
<translation id="4849286518551984791">Koordinisano univerzalno vreme (UTC/GMT)</translation>
<translation id="4849517651082200438">Ne instaliraj</translation>
<translation id="485053257961878904">Podešavanje sinhronizovanja obaveštenja nije uspelo</translation>
@@ -6137,7 +6137,7 @@ Vodite raÄuna da ne otkrijete nikakve osetljive informacije.</translation>
<translation id="701080569351381435">Prikaži izvor</translation>
<translation id="7014174261166285193">Instalacija nije uspela.</translation>
<translation id="7014480873681694324">Ukloni isticanje</translation>
-<translation id="7017004637493394352">Kažite ponovo „Ok Google“</translation>
+<translation id="7017004637493394352">Kažite ponovo „Hej Google“</translation>
<translation id="7017219178341817193">Dodajte novu stranicu</translation>
<translation id="7017354871202642555">Nije mogucÌe podesiti režim nakon podeÅ¡avanja prozora.</translation>
<translation id="7018275672629230621">ÄŒitanje istorije pregledanja i menjanje te istorije</translation>
@@ -6608,7 +6608,7 @@ Vodite raÄuna da ne otkrijete nikakve osetljive informacije.</translation>
<translation id="7515998400212163428">Android</translation>
<translation id="7516981202574715431">Aplikacija <ph name="APP_NAME" /> je pauzirana</translation>
<translation id="7520766081042531487">Portal u režimu bez arhiviranja: <ph name="SUBFRAME_SITE" /></translation>
-<translation id="7522255036471229694">Recite „Ok Google“</translation>
+<translation id="7522255036471229694">Recite „Hej Google“</translation>
<translation id="7523585675576642403">Promenite naziv profila</translation>
<translation id="7525067979554623046">Napravi</translation>
<translation id="7525625923260515951">Slušajte izabrani tekst</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_sr.xtb b/chromium/chrome/app/resources/generated_resources_sr.xtb
index 0049913729f..7f7a9cc8cc1 100644
--- a/chromium/chrome/app/resources/generated_resources_sr.xtb
+++ b/chromium/chrome/app/resources/generated_resources_sr.xtb
@@ -1470,7 +1470,7 @@
<translation id="2359071692152028734">Linux апликације могу да преÑтану да реагују.</translation>
<translation id="2359345697448000899">Управљајте додацима кликом на Ñтавку Додаци у менију Ðлатке.</translation>
<translation id="2359556993567737338">Повежите Bluetooth уређај</translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="2361100938102002520">Додајете профил којим Ñе управља у овај прегледач. ÐдминиÑтратор има контролу над профилом и може да приÑтупа његовим подацима.</translation>
<translation id="236117173274098341">Оптимизуј</translation>
<translation id="2361340419970998028">Повратне информације Ñе шаљу...</translation>
@@ -3865,7 +3865,7 @@
<translation id="4742970037960872810">Уклони иÑтицање</translation>
<translation id="4743260470722568160"><ph name="BEGIN_LINK" />Сазнајте како да ажурирате апликације<ph name="END_LINK" /></translation>
<translation id="4744981231093950366">{NUM_TABS,plural, =1{Укључи звук Ñајта}one{Укључи звук Ñајтова}few{Укључи звук Ñајтова}other{Укључи звук Ñајтова}}</translation>
-<translation id="474609389162964566">ПриÑтупајте Помоћнику помоћу фразе „Хеј Google“</translation>
+<translation id="474609389162964566">ПриÑтупајте Помоћнику фразом „Хеј Google“</translation>
<translation id="4746351372139058112">Поруке</translation>
<translation id="4748783296226936791">Сајтови Ñе обично повезују Ñа HID уређајима за потребе функција које кориÑте неуобичајене таÑтатуре, контролере за игре и друге уређаје</translation>
<translation id="4750185073185658673">Идите на телефон да биÑте прегледали још неколико дозвола. Уверите Ñе да Ñу Bluetooth и WiFi телефона укључени.</translation>
@@ -3957,7 +3957,7 @@
<translation id="4844633725025837809">Ради додатне заштите шифрујте лозинке на уређају пре него што Ñе Ñачувају у Google менаџеру лозинки</translation>
<translation id="4846680374085650406">Поштујете препоруку админиÑтратора за ово подешавање.</translation>
<translation id="4847902821209177679">Изабрали Ñте уÑлугу <ph name="TOPIC_SOURCE" /> <ph name="TOPIC_SOURCE_DESC" />, притиÑните Enter да биÑте изабрали албуме уÑлуге <ph name="TOPIC_SOURCE" /></translation>
-<translation id="4848191975108266266">Команда „Ок Google“ за Google помоћник</translation>
+<translation id="4848191975108266266">Команда „Хеј Google“ за Google помоћник</translation>
<translation id="4849286518551984791">КоординиÑано универзално време (UTC/GMT)</translation>
<translation id="4849517651082200438">Ðе инÑталирај</translation>
<translation id="485053257961878904">Подешавање Ñинхронизовања обавештења није уÑпело</translation>
@@ -6137,7 +6137,7 @@
<translation id="701080569351381435">Прикажи извор</translation>
<translation id="7014174261166285193">ИнÑталација није уÑпела.</translation>
<translation id="7014480873681694324">Уклони иÑтицање</translation>
-<translation id="7017004637493394352">Кажите поново „Ок Google“</translation>
+<translation id="7017004637493394352">Кажите поново „Хеј Google“</translation>
<translation id="7017219178341817193">Додајте нову Ñтраницу</translation>
<translation id="7017354871202642555">Ðије могуће подеÑити режим након подешавања прозора.</translation>
<translation id="7018275672629230621">Читање иÑторије прегледања и мењање те иÑторије</translation>
@@ -6608,7 +6608,7 @@
<translation id="7515998400212163428">Android</translation>
<translation id="7516981202574715431">Ðпликација <ph name="APP_NAME" /> је паузирана</translation>
<translation id="7520766081042531487">Портал у режиму без архивирања: <ph name="SUBFRAME_SITE" /></translation>
-<translation id="7522255036471229694">Реците „Ок Google“</translation>
+<translation id="7522255036471229694">Реците „Хеј Google“</translation>
<translation id="7523585675576642403">Промените назив профила</translation>
<translation id="7525067979554623046">Ðаправи</translation>
<translation id="7525625923260515951">Слушајте изабрани текÑÑ‚</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_te.xtb b/chromium/chrome/app/resources/generated_resources_te.xtb
index f9dc5619bdb..8e1f07c5d7d 100644
--- a/chromium/chrome/app/resources/generated_resources_te.xtb
+++ b/chromium/chrome/app/resources/generated_resources_te.xtb
@@ -829,7 +829,7 @@
<translation id="1779468444204342338">కనిషà±à° à°‚</translation>
<translation id="1779652936965200207">దయచేసి "<ph name="DEVICE_NAME" />"లో à°ˆ పాసà±â€Œà°•à±€à°¨à°¿ నమోదౠచేయండి:</translation>
<translation id="177989070088644880">యాపౠ(<ph name="ANDROID_PACKAGE_NAME" />)</translation>
-<translation id="1780152987505130652">సమూహానà±à°¨à°¿ మూసివేయి</translation>
+<translation id="1780152987505130652">à°—à±à°°à±‚à°ªà±â€Œà°¨à± మూసివేయి</translation>
<translation id="1780273119488802839">à°¬à±à°•à±â€Œà°®à°¾à°°à±à°•à±â€Œà°²à°¨à± దిగà±à°®à°¤à°¿ చేసà±à°¤à±‹à°‚ది...</translation>
<translation id="178092663238929451">మీ à°šà±à°Ÿà±à°Ÿà±‚ ఉనà±à°¨ à°µà±à°¯à°•à±à°¤à±à°²à°¤à±‹ ఫైళà±à°²à°¨à± à°…à°‚à°¦à±à°•à±‹à°µà°¡à°¾à°¨à°¿à°•à°¿, ఇంకా పంపడానికి 'సమీప షేరింగà±'నౠసెటపౠచేయండి</translation>
<translation id="1781291988450150470">à°ªà±à°°à°¸à±à°¤à±à°¤ పినà±â€Œ</translation>
@@ -2035,7 +2035,7 @@
<translation id="2893013536106749396">మీరౠమà±à°–à±à°¯à°‚ à°…à°¨à±à°•à±à°¨à±‡ విషయాల à°—à±à°°à°¿à°‚à°šà°¿ మీకౠతెలియజేసే కారà±à°¡à±â€Œà°²à°¨à± à°Žà°‚à°šà±à°•à±‹à°‚à°¡à°¿</translation>
<translation id="2893168226686371498">ఆటోమేటికౠబà±à°°à±Œà°œà°°à±</translation>
<translation id="2893180576842394309">శోధన, ఇతర Google సేవలనౠవà±à°¯à°•à±à°¤à°¿à°—తీకరించడానికి Google మీ à°šà°°à°¿à°¤à±à°°à°¨à± ఉపయోగించే అవకాశం ఉంటà±à°‚ది</translation>
-<translation id="2894757982205307093">సమూహంలో కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±</translation>
+<translation id="2894757982205307093">à°—à±à°°à±‚à°ªà±â€Œà°²à±‹ కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±</translation>
<translation id="289695669188700754">à°•à±€ ID: <ph name="KEY_ID" /></translation>
<translation id="2897713966423243833">మీరౠమీ à°…à°¨à±à°¨à°¿ à°…à°œà±à°žà°¾à°¤ విండోలనౠమూసివేసినపà±à°ªà±à°¡à±, à°ˆ à°…à°¨à±à°•à±‚à°² సెటà±à°Ÿà°¿à°‚గౠతీసివేయబడà±à°¤à±à°‚ది</translation>
<translation id="2897878306272793870">మీరౠ<ph name="TAB_COUNT" /> à°Ÿà±à°¯à°¾à°¬à±â€Œà°²à°¨à± తెరవాలనà±à°•à±à°‚à°Ÿà±à°¨à±à°¨à°¾à°°à°¾?</translation>
@@ -2102,7 +2102,7 @@
<translation id="2949289451367477459">à°¸à±à°¥à°¾à°¨à°¾à°¨à±à°¨à°¿ ఉపయోగించండి. à°¸à±à°¥à°¾à°¨ à°…à°¨à±à°®à°¤à°¿à°¨à°¿ కలిగిన యాపà±â€Œà°²à± మరియౠసేవలౠఈ పరికర à°¸à±à°¥à°¾à°¨à°¾à°¨à±à°¨à°¿ ఉపయోగించడానికి à°…à°¨à±à°®à°¤à°¿à°‚à°šà°‚à°¡à°¿. Google కాలానà±à°—à±à°£à°‚à°—à°¾ à°¸à±à°¥à°¾à°¨ డేటాని సేకరించవచà±à°šà± మరియౠసà±à°¥à°¾à°¨ à°–à°šà±à°šà°¿à°¤à°¤à±à°µà°‚ మరియౠసà±à°¥à°¾à°¨à°‚-ఆధారిత సేవలనౠమెరà±à°—à±à°ªà°°à°šà°¡à°‚ కోసం à°ˆ డేటాని అనామకంగా ఉపయోగించవచà±à°šà±. <ph name="BEGIN_LINK1" />మరింత తెలà±à°¸à±à°•à±‹à°‚à°¡à°¿<ph name="END_LINK1" /></translation>
<translation id="2950666755714083615">ననà±à°¨à± సైనౠఅపౠచేయనివà±à°µà±</translation>
<translation id="2953019166882260872">మీ ఫోనà±â€Œà°¨à± కేబà±à°²à±â€Œà°¤à±‹ కనెకà±à°Ÿà± చేయండి</translation>
-<translation id="2956070239128776395">విభాగం సమూహంలో ఉంది: <ph name="ERROR_LINE" /></translation>
+<translation id="2956070239128776395">విభాగం à°—à±à°°à±‚à°ªà±â€Œà°²à±‹ ఉంది: <ph name="ERROR_LINE" /></translation>
<translation id="2958721676848865875">à°ªà±à°¯à°¾à°•à± à°Žà°•à±à°¸à±â€Œà°Ÿà±†à°¨à±à°·à°¨à±â€Œ హెచà±à°šà°°à°¿à°•</translation>
<translation id="2959127025785722291">à°à°¦à±‹ తపà±à°ªà± జరిగింది. à°¸à±à°•à°¾à°¨à°¿à°‚గౠపూరà±à°¤à°¿ కాలేకపోయింది. దయచేసి మళà±à°²à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿.</translation>
<translation id="2959842337402130152">నిలà±à°µ à°¸à±à°¥à°²à°‚ లేని కారణంగా à°ªà±à°¨à°°à±à°¦à±à°§à°°à°¿à°‚à°šà°¡à°‚ సాధà±à°¯à°ªà°¡à°²à±‡à°¦à±. పరికరంలో <ph name="SPACE_REQUIRED" /> à°¸à±à°¥à°²à°‚ ఖాళీ చేసి, ఆపై మళà±à°²à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿.</translation>
@@ -2134,7 +2134,7 @@
<translation id="2989805286512600854">కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±â€Œà°²à±‹ తెరà±à°µà±</translation>
<translation id="2990313168615879645">Google ఖాతానౠజోడించà±</translation>
<translation id="2990583317361835189">మోషనౠసెనà±à°¸à°¾à°°à±â€Œà°²à°¨à± ఉపయోగించడానికి సైటà±â€Œà°²à°¨à± à°…à°¨à±à°®à°¤à°¿à°‚à°šà°•à°‚à°¡à°¿</translation>
-<translation id="2992931425024192067">మొతà±à°¤à°‚ నోటిఫికేషనౠకంటెంటà±â€Œà°¨à± చూపà±</translation>
+<translation id="2992931425024192067">మొతà±à°¤à°‚ నోటిఫికేషనౠకంటెంటà±â€Œà°¨à± చూపించండి</translation>
<translation id="2993517869960930405">యాపà±â€Œ సమాచారం</translation>
<translation id="2996286169319737844">మీ సింకà±â€Œ రహసà±à°¯ పదబంధంతో డేటా à°Žà°¨à±â€Œà°•à±à°°à°¿à°ªà±à°Ÿà± చేయబడింది. Google Payà°•à°¿ చెందిన పేమెంటౠఆపà±à°·à°¨à±â€Œà°²à± మరియౠఅడà±à°°à°¸à±â€Œà°²à± ఇందà±à°²à±‹ ఉండవà±.</translation>
<translation id="2996722619877761919">పొడవైన à°…à°‚à°šà±à°²à±‹ తిపà±à°ªà±</translation>
@@ -3999,7 +3999,7 @@
<translation id="4871322859485617074">పినà±â€Œà°²à±‹ చెలà±à°²à°¨à°¿ à°…à°•à±à°·à°°à°¾à°²à± ఉనà±à°¨à°¾à°¯à°¿</translation>
<translation id="4871370605780490696">à°¬à±à°•à±â€Œà°®à°¾à°°à±à°•à±â€Œà°¨à± జోడించండి</translation>
<translation id="4871568871368204250">సింకà±â€Œà°¨à°¿ ఆఫౠచేయి</translation>
-<translation id="4871719318659334896">సమూహానà±à°¨à°¿ మూసివేయి</translation>
+<translation id="4871719318659334896">à°—à±à°°à±‚à°ªà±â€Œà°¨à± మూసివేయి</translation>
<translation id="4873312501243535625">మీడియా ఫైలౠచెకà±à°•à°°à±</translation>
<translation id="4876273079589074638">à°•à±à°°à°¾à°·à± à°Žà°‚à°¦à±à°•à± జరిగిందనà±à°¨à°¦à°¿ తెలà±à°¸à±à°•à±‹à°µà°¡à°¾à°¨à°¿à°•à°¿, పరిషà±à°•à°°à°¿à°‚చడానికి మా ఇంజినీరà±â€Œà°²à°•à± సహాయం చేయండి. మీకౠసాధà±à°¯à°®à±ˆà°¤à±‡ విషయానà±à°¨à°¿ à°•à±à°°à°® పదà±à°§à°¤à°¿à°²à±‹ దశల వారీగా పేరà±à°•à±Šà°¨à°‚à°¡à°¿. వివరణ à°à°¦à±€ మరీ à°šà°¿à°¨à±à°¨à°—à°¾ ఉండకూడదà±!</translation>
<translation id="4876895919560854374">à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± లాకౠచేయండి మరియౠఅనà±â€Œà°²à°¾à°•à± చేయండి</translation>
@@ -5950,7 +5950,7 @@
<translation id="6828182567531805778">మీ డేటానౠసింకౠచేయడానికి మీ రహసà±à°¯ పదబంధానà±à°¨à°¿ నమోదౠచేయండి</translation>
<translation id="682871081149631693">QuickFix</translation>
<translation id="6828860976882136098">వినియోగదారà±à°²à°‚దరి కోసం ఆటోమేటికౠఅపà±â€Œà°¡à±‡à°Ÿà±â€Œà°²à°¨à± సెటపౠచేయడం విఫలమైంది (à°ªà±à°°à±€à°«à±à°²à°¯à°¿à°Ÿà± అమలౠఎరà±à°°à°°à±: <ph name="ERROR_NUMBER" />)</translation>
-<translation id="682971198310367122">Google గోపà±à°¯à°¤à°¾ విధానం</translation>
+<translation id="682971198310367122">Google గోపà±à°¯à°¤à°¾ పాలసీ</translation>
<translation id="6831043979455480757">à°…à°¨à±à°µà°¦à°¿à°‚à°šà±</translation>
<translation id="6833479554815567477">à°—à±à°°à±‚పౠనà±à°‚à°¡à°¿ à°Ÿà±à°¯à°¾à°¬à± తొలగించబడింది <ph name="GROUP_NAME" /> - <ph name="GROUP_CONTENTS" /></translation>
<translation id="683373380308365518">à°¸à±à°®à°¾à°°à±à°Ÿà± మరియౠసà±à°°à°•à±à°·à°¿à°¤ à°¬à±à°°à±Œà°œà°°à±â€Œà°•à± మారండి</translation>
@@ -7410,7 +7410,7 @@
<translation id="8260864402787962391">మౌసà±</translation>
<translation id="8261378640211443080">à°ˆ à°Žà°•à±à°¸à±â€Œà°Ÿà±†à°¨à±à°·à°¨à±â€Œ <ph name="IDS_EXTENSION_WEB_STORE_TITLE" />లో లిసà±à°Ÿà±â€Œ చేయబడలేదౠమరియౠమీకౠతెలియకà±à°‚డానే జోడించబడి ఉండవచà±à°šà±.</translation>
<translation id="8261506727792406068">తొలగించà±</translation>
-<translation id="8263336784344783289">à°ˆ సమూహానికి పేరౠపెటà±à°Ÿà°‚à°¡à°¿</translation>
+<translation id="8263336784344783289">à°ˆ à°—à±à°°à±‚à°ªà±â€Œà°¨à°•à± పేరౠపెటà±à°Ÿà°‚à°¡à°¿</translation>
<translation id="8263744495942430914"><ph name="FULLSCREEN_ORIGIN" /> మీ మౌసౠకరà±à°¸à°°à±â€Œà°¨à± నిలిపివేసింది.</translation>
<translation id="8264024885325823677">à°ˆ సెటà±à°Ÿà°¿à°‚గౠమీ నిరà±à°µà°¾à°¹à°•à±à°¡à°¿ à°¦à±à°µà°¾à°°à°¾ నిరà±à°µà°¹à°¿à°‚చబడà±à°¤à±à°‚ది.</translation>
<translation id="8264718194193514834"><ph name="EXTENSION_NAME" /> à°«à±à°²à±-à°¸à±à°•à±à°°à±€à°¨à±â€Œà°¨à± à°ªà±à°°à°¾à°°à°‚భించింది.</translation>
@@ -7913,7 +7913,7 @@
<translation id="8779944680596936487">సైటà±â€Œà°²à± తమ సొంత సైటà±â€Œà°²à±‹ మీ à°¬à±à°°à±Œà°œà°¿à°‚గౠయాకà±à°Ÿà°¿à°µà°¿à°Ÿà±€à°¨à°¿ చూడటానికి మాతà±à°°à°®à±‡ à°•à±à°•à±à°•à±€à°²à°¨à± ఉపయోగించగలవà±</translation>
<translation id="8780123805589053431">Google à°¨à±à°‚à°¡à°¿ à°šà°¿à°¤à±à°° వివరణలనౠపొందండి</translation>
<translation id="8780443667474968681">వాయిసౠశోధన ఆపివేయబడింది.</translation>
-<translation id="8781834595282316166">సమూహంలో కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±</translation>
+<translation id="8781834595282316166">à°—à±à°°à±‚à°ªà±â€Œà°²à±‹ కొతà±à°¤ à°Ÿà±à°¯à°¾à°¬à±</translation>
<translation id="8782565991310229362">కియోసà±à°•à± యాపౠఅమలౠరదà±à°¦à± చేయబడింది.</translation>
<translation id="8783526165720272136">మెసేజింగౠయాపà±â€Œà°²à±</translation>
<translation id="8783834180813871000">à°¬à±à°²à±‚టూతౠపెయిరింగౠకోడà±â€Œà°¨à± టైపౠచేసి, ఆపై Return లేదా Enter నొకà±à°•à°‚à°¡à°¿.</translation>
@@ -8361,9 +8361,9 @@
<translation id="932327136139879170">హోమà±</translation>
<translation id="932508678520956232">à°®à±à°¦à±à°°à°¿à°‚చడానà±à°¨à°¿ à°ªà±à°°à°¾à°°à°‚à°­à°¿à°‚à°šà°¡à°‚ సాధà±à°¯à°‚ కాలేదà±.</translation>
<translation id="933427034780221291">{NUM_FILES,plural, =1{à°­à°¦à±à°°à°¤à°¾ తనిఖీ చేయడానికి వీలà±à°²à±‡à°¨à°‚తగా à°ˆ ఫైలౠచాలా పెదà±à°¦à°—à°¾ ఉంది. మీరౠగరిషà±à° à°‚à°—à°¾ 50 MB వరకౠఉండే ఫైలà±à°¸à±â€Œà°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయగలరà±.}other{à°ˆ ఫైలà±à°¸à±â€Œà°²à±‹ కొనà±à°¨à°¿, à°­à°¦à±à°°à°¤à°¾ తనిఖీ చేయడానికి వీలà±à°²à±‡à°¨à°‚à°¤ పెదà±à°¦à°—à°¾ ఉనà±à°¨à°¾à°¯à°¿. మీరౠగరిషà±à° à°‚à°—à°¾ 50 MB వరకౠఉండే ఫైలà±à°¸à±â€Œà°¨à± à°…à°ªà±â€Œà°²à±‹à°¡à± చేయగలరà±.}}</translation>
-<translation id="93343527085570547">à°šà°Ÿà±à°Ÿà°ªà°°à°®à±ˆà°¨ కారణాలతో కంటెంటౠమారà±à°ªà±à°²à°¨à± à°…à°­à±à°¯à°°à±à°§à°¿à°‚చడానికి <ph name="BEGIN_LINK1" />à°šà°Ÿà±à°Ÿà°ªà°°à°®à±ˆà°¨ అంశాల సహాయ పేజీ<ph name="END_LINK1" />కౠవెళà±à°²à°‚à°¡à°¿. కొంత ఖాతా మరియౠసిసà±à°Ÿà°®à± సమాచారం Googleà°•à°¿ పంపబడవచà±à°šà±. సాంకేతిక సమసà±à°¯à°² పరిషà±à°•à°¾à°°à°¾à°¨à°¿à°•à°¿ మరియౠమా సేవలనౠమెరà±à°—à±à°ªà°°à°šà°¡à°¾à°¨à°¿à°•à°¿ మీరౠమాకౠఇచà±à°šà°¿à°¨ సమాచారానà±à°¨à°¿ మా <ph name="BEGIN_LINK2" />గోపà±à°¯à°¤à°¾ విధానం<ph name="END_LINK2" /> మరియౠ<ph name="BEGIN_LINK3" />సేవా నిబంధనలà±<ph name="END_LINK3" />కౠలోబడి మేమౠఉపయోగిసà±à°¤à°¾à°®à±.</translation>
+<translation id="93343527085570547">à°šà°Ÿà±à°Ÿà°ªà°°à°®à±ˆà°¨ కారణాలతో కంటెంటౠమారà±à°ªà±à°²à°¨à± à°…à°­à±à°¯à°°à±à°§à°¿à°‚చడానికి <ph name="BEGIN_LINK1" />à°šà°Ÿà±à°Ÿà°ªà°°à°®à±ˆà°¨ అంశాల సహాయ పేజీ<ph name="END_LINK1" />కౠవెళà±à°²à°‚à°¡à°¿. కొంత ఖాతా మరియౠసిసà±à°Ÿà°®à± సమాచారం Googleà°•à°¿ పంపబడవచà±à°šà±. సాంకేతిక సమసà±à°¯à°² పరిషà±à°•à°¾à°°à°¾à°¨à°¿à°•à°¿ మరియౠమా సేవలనౠమెరà±à°—à±à°ªà°°à°šà°¡à°¾à°¨à°¿à°•à°¿ మీరౠమాకౠఇచà±à°šà°¿à°¨ సమాచారానà±à°¨à°¿ మా <ph name="BEGIN_LINK2" />గోపà±à°¯à°¤à°¾ పాలసీ<ph name="END_LINK2" /> మరియౠ<ph name="BEGIN_LINK3" />సేవా నిబంధనలà±<ph name="END_LINK3" />కౠలోబడి మేమౠఉపయోగిసà±à°¤à°¾à°®à±.</translation>
<translation id="93393615658292258">పాసà±â€Œà°µà°°à±à°¡à± మాతà±à°°à°®à±‡</translation>
-<translation id="934244546219308557">à°ˆ సమూహానికి పేరౠపెటà±à°Ÿà°‚à°¡à°¿</translation>
+<translation id="934244546219308557">à°ˆ à°—à±à°°à±‚à°ªà±â€Œà°¨à°•à± పేరౠపెటà±à°Ÿà°‚à°¡à°¿</translation>
<translation id="934503638756687833">అవసరమైతే ఇకà±à°•à°¡ లిసà±à°Ÿà±â€Œ చేయబడని అంశాలనౠకూడా తీసివేయవచà±à°šà±. Chrome గోపà±à°¯à°¤ విధాన డాకà±à°¯à±à°®à±†à°‚à°Ÿà±â€Œà°²à±‹ &lt;a href="<ph name="URL" />"&gt;అవాంఛిత సాఫà±à°Ÿà±â€Œà°µà±‡à°°à± à°°à°•à±à°·à°£&lt;/a&gt; à°—à±à°°à°¿à°‚à°šà°¿ మరింత తెలà±à°¸à±à°•à±‹à°‚à°¡à°¿.</translation>
<translation id="93480724622239549">బగౠలేదా à°Žà°°à±à°°à°°à±</translation>
<translation id="935854577147268200">Smart Lock ఫోనౠమారింది. Smart Lockనౠఅపà±â€Œà°¡à±‡à°Ÿà± చేయడానికి మీ పాసà±â€Œà°µà°°à±à°¡à±â€Œà°¨à± నమోదౠచేయండి. తదà±à°ªà°°à°¿à°¸à°¾à°°à°¿, మీ ఫోనౠమీ <ph name="DEVICE_TYPE" />‌నౠఅనà±â€Œà°²à°¾à°•à± చేసà±à°¤à±à°‚ది. మీరౠసెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°²à±‹ Smart Lockనౠఆఫౠచేయవచà±à°šà±</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_th.xtb b/chromium/chrome/app/resources/generated_resources_th.xtb
index 10a3e6821e4..5122e2a35fc 100644
--- a/chromium/chrome/app/resources/generated_resources_th.xtb
+++ b/chromium/chrome/app/resources/generated_resources_th.xtb
@@ -1128,7 +1128,7 @@
<translation id="2082187087049518845">จัดà¸à¸¥à¸¸à¹ˆà¸¡à¹à¸—็บ</translation>
<translation id="2082510809738716738">เลือà¸à¸ªà¸µà¸˜à¸µà¸¡</translation>
<translation id="208586643495776849">โปรดลองอีà¸à¸„รั้ง</translation>
-<translation id="208634871997892083">VPN à¹à¸šà¸šà¹€à¸›à¸´à¸”ตลอดเวลา</translation>
+<translation id="208634871997892083">à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­ VPN ตลอดเวลา</translation>
<translation id="2087822576218954668">พิมพ์: <ph name="PRINT_NAME" /></translation>
<translation id="2088092308059522196">รองรับà¸à¸²à¸£à¸¥à¸‡à¸—ะเบียนหลังจาà¸à¸—ี่คุณติดตั้ง <ph name="DEVICE_OS" /> à¹à¸¥à¹‰à¸§à¹€à¸—่านั้น</translation>
<translation id="208928984520943006">เลื่อนขึ้นจาà¸à¸”้านล่างเพื่อไปยังหน้าจอหลัà¸à¹„ด้ทุà¸à¹€à¸¡à¸·à¹ˆà¸­</translation>
@@ -5682,7 +5682,7 @@
<translation id="6579705087617859690"><ph name="WINDOW_TITLE" /> - à¹à¸Šà¸£à¹Œà¹€à¸™à¸·à¹‰à¸­à¸«à¸²à¸šà¸™à¹€à¸”สà¸à¹Œà¸—็อปอยู่</translation>
<translation id="6580203076670148210">ความเร็วในà¸à¸²à¸£à¸ªà¹à¸à¸™</translation>
<translation id="6582080224869403177">รีเซ็ต <ph name="DEVICE_TYPE" /> เพื่ออัปเà¸à¸£à¸”ความปลอดภัย</translation>
-<translation id="6582274660680936615">คุณà¸à¸³à¸¥à¸±à¸‡à¹€à¸£à¸µà¸¢à¸à¸”ูในà¸à¸²à¸™à¸°à¸œà¸¹à¹‰à¹€à¸¢à¸µà¹ˆà¸¢à¸¡à¸Šà¸¡</translation>
+<translation id="6582274660680936615">คุณà¸à¸³à¸¥à¸±à¸‡à¹€à¸£à¸µà¸¢à¸à¸”ูในà¸à¸²à¸™à¸°à¸œà¸¹à¹‰à¸¡à¸²à¹€à¸¢à¸·à¸­à¸™</translation>
<translation id="6583328141350416497">ดาวน์โหลดต่อ</translation>
<translation id="6584878029876017575">à¸à¸²à¸£à¸£à¸±à¸šà¸£à¸­à¸‡à¸•à¸¥à¸­à¸”ชีพของ Microsoft</translation>
<translation id="6586099239452884121">à¸à¸²à¸£à¸—่องเว็บในโหมดผู้มาเยือน</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_uk.xtb b/chromium/chrome/app/resources/generated_resources_uk.xtb
index a43c7436acc..192af20891b 100644
--- a/chromium/chrome/app/resources/generated_resources_uk.xtb
+++ b/chromium/chrome/app/resources/generated_resources_uk.xtb
@@ -1077,7 +1077,7 @@
<translation id="2016473077102413275">Функції, Ñким потрібні зображеннÑ, не працюватимуть</translation>
<translation id="2016574333161572915">Тепер можете налаштувати Ð¾Ð±Ð»Ð°Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð»Ñ Google Meet</translation>
<translation id="2017334798163366053">Вимкнути збір даних про ефективніÑÑ‚ÑŒ</translation>
-<translation id="2018352199541442911">Ðа жаль, зараз приÑтрій зовнішньої пам’ÑÑ‚Ñ– не підтримуєтьÑÑ.</translation>
+<translation id="2018352199541442911">Ðа жаль, ваш зовнішній ноÑій наразі не підтримуєтьÑÑ.</translation>
<translation id="2018615379714355980">ПК підключено до дротової мережі, а Chromecast – до Wi-Fi</translation>
<translation id="2019718679933488176">&amp;Відкрити аудіо в новій вкладці</translation>
<translation id="2020183425253392403">Показати Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¼ÐµÑ€ÐµÐ¶ÐµÐ²Ð¾Ñ— адреÑи</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_ur.xtb b/chromium/chrome/app/resources/generated_resources_ur.xtb
index af977346c79..1a10baaec41 100644
--- a/chromium/chrome/app/resources/generated_resources_ur.xtb
+++ b/chromium/chrome/app/resources/generated_resources_ur.xtb
@@ -174,7 +174,7 @@
<translation id="1163931534039071049">&amp;Ùریم کا ماخذ دیکھیں</translation>
<translation id="1164891049599601209">Ùریب والی سائٹ پر درج کیا گیا</translation>
<translation id="1165039591588034296">خرابی</translation>
-<translation id="1166212789817575481">دائیں جانب ٹیبز بند کریں</translation>
+<translation id="1166212789817575481">دائیں جانب والے ٹیبز بند کریں</translation>
<translation id="1166583374608765787">نام میں Ûوئی اپ ڈیٹ کا Ø¬Ø§Ø¦Ø²Û Ù„ÛŒÚº</translation>
<translation id="1166596238782048887"><ph name="TAB_TITLE" /> <ph name="DESK_TITLE" /> ڈیسک سے تعلق رکھتی ÛÛ’</translation>
<translation id="1168020859489941584"><ph name="TIME_REMAINING" /> میں کھولا جا رÛا Ûے…</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_vi.xtb b/chromium/chrome/app/resources/generated_resources_vi.xtb
index 3f601fd1623..20f391850ad 100644
--- a/chromium/chrome/app/resources/generated_resources_vi.xtb
+++ b/chromium/chrome/app/resources/generated_resources_vi.xtb
@@ -1869,7 +1869,7 @@ và Ctrl+Alt+Giảm Ä‘á»™ sáng để thu nhá».</translation>
<translation id="2738771556149464852">Không được Sau</translation>
<translation id="2739191690716947896">Gỡ lỗi</translation>
<translation id="2739240477418971307">Thay đổi cài đặt trợ năng của bạn</translation>
-<translation id="274029851662193272">Hạ xuống</translation>
+<translation id="274029851662193272">Chìm</translation>
<translation id="2740363334137520315">Thêm một mục mới trong phần "Tìm kiếm thẻ" để dễ dàng tìm thấy thẻ đang phát nhạc hoặc video. Truy cập thông qua nút ở góc trên cùng của trình duyệt.</translation>
<translation id="2740531572673183784">Ok</translation>
<translation id="2741713322780029189">Mở Cửa sổ lệnh khôi phục</translation>
@@ -5837,7 +5837,7 @@ Bạn có thể gán nhiá»u công tắc cho thao tác này.</translation>
<translation id="671619610707606484">Thao tác này sẽ xóa <ph name="TOTAL_USAGE" /> dữ liệu lưu trữ trong các trang web</translation>
<translation id="6716798148881908873">Äã mất kết nối mạng. Hãy kiểm tra kết nối mạng của bạn hoặc thá»­ má»™t mạng Wi-Fi khác.</translation>
<translation id="671928215901716392">Khóa màn hình</translation>
-<translation id="6721744718589119342">Có thể chúng tôi sẽ gá»­i email cho baÌ£n khi coÌ thông tin khác hoặc nội dung cập nhật</translation>
+<translation id="6721744718589119342">Chúng tôi có thể gá»­i email cho baÌ£n để há»i thêm thông tin hoặc để cập nhật thông tin cho bạn</translation>
<translation id="6721972322305477112">&amp;Tệp</translation>
<translation id="672208878794563299">Trang web này sẽ há»i lại vào lần tá»›i.</translation>
<translation id="6723661294526996303">Nhập dấu trang và các mục cài đặt...</translation>
@@ -6840,7 +6840,7 @@ Bạn có thể gán nhiá»u công tắc cho thao tác này.</translation>
<translation id="7716781361494605745">URL chính sách của tổ chức phát hành chứng chỉ Netscape</translation>
<translation id="7717014941119698257">Äang tải xuống: <ph name="STATUS" /></translation>
<translation id="771721654176725387">Thao tác này sẽ xóa vÄ©nh viá»…n dữ liệu duyệt web của bạn khá»i thiết bị này. Äể khôi phục dữ liệu, hãy bật tùy chá»n đồng bá»™ hóa cho địa chỉ email</translation>
-<translation id="7717845620320228976">Kiểm tra bản cập nhật</translation>
+<translation id="7717845620320228976">Kiểm tra để tìm bản cập nhật</translation>
<translation id="7719367874908701697">Thu phóng trang</translation>
<translation id="7719588063158526969">Tên thiết bị quá dài</translation>
<translation id="7720216670798402294">Äá»c thông tin thiết bị và dữ liệu thiết bị ChromeOS.</translation>
@@ -7618,7 +7618,7 @@ Giữ tệp khóa của bạn ở nơi an toàn. Bạn sẽ cần tệp khóa đ
<translation id="8471525937465764768">Các trang web thÆ°á»ng kết nối vá»›i thiết bị USB để áp dụng những tính năng nhÆ° in tài liệu hoặc lÆ°u vào thiết bị lÆ°u trữ</translation>
<translation id="8471959340398751476">Tính năng nhận chiết khấu đang tắt. Bạn có thể bật tính năng này trong trình đơn tùy chỉnh</translation>
<translation id="8472623782143987204">được hỗ trợ bằng phần cứng</translation>
-<translation id="8473863474539038330">Äịa chỉ và các tùy chá»n khác</translation>
+<translation id="8473863474539038330">Äịa chỉ và các lá»±a chá»n khác</translation>
<translation id="8475313423285172237">Một chương trình khác trên máy tính của bạn đã thêm một tiện ích có thể thay đổi cách Chrome hoạt động.</translation>
<translation id="8477241577829954800">Äã thay thế</translation>
<translation id="8477384620836102176">&amp;Chung</translation>
diff --git a/chromium/chrome/app/resources/generated_resources_zh-CN.xtb b/chromium/chrome/app/resources/generated_resources_zh-CN.xtb
index 76c8e6bfd2a..0cc9b784386 100644
--- a/chromium/chrome/app/resources/generated_resources_zh-CN.xtb
+++ b/chromium/chrome/app/resources/generated_resources_zh-CN.xtb
@@ -1247,7 +1247,7 @@
<translation id="2187675480456493911">已与您å¸å·ä¸­çš„其他设备åŒæ­¥ã€‚其他用户修改的设置ä¸ä¼šè¿›è¡ŒåŒæ­¥ã€‚<ph name="LINK_BEGIN" />了解详情<ph name="LINK_END" /></translation>
<translation id="2187895286714876935">æœåŠ¡å™¨è¯ä¹¦å¯¼å…¥é”™è¯¯</translation>
<translation id="2187906491731510095">扩展程åºå·²æ›´æ–°</translation>
-<translation id="2188881192257509750">打开 <ph name="APPLICATION" /></translation>
+<translation id="2188881192257509750">打开<ph name="APPLICATION" /></translation>
<translation id="2189787291884708275">分享标签页中的音频</translation>
<translation id="2190069059097339078">WiFi 凭æ®èŽ·å–工具</translation>
<translation id="219008588003277019">Native Client 模å—:<ph name="NEXE_NAME" /></translation>
@@ -2521,7 +2521,7 @@
<translation id="343115368966109153">下载 <ph name="FILE_NAME" />?所有使用此设备的用户都能查看该文件。</translation>
<translation id="3432227430032737297">移除显示的所有 Cookie</translation>
<translation id="3432762828853624962">Shared Workers</translation>
-<translation id="3433507769937235446">退出时é”定</translation>
+<translation id="3433507769937235446">离开时é”定</translation>
<translation id="3433621910545056227">糟糕ï¼ç³»ç»Ÿæ— æ³•å»ºç«‹è®¾å¤‡å®‰è£…时间属性é”定。</translation>
<translation id="3434107140712555581"><ph name="BATTERY_PERCENTAGE" />%</translation>
<translation id="3434272557872943250">如果您已为孩å­å¼€å¯â€œå…¶ä»–网络与应用活动记录â€è®¾ç½®ï¼Œè¿™äº›æ•°æ®å¯èƒ½ä¼šè¢«ä¿å­˜åˆ°å…¶ Google å¸å·ä¸­ã€‚您å¯åœ¨ families.google.com 上详细了解这些设置以åŠå¦‚何调整它们。</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_ar.xtb b/chromium/chrome/app/resources/google_chrome_strings_ar.xtb
index 334de19ffad..88a11e1504f 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_ar.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_ar.xtb
@@ -9,7 +9,7 @@
<translation id="110877069173485804">â€Ù‡Ø°Ø§ Chrome الخاص بك</translation>
<translation id="1125124144982679672">â€Ù…ÙŽÙ† يستخدم متصÙÙ‘ÙØ­ ChromeØŸ</translation>
<translation id="1142745911746664600">â€ØªØ¹Ø°Ù‘رت إعادة التحميل Chrome</translation>
-<translation id="1152920704813762236">â€Ù„محة عن نظام التشغيل Chrome</translation>
+<translation id="1152920704813762236">â€Ù„محة عن ChromeOS</translation>
<translation id="1154147086299354128">â€&amp;Ùتح ÙÙŠ Chrome</translation>
<translation id="1178374936842835197">â€ÙÙŠ حال عدم عرض أحد الإعدادات على هذه الصÙحة، انتقÙÙ„ إلى <ph name="LINK_BEGIN" />
إعدادات نظام التشغيل ChromeOS Flex<ph name="LINK_END" /></translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_es.xtb b/chromium/chrome/app/resources/google_chrome_strings_es.xtb
index 8b8e0695af2..ef32e02a8aa 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_es.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_es.xtb
@@ -11,7 +11,7 @@ Es posible que algunas funciones no estén disponibles y que no se guarden los c
<translation id="110877069173485804">Este es tu navegador Chrome</translation>
<translation id="1125124144982679672">¿Qué perfil de Chrome quieres usar?</translation>
<translation id="1142745911746664600">No se puede actualizar Chrome</translation>
-<translation id="1152920704813762236">Información de ChromeOS</translation>
+<translation id="1152920704813762236">Información de Chrome OS</translation>
<translation id="1154147086299354128">&amp;Abrir en Chrome</translation>
<translation id="1178374936842835197">Si no se muestra alguno de los ajustes en esta página, mira en la <ph name="LINK_BEGIN" />
configuración de ChromeOS Flex<ph name="LINK_END" /></translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_eu.xtb b/chromium/chrome/app/resources/google_chrome_strings_eu.xtb
index 416294a54dc..017feb315a5 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_eu.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_eu.xtb
@@ -250,7 +250,7 @@ Google Chrome-k ezin ditu berreskuratu zure ezarpenak.</translation>
<translation id="6418662306461808273">Lehendik dagoen Chrome-ko profilera aldatu nahi duzu?</translation>
<translation id="6506909944137591434">Chrome-k kamera atzitzeko baimena behar du zure inguruaren 3D-ko mapa bat sortzeko</translation>
<translation id="6515495397637126556"><ph name="PAGE_TITLE" /> - Google Chrome Dev</translation>
-<translation id="659498884637196217">Google-ren Pasahitz-kudeatzailea atalean (gailu honetan)</translation>
+<translation id="659498884637196217">Google-ren Pasahitz-kudeatzailea zerbitzuan (gailu honetan)</translation>
<translation id="6632473616050862500"><ph name="BEGIN_LINK_CROS_OSS" />Kode irekiko software<ph name="END_LINK_CROS_OSS" /> gehigarriari esker da posible ChromeOS Flex.</translation>
<translation id="6676384891291319759">Sartu Interneten</translation>
<translation id="6679975945624592337">Utzi Google Chrome-ri atzeko planoan abiarazten</translation>
@@ -316,7 +316,7 @@ Baliteke webguneei eta aplikazioei emandako baimenak kontu honi aplikatzea. Goog
<translation id="7930071585467473040">Pasahitzak kopiatzen saiatzen ari da Google Chrome.</translation>
<translation id="7951272445806340501">Eguneratzea aplikatzeko, berrabiarazi egin behar da ChromeOS Flex.</translation>
<translation id="7962410387636238736">Ordenagailuak ez du jasoko Google Chrome-ren beste eguneratzerik, jada ez baita bateragarria Windows XP eta Windows Vista-rekin</translation>
-<translation id="8005666035647241369">Google-ren Pasahitz-kudeatzailea atalean (gailu honetan)</translation>
+<translation id="8005666035647241369">Google-ren Pasahitz-kudeatzailea zerbitzuan (gailu honetan)</translation>
<translation id="8008534537613507642">Berrinstalatu Chrome</translation>
<translation id="8013993649590906847">Irudiren batek ez badu azalpen lagungarririk, halako bat lortzen ahaleginduko da Chrome. Azalpen horiek sortzeko, irudiak Google-ri bidaltzen zaizkio.</translation>
<translation id="8064015586118426197">ChromeOS Flex</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_fr.xtb b/chromium/chrome/app/resources/google_chrome_strings_fr.xtb
index 096e0fd5923..f2b37c9551d 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_fr.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_fr.xtb
@@ -226,7 +226,7 @@ Google Chrome ne peut pas récupérer vos paramètres.</translation>
<translation id="5727531838415286053">Si Chrome vous a intégré aléatoirement à un essai actif, votre historique de navigation influe sur les annonces que vous voyez et les centres d'intérêt estimés ci-dessous. Pour protéger votre confidentialité, Chrome supprime vos centres d'intérêt tous les mois. Les centres d'intérêt s'actualisent, sauf si vous les supprimez.</translation>
<translation id="5736850870166430177">Si un site tente de voler votre mot de passe ou si vous téléchargez un fichier dangereux, Chrome peut également envoyer les URL concernées, y compris des extraits du contenu des pages, à la fonctionnalité de navigation sécurisée</translation>
<translation id="5756509061973259733">Il existe déjà un profil Chrome associé à ce compte sur cet appareil</translation>
-<translation id="5763280521700030406">Chrome a bloqué ce fichier. Impossible de vérifier si ce fichier est sûr, car il est trop volumineux. Réessayez avec des fichiers de 50 Mo maximum</translation>
+<translation id="5763280521700030406">Chrome a bloqué ce fichier, car il est trop volumineux pour être analysé. Réessayez avec des fichiers de 50 Mo maximum.</translation>
<translation id="5795887333006832406"><ph name="PAGE_TITLE" /> – Google Chrome Canary</translation>
<translation id="5804318322022881572">Impossible de lancer Chrome. Réessayez.</translation>
<translation id="5867197326698922595">Google Chrome tente de modifier les mots de passe.</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_gl.xtb b/chromium/chrome/app/resources/google_chrome_strings_gl.xtb
index 692445beff8..c6a9a7939c3 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_gl.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_gl.xtb
@@ -22,7 +22,7 @@
<translation id="137466361146087520">Google Chrome Beta</translation>
<translation id="1399397803214730675">Este ordenador ten instalada unha versión máis recente de Google Chrome. Se o software non funciona, desinstala Google Chrome e téntao de novo.</translation>
<translation id="139993653570221430">Se cambias de idea, podes modificar a configuración de Chrome en calquera momento. Con todo, as probas inscríbense na experiencia actual de publicación de anuncios, polo que non verás cambios de inmediato.</translation>
-<translation id="1434626383986940139">Aplicacións de valores controlados de Chrome</translation>
+<translation id="1434626383986940139">Aplicacións de Chrome Canary</translation>
<translation id="1507198376417198979">Personaliza o teu novo perfil de Chrome</translation>
<translation id="1516530951338665275">Google Chrome precisa acceso mediante Bluetooth para continuar coa vinculación. <ph name="IDS_BLUETOOTH_DEVICE_CHOOSER_AUTHORIZE_BLUETOOTH_LINK" /></translation>
<translation id="1547295885616600893">Chrome OS é posible grazas a <ph name="BEGIN_LINK_CROS_OSS" />software de código aberto<ph name="END_LINK_CROS_OSS" /> adicional.</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_mr.xtb b/chromium/chrome/app/resources/google_chrome_strings_mr.xtb
index 9a5101cfc1c..60c4cbc8c82 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_mr.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_mr.xtb
@@ -30,7 +30,7 @@
<translation id="1553358976309200471">Chrome अपडेट करा</translation>
<translation id="1583073672411044740"><ph name="EXISTING_USER" /> यांनी आधीपासून या Chrome पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤²à¤®à¤§à¥à¤¯à¥‡ साइन इन केलेले आहे. हे <ph name="USER_EMAIL_ADDRESS" /> साठी नवीन Chrome पà¥à¤°à¥‹à¤«à¤¾à¤‡à¤² तयार करेल</translation>
<translation id="1587223624401073077">Google Chrome तà¥à¤®à¤šà¤¾ कॅमेरा वापरत आहे.</translation>
-<translation id="1587325591171447154"><ph name="FILE_NAME" /> धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrom ने ते अवरोधित केले आहे.</translation>
+<translation id="1587325591171447154"><ph name="FILE_NAME" /> धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrom ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="1597911401261118146">तà¥à¤®à¤šà¥‡ पासवरà¥à¤¡ डेटा भंग आणि इतर सà¥à¤°à¤•à¥à¤·à¤¾ समसà¥à¤¯à¤¾à¤‚पासून सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ आहेत हे तपासणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, <ph name="BEGIN_LINK" />Chrome मधà¥à¤¯à¥‡ साइन इन करा<ph name="END_LINK" />.</translation>
<translation id="1619887657840448962">Chrome अधिक सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, आमà¥à¤¹à¥€ <ph name="IDS_EXTENSION_WEB_STORE_TITLE" /> मधà¥à¤¯à¥‡ सूचीबदà¥à¤§ नसलेला आणि तà¥à¤®à¤šà¥à¤¯à¤¾ माहितीशिवाय कदाचित जोडले गेलेले खालील à¤à¤•à¥à¤¸à¥à¤Ÿà¥‡à¤‚शन बंद केले आहे.</translation>
<translation id="1627304841979541023"><ph name="BEGIN_BOLD" />तà¥à¤®à¥à¤¹à¥€ तà¥à¤®à¤šà¤¾ डेटा कसा वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करू शकता:<ph name="END_BOLD" /> तà¥à¤®à¤šà¥à¤¯à¤¾ गोपनीयतेचे संरकà¥à¤·à¤£ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, आमà¥à¤¹à¥€ तà¥à¤®à¤šà¥€ चार आठवडà¥à¤¯à¤¾à¤‚पेकà¥à¤·à¤¾ जà¥à¤¨à¥€ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ ऑटो-डिलीट करतो. तà¥à¤®à¥à¤¹à¥€ बà¥à¤°à¤¾à¤‰à¤ करत राहाल, तसे सूचीमधà¥à¤¯à¥‡ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯ पà¥à¤¨à¥à¤¹à¤¾ दिसू शकते. किंवा Chrome ने विचारात घेऊ नये असे तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ वाटत असलेली सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ तà¥à¤®à¥à¤¹à¥€ काढून टाकू शकता.</translation>
@@ -183,7 +183,7 @@
<translation id="4728575227883772061">नमूद न केलेलà¥à¤¯à¤¾ à¤à¤°à¤°à¤®à¥à¤³à¥‡ सà¥à¤¥à¤¾à¤ªà¤¨à¤¾ अयशसà¥à¤µà¥€. जर Google Chrome सधà¥à¤¯à¤¾ सà¥à¤°à¥‚ असेल तर, कृपया तà¥à¤¯à¤¾à¤¸ बंद करा आणि पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा.</translation>
<translation id="4747730611090640388">Chrome तà¥à¤®à¤šà¥à¤¯à¤¾ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¤¾à¤‚बाबतचे अंदाज लावू शकते. तà¥à¤¯à¤¾à¤¨à¤‚तर, तà¥à¤®à¥à¤¹à¥€ पाहता तà¥à¤¯à¤¾ जाहिराती परà¥à¤¸à¤¨à¤²à¤¾à¤‡à¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¥à¤¹à¥€ भेट देता ती साइट Chrome कडे तà¥à¤®à¤šà¥€ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ पाहणà¥à¤¯à¤¾à¤šà¥€ अनà¥à¤®à¤¤à¥€ मागू शकते.</translation>
<translation id="4754614261631455953">Google Chrome कॅनरी (mDNS-मधà¥à¤¯à¥‡)</translation>
-<translation id="4771048833395599659">ही फाईल कदाचित धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrome ने ती अवरोधित केली आहे.</translation>
+<translation id="4771048833395599659">ही फाइल कदाचित धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrome ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="479167709087336770">Google Search मधà¥à¤¯à¥‡ वापरत असेलेले समान सà¥à¤ªà¥‡à¤² चेकर हे वापरते. तà¥à¤®à¥à¤¹à¥€ बà¥à¤°à¤¾à¤‰à¤à¤°à¤®à¤§à¥à¤¯à¥‡ टाइप करत असलेला मजकूर Google कडे पाठवला जातो. तà¥à¤®à¥à¤¹à¥€ हे वरà¥à¤¤à¤¨ कधीही सेटिंगà¥à¤œ मधà¥à¤¯à¥‡ बदलू शकता.</translation>
<translation id="4842397268809523050">तà¥à¤®à¤šà¥à¤¯à¤¾ डोमेनसाठी सिंक करणे उपलबà¥à¤§ नसलà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ ChromeOS Flex ला तà¥à¤®à¤šà¤¾ डेटा सिंक करता आला नाही.</translation>
<translation id="4873783916118289636">Chrome मधील महतà¥à¤¤à¥à¤µà¤¾à¤šà¥à¤¯à¤¾ गोपनीयता आणि सà¥à¤°à¤•à¥à¤·à¤¾ नियंतà¥à¤°à¤£à¤¾à¤‚चे पà¥à¤¨à¤°à¤¾à¤µà¤²à¥‹à¤•à¤¨ करा</translation>
@@ -217,7 +217,7 @@ Google Chrome तà¥à¤®à¤šà¥€ सेटिंगà¥à¤œ पà¥à¤¨à¥à¤¹à¤¾ मि
<translation id="5524761631371622910">चाचणà¥à¤¯à¤¾ सà¥à¤°à¥‚ असताना Chrome ने तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ रà¤à¤¡à¤® पदà¥à¤§à¤¤à¥€à¤¨à¥‡ अâ€à¥…कà¥à¤Ÿà¤¿à¤µà¥à¤¹ चाचणीमधà¥à¤¯à¥‡ सामील केलà¥à¤¯à¤¾à¤¸, तà¥à¤®à¤šà¤¾ बà¥à¤°à¤¾à¤‰à¤à¤¿à¤‚ग इतिहास हा तà¥à¤®à¥à¤¹à¥€ पाहता तà¥à¤¯à¤¾ जाहिराती आणि खाली दिलेली अंदाजित सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ यांवर परिणाम करतो. तà¥à¤®à¤šà¥à¤¯à¤¾ गोपनीयतेचे संरकà¥à¤·à¤£ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€, Chrome दर महिनà¥à¤¯à¤¾à¤²à¤¾ रोलिंगचà¥à¤¯à¤¾ आधारे तà¥à¤®à¤šà¥€ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ हटवते.</translation>
<translation id="556024056938947818">Google Chrome पासवरà¥à¤¡ दरà¥à¤¶à¤µà¤£à¥à¤¯à¤¾à¤šà¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करत आहे.</translation>
<translation id="5566025111015594046">Google Chrome (mDNS-मधà¥à¤¯à¥‡)</translation>
-<translation id="565744775970812598"><ph name="FILE_NAME" /> धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrom ने ते अवरोधित केले आहे.</translation>
+<translation id="565744775970812598"><ph name="FILE_NAME" /> धोकादायक असू शकते, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrome ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="5678190148303298925">{COUNT,plural, =0{तà¥à¤®à¤šà¥à¤¯à¤¾ ॲडमिनिसà¥à¤Ÿà¥à¤°à¥‡à¤Ÿà¤°à¤¨à¥‡ तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ हे अपडेट लागू करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ Chrome पà¥à¤¨à¥à¤¹à¤¾ लाà¤à¤š करणà¥à¤¯à¤¾à¤¸ सांगितले आहे}=1{तà¥à¤®à¤šà¥à¤¯à¤¾ ॲडमिनिसà¥à¤Ÿà¥à¤°à¥‡à¤Ÿà¤°à¤¨à¥‡ हे अपडेट लागू करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ Chrome पà¥à¤¨à¥à¤¹à¤¾ लाà¤à¤š करणà¥à¤¯à¤¾à¤¸ सांगितले आहे. तà¥à¤®à¤šà¥€ गà¥à¤ªà¥à¤¤ विंडो पà¥à¤¨à¥à¤¹à¤¾ उघडणार नाही.}other{तà¥à¤®à¤šà¥à¤¯à¤¾ ॲडमिनिसà¥à¤Ÿà¥à¤°à¥‡à¤Ÿà¤°à¤¨à¥‡ हे अपडेट लागू करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ Chrome पà¥à¤¨à¥à¤¹à¤¾ लाà¤à¤š करणà¥à¤¯à¤¾à¤¸ सांगितले आहे. तà¥à¤®à¤šà¥à¤¯à¤¾ # गà¥à¤ªà¥à¤¤ विंडो पà¥à¤¨à¥à¤¹à¤¾ उघडणार नाहीत.}}</translation>
<translation id="5686916850681061684">Google Chrome कसà¥à¤Ÿà¤®à¤¾à¤‡à¤ करा आणि नियंतà¥à¤°à¤¿à¤¤ करा. à¤à¤•à¤¾Â à¤—ोषà¥à¤Ÿà¥€à¤µà¤°Â à¤¤à¥à¤®à¥à¤¹à¥€Â à¤²à¤•à¥à¤·Â à¤¦à¥à¤¯à¤¾à¤¯à¤²à¤¾Â à¤¹à¤µà¥‡ - तपशिलांसाठी कà¥à¤²à¤¿à¤•Â à¤•à¤°à¤¾.</translation>
<translation id="5690427481109656848">Google LLC</translation>
@@ -326,7 +326,7 @@ Google Chrome तà¥à¤®à¤šà¥€ सेटिंगà¥à¤œ पà¥à¤¨à¥à¤¹à¤¾ मि
<translation id="8286862437124483331">Google Chrome पासवरà¥à¤¡ दरà¥à¤¶à¤µà¤¿à¤£à¥â€à¤¯à¤¾à¤šà¤¾ पà¥à¤°à¤¯à¤¤à¥â€à¤¨ करत आहे. यास अनà¥à¤®à¤¤à¥€ देणà¥â€à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¤šà¤¾ Windows पासवरà¥à¤¡ टाइप करा.</translation>
<translation id="828798499196665338">तà¥à¤®à¤šà¥à¤¯à¤¾ पालकांनी Chrome साठी "साइट, अâ€à¥…पà¥à¤¸ आणि à¤à¤•à¥à¤¸à¥à¤Ÿà¥‡à¤‚शनकरिता परवानगà¥à¤¯à¤¾" सà¥à¤°à¥‚ केलà¥à¤¯à¤¾ आहेत. हे <ph name="EXTENSION_TYPE_PARAMETER" /> सà¥à¤°à¥‚ करणà¥à¤¯à¤¾à¤¸ अनà¥à¤®à¤¤à¥€ नाही.</translation>
<translation id="8290100596633877290">अरेरे! Google Chrome कà¥à¤°à¥…श à¤à¤¾à¤²à¤¾. तà¥à¤µà¤°à¤¿à¤¤ पà¥à¤¨à¥à¤¹à¤¾ लाà¤à¤š करायचा?</translation>
-<translation id="8342675569599923794">ही फाईल धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrome ने ती अवरोधित केली आहे.</translation>
+<translation id="8342675569599923794">ही फाइल धोकादायक आहे, तà¥à¤¯à¤¾à¤®à¥à¤³à¥‡ Chrome ने ती बà¥à¤²à¥‰à¤• केली आहे.</translation>
<translation id="8349795646647783032"><ph name="BEGIN_BOLD" />आमà¥à¤¹à¥€ हा डेटा कसा वापरतो:<ph name="END_BOLD" /> साइट तà¥à¤®à¤šà¥à¤¯à¤¾ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¤¾à¤‚बदà¥à¤¦à¤²à¤šà¥€ माहिती Chrome वापरून सà¥à¤Ÿà¥‹à¤…र करू शकतात. उदाहरणारà¥à¤¥, तà¥à¤®à¥à¤¹à¥€ मॅरेथॉनसाठी शूज खरेदी करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ à¤à¤–ादà¥à¤¯à¤¾ साइटला भेट दिलà¥à¤¯à¤¾à¤¸, ती साइट तà¥à¤®à¤šà¥‡ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯ मॅरेथॉनमधà¥à¤¯à¥‡ धावणे मà¥à¤¹à¤£à¥‚न परिभाषित करू शकते. तà¥à¤¯à¤¾à¤¨à¤‚तर, तà¥à¤®à¥à¤¹à¥€ à¤à¤–ादà¥à¤¯à¤¾ शरà¥à¤¯à¤¤à¥€à¤¸à¤¾à¤ à¥€ नोंदणी करणà¥à¤¯à¤¾à¤•à¤°à¤¿à¤¤à¤¾ वेगळà¥à¤¯à¤¾ साइटला भेट दिलà¥à¤¯à¤¾à¤¸, ती साइट तà¥à¤®à¥à¤¹à¤¾à¤²à¤¾ तà¥à¤®à¤šà¥à¤¯à¤¾ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¤¾à¤‚नà¥à¤¸à¤¾à¤° धावणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€à¤šà¥à¤¯à¤¾ शूजची जाहिरात दाखवू शकते.</translation>
<translation id="8370517070665726704">कॉपीराइट <ph name="YEAR" /> Google LLC. सरà¥à¤µ हकà¥à¤• राखीव.</translation>
<translation id="8383226135083126309"><ph name="BEGIN_BOLD" />आमà¥à¤¹à¥€ हा डेटा कसा वापरतो:<ph name="END_BOLD" /> Chrome तà¥à¤®à¤šà¥à¤¯à¤¾ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¤¾à¤‚चा अंदाज लावू शकते. नंतर, तà¥à¤®à¥à¤¹à¥€ पाहता तà¥à¤¯à¤¾ जाहिराती परà¥à¤¸à¤¨à¤²à¤¾à¤‡à¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ तà¥à¤®à¥à¤¹à¥€ भेट देता ती साइट Chrome ला तà¥à¤®à¤šà¥€ सà¥à¤µà¤¾à¤°à¤¸à¥à¤¯à¥‡ पाहणà¥à¤¯à¤¾à¤šà¥€ विनंती करू शकते.</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_no.xtb b/chromium/chrome/app/resources/google_chrome_strings_no.xtb
index c061b599aa8..ae7f2483a61 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_no.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_no.xtb
@@ -362,7 +362,7 @@ Tillatelser du allerede har gitt nettsteder og apper, kan gjelde for denne konto
<translation id="8999208279178790196">{0,plural, =0{En Chrome-oppdatering er tilgjengelig}=1{En Chrome-oppdatering er tilgjengelig}other{En Chrome-oppdatering har vært tilgjengelig i # dager}}</translation>
<translation id="9053892488859122171">ChromeOS Flex-system</translation>
<translation id="9067395829937117663">Google Chrome krever Windows 7 eller nyere.</translation>
-<translation id="911206726377975832">Vil du slette all nettleserdata også?</translation>
+<translation id="911206726377975832">Vil du slette all nettlesingsdata også?</translation>
<translation id="9138603949443464873">For å bruke endringene dine må du starte Chrome på nytt</translation>
<translation id="9195993889682885387">Chrome kan anslå interessene dine basert på nettleserloggen din fra de siste ukene. Denne informasjonen blir værende på enheten din.</translation>
<translation id="919706545465235479">Oppdater Chrome for å starte synkroniseringen</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_sr-Latn.xtb b/chromium/chrome/app/resources/google_chrome_strings_sr-Latn.xtb
index efd6e384af9..b24a3945387 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_sr-Latn.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_sr-Latn.xtb
@@ -73,7 +73,7 @@ Neke funkcije su možda nedostupne i promene podeÅ¡avanja necÌe biti saÄuvane.
<translation id="2348335408836342058">Chrome traži dozvolu da pristupi kameri i mikrofonu za ovaj sajt</translation>
<translation id="234869673307233423">Chrome ne može da vam proverava lozinke. Probajte ponovo kasnije.</translation>
<translation id="235650106824528204">Administrator poslovnog profila može da ukloni sve podatke Chrome-a koji se generiÅ¡u tokom koriÅ¡cÌenja ovog profila (poput pravljenja obeleživaÄa, istorije, lozinki i drugih podeÅ¡avanja). <ph name="LEARN_MORE" /></translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="2401189691232800402">Sistem Chrome OS</translation>
<translation id="2429317896000329049">Google Chrome ne može da sinhronizuje podatke jer Sinhronizacija nije dostupna za domen.</translation>
<translation id="2467438592969358367">Google Chrome želi da izveze lozinke. Unesite lozinku za Windows da biste to omogucÌili.</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_sr.xtb b/chromium/chrome/app/resources/google_chrome_strings_sr.xtb
index a88cc3cb1fc..c6f16bae5a6 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_sr.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_sr.xtb
@@ -73,7 +73,7 @@
<translation id="2348335408836342058">Chrome тражи дозволу да приÑтупи камери и микрофону за овај Ñајт</translation>
<translation id="234869673307233423">Chrome не може да вам проверава лозинке. Пробајте поново каÑније.</translation>
<translation id="235650106824528204">ÐдминиÑтратор поÑловног профила може да уклони Ñве податке Chrome-а који Ñе генеришу током коришћења овог профила (попут прављења обележивача, иÑторије, лозинки и других подешавања). <ph name="LEARN_MORE" /></translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="2401189691232800402">СиÑтем Chrome ОС</translation>
<translation id="2429317896000329049">Google Chrome не може да Ñинхронизује податке јер Синхронизација није доÑтупна за домен.</translation>
<translation id="2467438592969358367">Google Chrome жели да извезе лозинке. УнеÑите лозинку за Windows да биÑте то омогућили.</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_tr.xtb b/chromium/chrome/app/resources/google_chrome_strings_tr.xtb
index 045e90543fd..c635cf85eec 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_tr.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_tr.xtb
@@ -9,7 +9,7 @@
<translation id="110877069173485804">Bu sizin Chrome'unuz</translation>
<translation id="1125124144982679672">Chrome'u kim kullanıyor?</translation>
<translation id="1142745911746664600">Chrome güncellenemiyor</translation>
-<translation id="1152920704813762236">&amp;ChromeOS hakkında</translation>
+<translation id="1152920704813762236">ChromeOS hakkında</translation>
<translation id="1154147086299354128">&amp;Chrome'da aç</translation>
<translation id="1178374936842835197">Bu sayfada bulunmayan bir ayar varsa <ph name="LINK_BEGIN" />ChromeOS Flex ayarlarınıza
<ph name="LINK_END" /> bakın</translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_vi.xtb b/chromium/chrome/app/resources/google_chrome_strings_vi.xtb
index e60dc1135ad..5b286f0d983 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_vi.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_vi.xtb
@@ -141,7 +141,7 @@
<translation id="3744202345691150878">Nhận trợ giúp vỠChromeOS</translation>
<translation id="3780814664026482060">Chrome - <ph name="PAGE_TITLE" /></translation>
<translation id="3785324443014631273">ChromeOS Flex không đồng bá»™ hoaÌ Ä‘Æ°Æ¡Ì£c dữ liệu của bạn do xảy ra lá»—i khi đăng nhập.</translation>
-<translation id="3795971588916395511">Google ChromeOS</translation>
+<translation id="3795971588916395511">Google Chrome OS</translation>
<translation id="3835168907083856002">Thao tác này sẽ tạo một hồ sơ mới trên Chrome cho <ph name="USER_EMAIL_ADDRESS" /></translation>
<translation id="386202838227397562">Vui lòng đóng tất cả cửa sổ của Google Chrome và thử lại.</translation>
<translation id="3865754807470779944">Bạn đã cài đặt Chrome phiên bản <ph name="PRODUCT_VERSION" /></translation>
@@ -149,7 +149,7 @@
<translation id="3889417619312448367">Gỡ cài đặt Google Chrome</translation>
<translation id="4035053306113201399">ChromeOS cần được khởi động lại để áp dụng bản cập nhật.</translation>
<translation id="4050175100176540509">Cải thiện bảo mật quan trá»ng và tính năng má»›i có trong phiên bản má»›i nhất.</translation>
-<translation id="4053720452172726777">Tùy chỉnh và Ä‘iá»u khiển Google Chrome</translation>
+<translation id="4053720452172726777">Tùy chỉnh và kiểm soát Google Chrome</translation>
<translation id="4106587138345390261">Chrome đang nghiên cứu các tính năng mới cho phép các trang web giữ nguyên trải nghiệm duyệt web nhưng dùng ít thông tin của bạn hơn</translation>
<translation id="4110895483821904099">Thiết lập hồ sơ Chrome mới</translation>
<translation id="4147555960264124640">Bạn Ä‘ang đăng nhập bằng tài khoản được quản lý và cấp cho quản trị viên của tài khoản quyá»n kiểm soát cấu hình trên Google Chrome của bạn. Dữ liệu Chrome của bạn, chẳng hạn nhÆ° ứng dụng, dấu trang, lịch sá»­, mật khẩu và các cài đặt khác sẽ vÄ©nh viá»…n được liên kết vá»›i <ph name="USER_NAME" />. Bạn có thể xóa dữ liệu này thông qua Trang tổng quan của tài khoản Google nhÆ°ng không thể liên kết dữ liệu này vá»›i tài khoản khác. <ph name="LEARN_MORE" /></translation>
diff --git a/chromium/chrome/app/resources/google_chrome_strings_zh-CN.xtb b/chromium/chrome/app/resources/google_chrome_strings_zh-CN.xtb
index 504051df27f..c0b50827437 100644
--- a/chromium/chrome/app/resources/google_chrome_strings_zh-CN.xtb
+++ b/chromium/chrome/app/resources/google_chrome_strings_zh-CN.xtb
@@ -9,7 +9,7 @@
<translation id="110877069173485804">这是您的专属Chrome</translation>
<translation id="1125124144982679672">è°åœ¨ä½¿ç”¨ Chrome?</translation>
<translation id="1142745911746664600">无法更新 Chrome</translation>
-<translation id="1152920704813762236">关于 Chrome æ“作系统</translation>
+<translation id="1152920704813762236">关于 ChromeOS</translation>
<translation id="1154147086299354128">在 Chrome 中打开(&amp;O)</translation>
<translation id="1178374936842835197">如果此页é¢ä¸­æœªæ˜¾ç¤ºæŸé¡¹è®¾ç½®ï¼Œè¯·åœ¨ <ph name="LINK_BEGIN" />ChromeOS Flex 设置<ph name="LINK_END" />中查找</translation>
<translation id="1182414570724401860">Chrome 建议您ä¸è¦ä¸‹è½½æˆ–打开此文件</translation>
diff --git a/chromium/chrome/browser/media/webrtc/region_capture_browsertest.cc b/chromium/chrome/browser/media/webrtc/region_capture_browsertest.cc
index a7c8804b221..baa9ac61fa4 100644
--- a/chromium/chrome/browser/media/webrtc/region_capture_browsertest.cc
+++ b/chromium/chrome/browser/media/webrtc/region_capture_browsertest.cc
@@ -40,6 +40,11 @@
namespace {
using content::WebContents;
+using testing::Bool;
+using testing::Combine;
+using testing::TestParamInfo;
+using testing::Values;
+using testing::WithParamInterface;
// TODO(crbug.com/1247761): Add tests that verify excessive calls to
// produceCropId() yield the empty string.
@@ -67,7 +72,7 @@ enum {
kServerCount // Must be last.
};
-enum {
+enum Tab {
kMainTab,
kOtherTab,
kTabCount // Must be last.
@@ -102,6 +107,9 @@ struct TabInfo {
}
void StartCaptureFromEmbeddedFrame() {
+ // Bring the tab into focus. This avoids getDisplayMedia rejection.
+ browser->tab_strip_model()->ActivateTabAt(tab_strip_index);
+
std::string script_result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(), "startCaptureFromEmbeddedFrame();",
@@ -155,6 +163,8 @@ struct TabInfo {
// detection of JS errors.
class RegionCaptureBrowserTest : public WebRtcTestBase {
public:
+ ~RegionCaptureBrowserTest() override = default;
+
void SetUpInProcessBrowserTestFixture() override {
WebRtcTestBase::SetUpInProcessBrowserTestFixture();
@@ -215,20 +225,18 @@ class RegionCaptureBrowserTest : public WebRtcTestBase {
// Set up all (necessary) tabs, loads iframes, and start capturing the
// relevant tab.
void SetUpTest(Frame capturing_entity, bool self_capture) {
- // Main page (for self-capture).
+ // Other page (for other-tab-capture).
+ SetUpPage("/webrtc/region_capture_other_main.html",
+ servers_[kOtherPageTopLevelDocument].get(),
+ "/webrtc/region_capture_other_embedded.html",
+ servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]);
+
+ // Main page (for self-capture). Instantiate it second to help it get focus.
SetUpPage("/webrtc/region_capture_main.html",
servers_[kMainPageTopLevelDocument].get(),
"/webrtc/region_capture_embedded.html",
servers_[kMainPageEmbeddedDocument].get(), &tabs_[kMainTab]);
- if (!self_capture) {
- // Other page (for other-tab-capture).
- SetUpPage("/webrtc/region_capture_other_main.html",
- servers_[kOtherPageTopLevelDocument].get(),
- "/webrtc/region_capture_other_embedded.html",
- servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]);
- }
-
DCHECK(command_line_);
command_line_->AppendSwitchASCII(
switches::kAutoSelectTabCaptureSourceByTitle,
@@ -376,28 +384,6 @@ IN_PROC_BROWSER_TEST_F(
EXPECT_EQ(tab.CropTo(""), "top-level-crop-success");
}
-IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
- CropToRejectedIfElementInAnotherTabTopLevel) {
- SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false);
-
- const std::string crop_id =
- tabs_[kOtherTab].ProduceCropId(Frame::kTopLevelDocument);
- ASSERT_THAT(crop_id, IsValidCropId());
-
- EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error");
-}
-
-IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
- CropToRejectedIfElementInAnotherTabEmbeddedFrame) {
- SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false);
-
- const std::string crop_id =
- tabs_[kOtherTab].ProduceCropId(Frame::kEmbeddedFrame);
- ASSERT_THAT(crop_id, IsValidCropId());
-
- EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error");
-}
-
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInTopLevelDocument) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
@@ -495,4 +481,85 @@ IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
"embedded-produce-crop-id-error");
}
+// Suite of tests ensuring that only self-capture may crop, and that it may
+// only crop to targets in its own tab, but that any target in its own tab
+// is permitted.
+class RegionCaptureSelfCaptureOnlyBrowserTest
+ : public RegionCaptureBrowserTest,
+ public WithParamInterface<std::tuple<Frame, bool, Tab, Frame>> {
+ public:
+ RegionCaptureSelfCaptureOnlyBrowserTest()
+ : capturing_entity_(std::get<0>(GetParam())),
+ self_capture_(std::get<1>(GetParam())),
+ target_element_tab_(std::get<2>(GetParam())),
+ target_frame_(std::get<3>(GetParam())) {}
+ ~RegionCaptureSelfCaptureOnlyBrowserTest() override = default;
+
+ protected:
+ // The capture is done from kMainTab in all instances of this parameterized
+ // test. |capturing_entity_| controls whether the capture is initiated
+ // from the top-level document of said tab, or an embedded frame.
+ const Frame capturing_entity_;
+
+ // Whether capturing self, or capturing the other tab.
+ const bool self_capture_;
+
+ // Whether the element on whose crop-ID we'll call cropTo():
+ // * |target_element_tab_| - whether it's in kMainTab or in kOtherTab.
+ // * |target_frame_| - whether it's in the top-level or an embedded frame.
+ const Tab target_element_tab_;
+ const Frame target_frame_; // Top-level or embedded frame.
+};
+
+std::string ParamsToString(
+ const TestParamInfo<RegionCaptureSelfCaptureOnlyBrowserTest::ParamType>&
+ info) {
+ return base::StrCat(
+ {std::get<0>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
+ : "EmbeddedFrame",
+ std::get<1>(info.param) ? "SelfCapturing" : "CapturingOtherTab",
+ "AndCroppingToElementIn",
+ std::get<2>(info.param) == kMainTab ? "OwnTabs" : "OtherTabs",
+ std::get<3>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
+ : "EmbeddedFrame"});
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ _,
+ RegionCaptureSelfCaptureOnlyBrowserTest,
+ Combine(Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame),
+ Bool(),
+ Values(kMainTab, kOtherTab),
+ Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame)),
+ ParamsToString);
+
+IN_PROC_BROWSER_TEST_P(RegionCaptureSelfCaptureOnlyBrowserTest, CropTo) {
+ SetUpTest(capturing_entity_, self_capture_);
+
+ // Prevent test false-positive - ensure that both tabs participating in the
+ // test have at least one associated crop-ID, or otherwise they would not
+ // have a CropIdWebContentsHelper.
+ // To make things even clearer, ensure both the top-level and the embedded
+ // frame have produced crop-IDs. (This should not be necessary, but is
+ // done as an extra buffer against false-positives.)
+ tabs_[kMainTab].ProduceCropId(Frame::kTopLevelDocument);
+ tabs_[kMainTab].ProduceCropId(Frame::kEmbeddedFrame);
+ tabs_[kOtherTab].ProduceCropId(Frame::kTopLevelDocument);
+ tabs_[kOtherTab].ProduceCropId(Frame::kEmbeddedFrame);
+
+ const std::string crop_id =
+ tabs_[target_element_tab_].ProduceCropId(target_frame_);
+ ASSERT_THAT(crop_id, IsValidCropId());
+
+ // Cropping only permitted if both conditions hold.
+ const bool expect_permitted =
+ (self_capture_ && target_element_tab_ == kMainTab);
+
+ const std::string expected_result = base::StrCat(
+ {capturing_entity_ == Frame::kTopLevelDocument ? "top-level" : "embedded",
+ "-", expect_permitted ? "crop-success" : "crop-error"});
+
+ EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), expected_result);
+}
+
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.ts b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.ts
index 64ededd8796..2b8ad610c16 100644
--- a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.ts
+++ b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.ts
@@ -174,8 +174,13 @@ export class PrintPreviewDestinationDialogElement extends
this.metrics_ = MetricsContext.destinationSearch();
}
this.$.dialog.showModal();
- this.loadingDestinations_ = this.destinationStore === undefined ||
+ const loading = this.destinationStore === undefined ||
this.destinationStore.isPrintDestinationSearchInProgress;
+ if (!loading) {
+ // All destinations have already loaded.
+ this.updateDestinations_();
+ }
+ this.loadingDestinations_ = loading;
this.metrics_.record(DestinationSearchBucket.DESTINATION_SHOWN);
}
diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
index a164805bde4..4c7b53daafc 100644
--- a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
+++ b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
@@ -293,8 +293,13 @@ export class PrintPreviewDestinationDialogCrosElement extends
this.metrics_ = MetricsContext.destinationSearch();
}
this.$.dialog.showModal();
- this.loadingDestinations_ = this.destinationStore === undefined ||
+ const loading = this.destinationStore === undefined ||
this.destinationStore.isPrintDestinationSearchInProgress;
+ if (!loading) {
+ // All destinations have already loaded.
+ this.updateDestinations_();
+ }
+ this.loadingDestinations_ = loading;
this.metrics_.record(DestinationSearchBucket.DESTINATION_SHOWN);
}
diff --git a/chromium/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html b/chromium/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html
index 046c4d849a5..9b6eb27fde9 100644
--- a/chromium/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html
+++ b/chromium/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html
@@ -24,11 +24,17 @@
--info-box-margin-top: 28px;
--info-box-width: auto;
--footer-margin: 16px;
- min-height: 500px;
+ display: flex;
+ flex-direction: column;
+ min-height: 512px;
position: relative;
width: 512px;
}
+ :host([is-modal-dialog_]) #contentContainer h2 {
+ margin-top: 0;
+ }
+
.secondary {
color: var(--cr-secondary-text-color);
}
@@ -41,6 +47,11 @@
width: 100%;
}
+ :host([is-modal-dialog_]) .action-container {
+ margin-top: auto;
+ position: relative;
+ }
+
#headerContainer {
background-color: var(--header-background-color);
height: var(--banner-height);
diff --git a/chromium/chrome/browser/signin/dice_web_signin_interceptor.cc b/chromium/chrome/browser/signin/dice_web_signin_interceptor.cc
index 0a46efcd203..1c87bfdad79 100644
--- a/chromium/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chromium/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -7,6 +7,7 @@
#include <string>
#include "base/check.h"
+#include "base/feature_list.h"
#include "base/hash/hash.h"
#include "base/i18n/case_conversion.h"
#include "base/metrics/field_trial_params.h"
@@ -33,6 +34,7 @@
#include "chrome/browser/signin/dice_signed_in_profile_creator.h"
#include "chrome/browser/signin/dice_web_signin_interceptor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/signin/signin_features.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
@@ -415,6 +417,12 @@ bool DiceWebSigninInterceptor::ShouldEnforceEnterpriseProfileSeparation(
bool DiceWebSigninInterceptor::ShouldShowEnterpriseDialog(
const AccountInfo& intercepted_account_info) const {
DCHECK(intercepted_account_info.IsValid());
+
+ if (!base::FeatureList::IsEnabled(
+ kShowEnterpriseDialogForAllManagedAccountsSignin)) {
+ return false;
+ }
+
// Check if the intercepted account is managed.
if (!intercepted_account_info.IsManaged())
return false;
@@ -541,6 +549,7 @@ void DiceWebSigninInterceptor::OnInterceptionReadyToBeProcessed(
}
} else if (ShouldShowEnterpriseDialog(info)) {
interception_type = SigninInterceptionType::kEnterpriseAcceptManagement;
+ show_link_data_option = true;
RecordSigninInterceptionHeuristicOutcome(
SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
} else if (!profile_->GetPrefs()->GetBoolean(
diff --git a/chromium/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc b/chromium/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
index 84dbe9154ff..9393650ea4e 100644
--- a/chromium/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
+++ b/chromium/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
@@ -19,6 +19,7 @@
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
+#include "chrome/browser/signin/signin_features.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/pref_names.h"
@@ -176,7 +177,8 @@ class DiceWebSigninInterceptorTest : public BrowserWithTestWindowTest {
testing::Mock::VerifyAndClearExpectations(mock_delegate());
histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
expected_outcome, 1);
- EXPECT_TRUE(interceptor()->is_interception_in_progress());
+ EXPECT_EQ(interceptor()->is_interception_in_progress(),
+ SigninInterceptionHeuristicOutcomeIsSuccess(expected_outcome));
}
protected:
@@ -415,6 +417,55 @@ class DiceWebSigninInterceptorManagedAccountTest
};
TEST_P(DiceWebSigninInterceptorManagedAccountTest,
+ NoForcedInterceptionShowsDialogIfFeatureEnabled) {
+ base::test::ScopedFeatureList scoped_list;
+ scoped_list.InitAndEnableFeature(
+ kShowEnterpriseDialogForAllManagedAccountsSignin);
+ // Reauth intercepted if enterprise confirmation not shown yet for forced
+ // managed separation.
+ AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
+ "alice@example.com", signin::ConsentLevel::kSignin);
+ MakeValidAccountInfo(&account_info, "example.com");
+ identity_test_env()->UpdateAccountInfoForAccount(account_info);
+ interceptor()->SetAccountLevelSigninRestrictionFetchResultForTesting("");
+
+ DiceWebSigninInterceptor::Delegate::BubbleParameters expected_parameters(
+ DiceWebSigninInterceptor::SigninInterceptionType::
+ kEnterpriseAcceptManagement,
+ account_info, account_info, SkColor(), /*show_guest_option=*/false,
+ /*show_link_data_option=*/true);
+ EXPECT_CALL(*mock_delegate(),
+ ShowSigninInterceptionBubble(
+ web_contents(), MatchBubbleParameters(expected_parameters),
+ testing::_));
+ TestAsynchronousInterception(
+ account_info, /*is_new_account=*/true, /*is_sync_signin=*/false,
+ SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
+}
+
+TEST_P(DiceWebSigninInterceptorManagedAccountTest,
+ NoForcedInterceptionShowsNoBubble) {
+ // Reauth intercepted if enterprise confirmation not shown yet for forced
+ // managed separation.
+ AccountInfo account_info = identity_test_env()->MakePrimaryAccountAvailable(
+ "alice@example.com", signin::ConsentLevel::kSignin);
+ MakeValidAccountInfo(&account_info, "example.com");
+ identity_test_env()->UpdateAccountInfoForAccount(account_info);
+ interceptor()->SetAccountLevelSigninRestrictionFetchResultForTesting("");
+
+ bool signin_interception_enabled = GetParam();
+ if (signin_interception_enabled) {
+ TestAsynchronousInterception(
+ account_info, /*is_new_account=*/true, /*is_sync_signin=*/false,
+ SigninInterceptionHeuristicOutcome::kAbortAccountInfoNotCompatible);
+ } else {
+ TestAsynchronousInterception(
+ account_info, /*is_new_account=*/true, /*is_sync_signin=*/false,
+ SigninInterceptionHeuristicOutcome::kAbortInterceptionDisabled);
+ }
+}
+
+TEST_P(DiceWebSigninInterceptorManagedAccountTest,
EnforceManagedAccountAsPrimaryReauth) {
profile()->GetPrefs()->SetBoolean(
prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
diff --git a/chromium/chrome/browser/signin/signin_features.cc b/chromium/chrome/browser/signin/signin_features.cc
index 3f1ad83c564..4f94113841f 100644
--- a/chromium/chrome/browser/signin/signin_features.cc
+++ b/chromium/chrome/browser/signin/signin_features.cc
@@ -12,3 +12,9 @@ const base::Feature kProcessGaiaRemoveLocalAccountHeader{
// Enables the sync promo after the sign-in intercept.
const base::Feature kSyncPromoAfterSigninIntercept{
"SyncPromoAfterSigninIntercept", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables showing the enterprise dialog after every signin into a managed
+// account.
+const base::Feature kShowEnterpriseDialogForAllManagedAccountsSignin{
+ "ShowEnterpriseDialogForAllManagedAccountsSignin",
+ base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromium/chrome/browser/signin/signin_features.h b/chromium/chrome/browser/signin/signin_features.h
index 2a618752791..ea315005165 100644
--- a/chromium/chrome/browser/signin/signin_features.h
+++ b/chromium/chrome/browser/signin/signin_features.h
@@ -11,4 +11,6 @@ extern const base::Feature kProcessGaiaRemoveLocalAccountHeader;
extern const base::Feature kSyncPromoAfterSigninIntercept;
+extern const base::Feature kShowEnterpriseDialogForAllManagedAccountsSignin;
+
#endif // CHROME_BROWSER_SIGNIN_SIGNIN_FEATURES_H_
diff --git a/chromium/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc b/chromium/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
index 54ca2ce784c..9ebfb2637e5 100644
--- a/chromium/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
+++ b/chromium/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
@@ -21,6 +21,7 @@
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/common/pref_names.h"
#include "components/history/core/browser/history_types.h"
#include "components/history_clusters/core/config.h"
#include "components/history_clusters/core/features.h"
@@ -259,6 +260,16 @@ void HistoryClustersHandler::LoadMoreClusters(const std::string& query) {
void HistoryClustersHandler::RemoveVisits(
std::vector<mojom::URLVisitPtr> visits,
RemoveVisitsCallback callback) {
+ // TODO(crbug.com/1327743): Enforced here because enforcing at the UI level is
+ // too complicated to merge. We can consider removing this clause or turning
+ // it to a DCHECK after we enforce it at the UI level, but it's essentially
+ // harmless to keep it here too.
+ if (!profile_->GetPrefs()->GetBoolean(
+ ::prefs::kAllowDeletingBrowserHistory)) {
+ std::move(callback).Run(false);
+ return;
+ }
+
// Reject the request if a pending task exists or the set of visits is empty.
if (remove_task_tracker_.HasTrackedTasks() || visits.empty()) {
std::move(callback).Run(false);
diff --git a/chromium/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler_unittest.cc b/chromium/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler_unittest.cc
index d812d82a08c..ee22b02bdbe 100644
--- a/chromium/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler_unittest.cc
+++ b/chromium/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_handler_unittest.cc
@@ -93,9 +93,7 @@ class PrivacySandboxDialogHandlerTest : public testing::Test {
content::TestWebUI* web_ui() { return web_ui_.get(); }
PrivacySandboxDialogHandler* handler() { return handler_.get(); }
TestingProfile* profile() { return &profile_; }
- raw_ptr<MockPrivacySandboxDialogView> dialog_mock() {
- return dialog_mock_.get();
- }
+ MockPrivacySandboxDialogView* dialog_mock() { return dialog_mock_.get(); }
MockPrivacySandboxService* mock_privacy_sandbox_service() {
return mock_privacy_sandbox_service_;
}
@@ -120,15 +118,18 @@ class PrivacySandboxConsentDialogHandlerTest
: public PrivacySandboxDialogHandlerTest {
protected:
std::unique_ptr<PrivacySandboxDialogHandler> CreateHandler() override {
+ // base::Unretained is safe because the created handler does not outlive the
+ // mock.
return std::make_unique<PrivacySandboxDialogHandler>(
- base::BindOnce(&MockPrivacySandboxDialogView::Close, dialog_mock()),
+ base::BindOnce(&MockPrivacySandboxDialogView::Close,
+ base::Unretained(dialog_mock())),
base::BindOnce(&MockPrivacySandboxDialogView::ResizeNativeView,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
base::BindOnce(&MockPrivacySandboxDialogView::ShowNativeView,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
base::BindOnce(
&MockPrivacySandboxDialogView::OpenPrivacySandboxSettings,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
PrivacySandboxService::DialogType::kConsent);
}
};
@@ -247,15 +248,18 @@ class PrivacySandboxNoticeDialogHandlerTest
: public PrivacySandboxDialogHandlerTest {
protected:
std::unique_ptr<PrivacySandboxDialogHandler> CreateHandler() override {
+ // base::Unretained is safe because the created handler does not outlive the
+ // mock.
return std::make_unique<PrivacySandboxDialogHandler>(
- base::BindOnce(&MockPrivacySandboxDialogView::Close, dialog_mock()),
+ base::BindOnce(&MockPrivacySandboxDialogView::Close,
+ base::Unretained(dialog_mock())),
base::BindOnce(&MockPrivacySandboxDialogView::ResizeNativeView,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
base::BindOnce(&MockPrivacySandboxDialogView::ShowNativeView,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
base::BindOnce(
&MockPrivacySandboxDialogView::OpenPrivacySandboxSettings,
- dialog_mock()),
+ base::Unretained(dialog_mock())),
PrivacySandboxService::DialogType::kNotice);
}
};
diff --git a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
index 8252758a441..32ac57e1e96 100644
--- a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
+++ b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
@@ -110,13 +110,13 @@ SkColor GetHighlightColor(absl::optional<SkColor> theme_color) {
EnterpriseProfileWelcomeHandler::EnterpriseProfileWelcomeHandler(
Browser* browser,
EnterpriseProfileWelcomeUI::ScreenType type,
- bool force_new_profile,
+ bool profile_creation_required_by_policy,
const AccountInfo& account_info,
absl::optional<SkColor> profile_color,
signin::SigninChoiceCallback proceed_callback)
: browser_(browser),
type_(type),
- force_new_profile_(force_new_profile),
+ profile_creation_required_by_policy_(profile_creation_required_by_policy),
email_(base::UTF8ToUTF16(account_info.email)),
domain_name_(gaia::ExtractDomainName(account_info.email)),
account_id_(account_info.account_id),
@@ -222,9 +222,8 @@ void EnterpriseProfileWelcomeHandler::HandleProceed(
if (proceed_callback_) {
bool use_existing_profile = args[0].GetIfBool().value_or(false);
std::move(proceed_callback_)
- .Run(use_existing_profile || !force_new_profile_
- ? signin::SIGNIN_CHOICE_CONTINUE
- : signin::SIGNIN_CHOICE_NEW_PROFILE);
+ .Run(use_existing_profile ? signin::SIGNIN_CHOICE_CONTINUE
+ : signin::SIGNIN_CHOICE_NEW_PROFILE);
}
}
@@ -280,6 +279,10 @@ base::Value EnterpriseProfileWelcomeHandler::GetProfileInfoValue() {
dict.SetStringKey("proceedLabel", l10n_util::GetStringUTF8(IDS_DONE));
break;
case EnterpriseProfileWelcomeUI::ScreenType::kEnterpriseAccountCreation:
+ title = l10n_util::GetStringUTF8(
+ profile_creation_required_by_policy_
+ ? IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE
+ : IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE);
dict.SetBoolKey("showEnterpriseBadge", false);
subtitle = GetManagedAccountTitleWithEmail(entry, domain_name_, email_);
enterprise_info = l10n_util::GetStringUTF8(
@@ -287,7 +290,7 @@ base::Value EnterpriseProfileWelcomeHandler::GetProfileInfoValue() {
dict.SetStringKey(
"proceedLabel",
l10n_util::GetStringUTF8(
- force_new_profile_
+ profile_creation_required_by_policy_
? IDS_ENTERPRISE_PROFILE_WELCOME_CREATE_PROFILE_BUTTON
: IDS_WELCOME_SIGNIN_VIEW_SIGNIN));
break;
diff --git a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
index 60e52c31d34..3368cc673f6 100644
--- a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
+++ b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
@@ -40,7 +40,7 @@ class EnterpriseProfileWelcomeHandler
EnterpriseProfileWelcomeHandler(
Browser* browser,
EnterpriseProfileWelcomeUI::ScreenType type,
- bool force_new_profile,
+ bool profile_creation_required_by_policy,
const AccountInfo& account_info,
absl::optional<SkColor> profile_color,
signin::SigninChoiceCallback proceed_callback);
@@ -110,7 +110,7 @@ class EnterpriseProfileWelcomeHandler
raw_ptr<Browser> browser_ = nullptr;
const EnterpriseProfileWelcomeUI::ScreenType type_;
- const bool force_new_profile_;
+ const bool profile_creation_required_by_policy_;
const std::u16string email_;
const std::string domain_name_;
const CoreAccountId account_id_;
diff --git a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc
index 521e5f4f262..60a585127f5 100644
--- a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc
+++ b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc
@@ -64,13 +64,13 @@ void EnterpriseProfileWelcomeUI::Initialize(
Browser* browser,
EnterpriseProfileWelcomeUI::ScreenType type,
const AccountInfo& account_info,
- bool force_new_profile,
+ bool profile_creation_required_by_policy,
bool show_link_data_option,
absl::optional<SkColor> profile_color,
signin::SigninChoiceCallback proceed_callback) {
auto handler = std::make_unique<EnterpriseProfileWelcomeHandler>(
- browser, type, force_new_profile, account_info, profile_color,
- std::move(proceed_callback));
+ browser, type, profile_creation_required_by_policy, account_info,
+ profile_color, std::move(proceed_callback));
handler_ = handler.get();
if (type ==
@@ -78,7 +78,7 @@ void EnterpriseProfileWelcomeUI::Initialize(
base::Value::Dict update_data;
update_data.Set("isModalDialog", true);
- int title_id = force_new_profile
+ int title_id = profile_creation_required_by_policy
? IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE
: IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE;
update_data.Set("enterpriseProfileWelcomeTitle",
diff --git a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h
index 00b15ee9765..58b1015b893 100644
--- a/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h
+++ b/chromium/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h
@@ -44,11 +44,22 @@ class EnterpriseProfileWelcomeUI : public content::WebUIController {
EnterpriseProfileWelcomeUI& operator=(const EnterpriseProfileWelcomeUI&) =
delete;
- // Initializes the EnterpriseProfileWelcomeUI.
+
+ // Initializes the EnterpriseProfileWelcomeUI, which will obtain the user's
+ // choice about how to set up the profile with the new account.
+ // `proceed_callback` will be called when the user performs an action to exit
+ // the screen. Their choice will depend on other flags passed to this method.
+ // If `profile_creation_required_by_policy` is true, the wording of the dialog
+ // will tell the user that an admin requires a new profile for the account,
+ // otherwise the default wording will be used.
+ // `show_link_data_option` will make the screen display a checkbox, and when
+ // selected, will indicate that the user wants the current profile to be used
+ // as dedicated profile for the new account, linking the current data with
+ // synced data from the new account.
void Initialize(Browser* browser,
ScreenType type,
const AccountInfo& account_info,
- bool force_new_profile,
+ bool profile_creation_required_by_policy,
bool show_link_data_option,
absl::optional<SkColor> profile_color,
signin::SigninChoiceCallback proceed_callback);
diff --git a/chromium/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc b/chromium/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc
index dfb0421304b..964d3e09413 100644
--- a/chromium/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc
+++ b/chromium/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc
@@ -10,6 +10,7 @@
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/new_tab_page/chrome_colors/selected_colors_info.h"
@@ -105,6 +106,11 @@ void TurnSyncOnHelperDelegateImpl::ShowEnterpriseAccountConfirmation(
const AccountInfo& account_info,
signin::SigninChoiceCallback callback) {
browser_ = EnsureBrowser(browser_, profile_);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ // Profile Separation Enforced is not supported on Lacros.
+ OnProfileCheckComplete(account_info, std::move(callback),
+ /*prompt_for_new_profile=*/false);
+#else
account_level_signin_restriction_policy_fetcher_ =
std::make_unique<policy::UserCloudSigninRestrictionPolicyFetcher>(
g_browser_process->browser_policy_connector(),
@@ -117,6 +123,7 @@ void TurnSyncOnHelperDelegateImpl::ShowEnterpriseAccountConfirmation(
base::BindOnce(&TurnSyncOnHelperDelegateImpl::OnProfileCheckComplete,
weak_ptr_factory_.GetWeakPtr(), account_info,
std::move(callback)));
+#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
}
void TurnSyncOnHelperDelegateImpl::ShowSyncConfirmation(
@@ -180,6 +187,7 @@ void TurnSyncOnHelperDelegateImpl::OnBrowserRemoved(Browser* browser) {
browser_ = nullptr;
}
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
void TurnSyncOnHelperDelegateImpl::OnProfileSigninRestrictionsFetched(
const AccountInfo& account_info,
signin::SigninChoiceCallback callback,
@@ -192,11 +200,14 @@ void TurnSyncOnHelperDelegateImpl::OnProfileSigninRestrictionsFetched(
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(browser_->profile()->GetPath());
- auto force_new_profile = signin_util::ProfileSeparationEnforcedByPolicy(
- browser_->profile(), signin_restriction);
+ auto profile_creation_required_by_policy =
+ signin_util::ProfileSeparationEnforcedByPolicy(browser_->profile(),
+ signin_restriction);
+ bool show_link_data_option = signin_util::
+ ProfileSeparationAllowsKeepingUnmanagedBrowsingDataInManagedProfile(
+ browser_->profile(), signin_restriction);
browser_->signin_view_controller()->ShowModalEnterpriseConfirmationDialog(
- account_info, force_new_profile,
- /*show_link_data_option=*/!force_new_profile,
+ account_info, profile_creation_required_by_policy, show_link_data_option,
GenerateNewProfileColor(entry).color,
base::BindOnce(
[](signin::SigninChoiceCallback callback, Browser* browser,
@@ -206,6 +217,7 @@ void TurnSyncOnHelperDelegateImpl::OnProfileSigninRestrictionsFetched(
},
std::move(callback), browser_.get()));
}
+#endif
void TurnSyncOnHelperDelegateImpl::OnProfileCheckComplete(
const AccountInfo& account_info,
@@ -215,11 +227,7 @@ void TurnSyncOnHelperDelegateImpl::OnProfileCheckComplete(
std::move(callback).Run(signin::SIGNIN_CHOICE_CANCEL);
return;
}
- ProfileAttributesEntry* entry =
- g_browser_process->profile_manager()
- ->GetProfileAttributesStorage()
- .GetProfileAttributesWithPath(browser_->profile()->GetPath());
-
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
if (prompt_for_new_profile) {
account_level_signin_restriction_policy_fetcher_
->GetManagedAccountsSigninRestriction(
@@ -229,16 +237,29 @@ void TurnSyncOnHelperDelegateImpl::OnProfileCheckComplete(
OnProfileSigninRestrictionsFetched,
weak_ptr_factory_.GetWeakPtr(), account_info,
std::move(callback)));
- } else {
- browser_->signin_view_controller()->ShowModalEnterpriseConfirmationDialog(
- account_info, /*force_new_profile=*/false,
- /*show_link_data_option*/ false, GenerateNewProfileColor(entry).color,
- base::BindOnce(
- [](signin::SigninChoiceCallback callback, Browser* browser,
- signin::SigninChoice choice) {
- browser->signin_view_controller()->CloseModalSignin();
- std::move(callback).Run(choice);
- },
- std::move(callback), browser_.get()));
+ return;
}
+#endif
+ DCHECK(!prompt_for_new_profile);
+ ProfileAttributesEntry* entry =
+ g_browser_process->profile_manager()
+ ->GetProfileAttributesStorage()
+ .GetProfileAttributesWithPath(browser_->profile()->GetPath());
+ browser_->signin_view_controller()->ShowModalEnterpriseConfirmationDialog(
+ account_info, /*profile_creation_required_by_policy=*/false,
+ /*show_link_data_option=*/false, GenerateNewProfileColor(entry).color,
+ base::BindOnce(
+ [](signin::SigninChoiceCallback callback, Browser* browser,
+ signin::SigninChoice choice) {
+ browser->signin_view_controller()->CloseModalSignin();
+ // When `show_link_data_option` is false,
+ // `ShowModalEnterpriseConfirmationDialog()` calls back with either
+ // `SIGNIN_CHOICE_CANCEL` or `SIGNIN_CHOICE_NEW_PROFILE`.
+ // The profile is clean here, no need to create a new one.
+ std::move(callback).Run(
+ choice == signin::SigninChoice::SIGNIN_CHOICE_CANCEL
+ ? signin::SigninChoice::SIGNIN_CHOICE_CANCEL
+ : signin::SigninChoice::SIGNIN_CHOICE_CONTINUE);
+ },
+ std::move(callback), browser_.get()));
}
diff --git a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_as.xtb b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_as.xtb
index cfe23535b44..c13f87898ae 100644
--- a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_as.xtb
+++ b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_as.xtb
@@ -251,7 +251,7 @@
<translation id="6992289844737586249">ছাইটসমূহে আপোনাৰ ডিভাইচৰ মাইকà§à§°à¦«'ন বà§à¦¯à§±à¦¹à¦¾à§° কৰাৰ পূরà§à¦¬à§‡ অনà§à¦®à¦¤à¦¿ ল'বলৈ দিয়ক (à¦à§Ÿà¦¾ চà§à¦ªà¦¾à§°à¦¿à¦› কৰা হয়)</translation>
<translation id="7000754031042624318">Androidৰ ছেটিংসমূহত অফ কৰা আছে</translation>
<translation id="7016516562562142042">বৰà§à¦¤à¦®à¦¾à¦¨à§° সনà§à¦§à¦¾à¦¨ ইঞà§à¦œà¦¿à¦¨à§° বাবে অনà§à¦®à§‹à¦¦à¦¨ জনোৱা হৈছে</translation>
-<translation id="702463548815491781">টকবেক বা ছà§à¦‡à¦šà§° দà§à¦¬à¦¾à§°à¦¾ বà§à¦¯à§±à¦¹à¦¾à§° কৰা অনà§à¦®à¦¤à¦¿ অন থকা অৱসà§à¦¥à¦¾à¦¨ à¦à§Ÿà¦¾ চà§à¦ªà¦¾à§°à¦¿à¦› কৰা হয়</translation>
+<translation id="702463548815491781">টকবেক বা ছà§à¦‡à¦šà§° দà§à¦¬à¦¾à§°à¦¾ à¦à¦•à§à¦¸à§‡à¦› কৰাৰ সà§à¦¬à¦¿à¦§à¦¾ অন থাকিলে à¦à§Ÿà¦¾ চà§à¦ªà¦¾à§°à¦¿à¦› কৰা হয়</translation>
<translation id="7053983685419859001">অৱৰোধ কৰক</translation>
<translation id="7066151586745993502">{NUM_SELECTED,plural, =1{১টা বসà§à¦¤à§ বাছনি কৰা হ’ল}one{#টা বসà§à¦¤à§ বাছনি কৰা হ’ল}other{#টা বসà§à¦¤à§ বাছনি কৰা হ’ল}}</translation>
<translation id="7087918508125750058"><ph name="ITEM_COUNT" />টা বাছনি কৰা হ’ল। সà§à¦•à§à§°à§€à¦£à§° ওপৰৰ অংশত কাষত থকা বিকলà§à¦ªà¦¸à¦®à§‚হ</translation>
diff --git a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_mr.xtb b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_mr.xtb
index 46c7d9fe653..9085935b7b0 100644
--- a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_mr.xtb
+++ b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_mr.xtb
@@ -130,7 +130,7 @@
<translation id="4194328954146351878">साइटना NFC डिवà¥à¤¹à¤¾à¤‡à¤¸à¤µà¤°à¥€à¤² माहिती पाहू देणà¥à¤¯à¤¾à¤šà¥€ किंवा बदलणà¥à¤¯à¤¾à¤šà¥€ अनà¥à¤®à¤¤à¥€ देणà¥à¤¯à¤¾à¤ªà¥‚रà¥à¤µà¥€ विचारा (शिफारस केलेले)</translation>
<translation id="4200726100658658164">सà¥à¤¥à¤¾à¤¨ सेटिंगà¥à¤œ उघडा</translation>
<translation id="4226663524361240545">सूचनांमà¥à¤³à¥‡ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¥‡ कंपन होऊ शकते</translation>
-<translation id="4259722352634471385">नेवà¥à¤¹à¤¿à¤—ेशन अवरोधित केले आहे: <ph name="URL" /></translation>
+<translation id="4259722352634471385">नेवà¥à¤¹à¤¿à¤—ेशन बà¥à¤²à¥‰à¤• केले आहे: <ph name="URL" /></translation>
<translation id="4278390842282768270">अनà¥à¤®à¤¤</translation>
<translation id="429312253194641664">साइट मीडिया पà¥à¤²à¥‡ करत आहे</translation>
<translation id="42981349822642051">विसà¥à¤¤à¥ƒà¤¤ करा</translation>
@@ -191,7 +191,7 @@
<translation id="5596627076506792578">अधिक परà¥à¤¯à¤¾à¤¯</translation>
<translation id="5649053991847567735">सà¥à¤µà¤¯à¤‚चलित डाउनलोड</translation>
<translation id="5668404140385795438">à¤à¥‚म वाढविणे निरà¥à¤¬à¤‚धित करणà¥à¤¯à¤¾à¤šà¥€ वेबसाइटची विनंती ओवà¥à¤¹à¤°à¤°à¤¾à¤‡à¤¡ करा</translation>
-<translation id="5677928146339483299">अवरोधित</translation>
+<translation id="5677928146339483299">बà¥à¤²à¥‰à¤• केले</translation>
<translation id="5689516760719285838">सà¥à¤¥à¤¾à¤¨</translation>
<translation id="5690795753582697420">Android सेटिंगà¥à¤œà¤®à¤§à¥à¤¯à¥‡ कॅमेरा बंद केला आहे</translation>
<translation id="5719847187258001597">हे <ph name="ORIGIN" /> किंवा तà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ अâ€à¥…पà¥à¤¸à¤¨à¥€ तà¥à¤®à¤šà¥à¤¯à¤¾ होम सà¥à¤•à¥à¤°à¥€à¤¨à¤µà¤° सà¥à¤Ÿà¥‹à¤…र केलेला सरà¥à¤µ डेटा आणि कà¥à¤•à¥€ साफ करेल.</translation>
@@ -210,11 +210,11 @@
<translation id="6042308850641462728">अधिक</translation>
<translation id="6064125863973209585">डाउनलोड पूरà¥à¤£ à¤à¤¾à¤²à¥‡</translation>
<translation id="6165508094623778733">अधिक जाणून घà¥à¤¯à¤¾</translation>
-<translation id="6177111841848151710">वरà¥à¤¤à¤®à¤¾à¤¨ शोध इंजिनसाठी अवरोधित केले</translation>
+<translation id="6177111841848151710">सधà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ शोध इंजीनसाठी बà¥à¤²à¥‰à¤• केले</translation>
<translation id="6177128806592000436">या साइटवरील तà¥à¤®à¤šà¥‡ कनेकà¥à¤¶à¤¨ सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ नाही</translation>
<translation id="6181444274883918285">साइट à¤à¤•à¥à¤¸à¥‡à¤ªà¥à¤¶à¤¨ जोडा</translation>
<translation id="6192792657125177640">अपवाद</translation>
-<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ अवरोधित करा</translation>
+<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ बà¥à¤²à¥‰à¤• करा</translation>
<translation id="6216432067784365534"><ph name="NAME_OF_LIST_ITEM" /> परà¥à¤¯à¤¾à¤¯</translation>
<translation id="6231752747840485235">'<ph name="APP_NAME" />' अनइंसà¥à¤Ÿà¥‰à¤² करायचे आहे का?</translation>
<translation id="6262279340360821358"><ph name="PERMISSION_1" /> आणि <ph name="PERMISSION_2" /> ला बà¥à¤²à¥‰à¤• केले</translation>
@@ -252,7 +252,7 @@
<translation id="7000754031042624318">Android सेटिंगà¥à¤œà¤®à¤§à¥â€à¤¯à¥‡ बंद केले</translation>
<translation id="7016516562562142042">वरà¥à¤¤à¤®à¤¾à¤¨ शोध इंजिनसाठी अनà¥à¤®à¤¤à¥€ दिली</translation>
<translation id="702463548815491781">TalkBack किंवा सà¥à¤µà¤¿à¤š ॲकà¥à¤¸à¥‡à¤¸ सà¥à¤°à¥‚ असताना शिफारस केलेले</translation>
-<translation id="7053983685419859001">अवरोधित करा</translation>
+<translation id="7053983685419859001">बà¥à¤²à¥‰à¤• करा</translation>
<translation id="7066151586745993502">{NUM_SELECTED,plural, =1{1 निवडला}other{# निवडले}}</translation>
<translation id="7087918508125750058"><ph name="ITEM_COUNT" /> निवडले. सà¥à¤•à¥à¤°à¥€à¤¨à¤šà¥à¤¯à¤¾ वरचà¥à¤¯à¤¾ बाजूला परà¥à¤¯à¤¾à¤¯ उपलबà¥à¤§ आहेत</translation>
<translation id="7141896414559753902">साइट पॉप-अप आणि रीडिरेकà¥à¤Ÿ दाखवणà¥à¤¯à¤¾à¤ªà¤¾à¤¸à¥‚न बà¥à¤²à¥‰à¤• करा (शिफारस केलेले)</translation>
diff --git a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_pt-BR.xtb b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_pt-BR.xtb
index a733017317a..234c73e4990 100644
--- a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_pt-BR.xtb
+++ b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_pt-BR.xtb
@@ -77,7 +77,7 @@
<translation id="2687403674020088961">Bloquear todos os cookies (não recomendado)</translation>
<translation id="2704606927547763573">Copiado</translation>
<translation id="2717722538473713889">Endereços de e-mail</translation>
-<translation id="2750481671343847896">Sites podem exibir solicitações de login de serviços de identidade.</translation>
+<translation id="2750481671343847896">Sites podem exibir solicitações de login de serviços de identificação.</translation>
<translation id="2785051990912111074">Essa ação vai apagar os cookies de <ph name="WEBSITE" /></translation>
<translation id="2822354292072154809">Tem certeza de que quer redefinir todas as permissões de site para <ph name="CHOSEN_OBJECT_NAME" />?</translation>
<translation id="2870560284913253234">Site</translation>
@@ -237,7 +237,7 @@
<translation id="6643016212128521049">Limpar</translation>
<translation id="6689172468748959065">Fotos do perfil</translation>
<translation id="6697925417670533197">Downloads ativos</translation>
-<translation id="6722828510648505498">Bloquear solicitações de login de serviços de identidade.</translation>
+<translation id="6722828510648505498">Bloquear solicitações de login de serviços de identificação.</translation>
<translation id="6746124502594467657">Mover para baixo</translation>
<translation id="6749077623962119521">Redefinir permissões?</translation>
<translation id="6766622839693428701">Deslize para baixo para fechar.</translation>
diff --git a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr-Latn.xtb b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr-Latn.xtb
index 75b507a70d9..e77ddc6dcd8 100644
--- a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr-Latn.xtb
+++ b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr-Latn.xtb
@@ -54,7 +54,7 @@
<translation id="2289270750774289114">Pitaj kada sajt želi da otkrije Bluetooth ureÄ‘aje u blizini (preporuÄeno)</translation>
<translation id="2315043854645842844">Operativni sistem ne podržava izbor sertifikata za klijenta.</translation>
<translation id="2321958826496381788">PrevlaÄite klizaÄ dok ovo ne budete mogli lako da proÄitate. Kada dvaput dodirnete pasus, tekst treba da bude bar ovoliki.</translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="2379925928934107488">Kada je to mogucÌe, tamna tema se primenjuje za sajtove kada je Chrome koristi</translation>
<translation id="2387895666653383613">Promena veliÄine teksta</translation>
<translation id="2402980924095424747"><ph name="MEGABYTES" /> MB</translation>
diff --git a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr.xtb b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr.xtb
index 930ba8f4245..fecc28d4489 100644
--- a/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr.xtb
+++ b/chromium/components/browser_ui/strings/android/translations/browser_ui_strings_sr.xtb
@@ -54,7 +54,7 @@
<translation id="2289270750774289114">Питај када Ñајт жели да открије Bluetooth уређаје у близини (препоручено)</translation>
<translation id="2315043854645842844">Оперативни ÑиÑтем не подржава избор Ñертификата за клијента.</translation>
<translation id="2321958826496381788">Превлачите клизач док ово не будете могли лако да прочитате. Када двапут додирнете паÑуÑ, текÑÑ‚ треба да буде бар оволики.</translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="2379925928934107488">Када је то могуће, тамна тема Ñе примењује за Ñајтове када је Chrome кориÑти</translation>
<translation id="2387895666653383613">Промена величине текÑта</translation>
<translation id="2402980924095424747"><ph name="MEGABYTES" /> MB</translation>
diff --git a/chromium/components/optimization_guide/core/optimization_guide_constants.cc b/chromium/components/optimization_guide/core/optimization_guide_constants.cc
index c6b1318da25..845f2d5e7e5 100644
--- a/chromium/components/optimization_guide/core/optimization_guide_constants.cc
+++ b/chromium/components/optimization_guide/core/optimization_guide_constants.cc
@@ -24,8 +24,11 @@ const base::FilePath::CharType kOptimizationGuideHintStore[] =
FILE_PATH_LITERAL("optimization_guide_hint_cache_store");
const base::FilePath::CharType
- kOptimizationGuidePredictionModelAndFeaturesStore[] =
- FILE_PATH_LITERAL("optimization_guide_model_and_features_store");
+ kOptimizationGuidePredictionModelMetadataStore[] =
+ FILE_PATH_LITERAL("optimization_guide_model_metadata_store");
+
+const base::FilePath::CharType kOptimizationGuidePredictionModelDownloads[] =
+ FILE_PATH_LITERAL("optimization_guide_prediction_model_downloads");
const base::FilePath::CharType kPageEntitiesMetadataStore[] =
FILE_PATH_LITERAL("page_content_annotations_page_entities_metadata_store");
diff --git a/chromium/components/optimization_guide/core/optimization_guide_constants.h b/chromium/components/optimization_guide/core/optimization_guide_constants.h
index d095761b879..916c18d1f6c 100644
--- a/chromium/components/optimization_guide/core/optimization_guide_constants.h
+++ b/chromium/components/optimization_guide/core/optimization_guide_constants.h
@@ -28,10 +28,15 @@ extern const char kLoadedHintLocalHistogramString[];
// The folder where the hint data will be stored on disk.
extern const base::FilePath::CharType kOptimizationGuideHintStore[];
-// The folder where the prediction model and host model features data will be
-// stored on disk.
+// The folder where the prediction model and associated metadata are
+// currently stored on disk. This is per profile.
extern const base::FilePath::CharType
- kOptimizationGuidePredictionModelAndFeaturesStore[];
+ kOptimizationGuidePredictionModelMetadataStore[];
+
+// The folder where the prediction model downloads are stored. This is per
+// profile.
+extern const base::FilePath::CharType
+ kOptimizationGuidePredictionModelDownloads[];
// The folder where the page entities metadata store will be stored on disk.
extern const base::FilePath::CharType kPageEntitiesMetadataStore[];
diff --git a/chromium/components/permissions/android/translations/permissions_android_strings_sr-Latn.xtb b/chromium/components/permissions/android/translations/permissions_android_strings_sr-Latn.xtb
index 410358954d8..d43519ad219 100644
--- a/chromium/components/permissions/android/translations/permissions_android_strings_sr-Latn.xtb
+++ b/chromium/components/permissions/android/translations/permissions_android_strings_sr-Latn.xtb
@@ -7,7 +7,7 @@
<translation id="1993768208584545658"><ph name="SITE" /> želi da se upari</translation>
<translation id="2077832278056815832">Zatvorite oblaÄicÌe ili preklopne elemente iz drugih aplikacija. Zatim probajte ponovo.</translation>
<translation id="230115972905494466">Nije pronađen nijedan kompatibilan uređaj</translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="2987449669841041897">Ovaj sajt ne može da vam traži dozvolu</translation>
<translation id="3036750288708366620"><ph name="BEGIN_LINK" />Potražite pomocÌ<ph name="END_LINK" /></translation>
<translation id="3773755127849930740"><ph name="BEGIN_LINK" />UkljuÄite Bluetooth<ph name="END_LINK" /> da biste omogucÌili uparivanje</translation>
diff --git a/chromium/components/permissions/android/translations/permissions_android_strings_sr.xtb b/chromium/components/permissions/android/translations/permissions_android_strings_sr.xtb
index 893467cabb5..45eb0879b5f 100644
--- a/chromium/components/permissions/android/translations/permissions_android_strings_sr.xtb
+++ b/chromium/components/permissions/android/translations/permissions_android_strings_sr.xtb
@@ -7,7 +7,7 @@
<translation id="1993768208584545658"><ph name="SITE" /> жели да Ñе упари</translation>
<translation id="2077832278056815832">Затворите облачиће или преклопне елементе из других апликација. Затим пробајте поново.</translation>
<translation id="230115972905494466">Ðије пронађен ниједан компатибилан уређај</translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="2987449669841041897">Овај Ñајт не може да вам тражи дозволу</translation>
<translation id="3036750288708366620"><ph name="BEGIN_LINK" />Потражите помоћ<ph name="END_LINK" /></translation>
<translation id="3773755127849930740"><ph name="BEGIN_LINK" />Укључите Bluetooth<ph name="END_LINK" /> да биÑте омогућили упаривање</translation>
diff --git a/chromium/components/policy/COMMON_METADATA b/chromium/components/policy/COMMON_METADATA
new file mode 100644
index 00000000000..04f18043fb9
--- /dev/null
+++ b/chromium/components/policy/COMMON_METADATA
@@ -0,0 +1,5 @@
+monorail {
+ component: "Enterprise"
+}
+
+team_email: "chromium-enterprise@chromium.org"
diff --git a/chromium/components/policy/DIR_METADATA b/chromium/components/policy/DIR_METADATA
new file mode 100644
index 00000000000..b9feba2750b
--- /dev/null
+++ b/chromium/components/policy/DIR_METADATA
@@ -0,0 +1,2 @@
+mixins: "//components/policy/COMMON_METADATA"
+
diff --git a/chromium/components/policy/ENTERPRISE_POLICY_OWNERS b/chromium/components/policy/ENTERPRISE_POLICY_OWNERS
new file mode 100644
index 00000000000..f85292d2051
--- /dev/null
+++ b/chromium/components/policy/ENTERPRISE_POLICY_OWNERS
@@ -0,0 +1,14 @@
+# Prefer enterprise-policy-review@ to individual owners. Sending code
+# reviews to the alias allows the owners to distribute code review loads.
+enterprise-policy-review@google.com
+
+# When making changes, also update EnterprisePolicyOwners in the GwsQ config:
+# http://google3/chrome/enterprise/gwsq/enterprise-policy-review.gwsq
+anqing@chromium.org
+emaxx@chromium.org
+hendrich@chromium.org
+pastarmovj@chromium.org
+pmarko@chromium.org
+poromov@chromium.org
+rsorokin@chromium.org
+zmin@chromium.org
diff --git a/chromium/components/policy/OWNERS b/chromium/components/policy/OWNERS
new file mode 100644
index 00000000000..5945fa7b466
--- /dev/null
+++ b/chromium/components/policy/OWNERS
@@ -0,0 +1,13 @@
+# Prefer enterprise-policy-review@ to individual owners. Sending code
+# reviews to the alias allows the owners to distribute code review loads.
+enterprise-policy-review@google.com
+
+# When making changes, also update EnterprisePolicyOwners in the GwsQ config:
+# http://google3/chrome/enterprise/gwsq/enterprise-policy-review.gwsq
+enterprise-policy-review@google.com
+
+file://components/policy/ENTERPRISE_POLICY_OWNERS
+
+ftirelo@chromium.org
+nicolaso@chromium.org
+ydago@chromium.org
diff --git a/chromium/components/policy/android/DEPS b/chromium/components/policy/android/DEPS
new file mode 100644
index 00000000000..877e2a4da34
--- /dev/null
+++ b/chromium/components/policy/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content/public/test/android",
+] \ No newline at end of file
diff --git a/chromium/components/policy/android/java_templates/PolicySwitches.java.tmpl b/chromium/components/policy/android/java_templates/PolicySwitches.java.tmpl
new file mode 100644
index 00000000000..621e6f618c3
--- /dev/null
+++ b/chromium/components/policy/android/java_templates/PolicySwitches.java.tmpl
@@ -0,0 +1,16 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+/**
+ * Contains command line switches that are specific to the policy component.
+ */
+public final class PolicySwitches {{
+
+{NATIVE_STRINGS}
+
+ // Prevents instantiation.
+ private PolicySwitches() {{}}
+}}
diff --git a/chromium/components/policy/android/junit/src/org/chromium/components/policy/AbstractAppRestrictionsProviderTest.java b/chromium/components/policy/android/junit/src/org/chromium/components/policy/AbstractAppRestrictionsProviderTest.java
new file mode 100644
index 00000000000..1317d6d2ef9
--- /dev/null
+++ b/chromium/components/policy/android/junit/src/org/chromium/components/policy/AbstractAppRestrictionsProviderTest.java
@@ -0,0 +1,211 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Robolectric test for AbstractAppRestrictionsProvider.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@LooperMode(LooperMode.Mode.LEGACY)
+public class AbstractAppRestrictionsProviderTest {
+ /**
+ * Minimal concrete class implementing AbstractAppRestrictionsProvider.
+ */
+ private class DummyAppRestrictionsProvider extends AbstractAppRestrictionsProvider {
+ public DummyAppRestrictionsProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected Bundle getApplicationRestrictions(String packageName) {
+ return null;
+ }
+
+ @Override
+ protected String getRestrictionChangeIntentAction() {
+ return null;
+ }
+ }
+
+ private class DummyContext extends ContextWrapper {
+ public DummyContext(Context baseContext) {
+ super(baseContext);
+ mReceiverCount = new AtomicInteger(0);
+ mLastRegisteredReceiverFlags = new AtomicInteger(0);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler, int flags) {
+ Intent intent =
+ super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags);
+ mReceiverCount.getAndIncrement();
+ mLastRegisteredReceiverFlags.set(flags);
+ return intent;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ Intent intent = super.registerReceiver(receiver, filter);
+ mReceiverCount.getAndIncrement();
+ return intent;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ Intent intent = super.registerReceiver(receiver, filter, flags);
+ mReceiverCount.getAndIncrement();
+ mLastRegisteredReceiverFlags.set(flags);
+ return intent;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ Intent intent =
+ super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+ mReceiverCount.getAndIncrement();
+ return intent;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ // Not to unregisterReceiver in Android o+, otherwise roboletric throws exception
+ // because it doesn't override registerReceiver() with flag paramenter.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ super.unregisterReceiver(receiver);
+ }
+ mReceiverCount.getAndDecrement();
+ }
+
+ public int getReceiverCount() {
+ return mReceiverCount.get();
+ }
+
+ public int getLastRegisteredReceiverFlags() {
+ return mLastRegisteredReceiverFlags.get();
+ }
+
+ private AtomicInteger mReceiverCount;
+ private AtomicInteger mLastRegisteredReceiverFlags;
+ }
+
+ /**
+ * Test method for {@link AbstractAppRestrictionsProvider#refresh()}.
+ */
+ @Test
+ public void testRefresh() {
+ // We want to control precisely when background tasks run
+ Robolectric.getBackgroundThreadScheduler().pause();
+
+ Context context = RuntimeEnvironment.application;
+
+ // Clear the preferences
+ ContextUtils.getAppSharedPreferences().edit().clear();
+
+ // Set up a bundle for testing.
+ Bundle b1 = new Bundle();
+ b1.putString("Key1", "value1");
+ b1.putInt("Key2", 42);
+
+ // Mock out the histogram functions, since they call statics.
+ AbstractAppRestrictionsProvider provider = spy(new DummyAppRestrictionsProvider(context));
+
+ // Set up the buffer to be returned by getApplicationRestrictions.
+ when(provider.getApplicationRestrictions(anyString())).thenReturn(b1);
+
+ // Prepare the provider
+ CombinedPolicyProvider combinedProvider = mock(CombinedPolicyProvider.class);
+ provider.setManagerAndSource(combinedProvider, 0);
+
+ provider.refresh();
+ verify(provider).getApplicationRestrictions(anyString());
+ verify(combinedProvider).onSettingsAvailable(0, b1);
+ }
+
+ /**
+ * Test method for {@link AbstractAppRestrictionsProvider#startListeningForPolicyChanges()}.
+ */
+ @Test
+ public void testStartListeningForPolicyChanges() {
+ DummyContext dummyContext = new DummyContext(ApplicationProvider.getApplicationContext());
+ AbstractAppRestrictionsProvider provider =
+ spy(new DummyAppRestrictionsProvider(dummyContext));
+ Intent intent = new Intent("org.chromium.test.policy.Hello");
+
+ // If getRestrictionsChangeIntentAction returns null then we should not start a broadcast
+ // receiver.
+ provider.startListeningForPolicyChanges();
+ Assert.assertEquals(0, dummyContext.getReceiverCount());
+
+ // If it returns a string then we should.
+ when(provider.getRestrictionChangeIntentAction())
+ .thenReturn("org.chromium.test.policy.Hello");
+ provider.startListeningForPolicyChanges();
+ Assert.assertEquals(1, dummyContext.getReceiverCount());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Assert.assertEquals(ContextUtils.RECEIVER_NOT_EXPORTED,
+ dummyContext.getLastRegisteredReceiverFlags());
+ }
+ }
+
+ /**
+ * Test method for {@link AbstractAppRestrictionsProvider#stopListening()}.
+ */
+ @Test
+ public void testStopListening() {
+ DummyContext dummyContext = new DummyContext(ApplicationProvider.getApplicationContext());
+ AbstractAppRestrictionsProvider provider =
+ spy(new DummyAppRestrictionsProvider(dummyContext));
+ Intent intent = new Intent("org.chromium.test.policy.Hello");
+
+ // First try with null result from getRestrictionsChangeIntentAction, only test here is no
+ // crash.
+ provider.stopListening();
+
+ // Now try starting and stopping listening properly.
+ when(provider.getRestrictionChangeIntentAction())
+ .thenReturn("org.chromium.test.policy.Hello");
+ provider.startListeningForPolicyChanges();
+ Assert.assertEquals(1, dummyContext.getReceiverCount());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Assert.assertEquals(ContextUtils.RECEIVER_NOT_EXPORTED,
+ dummyContext.getLastRegisteredReceiverFlags());
+ }
+ provider.stopListening();
+ Assert.assertEquals(0, dummyContext.getReceiverCount());
+ }
+}
diff --git a/chromium/components/policy/android/junit/src/org/chromium/components/policy/CombinedPolicyProviderTest.java b/chromium/components/policy/android/junit/src/org/chromium/components/policy/CombinedPolicyProviderTest.java
new file mode 100644
index 00000000000..a899751e577
--- /dev/null
+++ b/chromium/components/policy/android/junit/src/org/chromium/components/policy/CombinedPolicyProviderTest.java
@@ -0,0 +1,222 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+
+/**
+ * Robolectric tests for CombinedPolicyProvider
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class CombinedPolicyProviderTest {
+ private static final int NATIVE_POINTER = 1234;
+
+ @Rule
+ public JniMocker mocker = new JniMocker();
+ @Mock
+ private PolicyConverter mPolicyConverter;
+ @Mock
+ private CombinedPolicyProvider.Natives mCombinedPolicyConverterJniMock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mocker.mock(CombinedPolicyProviderJni.TEST_HOOKS, mCombinedPolicyConverterJniMock);
+ CombinedPolicyProvider.setForTesting(new CombinedPolicyProvider());
+ }
+
+ /**
+ * Dummy concrete class. Needed because PolicyProvider has final functions that cannot be
+ * stubbed and is abstract so can't be directly instantiated to be spied upon.
+ */
+ class DummyPolicyProvider extends PolicyProvider {
+ public DummyPolicyProvider() {}
+
+ @Override
+ public void refresh() {
+ // Do nothing
+ }
+ }
+
+ @Test
+ public void testRegisterProvider() {
+ // Have to spy not mock here so that the real constructor gets called, hence avoiding
+ // an assert in PolicyProvider.setManagerAndSource.
+ PolicyProvider provider = spy(new DummyPolicyProvider());
+ CombinedPolicyProvider.get().registerProvider(provider);
+ // Can't verify PolicyProvider.setManagerAndSource directly because it is final.
+ // This, at least, demonstrates that it has been called.
+ verify(provider).startListeningForPolicyChanges();
+ verify(provider, never()).refresh();
+ assertEquals(CombinedPolicyProvider.get(),
+ CombinedPolicyProvider.linkNative(NATIVE_POINTER, mPolicyConverter));
+ verify(provider).refresh();
+ PolicyProvider provider2 = spy(new DummyPolicyProvider());
+ CombinedPolicyProvider.get().registerProvider(provider2);
+ verify(provider2).startListeningForPolicyChanges();
+ verify(provider2).refresh();
+ }
+
+ @Test
+ public void testOnSettingsAvailable_noNative() {
+ // No native policy manager
+ PolicyProvider provider = new DummyPolicyProvider();
+ CombinedPolicyProvider.get().registerProvider(provider);
+ Bundle b = new Bundle();
+ b.putBoolean("BoolPolicy", true);
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+ verify(mPolicyConverter, never()).setPolicy(anyString(), any());
+ verify(mCombinedPolicyConverterJniMock, never()).flushPolicies(anyInt(), any());
+ }
+
+ @Test
+ public void testOnSettingsAvailable_oneProvider() {
+ CombinedPolicyProvider.linkNative(NATIVE_POINTER, mPolicyConverter);
+ verify(mCombinedPolicyConverterJniMock)
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ PolicyProvider provider = new DummyPolicyProvider();
+ CombinedPolicyProvider.get().registerProvider(provider);
+ Bundle b = new Bundle();
+ b.putBoolean("BoolPolicy", false);
+ b.putInt("IntPolicy", 42);
+ b.putString("StringPolicy", "A string");
+ b.putStringArray("StringArrayPolicy", new String[] {"String1", "String2"});
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+ verify(mPolicyConverter).setPolicy("BoolPolicy", false);
+ verify(mPolicyConverter).setPolicy("IntPolicy", 42);
+ verify(mPolicyConverter).setPolicy("StringPolicy", "A string");
+ verify(mPolicyConverter)
+ .setPolicy("StringArrayPolicy", new String[] {"String1", "String2"});
+ verify(mCombinedPolicyConverterJniMock, times(2))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ }
+
+ @Test
+ public void testOnSettingsAvailable_secondProvider() {
+ CombinedPolicyProvider.linkNative(NATIVE_POINTER, mPolicyConverter);
+ verify(mCombinedPolicyConverterJniMock)
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ PolicyProvider provider = new DummyPolicyProvider();
+ CombinedPolicyProvider.get().registerProvider(provider);
+ Bundle b = new Bundle();
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+ verify(mCombinedPolicyConverterJniMock, times(2))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+
+ // Second policy provider registered but no settings.
+ PolicyProvider provider2 = new DummyPolicyProvider();
+ CombinedPolicyProvider.get().registerProvider(provider2);
+ b = new Bundle();
+ b.putBoolean("BoolPolicy", true);
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+
+ // Second call should have been ignored, so nothing should have been set
+ verify(mPolicyConverter, never()).setPolicy(anyString(), anyBoolean());
+ // and flush should have been called precisely once.
+ verify(mCombinedPolicyConverterJniMock, times(2))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+
+ // Empty but valid bundle from second policy provider should set the policy and push it
+ // to the native code
+ b = new Bundle();
+ CombinedPolicyProvider.get().onSettingsAvailable(1, b);
+ verify(mPolicyConverter).setPolicy("BoolPolicy", true);
+ verify(mCombinedPolicyConverterJniMock, times(3))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ }
+
+ @Test
+ public void testCachePolicy() {
+ CombinedPolicyProvider.linkNative(NATIVE_POINTER, mPolicyConverter);
+ Assert.assertEquals(0, CombinedPolicyProvider.get().getPolicyProvidersForTesting().size());
+ Assert.assertTrue(CombinedPolicyProvider.get().isPolicyCacheEnabled());
+
+ CombinedPolicyProvider.get().registerProvider(new DummyPolicyProvider());
+ Assert.assertEquals(1, CombinedPolicyProvider.get().getPolicyProvidersForTesting().size());
+ Assert.assertFalse(CombinedPolicyProvider.get().isPolicyCacheEnabled());
+
+ CombinedPolicyProvider.get().registerProvider(new DummyPolicyProvider());
+ Assert.assertEquals(2, CombinedPolicyProvider.get().getPolicyProvidersForTesting().size());
+ Assert.assertFalse(CombinedPolicyProvider.get().isPolicyCacheEnabled());
+ }
+
+ @Test
+ public void testRefreshPolicies() {
+ CombinedPolicyProvider.linkNative(NATIVE_POINTER, mPolicyConverter);
+ verify(mCombinedPolicyConverterJniMock)
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ PolicyProvider provider = new DummyPolicyProvider();
+ PolicyProvider provider2 = new DummyPolicyProvider();
+ CombinedPolicyProvider.get().registerProvider(provider);
+ CombinedPolicyProvider.get().registerProvider(provider2);
+ Bundle b = new Bundle();
+ b.putBoolean("BoolPolicy", true);
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+ CombinedPolicyProvider.get().onSettingsAvailable(1, b);
+ verify(mCombinedPolicyConverterJniMock, times(2))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+
+ CombinedPolicyProvider.get().refreshPolicies();
+ // This should have cleared the cached policies, so onSettingsAvailable should now do
+ // nothing until both providers have settings.
+ CombinedPolicyProvider.get().onSettingsAvailable(0, b);
+ // Still only one call.
+ verify(mCombinedPolicyConverterJniMock, times(2))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ b = new Bundle();
+ b.putBoolean("BoolPolicy", false);
+ CombinedPolicyProvider.get().onSettingsAvailable(1, b);
+ // That should have caused the second flush.
+ verify(mCombinedPolicyConverterJniMock, times(3))
+ .flushPolicies(NATIVE_POINTER, CombinedPolicyProvider.get());
+ // And the policy should have been set to the new value.
+ verify(mPolicyConverter).setPolicy("BoolPolicy", false);
+ }
+
+ @Test
+ public void testTerminateIncognitoSession() {
+ CombinedPolicyProvider.PolicyChangeListener l =
+ mock(CombinedPolicyProvider.PolicyChangeListener.class);
+ CombinedPolicyProvider.get().addPolicyChangeListener(l);
+ CombinedPolicyProvider.get().terminateIncognitoSession();
+ verify(l).terminateIncognitoSession();
+ CombinedPolicyProvider.get().removePolicyChangeListener(l);
+ CombinedPolicyProvider.get().terminateIncognitoSession();
+ // Should still have only called the listener once
+ verify(l).terminateIncognitoSession();
+ }
+
+ @Test
+ public void testDestroy() {
+ PolicyProvider provider = spy(new DummyPolicyProvider());
+ CombinedPolicyProvider.get().registerProvider(provider);
+ CombinedPolicyProvider.get().destroy();
+ verify(provider).destroy();
+ }
+}
diff --git a/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheProviderTest.java b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheProviderTest.java
new file mode 100644
index 00000000000..0f454692425
--- /dev/null
+++ b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheProviderTest.java
@@ -0,0 +1,70 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Robolectric test for PolicyCacheProvider. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class PolicyCacheProviderTest {
+ private static final String POLICY_NAME_1 = "policy-name-1";
+ private static final String POLICY_NAME_2 = "policy-name-2";
+ private static final String POLICY_NAME_3 = "policy-name-3";
+ private static final String POLICY_NAME_4 = "policy-name-4";
+
+ private static final int INT_POLICY = 42;
+ private static final boolean BOOLEAN_POLICY = true;
+ private static final String STRING_POLICY = "policy-value";
+ private static final String DICT_POLICY = "{\"test\" : 3}";
+
+ private static final int SOURCE = 0;
+
+ @Mock
+ private CombinedPolicyProvider mCombinedPolicyProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testPolicyRefresh() {
+ ContextUtils.getApplicationContext()
+ .getSharedPreferences(PolicyCache.POLICY_PREF, Context.MODE_PRIVATE)
+ .edit()
+ .putInt(POLICY_NAME_1, INT_POLICY)
+ .putBoolean(POLICY_NAME_2, BOOLEAN_POLICY)
+ .putString(POLICY_NAME_3, STRING_POLICY)
+ .putString(POLICY_NAME_4, DICT_POLICY)
+ .apply();
+
+ PolicyCacheProvider provider = new PolicyCacheProvider();
+ provider.setManagerAndSource(mCombinedPolicyProvider, SOURCE);
+
+ provider.refresh();
+
+ verify(mCombinedPolicyProvider).onSettingsAvailable(eq(SOURCE), argThat(bundle -> {
+ return bundle.size() == 4 && bundle.getInt(POLICY_NAME_1) == INT_POLICY
+ && bundle.getBoolean(POLICY_NAME_2) == BOOLEAN_POLICY
+ && STRING_POLICY.equals(bundle.getString(POLICY_NAME_3))
+ && DICT_POLICY.equals(bundle.getString(POLICY_NAME_4));
+ }));
+ }
+}
diff --git a/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java
new file mode 100644
index 00000000000..d45179717c1
--- /dev/null
+++ b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java
@@ -0,0 +1,268 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Pair;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.CollectionUtil;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.build.BuildConfig;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** Robolectric test for PolicyCache. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public final class PolicyCacheTest {
+ private static final String POLICY_NAME = "policy-name";
+ private static final String POLICY_NAME_2 = "policy-name-2";
+ private static final String POLICY_NAME_3 = "policy-name-3";
+ private static final String POLICY_NAME_4 = "policy-name-4";
+ private static final String POLICY_NAME_5 = "policy-name-5";
+
+ private SharedPreferences mSharedPreferences;
+
+ private PolicyCache mPolicyCache;
+
+ @Mock
+ private PolicyMap mPolicyMap;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ PolicyCache.resetForTesting();
+ mPolicyCache = PolicyCache.get();
+ mSharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
+ PolicyCache.POLICY_PREF, Context.MODE_PRIVATE);
+
+ initPolicyMap();
+ }
+
+ private void initPolicyMap() {
+ when(mPolicyMap.getIntValue(anyString())).thenReturn(null);
+ when(mPolicyMap.getBooleanValue(anyString())).thenReturn(null);
+ when(mPolicyMap.getStringValue(anyString())).thenReturn(null);
+ when(mPolicyMap.getListValueAsString(anyString())).thenReturn(null);
+ when(mPolicyMap.getDictValueAsString(anyString())).thenReturn(null);
+ }
+
+ @After
+ public void tearDown() {}
+
+ @Test
+ public void testGetInt() {
+ Assert.assertNull(mPolicyCache.getIntValue(POLICY_NAME));
+ int expectedPolicyValue = 42;
+ mSharedPreferences.edit().putInt(POLICY_NAME, expectedPolicyValue).apply();
+ Assert.assertEquals(expectedPolicyValue, mPolicyCache.getIntValue(POLICY_NAME).intValue());
+ }
+
+ @Test
+ public void testGetBoolean() {
+ Assert.assertNull(mPolicyCache.getBooleanValue(POLICY_NAME));
+ boolean expectedPolicyValue = true;
+ mSharedPreferences.edit().putBoolean(POLICY_NAME, expectedPolicyValue).apply();
+ Assert.assertEquals(
+ expectedPolicyValue, mPolicyCache.getBooleanValue(POLICY_NAME).booleanValue());
+ }
+
+ @Test
+ public void testGetString() {
+ Assert.assertNull(mPolicyCache.getStringValue(POLICY_NAME));
+ String expectedPolicyValue = "test-value";
+ mSharedPreferences.edit().putString(POLICY_NAME, expectedPolicyValue).apply();
+ Assert.assertEquals(expectedPolicyValue, mPolicyCache.getStringValue(POLICY_NAME));
+ }
+
+ @Test
+ public void testGetList() throws JSONException {
+ Assert.assertNull(mPolicyCache.getListValue(POLICY_NAME));
+ String policyValue = "[42, \"test\", true]";
+ mSharedPreferences.edit().putString(POLICY_NAME, policyValue).apply();
+ JSONArray actualPolicyValue = mPolicyCache.getListValue(POLICY_NAME);
+ Assert.assertNotNull(actualPolicyValue);
+ Assert.assertEquals(3, actualPolicyValue.length());
+ Assert.assertEquals(42, actualPolicyValue.getInt(0));
+ Assert.assertEquals("test", actualPolicyValue.getString(1));
+ Assert.assertEquals(true, actualPolicyValue.getBoolean(2));
+ }
+
+ @Test
+ public void testGetInvalidList() throws JSONException {
+ String policyValue = "[42, \"test\"";
+ mSharedPreferences.edit().putString(POLICY_NAME, policyValue).apply();
+ Assert.assertNull(mPolicyCache.getListValue(POLICY_NAME));
+ }
+
+ @Test
+ public void testGetDict() throws JSONException {
+ Assert.assertNull(mPolicyCache.getDictValue(POLICY_NAME));
+ String policyValue = "{\"key1\":\"value1\", \"key2\":{\"a\":1, \"b\":2}}";
+ mSharedPreferences.edit().putString(POLICY_NAME, policyValue).apply();
+ JSONObject actualPolicyValue = mPolicyCache.getDictValue(POLICY_NAME);
+ Assert.assertNotNull(actualPolicyValue);
+ Assert.assertEquals(2, actualPolicyValue.length());
+ Assert.assertEquals("value1", actualPolicyValue.getString("key1"));
+ Assert.assertEquals(1, actualPolicyValue.getJSONObject("key2").getInt("a"));
+ Assert.assertEquals(2, actualPolicyValue.getJSONObject("key2").getInt("b"));
+ }
+
+ @Test
+ public void testGetInvalidDict() throws JSONException {
+ Assert.assertNull(mPolicyCache.getDictValue(POLICY_NAME));
+ String policyValue = "{\"key1\":\"value1\", \"key2\":{\"a\":1, \"b\":2}";
+ mSharedPreferences.edit().putString(POLICY_NAME, policyValue).apply();
+ Assert.assertNull(mPolicyCache.getListValue(POLICY_NAME));
+ }
+
+ @Test
+ public void testCachePolicies() {
+ cachePolicies(CollectionUtil.newHashMap(
+ Pair.create(POLICY_NAME, Pair.create(PolicyCache.Type.Integer, 1)),
+ Pair.create(POLICY_NAME_2, Pair.create(PolicyCache.Type.Boolean, true)),
+ Pair.create(POLICY_NAME_3, Pair.create(PolicyCache.Type.String, "2")),
+ Pair.create(POLICY_NAME_4, Pair.create(PolicyCache.Type.List, "[1]")),
+ Pair.create(POLICY_NAME_5, Pair.create(PolicyCache.Type.Dict, "{1:2}"))));
+
+ Assert.assertEquals(1, mSharedPreferences.getInt(POLICY_NAME, 0));
+ Assert.assertEquals(true, mSharedPreferences.getBoolean(POLICY_NAME_2, false));
+ Assert.assertEquals("2", mSharedPreferences.getString(POLICY_NAME_3, null));
+ Assert.assertEquals("[1]", mSharedPreferences.getString(POLICY_NAME_4, null));
+ Assert.assertEquals("{1:2}", mSharedPreferences.getString(POLICY_NAME_5, null));
+ }
+
+ @Test
+ public void testCacheUpdated() {
+ cachePolicies(CollectionUtil.newHashMap(
+ Pair.create(POLICY_NAME, Pair.create(PolicyCache.Type.Integer, 1))));
+ cachePolicies(CollectionUtil.newHashMap(
+ Pair.create(POLICY_NAME_2, Pair.create(PolicyCache.Type.Boolean, true))));
+
+ Assert.assertFalse(mSharedPreferences.contains(POLICY_NAME));
+ Assert.assertEquals(true, mSharedPreferences.getBoolean(POLICY_NAME_2, false));
+ }
+
+ @Test
+ public void testNotCachingUnnecessaryPolicy() {
+ when(mPolicyMap.getIntValue(eq(POLICY_NAME))).thenReturn(1);
+ when(mPolicyMap.getBooleanValue(eq(POLICY_NAME_2))).thenReturn(true);
+
+ mPolicyCache.cachePolicies(
+ mPolicyMap, Arrays.asList(Pair.create(POLICY_NAME_2, PolicyCache.Type.Boolean)));
+
+ Assert.assertFalse(mSharedPreferences.contains(POLICY_NAME));
+ Assert.assertEquals(true, mSharedPreferences.getBoolean(POLICY_NAME_2, false));
+ }
+
+ @Test
+ public void testNotCachingUnavailablePolicy() {
+ when(mPolicyMap.getBooleanValue(eq(POLICY_NAME_2))).thenReturn(true);
+
+ mPolicyCache.cachePolicies(mPolicyMap,
+ Arrays.asList(Pair.create(POLICY_NAME, PolicyCache.Type.Integer),
+ Pair.create(POLICY_NAME_2, PolicyCache.Type.Boolean)));
+
+ Assert.assertFalse(mSharedPreferences.contains(POLICY_NAME));
+ Assert.assertEquals(true, mSharedPreferences.getBoolean(POLICY_NAME_2, false));
+ }
+
+ @Test
+ public void testWriteOnlyAfterCacheUpdate() {
+ mSharedPreferences.edit()
+ .putInt(POLICY_NAME, 1)
+ .putBoolean(POLICY_NAME_2, true)
+ .putString(POLICY_NAME_3, "a")
+ .putString(POLICY_NAME_4, "[1]")
+ .putString(POLICY_NAME_5, "{1:2}")
+ .apply();
+ Assert.assertTrue(mPolicyCache.isReadable());
+
+ cachePolicies(CollectionUtil.newHashMap(
+ Pair.create(POLICY_NAME, Pair.create(PolicyCache.Type.Integer, 1)),
+ Pair.create(POLICY_NAME_2, Pair.create(PolicyCache.Type.Boolean, true)),
+ Pair.create(POLICY_NAME_3, Pair.create(PolicyCache.Type.String, "2")),
+ Pair.create(POLICY_NAME_4, Pair.create(PolicyCache.Type.List, "[1]")),
+ Pair.create(POLICY_NAME_5, Pair.create(PolicyCache.Type.Dict, "{1:2}"))));
+
+ Assert.assertFalse(mPolicyCache.isReadable());
+ if (BuildConfig.ENABLE_ASSERTS) {
+ assertAssertionError(() -> mPolicyCache.getIntValue(POLICY_NAME));
+ assertAssertionError(() -> mPolicyCache.getBooleanValue(POLICY_NAME_2));
+ assertAssertionError(() -> mPolicyCache.getStringValue(POLICY_NAME_3));
+ assertAssertionError(() -> mPolicyCache.getListValue(POLICY_NAME_4));
+ assertAssertionError(() -> mPolicyCache.getDictValue(POLICY_NAME_5));
+ }
+ }
+
+ /**
+ * @param policies A Map for policies that needs to be cached. Each policy
+ * name is mapped to a pair of policy type and policy value.
+ * Setting up {@link #mPolicyCache} mock and call {@link PolicyCache#cachePolicies}.
+ */
+ private void cachePolicies(Map<String, Pair<PolicyCache.Type, Object>> policies) {
+ List<Pair<String, PolicyCache.Type>> cachedPolicies = new ArrayList();
+ CollectionUtil.forEach(policies, entry -> {
+ String policyName = entry.getKey();
+ PolicyCache.Type policyType = entry.getValue().first;
+ Object policyValue = entry.getValue().second;
+ switch (policyType) {
+ case Integer:
+ when(mPolicyMap.getIntValue(eq(policyName))).thenReturn((Integer) policyValue);
+ break;
+ case Boolean:
+ when(mPolicyMap.getBooleanValue(eq(policyName)))
+ .thenReturn((Boolean) policyValue);
+ break;
+ case String:
+ when(mPolicyMap.getStringValue(eq(policyName)))
+ .thenReturn((String) policyValue);
+ break;
+ case List:
+ when(mPolicyMap.getListValueAsString(eq(policyName)))
+ .thenReturn((String) policyValue);
+ break;
+ case Dict:
+ when(mPolicyMap.getDictValueAsString(eq(policyName)))
+ .thenReturn((String) policyValue);
+ break;
+ }
+ cachedPolicies.add(Pair.create(policyName, policyType));
+ });
+ mPolicyCache.cachePolicies(mPolicyMap, cachedPolicies);
+ }
+
+ private void assertAssertionError(Runnable runnable) {
+ AssertionError assertionError = null;
+ try {
+ runnable.run();
+ } catch (AssertionError e) {
+ assertionError = e;
+ }
+
+ Assert.assertNotNull("AssertionError not thrown", assertionError);
+ }
+}
diff --git a/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyConverterTest.java b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyConverterTest.java
new file mode 100644
index 00000000000..f3f7a7e3bea
--- /dev/null
+++ b/chromium/components/policy/android/junit/src/org/chromium/components/policy/PolicyConverterTest.java
@@ -0,0 +1,79 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.policy;
+
+import static org.mockito.Mockito.verify;
+
+import android.os.Build;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+
+/**
+ * Robolectric test for AbstractAppRestrictionsProvider.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.M)
+public class PolicyConverterTest {
+ @Rule
+ public JniMocker mocker = new JniMocker();
+
+ @Mock
+ private PolicyConverter.Natives mPolicyConverterJniMock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mocker.mock(PolicyConverterJni.TEST_HOOKS, mPolicyConverterJniMock);
+ }
+
+ /**
+ * Test method for
+ * {@link org.chromium.components.policy.PolicyConverter#setPolicy(java.lang.String,
+ * java.lang.Object)}.
+ */
+ @Test
+ public void testSetPolicy() {
+ // Stub out the native methods.
+ PolicyConverter policyConverter = PolicyConverter.create(1234);
+
+ policyConverter.setPolicy("p1", true);
+ verify(mPolicyConverterJniMock).setPolicyBoolean(1234, policyConverter, "p1", true);
+ policyConverter.setPolicy("p1", 5678);
+ verify(mPolicyConverterJniMock).setPolicyInteger(1234, policyConverter, "p1", 5678);
+ policyConverter.setPolicy("p1", "hello");
+ verify(mPolicyConverterJniMock).setPolicyString(1234, policyConverter, "p1", "hello");
+ policyConverter.setPolicy("p1", new String[] {"hello", "goodbye"});
+ verify(mPolicyConverterJniMock)
+ .setPolicyStringArray(
+ 1234, policyConverter, "p1", new String[] {"hello", "goodbye"});
+ Bundle b1 = new Bundle();
+ b1.putInt("i1", 23);
+ b1.putString("s1", "a string");
+ Bundle[] ba = new Bundle[1];
+ ba[0] = new Bundle();
+ ba[0].putBoolean("ba1b", true);
+ ba[0].putString("ba1s", "another string");
+ b1.putParcelableArray("b1b", ba);
+ policyConverter.setPolicy("p1", b1);
+ verify(mPolicyConverterJniMock)
+ .setPolicyString(1234, policyConverter, "p1",
+ "{\"i1\":23,\"s1\":\"a string\","
+ + "\"b1b\":[{\"ba1b\":true,\"ba1s\":\"another string\"}]}");
+ policyConverter.setPolicy("p1", ba);
+ verify(mPolicyConverterJniMock)
+ .setPolicyString(1234, policyConverter, "p1",
+ "[{\"ba1b\":true,\"ba1s\":\"another string\"}]");
+ }
+}
diff --git a/chromium/components/policy/content/DEPS b/chromium/components/policy/content/DEPS
new file mode 100644
index 00000000000..ca396db26f8
--- /dev/null
+++ b/chromium/components/policy/content/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+ "+content/public/browser",
+ "+components/policy",
+ "+components/keyed_service",
+ "+components/prefs",
+ "+components/safe_search_api",
+ "+components/url_matcher",
+ "+components/user_prefs",
+ "+net/base",
+]
+
+specific_include_rules = {
+ "policy_blocklist_navigation_throttle_unittest\.cc": [
+ "+components/sync_preferences/testing_pref_service_syncable.h",
+ "+content/public/test",
+ ],
+}
diff --git a/chromium/components/policy/content/policy_blocklist_navigation_throttle.cc b/chromium/components/policy/content/policy_blocklist_navigation_throttle.cc
new file mode 100644
index 00000000000..0043b2e7a5b
--- /dev/null
+++ b/chromium/components/policy/content/policy_blocklist_navigation_throttle.cc
@@ -0,0 +1,116 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/content/policy_blocklist_navigation_throttle.h"
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "components/policy/content/policy_blocklist_service.h"
+#include "components/policy/core/browser/url_blocklist_manager.h"
+#include "components/policy/core/browser/url_blocklist_policy_handler.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "url/gurl.h"
+
+using URLBlocklistState = policy::URLBlocklist::URLBlocklistState;
+using SafeSitesFilterBehavior = policy::SafeSitesFilterBehavior;
+
+// Passing an Unretained pointer for the safe_sites_navigation_throttle_
+// callback is safe because this object owns safe_sites_navigation_throttle_,
+// which runs the callback from within the object.
+PolicyBlocklistNavigationThrottle::PolicyBlocklistNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context)
+ : content::NavigationThrottle(navigation_handle),
+ safe_sites_navigation_throttle_(
+ navigation_handle,
+ context,
+ base::BindRepeating(
+ &PolicyBlocklistNavigationThrottle::OnDeferredSafeSitesResult,
+ base::Unretained(this))),
+ blocklist_service_(PolicyBlocklistFactory::GetForBrowserContext(context)),
+ prefs_(user_prefs::UserPrefs::Get(context)) {
+ DCHECK(prefs_);
+}
+
+PolicyBlocklistNavigationThrottle::~PolicyBlocklistNavigationThrottle() =
+ default;
+
+bool PolicyBlocklistNavigationThrottle::IsBlockedViewSourceNavigation() {
+ content::NavigationEntry* nav_entry =
+ navigation_handle()->GetNavigationEntry();
+ if (!nav_entry || !nav_entry->IsViewSourceMode())
+ return false;
+
+ GURL view_source_url = GURL(std::string("view-source:") +
+ navigation_handle()->GetURL().spec());
+
+ return (blocklist_service_->GetURLBlocklistState(view_source_url) ==
+ URLBlocklistState::URL_IN_BLOCKLIST);
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+PolicyBlocklistNavigationThrottle::WillStartRequest() {
+ const GURL& url = navigation_handle()->GetURL();
+
+ // Ignore blob scheme because we may use it to deliver navigation responses
+ // to the renderer process.
+ if (url.SchemeIs(url::kBlobScheme))
+ return PROCEED;
+
+ URLBlocklistState blocklist_state =
+ blocklist_service_->GetURLBlocklistState(url);
+ if (blocklist_state == URLBlocklistState::URL_IN_BLOCKLIST) {
+ return ThrottleCheckResult(BLOCK_REQUEST,
+ net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ }
+
+ if (IsBlockedViewSourceNavigation()) {
+ return ThrottleCheckResult(BLOCK_REQUEST,
+ net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ }
+
+ if (blocklist_state == URLBlocklistState::URL_IN_ALLOWLIST)
+ return PROCEED;
+
+ return CheckSafeSitesFilter(url);
+}
+
+// SafeSitesNavigationThrottle is unconditional and does not check PrefService
+// because it is used outside //chrome. Therefore, the policy must be checked
+// here to determine whether to use SafeSitesNavigationThrottle.
+content::NavigationThrottle::ThrottleCheckResult
+PolicyBlocklistNavigationThrottle::CheckSafeSitesFilter(const GURL& url) {
+ SafeSitesFilterBehavior filter_behavior =
+ static_cast<SafeSitesFilterBehavior>(
+ prefs_->GetInteger(policy::policy_prefs::kSafeSitesFilterBehavior));
+ if (filter_behavior == SafeSitesFilterBehavior::kSafeSitesFilterDisabled)
+ return PROCEED;
+
+ DCHECK_EQ(filter_behavior, SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ return safe_sites_navigation_throttle_.WillStartRequest();
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+PolicyBlocklistNavigationThrottle::WillRedirectRequest() {
+ return WillStartRequest();
+}
+
+const char* PolicyBlocklistNavigationThrottle::GetNameForLogging() {
+ return "PolicyBlocklistNavigationThrottle";
+}
+
+void PolicyBlocklistNavigationThrottle::OnDeferredSafeSitesResult(
+ bool is_safe,
+ ThrottleCheckResult cancel_result) {
+ if (is_safe) {
+ Resume();
+ } else {
+ CancelDeferredNavigation(cancel_result);
+ }
+} \ No newline at end of file
diff --git a/chromium/components/policy/content/policy_blocklist_navigation_throttle.h b/chromium/components/policy/content/policy_blocklist_navigation_throttle.h
new file mode 100644
index 00000000000..c77d03332ba
--- /dev/null
+++ b/chromium/components/policy/content/policy_blocklist_navigation_throttle.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_NAVIGATION_THROTTLE_H_
+#define COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_NAVIGATION_THROTTLE_H_
+
+#include "components/policy/content/safe_sites_navigation_throttle.h"
+#include "content/public/browser/navigation_throttle.h"
+
+class GURL;
+class PolicyBlocklistService;
+class PrefService;
+
+// PolicyBlocklistNavigationThrottle provides a simple way to block a navigation
+// based on the URLBlocklistManager and Safe Search API. If the URL is on the
+// blocklist or allowlist, the throttle will immediately block or allow the
+// navigation. Otherwise, the URL will be checked against the Safe Search API if
+// the SafeSitesFilterBehavior policy is enabled. This final check may be
+// asynchronous if the result hasn't been cached yet.
+class PolicyBlocklistNavigationThrottle : public content::NavigationThrottle {
+ public:
+ PolicyBlocklistNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context);
+ PolicyBlocklistNavigationThrottle(const PolicyBlocklistNavigationThrottle&) =
+ delete;
+ PolicyBlocklistNavigationThrottle& operator=(
+ const PolicyBlocklistNavigationThrottle&) = delete;
+ ~PolicyBlocklistNavigationThrottle() override;
+
+ // NavigationThrottle overrides.
+ ThrottleCheckResult WillStartRequest() override;
+ ThrottleCheckResult WillRedirectRequest() override;
+ const char* GetNameForLogging() override;
+
+ private:
+ // Returns TRUE if this navigation is to view-source: and view-source is on
+ // the URLBlocklist.
+ bool IsBlockedViewSourceNavigation();
+
+ // To ensure both allow and block policies override Safe Sites,
+ // SafeSitesNavigationThrottle must be consulted as part of this throttle
+ // rather than added separately to the list of throttles.
+ ThrottleCheckResult CheckSafeSitesFilter(const GURL& url);
+ void OnDeferredSafeSitesResult(bool is_safe,
+ ThrottleCheckResult cancel_result);
+ SafeSitesNavigationThrottle safe_sites_navigation_throttle_;
+
+ raw_ptr<PolicyBlocklistService> blocklist_service_;
+
+ raw_ptr<PrefService> prefs_;
+};
+
+#endif // COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_NAVIGATION_THROTTLE_H_
diff --git a/chromium/components/policy/content/policy_blocklist_navigation_throttle_unittest.cc b/chromium/components/policy/content/policy_blocklist_navigation_throttle_unittest.cc
new file mode 100644
index 00000000000..ea3276c319e
--- /dev/null
+++ b/chromium/components/policy/content/policy_blocklist_navigation_throttle_unittest.cc
@@ -0,0 +1,448 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/values.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/policy/content/policy_blocklist_navigation_throttle.h"
+#include "components/policy/content/safe_search_service.h"
+#include "components/policy/content/safe_sites_navigation_throttle.h"
+#include "components/policy/core/browser/url_blocklist_manager.h"
+#include "components/policy/core/browser/url_blocklist_policy_handler.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/safe_search_api/stub_url_checker.h"
+#include "components/safe_search_api/url_checker.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+using SafeSitesFilterBehavior = policy::SafeSitesFilterBehavior;
+
+constexpr size_t kCacheSize = 2;
+
+} // namespace
+
+// TODO(crbug.com/1147231): Break out the tests into separate files. The
+// SafeSites tests should be parameterized to run the same tests on both types.
+class SafeSitesNavigationThrottleTest
+ : public content::RenderViewHostTestHarness,
+ public content::WebContentsObserver {
+ public:
+ SafeSitesNavigationThrottleTest() = default;
+ SafeSitesNavigationThrottleTest(const SafeSitesNavigationThrottleTest&) =
+ delete;
+ SafeSitesNavigationThrottleTest& operator=(
+ const SafeSitesNavigationThrottleTest&) = delete;
+ ~SafeSitesNavigationThrottleTest() override = default;
+
+ // content::RenderViewHostTestHarness:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+
+ // Prevent crashes in BrowserContextDependencyManager caused when tests
+ // that run in serial happen to reuse a memory address for a BrowserContext
+ // from a previously-run test.
+ // TODO(michaelpg): This shouldn't be the test's responsibility. Can we make
+ // BrowserContext just do this always, like Profile does?
+ BrowserContextDependencyManager::GetInstance()->MarkBrowserContextLive(
+ browser_context());
+
+ SafeSearchFactory::GetInstance()
+ ->GetForBrowserContext(browser_context())
+ ->SetSafeSearchURLCheckerForTest(
+ stub_url_checker_.BuildURLChecker(kCacheSize));
+
+ // Observe the WebContents to add the throttle.
+ Observe(RenderViewHostTestHarness::web_contents());
+ }
+
+ void TearDown() override {
+ BrowserContextDependencyManager::GetInstance()
+ ->DestroyBrowserContextServices(browser_context());
+ content::RenderViewHostTestHarness::TearDown();
+ }
+
+ protected:
+ // content::WebContentsObserver:
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override {
+ auto throttle = std::make_unique<SafeSitesNavigationThrottle>(
+ navigation_handle, browser_context());
+
+ navigation_handle->RegisterThrottleForTesting(std::move(throttle));
+ }
+
+ std::unique_ptr<content::NavigationSimulator> StartNavigation(
+ const GURL& first_url) {
+ auto navigation_simulator =
+ content::NavigationSimulator::CreateRendererInitiated(first_url,
+ main_rfh());
+ navigation_simulator->SetAutoAdvance(false);
+ navigation_simulator->Start();
+ return navigation_simulator;
+ }
+
+ // Tests that redirects from a safe site to a porn site are handled correctly.
+ // Also tests the same scenario when the sites are in the cache.
+ // If |expected_error_page_content| is not null, the canceled throttle check
+ // result's error_page_content will be expected to match it.
+ void TestSafeSitesRedirectAndCachedSites(
+ const char* expected_error_page_content);
+
+ // Tests responses for both a safe site and a porn site both when the sites
+ // are in the cache and not. If |expected_error_page_content| is not null, the
+ // canceled throttle check result's error_page_content will be expected to
+ // match it.
+ void TestSafeSitesCachedSites(const char* expected_error_page_content);
+
+ safe_search_api::StubURLChecker stub_url_checker_;
+};
+
+class SafeSitesNavigationThrottleWithErrorContentTest
+ : public SafeSitesNavigationThrottleTest {
+ protected:
+ static const char kErrorPageContent[];
+
+ // content::WebContentsObserver:
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override {
+ auto throttle = std::make_unique<SafeSitesNavigationThrottle>(
+ navigation_handle, browser_context(), kErrorPageContent);
+
+ navigation_handle->RegisterThrottleForTesting(std::move(throttle));
+ }
+};
+
+const char
+ SafeSitesNavigationThrottleWithErrorContentTest::kErrorPageContent[] =
+ "<html><body>URL was filtered.</body></html>";
+
+class PolicyBlocklistNavigationThrottleTest
+ : public SafeSitesNavigationThrottleTest {
+ public:
+ void SetUp() override {
+ SafeSitesNavigationThrottleTest::SetUp();
+
+ user_prefs::UserPrefs::Set(browser_context(), &pref_service_);
+ policy::URLBlocklistManager::RegisterProfilePrefs(pref_service_.registry());
+ }
+
+ protected:
+ // content::WebContentsObserver:
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override {
+ auto throttle = std::make_unique<PolicyBlocklistNavigationThrottle>(
+ navigation_handle, browser_context());
+
+ navigation_handle->RegisterThrottleForTesting(std::move(throttle));
+ }
+
+ void SetBlocklistUrlPattern(const std::string& pattern) {
+ auto value = std::make_unique<base::Value>(base::Value::Type::LIST);
+ value->Append(base::Value(pattern));
+ pref_service_.SetManagedPref(policy::policy_prefs::kUrlBlocklist,
+ std::move(value));
+ task_environment()->RunUntilIdle();
+ }
+
+ void SetAllowlistUrlPattern(const std::string& pattern) {
+ auto value = std::make_unique<base::Value>(base::Value::Type::LIST);
+ value->Append(base::Value(pattern));
+ pref_service_.SetManagedPref(policy::policy_prefs::kUrlAllowlist,
+ std::move(value));
+ task_environment()->RunUntilIdle();
+ }
+
+ void SetSafeSitesFilterBehavior(SafeSitesFilterBehavior filter_behavior) {
+ auto value =
+ std::make_unique<base::Value>(static_cast<int>(filter_behavior));
+ pref_service_.SetManagedPref(policy::policy_prefs::kSafeSitesFilterBehavior,
+ std::move(value));
+ }
+
+ sync_preferences::TestingPrefServiceSyncable pref_service_;
+};
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, Blocklist) {
+ SetBlocklistUrlPattern("example.com");
+
+ // Block a blocklisted site.
+ auto navigation_simulator = StartNavigation(GURL("http://www.example.com/"));
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, Allowlist) {
+ SetAllowlistUrlPattern("www.example.com");
+ SetBlocklistUrlPattern("example.com");
+
+ // Allow a allowlisted exception to a blocklisted domain.
+ auto navigation_simulator = StartNavigation(GURL("http://www.example.com/"));
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_Safe) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ stub_url_checker_.SetUpValidResponse(false /* is_porn */);
+
+ // Defer, then allow a safe site.
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_Porn) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+
+ // Defer, then cancel a porn site.
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_Allowlisted) {
+ SetAllowlistUrlPattern("example.com");
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+
+ // Even with SafeSites enabled, a allowlisted site is immediately allowed.
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_Schemes) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+
+ // The safe sites filter is only used for http(s) URLs. This test uses
+ // browser-initiated navigation, since renderer-initiated navigations to
+ // WebUI documents are not allowed.
+ auto navigation_simulator =
+ content::NavigationSimulator::CreateBrowserInitiated(
+ GURL("chrome://settings"), RenderViewHostTestHarness::web_contents());
+ navigation_simulator->SetAutoAdvance(false);
+ navigation_simulator->Start();
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_PolicyChange) {
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+
+ // The safe sites filter is initially disabled.
+ {
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ }
+
+ // Setting the pref enables the filter.
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ {
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+ }
+
+ // Updating the pref disables the filter.
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterDisabled);
+ {
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ }
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_Failure) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ stub_url_checker_.SetUpFailedResponse();
+
+ // If the Safe Search API request fails, the navigation is allowed.
+ auto navigation_simulator = StartNavigation(GURL("http://example.com/"));
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+}
+
+void SafeSitesNavigationThrottleTest::TestSafeSitesCachedSites(
+ const char* expected_error_page_content) {
+ // Check a couple of sites.
+ ASSERT_EQ(2u, kCacheSize);
+ const GURL safe_site = GURL("http://example.com/");
+ const GURL porn_site = GURL("http://example2.com/");
+
+ stub_url_checker_.SetUpValidResponse(false /* is_porn */);
+ {
+ auto navigation_simulator = StartNavigation(safe_site);
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+ {
+ auto navigation_simulator = StartNavigation(porn_site);
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+ if (expected_error_page_content) {
+ EXPECT_STREQ(expected_error_page_content,
+ navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content()
+ ->c_str());
+ } else {
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+ }
+
+ stub_url_checker_.ClearResponses();
+ {
+ // This check is synchronous since the site is in the cache.
+ auto navigation_simulator = StartNavigation(safe_site);
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+
+ {
+ // This check is synchronous since the site is in the cache.
+ auto navigation_simulator = StartNavigation(porn_site);
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+ if (expected_error_page_content) {
+ EXPECT_STREQ(expected_error_page_content,
+ navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content()
+ ->c_str());
+ } else {
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+ }
+}
+
+TEST_F(SafeSitesNavigationThrottleTest, SafeSites_CachedSites) {
+ TestSafeSitesCachedSites(nullptr);
+}
+
+TEST_F(SafeSitesNavigationThrottleWithErrorContentTest, SafeSites_CachedSites) {
+ TestSafeSitesCachedSites(&kErrorPageContent[0]);
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest, SafeSites_CachedSites) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+ TestSafeSitesCachedSites(nullptr);
+}
+
+void SafeSitesNavigationThrottleTest::TestSafeSitesRedirectAndCachedSites(
+ const char* expected_error_page_content) {
+ // Check a couple of sites.
+ ASSERT_EQ(2u, kCacheSize);
+ const GURL safe_site = GURL("http://example.com/");
+ const GURL porn_site = GURL("http://example2.com/");
+
+ stub_url_checker_.SetUpValidResponse(false /* is_porn */);
+ {
+ auto navigation_simulator = StartNavigation(safe_site);
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+
+ stub_url_checker_.SetUpValidResponse(true /* is_porn */);
+ navigation_simulator->Redirect(porn_site);
+ EXPECT_TRUE(navigation_simulator->IsDeferred());
+ navigation_simulator->Wait();
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+ if (expected_error_page_content) {
+ EXPECT_STREQ(expected_error_page_content,
+ navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content()
+ ->c_str());
+ } else {
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+ }
+
+ stub_url_checker_.ClearResponses();
+ {
+ // This check is synchronous since the site is in the cache.
+ auto navigation_simulator = StartNavigation(safe_site);
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::PROCEED,
+ navigation_simulator->GetLastThrottleCheckResult());
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+
+ navigation_simulator->Redirect(porn_site);
+ ASSERT_FALSE(navigation_simulator->IsDeferred());
+ EXPECT_EQ(content::NavigationThrottle::CANCEL,
+ navigation_simulator->GetLastThrottleCheckResult());
+ if (expected_error_page_content) {
+ EXPECT_STREQ(expected_error_page_content,
+ navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content()
+ ->c_str());
+ } else {
+ EXPECT_FALSE(navigation_simulator->GetLastThrottleCheckResult()
+ .error_page_content());
+ }
+ }
+}
+
+TEST_F(SafeSitesNavigationThrottleTest, SafeSites_RedirectAndCachedSites) {
+ TestSafeSitesRedirectAndCachedSites(nullptr);
+}
+
+TEST_F(SafeSitesNavigationThrottleWithErrorContentTest,
+ SafeSites_RedirectAndCachedSites) {
+ TestSafeSitesRedirectAndCachedSites(&kErrorPageContent[0]);
+}
+
+TEST_F(PolicyBlocklistNavigationThrottleTest,
+ SafeSites_RedirectAndCachedSites) {
+ SetSafeSitesFilterBehavior(SafeSitesFilterBehavior::kSafeSitesFilterEnabled);
+
+ TestSafeSitesRedirectAndCachedSites(nullptr);
+}
diff --git a/chromium/components/policy/content/policy_blocklist_service.cc b/chromium/components/policy/content/policy_blocklist_service.cc
new file mode 100644
index 00000000000..559d654b1e4
--- /dev/null
+++ b/chromium/components/policy/content/policy_blocklist_service.cc
@@ -0,0 +1,58 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/content/policy_blocklist_service.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+
+PolicyBlocklistService::PolicyBlocklistService(
+ std::unique_ptr<policy::URLBlocklistManager> url_blocklist_manager)
+ : url_blocklist_manager_(std::move(url_blocklist_manager)) {}
+
+PolicyBlocklistService::~PolicyBlocklistService() = default;
+
+policy::URLBlocklist::URLBlocklistState
+PolicyBlocklistService::GetURLBlocklistState(const GURL& url) const {
+ return url_blocklist_manager_->GetURLBlocklistState(url);
+}
+
+// static
+PolicyBlocklistFactory* PolicyBlocklistFactory::GetInstance() {
+ return base::Singleton<PolicyBlocklistFactory>::get();
+}
+
+// static
+PolicyBlocklistService* PolicyBlocklistFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<PolicyBlocklistService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+PolicyBlocklistFactory::PolicyBlocklistFactory()
+ : BrowserContextKeyedServiceFactory(
+ "PolicyBlocklist",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+PolicyBlocklistFactory::~PolicyBlocklistFactory() = default;
+
+KeyedService* PolicyBlocklistFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ PrefService* pref_service = user_prefs::UserPrefs::Get(context);
+ auto url_blocklist_manager = std::make_unique<policy::URLBlocklistManager>(
+ pref_service, policy::policy_prefs::kUrlBlocklist,
+ policy::policy_prefs::kUrlAllowlist);
+ return new PolicyBlocklistService(std::move(url_blocklist_manager));
+}
+
+content::BrowserContext* PolicyBlocklistFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return context;
+}
diff --git a/chromium/components/policy/content/policy_blocklist_service.h b/chromium/components/policy/content/policy_blocklist_service.h
new file mode 100644
index 00000000000..419cdf42f7c
--- /dev/null
+++ b/chromium/components/policy/content/policy_blocklist_service.h
@@ -0,0 +1,58 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_SERVICE_H_
+#define COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_SERVICE_H_
+
+#include <memory>
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/policy/core/browser/url_blocklist_manager.h"
+
+// PolicyBlocklistService and PolicyBlocklistFactory provide a way for
+// us to access URLBlocklistManager, a policy block list service based on
+// the Preference Service. The URLBlocklistManager responds to permission
+// changes and is per-Profile.
+class PolicyBlocklistService : public KeyedService {
+ public:
+ explicit PolicyBlocklistService(
+ std::unique_ptr<policy::URLBlocklistManager> url_blocklist_manager);
+ PolicyBlocklistService(const PolicyBlocklistService&) = delete;
+ PolicyBlocklistService& operator=(const PolicyBlocklistService&) = delete;
+ ~PolicyBlocklistService() override;
+
+ policy::URLBlocklist::URLBlocklistState GetURLBlocklistState(
+ const GURL& url) const;
+
+ private:
+ std::unique_ptr<policy::URLBlocklistManager> url_blocklist_manager_;
+};
+
+class PolicyBlocklistFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ PolicyBlocklistFactory(const PolicyBlocklistFactory&) = delete;
+ PolicyBlocklistFactory& operator=(const PolicyBlocklistFactory&) = delete;
+
+ static PolicyBlocklistFactory* GetInstance();
+ static PolicyBlocklistService* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ private:
+ PolicyBlocklistFactory();
+ ~PolicyBlocklistFactory() override;
+ friend struct base::DefaultSingletonTraits<PolicyBlocklistFactory>;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ // Finds which browser context (if any) to use.
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+
+};
+
+#endif // COMPONENTS_POLICY_CONTENT_POLICY_BLOCKLIST_SERVICE_H_
diff --git a/chromium/components/policy/content/safe_search_service.cc b/chromium/components/policy/content/safe_search_service.cc
new file mode 100644
index 00000000000..704571fdc39
--- /dev/null
+++ b/chromium/components/policy/content/safe_search_service.cc
@@ -0,0 +1,109 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/content/safe_search_service.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/safe_search_api/safe_search/safe_search_url_checker_client.h"
+#include "components/safe_search_api/url_checker.h"
+#include "components/url_matcher/url_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "net/base/net_errors.h"
+
+namespace {
+
+// Calls the SafeSearchService callback with the result of the Safe Search
+// API check.
+void OnCheckURLDone(SafeSearchService::CheckSafeSearchCallback callback,
+ const GURL& /* url */,
+ safe_search_api::Classification classification,
+ bool /* uncertain */) {
+ std::move(callback).Run(classification ==
+ safe_search_api::Classification::SAFE);
+}
+
+} // namespace
+
+SafeSearchService::SafeSearchService(content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {}
+
+SafeSearchService::~SafeSearchService() = default;
+
+bool SafeSearchService::CheckSafeSearchURL(const GURL& url,
+ CheckSafeSearchCallback callback) {
+ if (!safe_search_url_checker_) {
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("safe_search_service", R"(
+ semantics {
+ sender: "Cloud Policy"
+ description:
+ "Checks whether a given URL (or set of URLs) is considered safe "
+ "by Google SafeSearch."
+ trigger:
+ "If the policy for safe sites is enabled, this is sent for every "
+ "top-level navigation if the result isn't already cached."
+ data: "URL to be checked."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature is off by default and cannot be controlled in "
+ "settings."
+ chrome_policy {
+ SafeSitesFilterBehavior {
+ SafeSitesFilterBehavior: 0
+ }
+ }
+ })");
+
+ safe_search_url_checker_ = std::make_unique<safe_search_api::URLChecker>(
+ std::make_unique<safe_search_api::SafeSearchURLCheckerClient>(
+ browser_context_->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess(),
+ traffic_annotation));
+ }
+
+ return safe_search_url_checker_->CheckURL(
+ url_matcher::util::Normalize(url),
+ base::BindOnce(&OnCheckURLDone, std::move(callback)));
+}
+
+void SafeSearchService::SetSafeSearchURLCheckerForTest(
+ std::unique_ptr<safe_search_api::URLChecker> safe_search_url_checker) {
+ safe_search_url_checker_ = std::move(safe_search_url_checker);
+}
+
+// static
+SafeSearchFactory* SafeSearchFactory::GetInstance() {
+ return base::Singleton<SafeSearchFactory>::get();
+}
+
+// static
+SafeSearchService* SafeSearchFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<SafeSearchService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+SafeSearchFactory::SafeSearchFactory()
+ : BrowserContextKeyedServiceFactory(
+ "SafeSearch",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+SafeSearchFactory::~SafeSearchFactory() = default;
+
+KeyedService* SafeSearchFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ return new SafeSearchService(context);
+}
+
+content::BrowserContext* SafeSearchFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return context;
+}
diff --git a/chromium/components/policy/content/safe_search_service.h b/chromium/components/policy/content/safe_search_service.h
new file mode 100644
index 00000000000..fc1aa86c789
--- /dev/null
+++ b/chromium/components/policy/content/safe_search_service.h
@@ -0,0 +1,71 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CONTENT_SAFE_SEARCH_SERVICE_H_
+#define COMPONENTS_POLICY_CONTENT_SAFE_SEARCH_SERVICE_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class GURL;
+
+namespace safe_search_api {
+class URLChecker;
+} // namespace safe_search_api
+
+// SafeSearchService and SafeSearchFactory provide a way for
+// us to access a Context-specific instance of safe_search_api.
+class SafeSearchService : public KeyedService {
+ public:
+ using CheckSafeSearchCallback = base::OnceCallback<void(bool is_safe)>;
+
+ explicit SafeSearchService(content::BrowserContext* browser_context);
+ SafeSearchService(const SafeSearchService&) = delete;
+ SafeSearchService& operator=(const SafeSearchService&) = delete;
+
+ ~SafeSearchService() override;
+
+ // Starts a call to the Safe Search API for the given URL to determine whether
+ // the URL is "safe" (not porn). Returns whether |callback| was run
+ // synchronously.
+ bool CheckSafeSearchURL(const GURL& url, CheckSafeSearchCallback callback);
+
+ // Creates a SafeSearch URLChecker using a given URLLoaderFactory for testing.
+ void SetSafeSearchURLCheckerForTest(
+ std::unique_ptr<safe_search_api::URLChecker> safe_search_url_checker);
+
+ private:
+ const raw_ptr<content::BrowserContext> browser_context_;
+ std::unique_ptr<safe_search_api::URLChecker> safe_search_url_checker_;
+};
+
+class SafeSearchFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ SafeSearchFactory(const SafeSearchFactory&) = delete;
+ SafeSearchFactory& operator=(const SafeSearchFactory&) = delete;
+
+ static SafeSearchFactory* GetInstance();
+ static SafeSearchService* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ private:
+ SafeSearchFactory();
+ ~SafeSearchFactory() override;
+ friend struct base::DefaultSingletonTraits<SafeSearchFactory>;
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+
+ // Finds which browser context (if any) to use.
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+#endif // COMPONENTS_POLICY_CONTENT_SAFE_SEARCH_SERVICE_H_
diff --git a/chromium/components/policy/content/safe_sites_navigation_throttle.cc b/chromium/components/policy/content/safe_sites_navigation_throttle.cc
new file mode 100644
index 00000000000..8c420e1b3d6
--- /dev/null
+++ b/chromium/components/policy/content/safe_sites_navigation_throttle.cc
@@ -0,0 +1,111 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/content/safe_sites_navigation_throttle.h"
+
+#include "base/bind.h"
+#include "base/strings/string_piece.h"
+#include "components/policy/content/safe_search_service.h"
+#include "components/url_matcher/url_util.h"
+#include "content/public/browser/navigation_handle.h"
+#include "url/gurl.h"
+
+SafeSitesNavigationThrottle::SafeSitesNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context)
+ : SafeSitesNavigationThrottle(
+ navigation_handle,
+ context,
+ base::BindRepeating(&SafeSitesNavigationThrottle::OnDeferredResult,
+ base::Unretained(this))) {}
+
+SafeSitesNavigationThrottle::SafeSitesNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context,
+ DeferredResultCallback deferred_result_callback)
+ : NavigationThrottle(navigation_handle),
+ safe_seach_service_(SafeSearchFactory::GetForBrowserContext(context)),
+ deferred_result_callback_(std::move(deferred_result_callback)) {}
+
+// Use of Unretained for is safe because it is called synchronously from this
+// object.
+SafeSitesNavigationThrottle::SafeSitesNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context,
+ base::StringPiece safe_sites_error_page_content)
+ : NavigationThrottle(navigation_handle),
+ safe_seach_service_(SafeSearchFactory::GetForBrowserContext(context)),
+ deferred_result_callback_(
+ base::BindRepeating(&SafeSitesNavigationThrottle::OnDeferredResult,
+ base::Unretained(this))),
+ safe_sites_error_page_content_(
+ std::string(safe_sites_error_page_content)) {}
+
+SafeSitesNavigationThrottle::~SafeSitesNavigationThrottle() = default;
+
+content::NavigationThrottle::ThrottleCheckResult
+SafeSitesNavigationThrottle::WillStartRequest() {
+ const GURL& url = navigation_handle()->GetURL();
+
+ // Ignore blob scheme because we may use it to deliver navigation responses
+ // to the renderer process.
+ if (url.SchemeIs(url::kBlobScheme))
+ return PROCEED;
+
+ // Safe Sites filter applies to top-level HTTP[S] requests.
+ if (!url.SchemeIsHTTPOrHTTPS())
+ return PROCEED;
+
+ GURL effective_url = url_matcher::util::GetEmbeddedURL(url);
+ if (!effective_url.is_valid())
+ effective_url = url;
+
+ bool synchronous = safe_seach_service_->CheckSafeSearchURL(
+ effective_url,
+ base::BindOnce(&SafeSitesNavigationThrottle::CheckSafeSearchCallback,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (!synchronous) {
+ deferred_ = true;
+ return DEFER;
+ }
+
+ if (should_cancel_)
+ return CreateCancelResult();
+ return PROCEED;
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+SafeSitesNavigationThrottle::WillRedirectRequest() {
+ return WillStartRequest();
+}
+
+const char* SafeSitesNavigationThrottle::GetNameForLogging() {
+ return "SafeSitesNavigationThrottle";
+}
+
+void SafeSitesNavigationThrottle::CheckSafeSearchCallback(bool is_safe) {
+ if (!deferred_) {
+ should_cancel_ = !is_safe;
+ return;
+ }
+
+ deferred_ = false;
+ deferred_result_callback_.Run(is_safe, CreateCancelResult());
+}
+
+void SafeSitesNavigationThrottle::OnDeferredResult(
+ bool is_safe,
+ ThrottleCheckResult cancel_result) {
+ if (is_safe) {
+ Resume();
+ } else {
+ CancelDeferredNavigation(cancel_result);
+ }
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+SafeSitesNavigationThrottle::CreateCancelResult() const {
+ return ThrottleCheckResult(CANCEL, net::ERR_BLOCKED_BY_ADMINISTRATOR,
+ safe_sites_error_page_content_);
+} \ No newline at end of file
diff --git a/chromium/components/policy/content/safe_sites_navigation_throttle.h b/chromium/components/policy/content/safe_sites_navigation_throttle.h
new file mode 100644
index 00000000000..0439222fcdf
--- /dev/null
+++ b/chromium/components/policy/content/safe_sites_navigation_throttle.h
@@ -0,0 +1,83 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CONTENT_SAFE_SITES_NAVIGATION_THROTTLE_H_
+#define COMPONENTS_POLICY_CONTENT_SAFE_SITES_NAVIGATION_THROTTLE_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece_forward.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class SafeSearchService;
+
+namespace content {
+class BrowserContext;
+class NavigationHandle;
+} // namespace content
+
+// SafeSitesNavigationThrottle provides a simple way to block a navigation
+// based on the Safe Search API. The URL is checked against the Safe Search API.
+// The check may be asynchronous if the result hasn't been cached yet.
+// This class does not check the SafeSitesFilterBehavior policy.
+class SafeSitesNavigationThrottle : public content::NavigationThrottle {
+ public:
+ // Called when the SafeSearch result is available after being deferred.
+ // if !|is_safe|, |cancel_result| contains the result to pass to
+ // CancelDeferredNavigation(). Other NavigationThrottles using this object
+ // must handle resolving deferred navigation themselves due to checks in
+ // NavigationThrottleRunner.
+ using DeferredResultCallback =
+ base::RepeatingCallback<void(bool is_safe,
+ ThrottleCheckResult cancel_result)>;
+
+ SafeSitesNavigationThrottle(content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context);
+ SafeSitesNavigationThrottle(content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context,
+ DeferredResultCallback deferred_result_callback);
+ SafeSitesNavigationThrottle(content::NavigationHandle* navigation_handle,
+ content::BrowserContext* context,
+ base::StringPiece safe_sites_error_page_content);
+ SafeSitesNavigationThrottle(const SafeSitesNavigationThrottle&) = delete;
+ SafeSitesNavigationThrottle& operator=(const SafeSitesNavigationThrottle&) =
+ delete;
+ ~SafeSitesNavigationThrottle() override;
+
+ // NavigationThrottle overrides.
+ ThrottleCheckResult WillStartRequest() override;
+ ThrottleCheckResult WillRedirectRequest() override;
+ const char* GetNameForLogging() override;
+
+ private:
+ // Callback from SafeSearchService.
+ void CheckSafeSearchCallback(bool is_safe);
+
+ // The default implementation DeferredResultCallback.
+ void OnDeferredResult(bool is_safe, ThrottleCheckResult cancel_result);
+
+ // Creates the result to be returned when navigation is canceled.
+ ThrottleCheckResult CreateCancelResult() const;
+
+ raw_ptr<SafeSearchService> safe_seach_service_;
+
+ const DeferredResultCallback deferred_result_callback_;
+
+ // HTML to be displayed when navigation is canceled by the Safe Sites filter.
+ // If null, a default error page will be displayed.
+ const absl::optional<std::string> safe_sites_error_page_content_;
+
+ // Whether the request was deferred in order to check the Safe Search API.
+ bool deferred_ = false;
+
+ // Whether the Safe Search API callback determined the in-progress navigation
+ // should be canceled.
+ bool should_cancel_ = false;
+
+ base::WeakPtrFactory<SafeSitesNavigationThrottle> weak_ptr_factory_{this};
+};
+
+#endif // COMPONENTS_POLICY_CONTENT_SAFE_SITES_NAVIGATION_THROTTLE_H_
diff --git a/chromium/components/policy/core/DEPS b/chromium/components/policy/core/DEPS
new file mode 100644
index 00000000000..ab42c800b6a
--- /dev/null
+++ b/chromium/components/policy/core/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+components/enterprise",
+ "+components/json_schema",
+ "+components/prefs",
+ "+components/version_info",
+ "+crypto",
+ "+google_apis",
+ "+net/base",
+ "+third_party/re2",
+ "+services/network/public/cpp",
+]
diff --git a/chromium/components/policy/core/browser/DEPS b/chromium/components/policy/core/browser/DEPS
new file mode 100644
index 00000000000..18ff86b6c43
--- /dev/null
+++ b/chromium/components/policy/core/browser/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+components/google/core",
+ "+components/keyed_service",
+ "+components/pref_registry",
+ "+components/reporting",
+ "+components/signin",
+ "+components/strings/grit/components_strings.h",
+ "+components/url_formatter",
+ "+components/url_matcher",
+ "+components/version_info",
+ "+extensions/buildflags",
+ "+services/network/public/mojom/url_response_head.mojom.h",
+ "+services/network/test",
+ "+third_party/icu",
+ "+ui/base/l10n",
+ "+ui/base/resource",
+ "+ui/base/webui/web_ui_util.h",
+]
diff --git a/chromium/components/policy/core/browser/android/policy_cache_updater_android.cc b/chromium/components/policy/core/browser/android/policy_cache_updater_android.cc
new file mode 100644
index 00000000000..98b2d45f1fd
--- /dev/null
+++ b/chromium/components/policy/core/browser/android/policy_cache_updater_android.cc
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/android/policy_cache_updater_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/bind.h"
+#include "components/policy/android/jni_headers/PolicyCacheUpdater_jni.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/android/policy_map_android.h"
+#include "components/policy/core/common/policy_namespace.h"
+
+namespace policy {
+namespace android {
+
+namespace {
+
+bool CanCache(PolicyErrorMap* policy_error_map,
+ const std::set<std::string>& future_policies,
+ const PolicyMap::const_iterator iter) {
+ return !policy_error_map->HasError(iter->first) &&
+ future_policies.find(iter->first) == future_policies.end() &&
+ !iter->second.ignored() &&
+ !iter->second.HasMessage(PolicyMap::MessageType::kError);
+}
+
+} // namespace
+
+PolicyCacheUpdater::PolicyCacheUpdater(
+ PolicyService* policy_service,
+ const ConfigurationPolicyHandlerList* handler_list)
+ : policy_service_(policy_service), handler_list_(handler_list) {
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, this);
+ UpdateCache(
+ policy_service->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+}
+
+PolicyCacheUpdater::~PolicyCacheUpdater() {
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, this);
+}
+
+void PolicyCacheUpdater::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ UpdateCache(current);
+}
+
+void PolicyCacheUpdater::UpdateCache(const PolicyMap& current_policy_map) {
+ PolicyMap policy_map = current_policy_map.Clone();
+ PolicyErrorMap errors;
+ std::set<std::string> future_policies;
+ handler_list_->ApplyPolicySettings(policy_map,
+ /*prefs=*/nullptr, &errors,
+ /*deprecated_policies*/ nullptr,
+ &future_policies);
+ policy_map.EraseNonmatching(
+ base::BindRepeating(&CanCache, &errors, future_policies));
+ PolicyMapAndroid policy_map_android(policy_map);
+ Java_PolicyCacheUpdater_cachePolicies(base::android::AttachCurrentThread(),
+ policy_map_android.GetJavaObject());
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/android/policy_cache_updater_android.h b/chromium/components/policy/core/browser/android/policy_cache_updater_android.h
new file mode 100644
index 00000000000..3cb9fb01341
--- /dev/null
+++ b/chromium/components/policy/core/browser/android/policy_cache_updater_android.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_ANDROID_POLICY_CACHE_UPDATER_ANDROID_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_ANDROID_POLICY_CACHE_UPDATER_ANDROID_H_
+
+#include "base/memory/raw_ptr.h"
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+#include "components/policy/core/common/policy_service.h"
+
+namespace policy {
+
+class ConfigurationPolicyHandlerList;
+
+namespace android {
+
+class POLICY_EXPORT PolicyCacheUpdater : public PolicyService::Observer {
+ public:
+ PolicyCacheUpdater(PolicyService* policy_service,
+ const ConfigurationPolicyHandlerList* handler_list);
+ PolicyCacheUpdater(const PolicyCacheUpdater&) = delete;
+ PolicyCacheUpdater& operator=(const PolicyCacheUpdater&) = delete;
+ ~PolicyCacheUpdater() override;
+
+ // PolicyService::Observer
+ void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) override;
+
+ private:
+ void UpdateCache(const PolicyMap& current_policy_map);
+
+ raw_ptr<PolicyService> policy_service_;
+ raw_ptr<const ConfigurationPolicyHandlerList> handler_list_;
+};
+
+} // namespace android
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_ANDROID_POLICY_CACHE_UPDATER_ANDROID_H_
diff --git a/chromium/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc b/chromium/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
new file mode 100644
index 00000000000..2d8fb275e5b
--- /dev/null
+++ b/chromium/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/android/policy_cache_updater_android.h"
+
+#include <jni.h>
+#include <memory>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "components/policy/android/test_jni_headers/PolicyCacheUpdaterTestSupporter_jni.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace policy {
+namespace android {
+namespace {
+// The list of policeis that can be cached is controlled by Java library. Hence
+// we use real policy name for testing.
+constexpr char kPolicyName[] = "BrowserSignin";
+constexpr int kPolicyValue = 1;
+
+class StubPolicyHandler : public ConfigurationPolicyHandler {
+ public:
+ StubPolicyHandler(const std::string& policy_name, bool has_error)
+ : policy_name_(policy_name), has_error_(has_error) {}
+ StubPolicyHandler(const StubPolicyHandler&) = delete;
+ StubPolicyHandler& operator=(const StubPolicyHandler&) = delete;
+ ~StubPolicyHandler() override = default;
+
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override {
+ if (has_error_) {
+ errors->AddError(policy_name_, IDS_POLICY_BLOCKED);
+ }
+ return policies.GetValue(kPolicyName, base::Value::Type::INTEGER) &&
+ !has_error_;
+ }
+
+ private:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override {}
+ std::string policy_name_;
+ bool has_error_;
+};
+
+} // namespace
+
+class PolicyCacheUpdaterAndroidTest : public ::testing::Test {
+ public:
+ PolicyCacheUpdaterAndroidTest() {
+ policy_provider_.SetDefaultReturns(
+ true /* is_initialization_complete_return */,
+ true /* is_first_policy_load_complete_return */);
+ j_support_ = Java_PolicyCacheUpdaterTestSupporter_Constructor(env_);
+ policy_service_ = std::make_unique<policy::PolicyServiceImpl>(
+ std::vector<ConfigurationPolicyProvider*>({&policy_provider_}));
+ policy_handler_list_ = std::make_unique<ConfigurationPolicyHandlerList>(
+ ConfigurationPolicyHandlerList::
+ PopulatePolicyHandlerParametersCallback(),
+ GetChromePolicyDetailsCallback(),
+ /*allow_future_policies=*/false);
+ }
+ ~PolicyCacheUpdaterAndroidTest() override = default;
+
+ void SetPolicy(const std::string& policy, int policy_value) {
+ policy_map_.Set(policy, PolicyLevel::POLICY_LEVEL_MANDATORY,
+ PolicyScope::POLICY_SCOPE_MACHINE,
+ PolicySource::POLICY_SOURCE_PLATFORM,
+ base::Value(policy_value),
+ /*external_data_fetcher=*/nullptr);
+ }
+
+ void UpdatePolicy() { policy_provider_.UpdateChromePolicy(policy_map_); }
+
+ void VerifyPolicyName(const std::string& policy,
+ bool has_value,
+ int expected_value) {
+ Java_PolicyCacheUpdaterTestSupporter_verifyPolicyCacheIntValue(
+ env_, j_support_, base::android::ConvertUTF8ToJavaString(env_, policy),
+ has_value, expected_value);
+ }
+
+ ConfigurationPolicyHandlerList* policy_handler_list() {
+ return policy_handler_list_.get();
+ }
+
+ PolicyService* policy_service() { return policy_service_.get(); }
+
+ PolicyMap* policy_map() { return &policy_map_; }
+
+ private:
+ raw_ptr<JNIEnv> env_ = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> j_support_;
+ PolicyMap policy_map_;
+ testing::NiceMock<MockConfigurationPolicyProvider> policy_provider_;
+ std::unique_ptr<PolicyService> policy_service_;
+ std::unique_ptr<ConfigurationPolicyHandlerList> policy_handler_list_;
+ base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestCachePolicy) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ SetPolicy(kPolicyName, kPolicyValue);
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyNotExist) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyErrorPolicy) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/true));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ SetPolicy(kPolicyName, kPolicyValue);
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapIgnoredPolicy) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ SetPolicy(kPolicyName, kPolicyValue);
+ policy_map()->GetMutable(kPolicyName)->SetIgnored();
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapErrorMessagePolicy) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ SetPolicy(kPolicyName, kPolicyValue);
+ policy_map()
+ ->GetMutable(kPolicyName)
+ ->AddMessage(PolicyMap::MessageType::kError, IDS_POLICY_BLOCKED);
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapWarningMessagePolicy) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ SetPolicy(kPolicyName, kPolicyValue);
+ policy_map()
+ ->GetMutable(kPolicyName)
+ ->AddMessage(PolicyMap::MessageType::kWarning, IDS_POLICY_BLOCKED);
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+}
+
+TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicUpdatedBeforeUpdaterCreated) {
+ policy_handler_list()->AddHandler(
+ std::make_unique<StubPolicyHandler>(kPolicyName, /*has_error=*/false));
+
+ SetPolicy(kPolicyName, kPolicyValue);
+ UpdatePolicy();
+ VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+ PolicyCacheUpdater updater(policy_service(), policy_handler_list());
+ VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/browser_policy_connector.cc b/chromium/components/policy/core/browser/browser_policy_connector.cc
new file mode 100644
index 00000000000..56f8f7fdc9d
--- /dev/null
+++ b/chromium/components/policy/core/browser/browser_policy_connector.cc
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/browser_policy_connector.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_statistics_collector.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+
+namespace policy {
+
+namespace {
+
+// The URL for the device management server.
+const char kDefaultDeviceManagementServerUrl[] =
+ "https://m.google.com/devicemanagement/data/api";
+
+const char kDefaultEncryptedReportingServerUrl[] =
+ "https://chromereporting-pa.googleapis.com/v1/record";
+
+// The URL for the realtime reporting server.
+const char kDefaultRealtimeReportingServerUrl[] =
+ "https://chromereporting-pa.googleapis.com/v1/events";
+
+// Regexes that match many of the larger public email providers as we know
+// these users are not from hosted enterprise domains.
+const wchar_t* const kNonManagedDomainPatterns[] = {
+ L"aol\\.com",
+ L"comcast\\.net",
+ L"googlemail\\.com",
+ L"gmail\\.com",
+ L"gmx\\.de",
+ L"hotmail(\\.co|\\.com|)\\.[^.]+", // hotmail.com, hotmail.it, hotmail.co.uk
+ L"live\\.com",
+ L"mail\\.ru",
+ L"msn\\.com",
+ L"naver\\.com",
+ L"orange\\.fr",
+ L"outlook\\.com",
+ L"qq\\.com",
+ L"yahoo(\\.co|\\.com|)\\.[^.]+", // yahoo.com, yahoo.co.uk, yahoo.com.tw
+ L"yandex\\.ru",
+ L"web\\.de",
+ L"wp\\.pl",
+ L"consumer\\.example\\.com",
+};
+
+const char* non_managed_domain_for_testing = nullptr;
+
+// Returns true if |domain| matches the regex |pattern|.
+bool MatchDomain(const std::u16string& domain,
+ const std::u16string& pattern,
+ size_t index) {
+ UErrorCode status = U_ZERO_ERROR;
+ const icu::UnicodeString icu_pattern(pattern.data(), pattern.length());
+ icu::RegexMatcher matcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status);
+ if (!U_SUCCESS(status)) {
+ // http://crbug.com/365351 - if for some reason the matcher creation fails
+ // just return that the pattern doesn't match the domain. This is safe
+ // because the calling method (IsNonEnterpriseUser()) is just used to enable
+ // an optimization for non-enterprise users - better to skip the
+ // optimization than crash.
+ DLOG(ERROR) << "Possible invalid domain pattern: " << pattern
+ << " - Error: " << status;
+ return false;
+ }
+ icu::UnicodeString icu_input(domain.data(), domain.length());
+ matcher.reset(icu_input);
+ status = U_ZERO_ERROR;
+ UBool match = matcher.matches(status);
+ DCHECK(U_SUCCESS(status));
+ return !!match; // !! == convert from UBool to bool.
+}
+
+} // namespace
+
+BrowserPolicyConnector::BrowserPolicyConnector(
+ const HandlerListFactory& handler_list_factory)
+ : BrowserPolicyConnectorBase(handler_list_factory) {
+}
+
+BrowserPolicyConnector::~BrowserPolicyConnector() {
+}
+
+void BrowserPolicyConnector::InitInternal(
+ PrefService* local_state,
+ std::unique_ptr<DeviceManagementService> device_management_service) {
+ device_management_service_ = std::move(device_management_service);
+
+ policy_statistics_collector_ =
+ std::make_unique<policy::PolicyStatisticsCollector>(
+ base::BindRepeating(&GetChromePolicyDetails), GetChromeSchema(),
+ GetPolicyService(), local_state, base::ThreadTaskRunnerHandle::Get());
+ policy_statistics_collector_->Initialize();
+}
+
+void BrowserPolicyConnector::Shutdown() {
+ BrowserPolicyConnectorBase::Shutdown();
+ device_management_service_.reset();
+}
+
+void BrowserPolicyConnector::ScheduleServiceInitialization(
+ int64_t delay_milliseconds) {
+ // Skip device initialization if the BrowserPolicyConnector was never
+ // initialized (unit tests).
+ if (device_management_service_)
+ device_management_service_->ScheduleInitialization(delay_milliseconds);
+}
+
+bool BrowserPolicyConnector::ProviderHasPolicies(
+ const ConfigurationPolicyProvider* provider) const {
+ if (!provider)
+ return false;
+ for (const auto& pair : provider->policies()) {
+ if (!pair.second.empty())
+ return true;
+ }
+ return false;
+}
+
+std::string BrowserPolicyConnector::GetDeviceManagementUrl() const {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kDeviceManagementUrl) &&
+ IsCommandLineSwitchSupported())
+ return command_line->GetSwitchValueASCII(switches::kDeviceManagementUrl);
+ else
+ return kDefaultDeviceManagementServerUrl;
+}
+
+std::string BrowserPolicyConnector::GetRealtimeReportingUrl() const {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kRealtimeReportingUrl) &&
+ IsCommandLineSwitchSupported())
+ return command_line->GetSwitchValueASCII(switches::kRealtimeReportingUrl);
+ else
+ return kDefaultRealtimeReportingServerUrl;
+}
+
+std::string BrowserPolicyConnector::GetEncryptedReportingUrl() const {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kEncryptedReportingUrl) &&
+ IsCommandLineSwitchSupported())
+ return command_line->GetSwitchValueASCII(switches::kEncryptedReportingUrl);
+ else
+ return kDefaultEncryptedReportingServerUrl;
+}
+
+// static
+bool BrowserPolicyConnector::IsNonEnterpriseUser(const std::string& username) {
+ TRACE_EVENT0("browser", "BrowserPolicyConnector::IsNonEnterpriseUser");
+ if (username.empty() || username.find('@') == std::string::npos) {
+ // An empty username means incognito user in case of ChromiumOS and
+ // no logged-in user in case of Chromium (SigninService). Many tests use
+ // nonsense email addresses (e.g. 'test') so treat those as non-enterprise
+ // users.
+ return true;
+ }
+ const std::u16string domain = base::UTF8ToUTF16(
+ gaia::ExtractDomainName(gaia::CanonicalizeEmail(username)));
+ for (size_t i = 0; i < std::size(kNonManagedDomainPatterns); i++) {
+ std::u16string pattern = base::WideToUTF16(kNonManagedDomainPatterns[i]);
+ if (MatchDomain(domain, pattern, i))
+ return true;
+ }
+ if (non_managed_domain_for_testing &&
+ domain == base::UTF8ToUTF16(non_managed_domain_for_testing)) {
+ return true;
+ }
+ return false;
+}
+
+// static
+void BrowserPolicyConnector::SetNonEnterpriseDomainForTesting(
+ const char* domain) {
+ non_managed_domain_for_testing = domain;
+}
+
+// static
+void BrowserPolicyConnector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(
+ policy_prefs::kUserPolicyRefreshRate,
+ CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs);
+ registry->RegisterBooleanPref(
+ policy_prefs::kCloudManagementEnrollmentMandatory, false);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/browser_policy_connector.h b/chromium/components/policy/core/browser/browser_policy_connector.h
new file mode 100644
index 00000000000..6babaffe924
--- /dev/null
+++ b/chromium/components/policy/core/browser/browser_policy_connector.h
@@ -0,0 +1,108 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/browser/browser_policy_connector_base.h"
+#include "components/policy/policy_export.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class DeviceManagementService;
+class PolicyStatisticsCollector;
+
+// The BrowserPolicyConnector keeps some shared components of the policy system.
+// This is a basic implementation that gets extended by platform-specific
+// subclasses.
+class POLICY_EXPORT BrowserPolicyConnector : public BrowserPolicyConnectorBase {
+ public:
+ BrowserPolicyConnector(const BrowserPolicyConnector&) = delete;
+ BrowserPolicyConnector& operator=(const BrowserPolicyConnector&) = delete;
+ ~BrowserPolicyConnector() override;
+
+ // Finalizes the initialization of the connector. This call can be skipped on
+ // tests that don't require the full policy system running.
+ virtual void Init(
+ PrefService* local_state,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) = 0;
+
+ // Checks whether this device is under any kind of enterprise management.
+ virtual bool IsDeviceEnterpriseManaged() const = 0;
+
+ // Checks whether there are any machine-level policies configured.
+ virtual bool HasMachineLevelPolicies() = 0;
+
+ // Cleans up the connector before it can be safely deleted.
+ void Shutdown() override;
+
+ // Schedules initialization of the cloud policy backend services, if the
+ // services are already constructed.
+ void ScheduleServiceInitialization(int64_t delay_milliseconds);
+
+ DeviceManagementService* device_management_service() {
+ return device_management_service_.get();
+ }
+
+ // Returns the URL for the device management service endpoint.
+ std::string GetDeviceManagementUrl() const;
+
+ // Returns the URL for the realtime reporting service endpoint.
+ std::string GetRealtimeReportingUrl() const;
+
+ // Returns the URL for the encrypted reporting service endpoint.
+ std::string GetEncryptedReportingUrl() const;
+
+ // Check whether a user is known to be non-enterprise. Domains such as
+ // gmail.com and googlemail.com are known to not be managed. Also returns
+ // false if the username is empty.
+ static bool IsNonEnterpriseUser(const std::string& username);
+
+ // Allows to register domain for tests that is recognized as non-enterprise.
+ // Note that |domain| basically needs to live until this method is invoked
+ // with a nullptr.
+ static void SetNonEnterpriseDomainForTesting(const char* domain);
+
+ // Registers refresh rate prefs.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Returns true if the command line switch of policy can be used.
+ virtual bool IsCommandLineSwitchSupported() const = 0;
+
+ protected:
+ // Builds an uninitialized BrowserPolicyConnector.
+ // Init() should be called to create and start the policy components.
+ explicit BrowserPolicyConnector(
+ const HandlerListFactory& handler_list_factory);
+
+ // Helper for the public Init() that must be called by subclasses.
+ void InitInternal(
+ PrefService* local_state,
+ std::unique_ptr<DeviceManagementService> device_management_service);
+
+ // Returns true if the given |provider| has any registered policies.
+ bool ProviderHasPolicies(const ConfigurationPolicyProvider* provider) const;
+
+ private:
+ std::unique_ptr<PolicyStatisticsCollector> policy_statistics_collector_;
+
+ std::unique_ptr<DeviceManagementService> device_management_service_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_H_
diff --git a/chromium/components/policy/core/browser/browser_policy_connector_base.cc b/chromium/components/policy/core/browser/browser_policy_connector_base.cc
new file mode 100644
index 00000000000..76903d887a9
--- /dev/null
+++ b/chromium/components/policy/core/browser/browser_policy_connector_base.cc
@@ -0,0 +1,171 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/browser_policy_connector_base.h"
+
+#include <stddef.h>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "components/policy/core/common/chrome_schema.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace policy {
+
+namespace {
+
+// Used in BrowserPolicyConnectorBase::SetPolicyProviderForTesting.
+bool g_created_policy_service = false;
+ConfigurationPolicyProvider* g_testing_provider = nullptr;
+PolicyService* g_testing_policy_service = nullptr;
+
+} // namespace
+
+BrowserPolicyConnectorBase::BrowserPolicyConnectorBase(
+ const HandlerListFactory& handler_list_factory) {
+ // GetPolicyService() must be ready after the constructor is done.
+ // The connector is created very early during startup, when the browser
+ // threads aren't running yet; initialize components that need local_state,
+ // the system request context or other threads (e.g. FILE) at
+ // SetPolicyProviders().
+
+ // Initialize the SchemaRegistry with the Chrome schema before creating any
+ // of the policy providers in subclasses.
+ const Schema& chrome_schema = policy::GetChromeSchema();
+ handler_list_ = handler_list_factory.Run(chrome_schema);
+ schema_registry_.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""),
+ chrome_schema);
+}
+
+BrowserPolicyConnectorBase::~BrowserPolicyConnectorBase() {
+ if (is_initialized()) {
+ // Shutdown() wasn't invoked by our owner after having called
+ // SetPolicyProviders(). This usually means it's an early shutdown and
+ // BrowserProcessImpl::StartTearDown() wasn't invoked.
+ // Cleanup properly in those cases and avoid crashing the ToastCrasher test.
+ Shutdown();
+ }
+}
+
+void BrowserPolicyConnectorBase::Shutdown() {
+ is_initialized_ = false;
+ if (g_testing_provider)
+ g_testing_provider->Shutdown();
+ for (const auto& provider : policy_providers_)
+ provider->Shutdown();
+ // Drop g_testing_provider so that tests executed with --single-process-tests
+ // can call SetPolicyProviderForTesting() again. It is still owned by the
+ // test.
+ g_testing_provider = nullptr;
+ g_created_policy_service = false;
+}
+
+const Schema& BrowserPolicyConnectorBase::GetChromeSchema() const {
+ return policy::GetChromeSchema();
+}
+
+CombinedSchemaRegistry* BrowserPolicyConnectorBase::GetSchemaRegistry() {
+ return &schema_registry_;
+}
+
+PolicyService* BrowserPolicyConnectorBase::GetPolicyService() {
+ if (g_testing_policy_service)
+ return g_testing_policy_service;
+
+ if (policy_service_)
+ return policy_service_.get();
+
+ DCHECK(!is_initialized_);
+ is_initialized_ = true;
+
+ policy_providers_ = CreatePolicyProviders();
+
+ if (g_testing_provider)
+ g_testing_provider->Init(GetSchemaRegistry());
+
+ for (const auto& provider : policy_providers_)
+ provider->Init(GetSchemaRegistry());
+
+ g_created_policy_service = true;
+ policy_service_ =
+ std::make_unique<PolicyServiceImpl>(GetProvidersForPolicyService());
+ return policy_service_.get();
+}
+
+bool BrowserPolicyConnectorBase::HasPolicyService() {
+ return g_testing_policy_service || policy_service_;
+}
+
+const ConfigurationPolicyHandlerList*
+BrowserPolicyConnectorBase::GetHandlerList() const {
+ return handler_list_.get();
+}
+
+std::vector<ConfigurationPolicyProvider*>
+BrowserPolicyConnectorBase::GetPolicyProviders() const {
+ std::vector<ConfigurationPolicyProvider*> providers;
+ for (const auto& provider : policy_providers_)
+ providers.push_back(provider.get());
+
+ return providers;
+}
+
+// static
+void BrowserPolicyConnectorBase::SetPolicyProviderForTesting(
+ ConfigurationPolicyProvider* provider) {
+ // If this function is used by a test then it must be called before the
+ // browser is created, and GetPolicyService() gets called.
+ CHECK(!g_created_policy_service);
+ g_testing_provider = provider;
+}
+
+// static
+void BrowserPolicyConnectorBase::SetPolicyServiceForTesting(
+ PolicyService* policy_service) {
+ g_testing_policy_service = policy_service;
+}
+
+void BrowserPolicyConnectorBase::NotifyWhenResourceBundleReady(
+ base::OnceClosure closure) {
+ DCHECK(!ui::ResourceBundle::HasSharedInstance());
+ resource_bundle_callbacks_.push_back(std::move(closure));
+}
+
+// static
+ConfigurationPolicyProvider*
+BrowserPolicyConnectorBase::GetPolicyProviderForTesting() {
+ return g_testing_provider;
+}
+
+std::vector<ConfigurationPolicyProvider*>
+BrowserPolicyConnectorBase::GetProvidersForPolicyService() {
+ std::vector<ConfigurationPolicyProvider*> providers;
+ if (g_testing_provider) {
+ providers.push_back(g_testing_provider);
+ return providers;
+ }
+ providers.reserve(policy_providers_.size());
+ for (const auto& policy : policy_providers_)
+ providers.push_back(policy.get());
+ return providers;
+}
+
+std::vector<std::unique_ptr<ConfigurationPolicyProvider>>
+BrowserPolicyConnectorBase::CreatePolicyProviders() {
+ return {};
+}
+
+void BrowserPolicyConnectorBase::OnResourceBundleCreated() {
+ std::vector<base::OnceClosure> resource_bundle_callbacks;
+ std::swap(resource_bundle_callbacks, resource_bundle_callbacks_);
+ for (auto& closure : resource_bundle_callbacks)
+ std::move(closure).Run();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/browser_policy_connector_base.h b/chromium/components/policy/core/browser/browser_policy_connector_base.h
new file mode 100644
index 00000000000..e27e95697a3
--- /dev/null
+++ b/chromium/components/policy/core/browser/browser_policy_connector_base.h
@@ -0,0 +1,128 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_BASE_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_BASE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class ConfigurationPolicyProvider;
+class PolicyService;
+class PolicyServiceImpl;
+
+// The BrowserPolicyConnectorBase keeps and initializes some core elements of
+// the policy component, mainly the PolicyProviders and the PolicyService.
+class POLICY_EXPORT BrowserPolicyConnectorBase {
+ public:
+ BrowserPolicyConnectorBase(const BrowserPolicyConnectorBase&) = delete;
+ BrowserPolicyConnectorBase& operator=(const BrowserPolicyConnectorBase&) =
+ delete;
+
+ // Invoke Shutdown() before deleting, see below.
+ virtual ~BrowserPolicyConnectorBase();
+
+ // Stops the policy providers and cleans up the connector before it can be
+ // safely deleted. This must be invoked before the destructor and while the
+ // threads are still running. The policy providers are still valid but won't
+ // update anymore after this call. Subclasses can override this for cleanup
+ // and should call the parent method.
+ virtual void Shutdown();
+
+ // Returns true if SetPolicyProviders() has been called but Shutdown() hasn't
+ // been yet.
+ bool is_initialized() const { return is_initialized_; }
+
+ // Returns a handle to the Chrome schema.
+ const Schema& GetChromeSchema() const;
+
+ // Returns the global CombinedSchemaRegistry. SchemaRegistries from Profiles
+ // should be tracked by the global registry, so that the global policy
+ // providers also load policies for the components of each Profile.
+ CombinedSchemaRegistry* GetSchemaRegistry();
+
+ // Returns the browser-global PolicyService, that contains policies for the
+ // whole browser.
+ PolicyService* GetPolicyService();
+
+ // Returns true if the PolicyService object has already been created.
+ bool HasPolicyService();
+
+ const ConfigurationPolicyHandlerList* GetHandlerList() const;
+
+ std::vector<ConfigurationPolicyProvider*> GetPolicyProviders() const;
+
+ // Sets a |provider| that will be included in PolicyServices returned by
+ // GetPolicyService. This is a static method because local state is
+ // created immediately after the connector, and tests don't have a chance to
+ // inject the provider otherwise. |provider| must outlive the connector, and
+ // its ownership is not taken though the connector will initialize and shut it
+ // down.
+ static void SetPolicyProviderForTesting(
+ ConfigurationPolicyProvider* provider);
+ ConfigurationPolicyProvider* GetPolicyProviderForTesting();
+
+ // Sets the policy service to be returned by |GetPolicyService| during tests.
+ static void SetPolicyServiceForTesting(PolicyService* policy_service);
+
+ // Adds a callback that is notified the the ResourceBundle is loaded.
+ void NotifyWhenResourceBundleReady(base::OnceClosure closure);
+
+ protected:
+ // Builds an uninitialized BrowserPolicyConnectorBase. SetPolicyProviders()
+ // should be called to create and start the policy components.
+ explicit BrowserPolicyConnectorBase(
+ const HandlerListFactory& handler_list_factory);
+
+ // Called from GetPolicyService() to create the set of
+ // ConfigurationPolicyProviders that are used, in decreasing order of
+ // priority.
+ virtual std::vector<std::unique_ptr<ConfigurationPolicyProvider>>
+ CreatePolicyProviders();
+
+ // Must be called when ui::ResourceBundle has been loaded, results in running
+ // any callbacks scheduled in NotifyWhenResourceBundleReady().
+ void OnResourceBundleCreated();
+
+ private:
+ // Returns the providers to pass to the PolicyService. Generally this is the
+ // same as |policy_providers_|, unless SetPolicyProviderForTesting() has been
+ // called.
+ std::vector<ConfigurationPolicyProvider*> GetProvidersForPolicyService();
+
+ // Set to true when the PolicyService has been created, and false in
+ // Shutdown(). Once created the PolicyService is destroyed in the destructor,
+ // not Shutdown().
+ bool is_initialized_ = false;
+
+ // Used to convert policies to preferences. The providers declared below
+ // may trigger policy updates during shutdown, which will result in
+ // |handler_list_| being consulted for policy translation.
+ // Therefore, it's important to destroy |handler_list_| after the providers.
+ std::unique_ptr<ConfigurationPolicyHandlerList> handler_list_;
+
+ // The global SchemaRegistry, which will track all the other registries.
+ CombinedSchemaRegistry schema_registry_;
+
+ // The browser-global policy providers, in decreasing order of priority.
+ std::vector<std::unique_ptr<ConfigurationPolicyProvider>> policy_providers_;
+
+ // Must be deleted before all the policy providers.
+ std::unique_ptr<PolicyServiceImpl> policy_service_;
+
+ // Callbacks scheduled via NotifyWhenResourceBundleReady().
+ std::vector<base::OnceClosure> resource_bundle_callbacks_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_BROWSER_POLICY_CONNECTOR_BASE_H_
diff --git a/chromium/components/policy/core/browser/browser_policy_connector_unittest.cc b/chromium/components/policy/core/browser/browser_policy_connector_unittest.cc
new file mode 100644
index 00000000000..672685aff23
--- /dev/null
+++ b/chromium/components/policy/core/browser/browser_policy_connector_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/browser_policy_connector.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+TEST(BrowserPolicyConnectorTest, IsNonEnterpriseUser) {
+ // List of example emails that are not enterprise users.
+ static const char* kNonEnterpriseUsers[] = {
+ "fizz@aol.com",
+ "foo@gmail.com",
+ "bar@googlemail.com",
+ "baz@hotmail.it",
+ "baz@hotmail.co.uk",
+ "baz@hotmail.com.tw",
+ "user@msn.com",
+ "another_user@live.com",
+ "foo@qq.com",
+ "i_love@yahoo.com",
+ "i_love@yahoo.com.tw",
+ "i_love@yahoo.jp",
+ "i_love@yahoo.co.uk",
+ "user@yandex.ru"
+ };
+
+ // List of example emails that are potential enterprise users.
+ static const char* kEnterpriseUsers[] = {
+ "foo@google.com",
+ "chrome_rules@chromium.org",
+ "user@hotmail.enterprise.com",
+ };
+
+ for (unsigned int i = 0; i < std::size(kNonEnterpriseUsers); ++i) {
+ std::string username(kNonEnterpriseUsers[i]);
+ EXPECT_TRUE(BrowserPolicyConnector::IsNonEnterpriseUser(username)) <<
+ "IsNonEnterpriseUser returned false for " << username;
+ }
+ for (unsigned int i = 0; i < std::size(kEnterpriseUsers); ++i) {
+ std::string username(kEnterpriseUsers[i]);
+ EXPECT_FALSE(BrowserPolicyConnector::IsNonEnterpriseUser(username)) <<
+ "IsNonEnterpriseUser returned true for " << username;
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/cloud/message_util.cc b/chromium/components/policy/core/browser/cloud/message_util.cc
new file mode 100644
index 00000000000..385884d3eae
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/message_util.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/cloud/message_util.h"
+
+#include "base/notreached.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+int GetIDSForDMStatus(DeviceManagementStatus status) {
+ switch (status) {
+ case DM_STATUS_SUCCESS:
+ return IDS_POLICY_DM_STATUS_SUCCESS;
+ case DM_STATUS_REQUEST_INVALID:
+ return IDS_POLICY_DM_STATUS_REQUEST_INVALID;
+ case DM_STATUS_REQUEST_FAILED:
+ return IDS_POLICY_DM_STATUS_REQUEST_FAILED;
+ case DM_STATUS_TEMPORARY_UNAVAILABLE:
+ return IDS_POLICY_DM_STATUS_TEMPORARY_UNAVAILABLE;
+ case DM_STATUS_HTTP_STATUS_ERROR:
+ return IDS_POLICY_DM_STATUS_HTTP_STATUS_ERROR;
+ case DM_STATUS_RESPONSE_DECODING_ERROR:
+ return IDS_POLICY_DM_STATUS_RESPONSE_DECODING_ERROR;
+ case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
+ return IDS_POLICY_DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
+ case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
+ return IDS_POLICY_DM_STATUS_SERVICE_DEVICE_NOT_FOUND;
+ case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
+ return IDS_POLICY_DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+ case DM_STATUS_SERVICE_ACTIVATION_PENDING:
+ return IDS_POLICY_DM_STATUS_SERVICE_ACTIVATION_PENDING;
+ case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
+ return IDS_POLICY_DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER;
+ case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
+ return IDS_POLICY_DM_STATUS_SERVICE_DEVICE_ID_CONFLICT;
+ case DM_STATUS_SERVICE_MISSING_LICENSES:
+ return IDS_POLICY_DM_STATUS_SERVICE_MISSING_LICENSES;
+ case DM_STATUS_SERVICE_DEPROVISIONED:
+ return IDS_POLICY_DM_STATUS_SERVICE_DEPROVISIONED;
+ case DM_STATUS_SERVICE_DOMAIN_MISMATCH:
+ return IDS_POLICY_DM_STATUS_SERVICE_DOMAIN_MISMATCH;
+ case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
+ return IDS_POLICY_DM_STATUS_SERVICE_POLICY_NOT_FOUND;
+ case DM_STATUS_CANNOT_SIGN_REQUEST:
+ return IDS_POLICY_DM_STATUS_CANNOT_SIGN_REQUEST;
+ case DM_STATUS_REQUEST_TOO_LARGE:
+ return IDS_POLICY_DM_STATUS_REQUEST_TOO_LARGE;
+ case DM_STATUS_SERVICE_ARC_DISABLED:
+ // This error is never shown on the UI.
+ return IDS_POLICY_DM_STATUS_UNKNOWN_ERROR;
+ case DM_STATUS_SERVICE_TOO_MANY_REQUESTS:
+ return IDS_POLICY_DM_STATUS_SERVICE_TOO_MANY_REQUESTS;
+ case DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE:
+ return IDS_POLICY_DM_STATUS_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE;
+ case DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL:
+ return IDS_POLICY_DM_STATUS_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL;
+ case DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED:
+ // This is shown only on registration failed.
+ return IDS_POLICY_DM_STATUS_UNKNOWN_ERROR;
+ case DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE:
+ return IDS_POLICY_DM_STATUS_SERVICE_DOMAIN_MISMATCH;
+ }
+ NOTREACHED() << "Unhandled DM status " << status;
+ return IDS_POLICY_DM_STATUS_UNKNOWN_ERROR;
+}
+
+int GetIDSForValidationStatus(CloudPolicyValidatorBase::Status status) {
+ switch (status) {
+ case CloudPolicyValidatorBase::VALIDATION_OK:
+ return IDS_POLICY_VALIDATION_OK;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_INITIAL_SIGNATURE:
+ return IDS_POLICY_VALIDATION_BAD_INITIAL_SIGNATURE;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE:
+ return IDS_POLICY_VALIDATION_BAD_SIGNATURE;
+ case CloudPolicyValidatorBase::VALIDATION_ERROR_CODE_PRESENT:
+ return IDS_POLICY_VALIDATION_ERROR_CODE_PRESENT;
+ case CloudPolicyValidatorBase::VALIDATION_PAYLOAD_PARSE_ERROR:
+ return IDS_POLICY_VALIDATION_PAYLOAD_PARSE_ERROR;
+ case CloudPolicyValidatorBase::VALIDATION_WRONG_POLICY_TYPE:
+ return IDS_POLICY_VALIDATION_WRONG_POLICY_TYPE;
+ case CloudPolicyValidatorBase::VALIDATION_WRONG_SETTINGS_ENTITY_ID:
+ return IDS_POLICY_VALIDATION_WRONG_SETTINGS_ENTITY_ID;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_TIMESTAMP:
+ return IDS_POLICY_VALIDATION_BAD_TIMESTAMP;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_DM_TOKEN:
+ return IDS_POLICY_VALIDATION_BAD_DM_TOKEN;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_DEVICE_ID:
+ return IDS_POLICY_VALIDATION_BAD_DEVICE_ID;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_USER:
+ return IDS_POLICY_VALIDATION_BAD_USER;
+ case CloudPolicyValidatorBase::VALIDATION_POLICY_PARSE_ERROR:
+ return IDS_POLICY_VALIDATION_POLICY_PARSE_ERROR;
+ case CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE:
+ return IDS_POLICY_VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
+ case CloudPolicyValidatorBase::VALIDATION_VALUE_WARNING:
+ return IDS_POLICY_VALIDATION_VALUE_WARNING;
+ case CloudPolicyValidatorBase::VALIDATION_VALUE_ERROR:
+ return IDS_POLICY_VALIDATION_VALUE_ERROR;
+ case CloudPolicyValidatorBase::VALIDATION_STATUS_SIZE:
+ NOTREACHED();
+ }
+ NOTREACHED() << "Unhandled validation status " << status;
+ return IDS_POLICY_VALIDATION_UNKNOWN_ERROR;
+}
+
+int GetIDSForStoreStatus(CloudPolicyStore::Status status) {
+ switch (status) {
+ case CloudPolicyStore::STATUS_OK:
+ return IDS_POLICY_STORE_STATUS_OK;
+ case CloudPolicyStore::STATUS_LOAD_ERROR:
+ return IDS_POLICY_STORE_STATUS_LOAD_ERROR;
+ case CloudPolicyStore::STATUS_STORE_ERROR:
+ return IDS_POLICY_STORE_STATUS_STORE_ERROR;
+ case CloudPolicyStore::STATUS_PARSE_ERROR:
+ return IDS_POLICY_STORE_STATUS_PARSE_ERROR;
+ case CloudPolicyStore::STATUS_SERIALIZE_ERROR:
+ return IDS_POLICY_STORE_STATUS_SERIALIZE_ERROR;
+ case CloudPolicyStore::STATUS_VALIDATION_ERROR:
+ // This is handled separately below to include the validation error.
+ break;
+ case CloudPolicyStore::STATUS_BAD_STATE:
+ return IDS_POLICY_STORE_STATUS_BAD_STATE;
+ }
+ NOTREACHED() << "Unhandled store status " << status;
+ return IDS_POLICY_STORE_STATUS_UNKNOWN_ERROR;
+}
+
+} // namespace
+
+std::u16string FormatDeviceManagementStatus(DeviceManagementStatus status) {
+ return l10n_util::GetStringUTF16(GetIDSForDMStatus(status));
+}
+
+std::u16string FormatValidationStatus(
+ CloudPolicyValidatorBase::Status validation_status) {
+ return l10n_util::GetStringUTF16(
+ GetIDSForValidationStatus(validation_status));
+}
+
+std::u16string FormatStoreStatus(
+ CloudPolicyStore::Status store_status,
+ CloudPolicyValidatorBase::Status validation_status) {
+ if (store_status == CloudPolicyStore::STATUS_VALIDATION_ERROR) {
+ return l10n_util::GetStringFUTF16(
+ IDS_POLICY_STORE_STATUS_VALIDATION_ERROR,
+ l10n_util::GetStringUTF16(
+ GetIDSForValidationStatus(validation_status)));
+ }
+
+ return l10n_util::GetStringUTF16(GetIDSForStoreStatus(store_status));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/cloud/message_util.h b/chromium/components/policy/core/browser/cloud/message_util.h
new file mode 100644
index 00000000000..c99327d04fc
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/message_util.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CLOUD_MESSAGE_UTIL_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CLOUD_MESSAGE_UTIL_H_
+
+#include <string>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Returns a string describing |status| suitable for display in UI.
+POLICY_EXPORT std::u16string FormatDeviceManagementStatus(
+ DeviceManagementStatus status);
+
+// Returns a string describing |validation_status| suitable for display in UI.
+POLICY_EXPORT std::u16string FormatValidationStatus(
+ CloudPolicyValidatorBase::Status validation_status);
+
+// Returns a textual description of |store_status| for display in the UI. If
+// |store_status| is STATUS_VALIDATION_FAILED, |validation_status| will be
+// consulted to create a description of the validation failure.
+POLICY_EXPORT std::u16string FormatStoreStatus(
+ CloudPolicyStore::Status store_status,
+ CloudPolicyValidatorBase::Status validation_status);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CLOUD_MESSAGE_UTIL_H_
diff --git a/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.cc b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.cc
new file mode 100644
index 00000000000..219f5b61aaf
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.cc
@@ -0,0 +1,350 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/cloud/user_policy_signin_service_base.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/dcheck_is_on.h"
+#include "base/location.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
+#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/account_info.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+namespace {
+
+#if BUILDFLAG(IS_ANDROID)
+const em::DeviceRegisterRequest::Type kCloudPolicyRegistrationType =
+ em::DeviceRegisterRequest::ANDROID_BROWSER;
+#elif BUILDFLAG(IS_IOS)
+// TODO(crbug.com/1312263): Use em::DeviceRegisterRequest::IOS_BROWSER when
+// supported in the dmserver. The type for Desktop is temporarily used on iOS
+// to allow early testing of the feature before the DMServer can support iOS
+// User Policy.
+const em::DeviceRegisterRequest::Type kCloudPolicyRegistrationType =
+ em::DeviceRegisterRequest::BROWSER;
+#else
+const em::DeviceRegisterRequest::Type kCloudPolicyRegistrationType =
+ em::DeviceRegisterRequest::BROWSER;
+#endif
+
+} // namespace
+
+namespace policy {
+
+UserPolicySigninServiceBase::UserPolicySigninServiceBase(
+ PrefService* local_state,
+ DeviceManagementService* device_management_service,
+ UserCloudPolicyManager* policy_manager,
+ signin::IdentityManager* identity_manager,
+ scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory)
+ : policy_manager_(policy_manager),
+ identity_manager_(identity_manager),
+ local_state_(local_state),
+ device_management_service_(device_management_service),
+ system_url_loader_factory_(system_url_loader_factory) {}
+
+UserPolicySigninServiceBase::~UserPolicySigninServiceBase() {}
+
+void UserPolicySigninServiceBase::FetchPolicyForSignedInUser(
+ const AccountId& account_id,
+ const std::string& dm_token,
+ const std::string& client_id,
+ scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory,
+ PolicyFetchCallback callback) {
+ UserCloudPolicyManager* manager = policy_manager();
+ DCHECK(manager);
+
+ // Initialize the cloud policy manager there was no prior initialization.
+ if (!manager->core()->client()) {
+ std::unique_ptr<CloudPolicyClient> client =
+ UserCloudPolicyManager::CreateCloudPolicyClient(
+ device_management_service_, profile_url_loader_factory);
+ client->SetupRegistration(
+ dm_token, client_id,
+ std::vector<std::string>() /* user_affiliation_ids */);
+ DCHECK(client->is_registered());
+ DCHECK(!manager->core()->client());
+ InitializeUserCloudPolicyManager(account_id, std::move(client));
+ }
+
+ DCHECK(manager->IsClientRegistered());
+
+ // Now initiate a policy fetch.
+ manager->core()->service()->RefreshPolicy(std::move(callback));
+}
+
+void UserPolicySigninServiceBase::OnPolicyFetched(CloudPolicyClient* client) {}
+
+void UserPolicySigninServiceBase::OnRegistrationStateChanged(
+ CloudPolicyClient* client) {}
+
+void UserPolicySigninServiceBase::OnClientError(CloudPolicyClient* client) {
+ if (client->is_registered()) {
+ // If the client is already registered, it means this error must have
+ // come from a policy fetch.
+ if (client->status() == DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED) {
+ // OK, policy fetch failed with MANAGEMENT_NOT_SUPPORTED - this is our
+ // trigger to revert to "unmanaged" mode (we will check for management
+ // being re-enabled on the next restart and/or login).
+ DVLOG(1) << "DMServer returned NOT_SUPPORTED error - removing policy";
+
+ // Can't shutdown now because we're in the middle of a callback from
+ // the CloudPolicyClient, so queue up a task to do the shutdown.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &UserPolicySigninServiceBase::ShutdownUserCloudPolicyManager,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ DVLOG(1) << "Error fetching policy: " << client->status();
+ }
+ }
+}
+
+void UserPolicySigninServiceBase::Shutdown() {
+ PrepareForUserCloudPolicyManagerShutdown();
+}
+
+void UserPolicySigninServiceBase::PrepareForUserCloudPolicyManagerShutdown() {
+ registration_helper_.reset();
+ UserCloudPolicyManager* manager = policy_manager();
+ if (manager && manager->core()->client())
+ manager->core()->client()->RemoveObserver(this);
+ if (manager && manager->core()->service())
+ manager->core()->service()->RemoveObserver(this);
+}
+
+std::unique_ptr<CloudPolicyClient>
+UserPolicySigninServiceBase::CreateClientForRegistrationOnly(
+ const std::string& username) {
+ DCHECK(!username.empty());
+ // We should not be called with a client already initialized.
+ DCHECK(!policy_manager() || !policy_manager()->core()->client());
+
+ // If the user should not get policy, just bail out.
+ if (!policy_manager() || !ShouldLoadPolicyForUser(username)) {
+ DVLOG(1) << "Signed in user is not in the allowlist";
+ return nullptr;
+ }
+
+ // If the DeviceManagementService is not yet initialized, start it up now.
+ device_management_service_->ScheduleInitialization(0);
+
+ // Create a new CloudPolicyClient for fetching the DMToken.
+ return UserCloudPolicyManager::CreateCloudPolicyClient(
+ device_management_service_, system_url_loader_factory_);
+}
+
+bool UserPolicySigninServiceBase::ShouldLoadPolicyForUser(
+ const std::string& username) {
+ if (username.empty())
+ return false; // Not signed in.
+
+ return !BrowserPolicyConnector::IsNonEnterpriseUser(username);
+}
+
+void UserPolicySigninServiceBase::InitializeForSignedInUser(
+ const AccountId& account_id,
+ scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory) {
+ DCHECK(account_id.is_valid());
+ UserCloudPolicyManager* manager = policy_manager();
+ if (!ShouldLoadPolicyForUser(account_id.GetUserEmail())) {
+ manager->SetPoliciesRequired(false);
+ DVLOG(1) << "Policy load not enabled for user: "
+ << account_id.GetUserEmail();
+ return;
+ }
+
+ // Initialize the UCPM if it is not already initialized.
+ if (!manager->core()->service()) {
+ // If there is no cached DMToken then we can detect this when the
+ // OnCloudPolicyServiceInitializationCompleted() callback is invoked and
+ // this will initiate a policy fetch.
+ InitializeUserCloudPolicyManager(
+ account_id,
+ UserCloudPolicyManager::CreateCloudPolicyClient(
+ device_management_service_, profile_url_loader_factory));
+ } else {
+ manager->SetSigninAccountId(account_id);
+ }
+
+ // If the CloudPolicyService is initialized, kick off registration.
+ // Otherwise OnCloudPolicyServiceInitializationCompleted is invoked as soon as
+ // the service finishes its initialization.
+ if (manager->core()->service()->IsInitializationComplete())
+ OnCloudPolicyServiceInitializationCompleted();
+}
+
+void UserPolicySigninServiceBase::InitializeUserCloudPolicyManager(
+ const AccountId& account_id,
+ std::unique_ptr<CloudPolicyClient> client) {
+ DCHECK(client);
+ UserCloudPolicyManager* manager = policy_manager();
+ manager->SetSigninAccountId(account_id);
+ DCHECK(!manager->core()->client());
+ manager->Connect(local_state_, std::move(client));
+ DCHECK(manager->core()->service());
+
+ // Observe the client to detect errors fetching policy.
+ manager->core()->client()->AddObserver(this);
+ // Observe the service to determine when it's initialized.
+ manager->core()->service()->AddObserver(this);
+}
+
+void UserPolicySigninServiceBase::ShutdownUserCloudPolicyManager() {
+ PrepareForUserCloudPolicyManagerShutdown();
+ UserCloudPolicyManager* manager = policy_manager();
+ if (manager)
+ manager->DisconnectAndRemovePolicy();
+}
+
+void UserPolicySigninServiceBase::CancelPendingRegistration() {
+ weak_factory_for_registration_.InvalidateWeakPtrs();
+ registration_helper_.reset();
+}
+
+void UserPolicySigninServiceBase::CallPolicyRegistrationCallback(
+ std::unique_ptr<CloudPolicyClient> client,
+ PolicyRegistrationCallback callback) {
+ registration_helper_.reset();
+ std::move(callback).Run(client->dm_token(), client->client_id());
+}
+
+void UserPolicySigninServiceBase::RegisterForPolicyWithAccountId(
+ const std::string& username,
+ const CoreAccountId& account_id,
+ PolicyRegistrationCallback callback) {
+ DCHECK(!account_id.empty());
+
+ if (policy_manager() && policy_manager()->IsClientRegistered()) {
+ // Reuse the already fetched DM token if the client of the manager is
+ // already registered.
+ std::move(callback).Run(policy_manager()->core()->client()->dm_token(),
+ policy_manager()->core()->client()->client_id());
+ return;
+ }
+
+ // Create a new CloudPolicyClient for fetching the DMToken. This is a
+ // different client from the one used by the manager.
+ std::unique_ptr<CloudPolicyClient> policy_client =
+ CreateClientForRegistrationOnly(username);
+ if (!policy_client) {
+ std::move(callback).Run(std::string(), std::string());
+ return;
+ }
+
+ CancelPendingRegistration();
+
+ // Fire off the registration process. Callback owns and keeps the
+ // CloudPolicyClient alive for the length of the registration process.
+ registration_helper_ = std::make_unique<CloudPolicyClientRegistrationHelper>(
+ policy_client.get(), kCloudPolicyRegistrationType);
+
+ // Using a raw pointer to |this| is okay, because the service owns
+ // |registration_helper_|.
+ auto registration_callback = base::BindOnce(
+ &UserPolicySigninServiceBase::CallPolicyRegistrationCallback,
+ base::Unretained(this), std::move(policy_client), std::move(callback));
+ registration_helper_->StartRegistration(identity_manager(), account_id,
+ std::move(registration_callback));
+}
+
+void UserPolicySigninServiceBase::RegisterCloudPolicyService() {
+ DCHECK(
+ identity_manager()->HasPrimaryAccount(GetConsentLevelForRegistration()));
+ DCHECK(policy_manager()->core()->client());
+ DCHECK(!policy_manager()->IsClientRegistered());
+
+ DVLOG(1) << "Fetching new DM Token";
+
+ // Do nothing if already starting the registration process in which case there
+ // will be an instance of |registration_helper_|.
+ if (registration_helper_)
+ return;
+
+ UpdateLastPolicyCheckTime();
+
+ // Start the process of registering the CloudPolicyClient. Once it completes,
+ // policy fetch will automatically happen.
+ registration_helper_ = std::make_unique<CloudPolicyClientRegistrationHelper>(
+ policy_manager()->core()->client(), kCloudPolicyRegistrationType);
+ registration_helper_->StartRegistration(
+ identity_manager(),
+ identity_manager()->GetPrimaryAccountId(GetConsentLevelForRegistration()),
+ base::BindOnce(&UserPolicySigninServiceBase::OnRegistrationComplete,
+ base::Unretained(this)));
+}
+
+void UserPolicySigninServiceBase::OnRegistrationComplete() {
+ ProhibitSignoutIfNeeded();
+ registration_helper_.reset();
+}
+
+base::TimeDelta UserPolicySigninServiceBase::GetTryRegistrationDelay() {
+ return base::TimeDelta();
+}
+
+void UserPolicySigninServiceBase::
+ OnCloudPolicyServiceInitializationCompleted() {
+ UserCloudPolicyManager* manager = policy_manager();
+ DCHECK(manager->core()->service()->IsInitializationComplete());
+ // The service is now initialized - if the client is not yet registered, then
+ // it means that there is no cached policy and so we need to initiate a new
+ // client registration.
+ if (manager->IsClientRegistered()) {
+ DVLOG(1) << "Client already registered - not fetching DMToken";
+ ProhibitSignoutIfNeeded();
+ return;
+ }
+
+ if (!CanApplyPolicies(/*check_for_refresh_token=*/true)) {
+ // No token yet. This can only happen on Desktop platforms which should
+ // listen to OnRefreshTokenUpdatedForAccount() and will re-attempt
+ // registration once the token is available.
+ DLOG(WARNING) << "No OAuth Refresh Token - delaying policy download";
+ return;
+ }
+
+ base::TimeDelta try_registration_delay = GetTryRegistrationDelay();
+ if (try_registration_delay.is_zero()) {
+ // If the try registration delay is 0, register the cloud policy service
+ // immediately without queueing a task. This is the case for Desktop.
+ RegisterCloudPolicyService();
+ } else {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&UserPolicySigninServiceBase::RegisterCloudPolicyService,
+ weak_factory_for_registration_.GetWeakPtr()),
+ try_registration_delay);
+ }
+
+ ProhibitSignoutIfNeeded();
+}
+
+void UserPolicySigninServiceBase::ProhibitSignoutIfNeeded() {}
+
+bool UserPolicySigninServiceBase::CanApplyPolicies(
+ bool check_for_refresh_token) {
+ return false;
+}
+
+void UserPolicySigninServiceBase::UpdateLastPolicyCheckTime() {}
+
+signin::ConsentLevel
+UserPolicySigninServiceBase::GetConsentLevelForRegistration() {
+ return signin::ConsentLevel::kSignin;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.h b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.h
new file mode 100644
index 00000000000..cbb425fe9b3
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_base.h
@@ -0,0 +1,225 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "google_apis/gaia/core_account_id.h"
+
+class AccountId;
+class PrefService;
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class DeviceManagementService;
+class UserCloudPolicyManager;
+class CloudPolicyClientRegistrationHelper;
+class CloudPolicyClient;
+
+// The UserPolicySigninService is responsible for interacting with the policy
+// infrastructure (mainly UserCloudPolicyManager) to load policy for the signed
+// in user. This is the base class that contains shared behavior.
+//
+// At signin time, this class initializes the UserCloudPolicyManager and loads
+// policy before any other signed in services are initialized. After each
+// restart, this class ensures that the CloudPolicyClient is registered (in case
+// the policy server was offline during the initial policy fetch) and if not it
+// initiates a fresh registration process.
+//
+// Finally, if the user signs out, this class is responsible for shutting down
+// the policy infrastructure to ensure that any cached policy is cleared.
+class POLICY_EXPORT UserPolicySigninServiceBase
+ : public KeyedService,
+ public CloudPolicyClient::Observer,
+ public CloudPolicyService::Observer {
+ public:
+ // The callback invoked once policy registration is complete. Passed
+ // |dm_token| and |client_id| parameters are empty if policy registration
+ // failed.
+ typedef base::OnceCallback<void(const std::string& dm_token,
+ const std::string& client_id)>
+ PolicyRegistrationCallback;
+
+ // The callback invoked once policy fetch is complete. Passed boolean
+ // parameter is set to true if the policy fetch succeeded.
+ typedef base::OnceCallback<void(bool)> PolicyFetchCallback;
+
+ // Creates a UserPolicySigninServiceBase associated with the passed |profile|.
+ UserPolicySigninServiceBase(
+ PrefService* local_state,
+ DeviceManagementService* device_management_service,
+ UserCloudPolicyManager* policy_manager,
+ signin::IdentityManager* identity_manager,
+ scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory);
+ UserPolicySigninServiceBase(const UserPolicySigninServiceBase&) = delete;
+ UserPolicySigninServiceBase& operator=(const UserPolicySigninServiceBase&) =
+ delete;
+ ~UserPolicySigninServiceBase() override;
+
+ // Initiates a policy fetch as part of user signin, using a |dm_token| and
+ // |client_id| fetched via RegisterForPolicyXXX(). |callback| is invoked
+ // once the policy fetch is complete, passing true if the policy fetch
+ // succeeded.
+ // Virtual for testing.
+ virtual void FetchPolicyForSignedInUser(
+ const AccountId& account_id,
+ const std::string& dm_token,
+ const std::string& client_id,
+ scoped_refptr<network::SharedURLLoaderFactory> profile_url_loader_factory,
+ PolicyFetchCallback callback);
+
+ // CloudPolicyService::Observer implementation:
+ void OnCloudPolicyServiceInitializationCompleted() override;
+
+ // CloudPolicyClient::Observer implementation:
+ void OnPolicyFetched(CloudPolicyClient* client) override;
+ void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+ void OnClientError(CloudPolicyClient* client) override;
+
+ // KeyedService implementation:
+ void Shutdown() override;
+
+ // Registers to DM Server to get a DM token to fetch user policies for that
+ // account.
+ //
+ // Registration goes through the following steps:
+ // 1) Request an OAuth2 access token to let the account access the service.
+ // 2) Fetch the user info tied to the access token.
+ // 3) Register the account to DMServer using the access token and user info
+ // to get a DM token that allows fetching user policies for the
+ // registered account.
+ // 4) Invoke |callback| when the DM token is available. Will pass an empty
+ // token when the account isn't managed OR there is an error during the
+ // registration.
+ virtual void RegisterForPolicyWithAccountId(
+ const std::string& username,
+ const CoreAccountId& account_id,
+ PolicyRegistrationCallback callback);
+
+ protected:
+ // Invoked to initialize the cloud policy service for |account_id|, which is
+ // the account associated with the Profile that owns this service. This is
+ // invoked from InitializeOnProfileReady() if the Profile already has a
+ // signed-in account at startup, or (on the desktop platforms) as soon as the
+ // user signs-in and an OAuth2 login refresh token becomes available.
+ void InitializeForSignedInUser(const AccountId& account_id,
+ scoped_refptr<network::SharedURLLoaderFactory>
+ profile_url_loader_factory);
+
+ // Initializes the cloud policy manager with the passed |client|. This is
+ // called from InitializeForSignedInUser() when the Profile already has a
+ // signed in account at startup, and from FetchPolicyForSignedInUser() during
+ // the initial policy fetch after signing in.
+ virtual void InitializeUserCloudPolicyManager(
+ const AccountId& account_id,
+ std::unique_ptr<CloudPolicyClient> client);
+
+ // Prepares for the UserCloudPolicyManager to be shutdown due to
+ // user signout or profile destruction.
+ virtual void PrepareForUserCloudPolicyManagerShutdown();
+
+ // Shuts down the UserCloudPolicyManager (for example, after the user signs
+ // out) and deletes any cached policy.
+ virtual void ShutdownUserCloudPolicyManager();
+
+ // Updates the timestamp of the last policy check. Implemented on mobile
+ // platforms for network efficiency.
+ virtual void UpdateLastPolicyCheckTime();
+
+ // Gets the sign-in consent level required to perform registration.
+ virtual signin::ConsentLevel GetConsentLevelForRegistration();
+
+ // Gets the delay before the next registration.
+ virtual base::TimeDelta GetTryRegistrationDelay();
+
+ // Prohibits signout if needed when the account is registered for cloud policy
+ // . Might be no-op for some platforms (eg., iOS and Android).
+ virtual void ProhibitSignoutIfNeeded();
+
+ // Returns true when policies can be applied for the profile. The profile has
+ // to be at least tied to an account.
+ virtual bool CanApplyPolicies(bool check_for_refresh_token);
+
+ // Cancels the pending task that does registration. This invalidates the
+ // |weak_factory_for_registration_| weak pointers used for registration.
+ void CancelPendingRegistration();
+
+ // Convenience helpers to get the associated UserCloudPolicyManager and
+ // IdentityManager.
+ UserCloudPolicyManager* policy_manager() { return policy_manager_; }
+ signin::IdentityManager* identity_manager() { return identity_manager_; }
+
+ signin::ConsentLevel consent_level() const { return consent_level_; }
+
+ scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory() {
+ return system_url_loader_factory_;
+ }
+
+ private:
+ // Returns a CloudPolicyClient to perform a registration with the DM server,
+ // or NULL if |username| shouldn't register for policy management.
+ std::unique_ptr<CloudPolicyClient> CreateClientForRegistrationOnly(
+ const std::string& username);
+
+ // Returns false if cloud policy is disabled or if the passed |email_address|
+ // is definitely not from a hosted domain (according to the list in
+ // BrowserPolicyConnector::IsNonEnterpriseUser()).
+ bool ShouldLoadPolicyForUser(const std::string& email_address);
+
+ // Handler to call the policy registration callback that provides the DM
+ // token.
+ void CallPolicyRegistrationCallback(std::unique_ptr<CloudPolicyClient> client,
+ PolicyRegistrationCallback callback);
+
+ // Fetches an OAuth token to allow the cloud policy service to register with
+ // the cloud policy server. |oauth_login_token| should contain an OAuth login
+ // refresh token that can be downscoped to get an access token for the
+ // device_management service.
+ void RegisterCloudPolicyService();
+
+ // Callback invoked when policy registration has finished.
+ void OnRegistrationComplete();
+
+ // Weak pointer to the UserCloudPolicyManager and IdentityManager this service
+ // is associated with.
+ raw_ptr<UserCloudPolicyManager> policy_manager_;
+ raw_ptr<signin::IdentityManager> identity_manager_;
+
+ raw_ptr<PrefService> local_state_;
+ raw_ptr<DeviceManagementService> device_management_service_;
+ scoped_refptr<network::SharedURLLoaderFactory> system_url_loader_factory_;
+
+ signin::ConsentLevel consent_level_ = signin::ConsentLevel::kSignin;
+
+ // Helper for registering the client to DMServer to get a DM token using a
+ // cloud policy client. When there is an instance of |registration_helper_|,
+ // it means that registration is ongoing. There is no registration when null.
+ std::unique_ptr<CloudPolicyClientRegistrationHelper> registration_helper_;
+
+ base::WeakPtrFactory<UserPolicySigninServiceBase> weak_factory_{this};
+
+ // Weak pointer factory used for registration.
+ base::WeakPtrFactory<UserPolicySigninServiceBase>
+ weak_factory_for_registration_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_BASE_H_
diff --git a/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.cc b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.cc
new file mode 100644
index 00000000000..7371aff11be
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.cc
@@ -0,0 +1,81 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/cloud/user_policy_signin_service_util.h"
+
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "net/base/network_change_notifier.h"
+
+namespace policy {
+
+bool IsSignoutEvent(const signin::PrimaryAccountChangeEvent& event) {
+ return event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
+ signin::PrimaryAccountChangeEvent::Type::kCleared;
+}
+
+bool IsTurnOffSyncEvent(const signin::PrimaryAccountChangeEvent& event) {
+ return event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
+ signin::PrimaryAccountChangeEvent::Type::kCleared;
+}
+
+bool IsAnySigninEvent(const signin::PrimaryAccountChangeEvent& event) {
+ return event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
+ signin::PrimaryAccountChangeEvent::Type::kSet ||
+ event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
+ signin::PrimaryAccountChangeEvent::Type::kSet;
+}
+
+bool CanApplyPoliciesForSignedInUser(
+ bool check_for_refresh_token,
+ signin::ConsentLevel consent_level,
+ signin::IdentityManager* identity_manager) {
+ return (
+ check_for_refresh_token
+ ? identity_manager->HasPrimaryAccountWithRefreshToken(consent_level)
+ : identity_manager->HasPrimaryAccount(consent_level));
+}
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+
+base::Time GetLastPolicyCheckTimeFromPrefs(PrefService* prefs) {
+ return base::Time::FromInternalValue(
+ prefs->GetInt64(policy::policy_prefs::kLastPolicyCheckTime));
+}
+
+void UpdateLastPolicyCheckTimeInPrefs(PrefService* prefs) {
+ // Persist the current time as the last policy registration attempt time.
+ prefs->SetInt64(policy_prefs::kLastPolicyCheckTime,
+ base::Time::Now().ToInternalValue());
+}
+
+base::TimeDelta GetTryRegistrationDelayFromPrefs(PrefService* prefs) {
+ net::NetworkChangeNotifier::ConnectionType connection_type =
+ net::NetworkChangeNotifier::GetConnectionType();
+ base::TimeDelta retry_delay = base::Days(3);
+ if (connection_type == net::NetworkChangeNotifier::CONNECTION_ETHERNET ||
+ connection_type == net::NetworkChangeNotifier::CONNECTION_WIFI) {
+ retry_delay = base::Days(1);
+ }
+
+ base::Time last_check_time = GetLastPolicyCheckTimeFromPrefs(prefs);
+ base::Time now = base::Time::Now();
+ base::Time next_check_time = last_check_time + retry_delay;
+
+ // If the current timestamp (|now|) falls between |last_check_time| and
+ // |next_check_time|, return the necessary |try_registration_delay| to reach
+ // |next_check_time| from current time (|now|)). Returns the default
+ // |try_registration_delay| otherwise to perform the overdue registration
+ // asap.
+ base::TimeDelta try_registration_delay = base::Seconds(5);
+ if (now > last_check_time && now < next_check_time)
+ try_registration_delay = next_check_time - now;
+
+ return try_registration_delay;
+}
+
+#endif
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.h b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.h
new file mode 100644
index 00000000000..4432bbc2d90
--- /dev/null
+++ b/chromium/components/policy/core/browser/cloud/user_policy_signin_service_util.h
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
+
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/policy/policy_export.h"
+#include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/identity_manager/primary_account_change_event.h"
+
+class PrefService;
+
+namespace signin {
+class IdentityManager;
+} // namespace signin
+
+namespace policy {
+
+// Returns true if the account was signed out.
+POLICY_EXPORT bool IsSignoutEvent(
+ const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if sync was turned off for the account.
+POLICY_EXPORT bool IsTurnOffSyncEvent(
+ const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if the event is related to sign-in.
+POLICY_EXPORT bool IsAnySigninEvent(
+ const signin::PrimaryAccountChangeEvent& event);
+
+// Returns true if policies can be applied for the signed in user.
+POLICY_EXPORT bool CanApplyPoliciesForSignedInUser(
+ bool check_for_refresh_token,
+ signin::ConsentLevel consent_level,
+ signin::IdentityManager* identity_manager);
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+
+// Gets the timestamp representing the last time the registration was done.
+POLICY_EXPORT base::Time GetLastPolicyCheckTimeFromPrefs(PrefService* prefs);
+
+// Updates the timestamp representing the last time the registration was done
+// with the current time.
+POLICY_EXPORT void UpdateLastPolicyCheckTimeInPrefs(PrefService* prefs);
+
+// Gets the delay between each registration try. Used for mobile to throttle
+// network calls.
+POLICY_EXPORT base::TimeDelta GetTryRegistrationDelayFromPrefs(
+ PrefService* prefs);
+
+#endif
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CLOUD_USER_POLICY_SIGNIN_SERVICE_UTIL_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler.cc b/chromium/components/policy/core/browser/configuration_policy_handler.cc
new file mode 100644
index 00000000000..c58f855a36a
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler.cc
@@ -0,0 +1,730 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_handler.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/cxx17_backports.h"
+#include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+const size_t kMaxUrlFiltersPerPolicy = 1000;
+
+// ConfigurationPolicyHandler implementation
+// -----------------------------------
+
+ConfigurationPolicyHandler::ConfigurationPolicyHandler() {}
+
+ConfigurationPolicyHandler::~ConfigurationPolicyHandler() {}
+
+void ConfigurationPolicyHandler::PrepareForDisplaying(
+ PolicyMap* policies) const {}
+
+void ConfigurationPolicyHandler::ApplyPolicySettingsWithParameters(
+ const PolicyMap& policies,
+ const PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs) {
+ ApplyPolicySettings(policies, prefs);
+}
+
+// NamedPolicyHandler implementation -------------------------------------------
+NamedPolicyHandler::NamedPolicyHandler(const char* policy_name)
+ : policy_name_(policy_name) {}
+
+NamedPolicyHandler::~NamedPolicyHandler() = default;
+
+const char* NamedPolicyHandler::policy_name() const {
+ return policy_name_;
+}
+
+// TypeCheckingPolicyHandler implementation ------------------------------------
+
+TypeCheckingPolicyHandler::TypeCheckingPolicyHandler(
+ const char* policy_name,
+ base::Value::Type value_type)
+ : NamedPolicyHandler(policy_name), value_type_(value_type) {}
+
+TypeCheckingPolicyHandler::~TypeCheckingPolicyHandler() {}
+
+bool TypeCheckingPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ const base::Value* value = nullptr;
+ return CheckAndGetValue(policies, errors, &value);
+}
+
+bool TypeCheckingPolicyHandler::CheckAndGetValue(const PolicyMap& policies,
+ PolicyErrorMap* errors,
+ const base::Value** value) {
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ *value = policies.GetValueUnsafe(policy_name());
+ if (*value && (*value)->type() != value_type_) {
+ errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(value_type_));
+ return false;
+ }
+ return true;
+}
+
+// StringListPolicyHandler implementation --------------------------------------
+
+ListPolicyHandler::ListPolicyHandler(const char* policy_name,
+ base::Value::Type list_entry_type)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST),
+ list_entry_type_(list_entry_type) {}
+
+ListPolicyHandler::~ListPolicyHandler() {}
+
+bool ListPolicyHandler::CheckPolicySettings(const policy::PolicyMap& policies,
+ policy::PolicyErrorMap* errors) {
+ return CheckAndGetList(policies, errors, nullptr);
+}
+
+void ListPolicyHandler::ApplyPolicySettings(const policy::PolicyMap& policies,
+ PrefValueMap* prefs) {
+ base::Value list(base::Value::Type::NONE);
+ if (CheckAndGetList(policies, nullptr, &list) && list.is_list())
+ ApplyList(std::move(list), prefs);
+}
+
+bool ListPolicyHandler::CheckAndGetList(const policy::PolicyMap& policies,
+ policy::PolicyErrorMap* errors,
+ base::Value* filtered_list) {
+ const base::Value* value = nullptr;
+ if (!CheckAndGetValue(policies, errors, &value))
+ return false;
+
+ if (!value)
+ return true;
+
+ // Filter the list, rejecting any invalid strings.
+ base::Value::ConstListView list = value->GetListDeprecated();
+ if (filtered_list)
+ *filtered_list = base::Value(base::Value::Type::LIST);
+ for (size_t list_index = 0; list_index < list.size(); ++list_index) {
+ const base::Value& entry = list[list_index];
+ if (entry.type() != list_entry_type_) {
+ if (errors) {
+ errors->AddError(policy_name(), list_index, IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(list_entry_type_));
+ }
+ continue;
+ }
+
+ if (!CheckListEntry(entry)) {
+ if (errors) {
+ errors->AddError(policy_name(), list_index,
+ IDS_POLICY_VALUE_FORMAT_ERROR);
+ }
+ continue;
+ }
+
+ if (filtered_list)
+ filtered_list->Append(entry.Clone());
+ }
+
+ return true;
+}
+
+bool ListPolicyHandler::CheckListEntry(const base::Value& value) {
+ return true;
+}
+
+// IntRangePolicyHandlerBase implementation ------------------------------------
+
+IntRangePolicyHandlerBase::IntRangePolicyHandlerBase(const char* policy_name,
+ int min,
+ int max,
+ bool clamp)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::INTEGER),
+ min_(min),
+ max_(max),
+ clamp_(clamp) {}
+
+bool IntRangePolicyHandlerBase::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ const base::Value* value;
+ return CheckAndGetValue(policies, errors, &value) &&
+ EnsureInRange(value, nullptr, errors);
+}
+
+IntRangePolicyHandlerBase::~IntRangePolicyHandlerBase() {}
+
+bool IntRangePolicyHandlerBase::EnsureInRange(const base::Value* input,
+ int* output,
+ PolicyErrorMap* errors) {
+ if (!input)
+ return true;
+
+ DCHECK(input->is_int());
+ int value = input->GetInt();
+
+ if (value < min_ || value > max_) {
+ if (errors) {
+ errors->AddError(policy_name(), IDS_POLICY_OUT_OF_RANGE_ERROR,
+ base::NumberToString(value));
+ }
+
+ if (!clamp_)
+ return false;
+
+ value = base::clamp(value, min_, max_);
+ }
+
+ if (output)
+ *output = value;
+ return true;
+}
+
+// StringMappingListPolicyHandler implementation -----------------------------
+
+StringMappingListPolicyHandler::MappingEntry::MappingEntry(
+ const char* policy_value,
+ std::unique_ptr<base::Value> map)
+ : enum_value(policy_value), mapped_value(std::move(map)) {}
+
+StringMappingListPolicyHandler::MappingEntry::~MappingEntry() {}
+
+StringMappingListPolicyHandler::StringMappingListPolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ const GenerateMapCallback& callback)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST),
+ pref_path_(pref_path),
+ map_getter_(callback) {}
+
+StringMappingListPolicyHandler::~StringMappingListPolicyHandler() {}
+
+bool StringMappingListPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ const base::Value* value;
+ return CheckAndGetValue(policies, errors, &value) &&
+ Convert(value, nullptr, errors);
+}
+
+void StringMappingListPolicyHandler::ApplyPolicySettings(
+ const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ const base::Value* value =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ base::ListValue list;
+ if (value && Convert(value, &list, nullptr))
+ prefs->SetValue(pref_path_, std::move(list));
+}
+
+bool StringMappingListPolicyHandler::Convert(const base::Value* input,
+ base::ListValue* output,
+ PolicyErrorMap* errors) {
+ if (!input)
+ return true;
+
+ DCHECK(input->is_list());
+ int index = -1;
+ for (const auto& entry : input->GetListDeprecated()) {
+ ++index;
+ if (!entry.is_string()) {
+ if (errors) {
+ errors->AddError(policy_name(), index, IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ }
+ continue;
+ }
+
+ std::unique_ptr<base::Value> mapped_value = Map(entry.GetString());
+ if (mapped_value) {
+ if (output)
+ output->Append(std::move(mapped_value));
+ } else if (errors) {
+ errors->AddError(policy_name(), index, IDS_POLICY_OUT_OF_RANGE_ERROR);
+ }
+ }
+
+ return true;
+}
+
+std::unique_ptr<base::Value> StringMappingListPolicyHandler::Map(
+ const std::string& entry_value) {
+ // Lazily generate the map of policy strings to mapped values.
+ if (map_.empty())
+ map_getter_.Run(&map_);
+
+ for (const auto& mapping_entry : map_) {
+ if (mapping_entry->enum_value == entry_value) {
+ return base::Value::ToUniquePtrValue(
+ mapping_entry->mapped_value->Clone());
+ }
+ }
+ return nullptr;
+}
+
+// IntRangePolicyHandler implementation ----------------------------------------
+
+IntRangePolicyHandler::IntRangePolicyHandler(const char* policy_name,
+ const char* pref_path,
+ int min,
+ int max,
+ bool clamp)
+ : IntRangePolicyHandlerBase(policy_name, min, max, clamp),
+ pref_path_(pref_path) {}
+
+IntRangePolicyHandler::~IntRangePolicyHandler() {}
+
+void IntRangePolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ const base::Value* value =
+ policies.GetValue(policy_name(), base::Value::Type::INTEGER);
+ int value_in_range;
+ if (value && EnsureInRange(value, &value_in_range, nullptr))
+ prefs->SetInteger(pref_path_, value_in_range);
+}
+
+// IntPercentageToDoublePolicyHandler implementation ---------------------------
+
+IntPercentageToDoublePolicyHandler::IntPercentageToDoublePolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ int min,
+ int max,
+ bool clamp)
+ : IntRangePolicyHandlerBase(policy_name, min, max, clamp),
+ pref_path_(pref_path) {}
+
+IntPercentageToDoublePolicyHandler::~IntPercentageToDoublePolicyHandler() {}
+
+void IntPercentageToDoublePolicyHandler::ApplyPolicySettings(
+ const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ const base::Value* value =
+ policies.GetValue(policy_name(), base::Value::Type::INTEGER);
+ int percentage;
+ if (value && EnsureInRange(value, &percentage, nullptr))
+ prefs->SetDouble(pref_path_, static_cast<double>(percentage) / 100.);
+}
+
+// SimplePolicyHandler implementation ------------------------------------------
+
+SimplePolicyHandler::SimplePolicyHandler(const char* policy_name,
+ const char* pref_path,
+ base::Value::Type value_type)
+ : TypeCheckingPolicyHandler(policy_name, value_type),
+ pref_path_(pref_path) {}
+
+SimplePolicyHandler::~SimplePolicyHandler() {}
+
+void SimplePolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* value = policies.GetValueUnsafe(policy_name());
+ if (value)
+ prefs->SetValue(pref_path_, value->Clone());
+}
+
+// SchemaValidatingPolicyHandler implementation --------------------------------
+
+SchemaValidatingPolicyHandler::SchemaValidatingPolicyHandler(
+ const char* policy_name,
+ Schema schema,
+ SchemaOnErrorStrategy strategy)
+ : NamedPolicyHandler(policy_name), schema_(schema), strategy_(strategy) {
+ DCHECK(schema_.valid());
+}
+
+SchemaValidatingPolicyHandler::~SchemaValidatingPolicyHandler() {}
+
+bool SchemaValidatingPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* value = policies.GetValueUnsafe(policy_name());
+ if (!value)
+ return true;
+
+ std::string error_path;
+ std::string error;
+ bool result = schema_.Validate(*value, strategy_, &error_path, &error);
+
+ if (errors && !error.empty()) {
+ if (error_path.empty())
+ error_path = "(ROOT)";
+ errors->AddError(policy_name(), error_path, error);
+ }
+
+ return result;
+}
+
+bool SchemaValidatingPolicyHandler::CheckAndGetValue(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors,
+ std::unique_ptr<base::Value>* output) {
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* value = policies.GetValueUnsafe(policy_name());
+ if (!value)
+ return true;
+
+ *output = base::Value::ToUniquePtrValue(value->Clone());
+ std::string error_path;
+ std::string error;
+ bool result =
+ schema_.Normalize(output->get(), strategy_, &error_path, &error, nullptr);
+
+ if (errors && !error.empty()) {
+ if (error_path.empty())
+ error_path = "(ROOT)";
+ errors->AddError(policy_name(), error_path, error);
+ }
+
+ return result;
+}
+
+// SimpleSchemaValidatingPolicyHandler implementation --------------------------
+
+SimpleSchemaValidatingPolicyHandler::SimpleSchemaValidatingPolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ Schema schema,
+ SchemaOnErrorStrategy strategy,
+ RecommendedPermission recommended_permission,
+ MandatoryPermission mandatory_permission)
+ : SchemaValidatingPolicyHandler(policy_name,
+ schema.GetKnownProperty(policy_name),
+ strategy),
+ pref_path_(pref_path),
+ allow_recommended_(recommended_permission == RECOMMENDED_ALLOWED),
+ allow_mandatory_(mandatory_permission == MANDATORY_ALLOWED) {}
+
+SimpleSchemaValidatingPolicyHandler::~SimpleSchemaValidatingPolicyHandler() {}
+
+bool SimpleSchemaValidatingPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ const PolicyMap::Entry* policy_entry = policies.Get(policy_name());
+ if (!policy_entry)
+ return true;
+ if ((policy_entry->level == policy::POLICY_LEVEL_MANDATORY &&
+ !allow_mandatory_) ||
+ (policy_entry->level == policy::POLICY_LEVEL_RECOMMENDED &&
+ !allow_recommended_)) {
+ if (errors)
+ errors->AddError(policy_name(), IDS_POLICY_LEVEL_ERROR);
+ return false;
+ }
+
+ return SchemaValidatingPolicyHandler::CheckPolicySettings(policies, errors);
+}
+
+void SimpleSchemaValidatingPolicyHandler::ApplyPolicySettings(
+ const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* value = policies.GetValueUnsafe(policy_name());
+ if (value)
+ prefs->SetValue(pref_path_, value->Clone());
+}
+
+// SimpleJsonStringSchemaValidatingPolicyHandler implementation ----------------
+
+SimpleJsonStringSchemaValidatingPolicyHandler::
+ SimpleJsonStringSchemaValidatingPolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ Schema schema,
+ SimpleSchemaValidatingPolicyHandler::RecommendedPermission
+ recommended_permission,
+ SimpleSchemaValidatingPolicyHandler::MandatoryPermission
+ mandatory_permission)
+ : NamedPolicyHandler(policy_name),
+ schema_(schema.GetKnownProperty(policy_name)),
+ pref_path_(pref_path),
+ allow_recommended_(
+ recommended_permission ==
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_ALLOWED),
+ allow_mandatory_(mandatory_permission ==
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED) {
+}
+
+SimpleJsonStringSchemaValidatingPolicyHandler::
+ ~SimpleJsonStringSchemaValidatingPolicyHandler() {}
+
+bool SimpleJsonStringSchemaValidatingPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* root_value = policies.GetValueUnsafe(policy_name());
+ if (!root_value)
+ return true;
+
+ const PolicyMap::Entry* policy_entry = policies.Get(policy_name());
+ if ((policy_entry->level == policy::POLICY_LEVEL_MANDATORY &&
+ !allow_mandatory_) ||
+ (policy_entry->level == policy::POLICY_LEVEL_RECOMMENDED &&
+ !allow_recommended_)) {
+ if (errors)
+ errors->AddError(policy_name(), IDS_POLICY_LEVEL_ERROR);
+ return false;
+ }
+
+ if (IsListSchema())
+ return CheckListOfJsonStrings(root_value, errors);
+
+ return CheckSingleJsonString(root_value, errors);
+}
+
+bool SimpleJsonStringSchemaValidatingPolicyHandler::CheckSingleJsonString(
+ const base::Value* root_value,
+ PolicyErrorMap* errors) {
+ // First validate the root value is a string.
+ if (!root_value->is_string()) {
+ if (errors) {
+ errors->AddError(policy_name(), "(ROOT)", IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ }
+ return false;
+ }
+
+ // If that succeeds, validate the JSON inside the string.
+ const std::string& json_string = root_value->GetString();
+ if (!ValidateJsonString(json_string, errors, 0))
+ RecordJsonError();
+
+ // Very lenient - return true as long as the root value is a string.
+ return true;
+}
+
+bool SimpleJsonStringSchemaValidatingPolicyHandler::CheckListOfJsonStrings(
+ const base::Value* root_value,
+ PolicyErrorMap* errors) {
+ // First validate the root value is a list.
+ if (!root_value->is_list()) {
+ if (errors) {
+ errors->AddError(policy_name(), "(ROOT)", IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::LIST));
+ }
+ return false;
+ }
+
+ // If that succeeds, validate all the list items are strings and validate
+ // the JSON inside the strings.
+ base::Value::ConstListView list = root_value->GetListDeprecated();
+ bool json_error_seen = false;
+
+ for (size_t index = 0; index < list.size(); ++index) {
+ const base::Value& entry = list[index];
+ if (!entry.is_string()) {
+ if (errors) {
+ errors->AddError(policy_name(), index, IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ }
+ continue;
+ }
+
+ const std::string& json_string = entry.GetString();
+ if (!ValidateJsonString(json_string, errors, index))
+ json_error_seen = true;
+ }
+
+ if (json_error_seen)
+ RecordJsonError();
+
+ // Very lenient - return true as long as the root value is a list.
+ return true;
+}
+
+bool SimpleJsonStringSchemaValidatingPolicyHandler::ValidateJsonString(
+ const std::string& json_string,
+ PolicyErrorMap* errors,
+ int index) {
+ base::JSONReader::ValueWithError value_with_error =
+ base::JSONReader::ReadAndReturnValueWithError(
+ json_string, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ if (!value_with_error.value) {
+ if (errors) {
+ errors->AddError(policy_name(), ErrorPath(index, ""),
+ IDS_POLICY_INVALID_JSON_ERROR,
+ value_with_error.error_message);
+ }
+ return false;
+ }
+
+ std::string schema_error;
+ std::string error_path;
+ const Schema json_string_schema =
+ IsListSchema() ? schema_.GetItems() : schema_;
+ // Even though we are validating this schema here, we don't actually change
+ // the policy if it fails to validate. This validation is just so we can show
+ // the user errors.
+ bool validated = json_string_schema.Validate(value_with_error.value.value(),
+ SCHEMA_ALLOW_UNKNOWN,
+ &error_path, &schema_error);
+ if (errors && !schema_error.empty())
+ errors->AddError(policy_name(), ErrorPath(index, error_path), schema_error);
+ if (!validated)
+ return false;
+
+ return true;
+}
+
+std::string SimpleJsonStringSchemaValidatingPolicyHandler::ErrorPath(
+ int index,
+ std::string json_error_path) {
+ if (IsListSchema()) {
+ return json_error_path.empty()
+ ? base::StringPrintf("items[%d]", index)
+ : base::StringPrintf("items[%d].%s", index,
+ json_error_path.c_str());
+ }
+ return json_error_path.empty() ? "(ROOT)" : json_error_path;
+}
+
+void SimpleJsonStringSchemaValidatingPolicyHandler::ApplyPolicySettings(
+ const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ if (!pref_path_)
+ return;
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* value = policies.GetValueUnsafe(policy_name());
+ if (value)
+ prefs->SetValue(pref_path_, value->Clone());
+}
+
+void SimpleJsonStringSchemaValidatingPolicyHandler::RecordJsonError() {
+ const PolicyDetails* details = GetChromePolicyDetails(policy_name());
+ if (details) {
+ base::UmaHistogramSparse("EnterpriseCheck.InvalidJsonPolicies",
+ details->id);
+ }
+}
+
+bool SimpleJsonStringSchemaValidatingPolicyHandler::IsListSchema() const {
+ return schema_.type() == base::Value::Type::LIST;
+}
+
+// LegacyPoliciesDeprecatingPolicyHandler implementation -----------------------
+
+LegacyPoliciesDeprecatingPolicyHandler::LegacyPoliciesDeprecatingPolicyHandler(
+ std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
+ legacy_policy_handlers,
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler)
+ : legacy_policy_handlers_(std::move(legacy_policy_handlers)),
+ new_policy_handler_(std::move(new_policy_handler)) {}
+
+LegacyPoliciesDeprecatingPolicyHandler::
+ ~LegacyPoliciesDeprecatingPolicyHandler() {}
+
+bool LegacyPoliciesDeprecatingPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ if (policies.Get(new_policy_handler_->policy_name()))
+ return new_policy_handler_->CheckPolicySettings(policies, errors);
+
+ // The new policy is not set, fall back to legacy ones.
+ bool valid_policy_found = false;
+ for (const auto& handler : legacy_policy_handlers_) {
+ if (handler->CheckPolicySettings(policies, errors))
+ valid_policy_found = true;
+ }
+ return valid_policy_found;
+}
+
+void LegacyPoliciesDeprecatingPolicyHandler::ApplyPolicySettingsWithParameters(
+ const policy::PolicyMap& policies,
+ const policy::PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs) {
+ if (policies.Get(new_policy_handler_->policy_name())) {
+ new_policy_handler_->ApplyPolicySettingsWithParameters(policies, parameters,
+ prefs);
+ return;
+ }
+
+ // The new policy is not set, fall back to legacy ones.
+ PolicyErrorMap scoped_errors;
+ for (const auto& handler : legacy_policy_handlers_) {
+ if (handler->CheckPolicySettings(policies, &scoped_errors))
+ handler->ApplyPolicySettingsWithParameters(policies, parameters, prefs);
+ }
+}
+
+void LegacyPoliciesDeprecatingPolicyHandler::ApplyPolicySettings(
+ const policy::PolicyMap& /* policies */,
+ PrefValueMap* /* prefs */) {
+ NOTREACHED();
+}
+
+// SimpleDeprecatingPolicyHandler implementation -----------------------
+
+SimpleDeprecatingPolicyHandler::SimpleDeprecatingPolicyHandler(
+ std::unique_ptr<NamedPolicyHandler> legacy_policy_handler,
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler)
+ : legacy_policy_handler_(std::move(legacy_policy_handler)),
+ new_policy_handler_(std::move(new_policy_handler)) {}
+
+SimpleDeprecatingPolicyHandler::~SimpleDeprecatingPolicyHandler() = default;
+
+bool SimpleDeprecatingPolicyHandler::CheckPolicySettings(
+ const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ if (policies.Get(new_policy_handler_->policy_name())) {
+ if (policies.Get(legacy_policy_handler_->policy_name())) {
+ errors->AddError(legacy_policy_handler_->policy_name(),
+ IDS_POLICY_OVERRIDDEN,
+ new_policy_handler_->policy_name());
+ }
+ return new_policy_handler_->CheckPolicySettings(policies, errors);
+ }
+
+ // The new policy is not set, fall back to legacy ones.
+ return legacy_policy_handler_->CheckPolicySettings(policies, errors);
+}
+
+void SimpleDeprecatingPolicyHandler::ApplyPolicySettingsWithParameters(
+ const policy::PolicyMap& policies,
+ const policy::PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs) {
+ if (policies.Get(new_policy_handler_->policy_name())) {
+ new_policy_handler_->ApplyPolicySettingsWithParameters(policies, parameters,
+ prefs);
+ } else {
+ legacy_policy_handler_->ApplyPolicySettingsWithParameters(
+ policies, parameters, prefs);
+ }
+}
+
+void SimpleDeprecatingPolicyHandler::ApplyPolicySettings(
+ const policy::PolicyMap& /* policies */,
+ PrefValueMap* /* prefs */) {
+ NOTREACHED();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler.h b/chromium/components/policy/core/browser/configuration_policy_handler.h
new file mode 100644
index 00000000000..4b2326be68c
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler.h
@@ -0,0 +1,531 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/values.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+class PrefValueMap;
+
+namespace policy {
+
+class PolicyErrorMap;
+struct PolicyHandlerParameters;
+class PolicyMap;
+
+extern POLICY_EXPORT const size_t kMaxUrlFiltersPerPolicy;
+
+// Maps a policy type to a preference path, and to the expected value type.
+struct POLICY_EXPORT PolicyToPreferenceMapEntry {
+ const char* const policy_name;
+ const char* const preference_path;
+ const base::Value::Type value_type;
+};
+
+// An abstract super class that subclasses should implement to map policies to
+// their corresponding preferences, and to check whether the policies are valid.
+class POLICY_EXPORT ConfigurationPolicyHandler {
+ public:
+ ConfigurationPolicyHandler();
+ ConfigurationPolicyHandler(const ConfigurationPolicyHandler&) = delete;
+ ConfigurationPolicyHandler& operator=(const ConfigurationPolicyHandler&) =
+ delete;
+ virtual ~ConfigurationPolicyHandler();
+
+ // Returns whether the policy settings handled by this
+ // ConfigurationPolicyHandler can be applied. Fills |errors| with error
+ // messages or warnings. |errors| may contain error messages even when
+ // |CheckPolicySettings()| returns true.
+ virtual bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) = 0;
+
+ // Processes the policies handled by this ConfigurationPolicyHandler and sets
+ // the appropriate preferences in |prefs|.
+ //
+ // This method should only be called after |CheckPolicySettings()| returns
+ // true.
+ virtual void ApplyPolicySettingsWithParameters(
+ const PolicyMap& policies,
+ const PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs);
+
+ // Modifies the values of some of the policies in |policies| so that they
+ // are more suitable to display to the user. This can be used to remove
+ // sensitive values such as passwords, or to pretty-print values.
+ virtual void PrepareForDisplaying(PolicyMap* policies) const;
+
+ protected:
+ // This is a convenience version of ApplyPolicySettingsWithParameters()
+ // for derived classes that leaves out the |parameters|. Anyone extending
+ // ConfigurationPolicyHandler should implement either
+ // ApplyPolicySettingsWithParameters directly and implement
+ // ApplyPolicySettings with a NOTREACHED or implement only
+ // ApplyPolicySettings.
+ virtual void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) = 0;
+};
+
+// Abstract class derived from ConfigurationPolicyHandler that should be
+// subclassed to handle policies that have a name.
+class POLICY_EXPORT NamedPolicyHandler : public ConfigurationPolicyHandler {
+ public:
+ explicit NamedPolicyHandler(const char* policy_name);
+ ~NamedPolicyHandler() override;
+ NamedPolicyHandler(const NamedPolicyHandler&) = delete;
+ NamedPolicyHandler& operator=(const NamedPolicyHandler&) = delete;
+
+ const char* policy_name() const;
+
+ private:
+ // The name of the policy.
+ const char* policy_name_;
+};
+
+// Abstract class derived from ConfigurationPolicyHandler that should be
+// subclassed to handle a single policy (not a combination of policies).
+class POLICY_EXPORT TypeCheckingPolicyHandler : public NamedPolicyHandler {
+ public:
+ TypeCheckingPolicyHandler(const char* policy_name,
+ base::Value::Type value_type);
+ TypeCheckingPolicyHandler(const TypeCheckingPolicyHandler&) = delete;
+ TypeCheckingPolicyHandler& operator=(const TypeCheckingPolicyHandler&) =
+ delete;
+ ~TypeCheckingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+
+ protected:
+ // Runs policy checks and returns the policy value if successful.
+ bool CheckAndGetValue(const PolicyMap& policies,
+ PolicyErrorMap* errors,
+ const base::Value** value);
+
+ private:
+ // The type the value of the policy should have.
+ base::Value::Type value_type_;
+};
+
+// Policy handler that makes sure the policy value is a list and filters out any
+// list entries that are not of type |list_entry_type|. Derived methods may
+// apply additional filters on list entries and transform the filtered list.
+class POLICY_EXPORT ListPolicyHandler : public TypeCheckingPolicyHandler {
+ public:
+ ListPolicyHandler(const char* policy_name, base::Value::Type list_entry_type);
+ ListPolicyHandler(const ListPolicyHandler&) = delete;
+ ListPolicyHandler& operator=(const ListPolicyHandler&) = delete;
+ ~ListPolicyHandler() override;
+
+ // TypeCheckingPolicyHandler methods:
+ // Marked as final since overriding them could bypass filtering. Override
+ // CheckListEntry() and ApplyList() instead.
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) final;
+
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) final;
+
+ protected:
+ // Override this method to apply a filter for each |value| in the list.
+ // |value| is guaranteed to be of type |list_entry_type_| at this point.
+ // Returning false removes the value from |filtered_list| passed into
+ // ApplyList(). By default, any value of type |list_entry_type_| is accepted.
+ virtual bool CheckListEntry(const base::Value& value);
+
+ // Implement this method to apply the |filtered_list| of values of type
+ // |list_entry_type_| as returned from CheckAndGetList() to |prefs|.
+ virtual void ApplyList(base::Value filtered_list, PrefValueMap* prefs) = 0;
+
+ private:
+ // Checks whether the policy value is indeed a list, filters out all entries
+ // that are not of type |list_entry_type_| or where CheckListEntry() returns
+ // false, and returns the |filtered_list| if not nullptr. Sets errors for
+ // filtered list entries if |errors| is not nullptr.
+ bool CheckAndGetList(const policy::PolicyMap& policies,
+ policy::PolicyErrorMap* errors,
+ base::Value* filtered_list);
+
+ // Expected value type for list entries. All other types are filtered out.
+ base::Value::Type list_entry_type_;
+};
+
+// Abstract class derived from TypeCheckingPolicyHandler that ensures an int
+// policy's value lies in an allowed range. Either clamps or rejects values
+// outside the range.
+class POLICY_EXPORT IntRangePolicyHandlerBase
+ : public TypeCheckingPolicyHandler {
+ public:
+ IntRangePolicyHandlerBase(const char* policy_name,
+ int min,
+ int max,
+ bool clamp);
+ IntRangePolicyHandlerBase(const IntRangePolicyHandlerBase&) = delete;
+ IntRangePolicyHandlerBase& operator=(const IntRangePolicyHandlerBase&) =
+ delete;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+
+ protected:
+ ~IntRangePolicyHandlerBase() override;
+
+ // Ensures that the value is in the allowed range. Returns false if the value
+ // cannot be parsed or lies outside the allowed range and clamping is
+ // disabled.
+ bool EnsureInRange(const base::Value* input,
+ int* output,
+ PolicyErrorMap* errors);
+
+ private:
+ // The minimum value allowed.
+ int min_;
+
+ // The maximum value allowed.
+ int max_;
+
+ // Whether to clamp values lying outside the allowed range instead of
+ // rejecting them.
+ bool clamp_;
+};
+
+// ConfigurationPolicyHandler for policies that map directly to a preference.
+class POLICY_EXPORT SimplePolicyHandler : public TypeCheckingPolicyHandler {
+ public:
+ SimplePolicyHandler(const char* policy_name,
+ const char* pref_path,
+ base::Value::Type value_type);
+ SimplePolicyHandler(const SimplePolicyHandler&) = delete;
+ SimplePolicyHandler& operator=(const SimplePolicyHandler&) = delete;
+ ~SimplePolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ // The DictionaryValue path of the preference the policy maps to.
+ const char* pref_path_;
+};
+
+// Base class that encapsulates logic for mapping from a string enum list
+// to a separate matching type value.
+class POLICY_EXPORT StringMappingListPolicyHandler
+ : public TypeCheckingPolicyHandler {
+ public:
+ // Data structure representing the map between policy strings and
+ // matching pref values.
+ class POLICY_EXPORT MappingEntry {
+ public:
+ MappingEntry(const char* policy_value, std::unique_ptr<base::Value> map);
+ ~MappingEntry();
+
+ const char* enum_value;
+ std::unique_ptr<base::Value> mapped_value;
+ };
+
+ // Callback that generates the map for this instance.
+ using GenerateMapCallback = base::RepeatingCallback<void(
+ std::vector<std::unique_ptr<MappingEntry>>*)>;
+
+ StringMappingListPolicyHandler(const char* policy_name,
+ const char* pref_path,
+ const GenerateMapCallback& map_generator);
+ StringMappingListPolicyHandler(const StringMappingListPolicyHandler&) =
+ delete;
+ StringMappingListPolicyHandler& operator=(
+ const StringMappingListPolicyHandler&) = delete;
+ ~StringMappingListPolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ // Attempts to convert the list in |input| to |output| according to the table,
+ // returns false on errors.
+ bool Convert(const base::Value* input,
+ base::ListValue* output,
+ PolicyErrorMap* errors);
+
+ // Helper method that converts from a policy value string to the associated
+ // pref value.
+ std::unique_ptr<base::Value> Map(const std::string& entry_value);
+
+ // Name of the pref to write.
+ const char* pref_path_;
+
+ // The callback invoked to generate the map for this instance.
+ GenerateMapCallback map_getter_;
+
+ // Map of string policy values to local pref values. This is generated lazily
+ // so the generation does not have to happen if no policy is present.
+ std::vector<std::unique_ptr<MappingEntry>> map_;
+};
+
+// A policy handler implementation that ensures an int policy's value lies in an
+// allowed range.
+class POLICY_EXPORT IntRangePolicyHandler : public IntRangePolicyHandlerBase {
+ public:
+ IntRangePolicyHandler(const char* policy_name,
+ const char* pref_path,
+ int min,
+ int max,
+ bool clamp);
+ IntRangePolicyHandler(const IntRangePolicyHandler&) = delete;
+ IntRangePolicyHandler& operator=(const IntRangePolicyHandler&) = delete;
+ ~IntRangePolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ // Name of the pref to write.
+ const char* pref_path_;
+};
+
+// A policy handler implementation that maps an int percentage value to a
+// double.
+class POLICY_EXPORT IntPercentageToDoublePolicyHandler
+ : public IntRangePolicyHandlerBase {
+ public:
+ IntPercentageToDoublePolicyHandler(const char* policy_name,
+ const char* pref_path,
+ int min,
+ int max,
+ bool clamp);
+ IntPercentageToDoublePolicyHandler(
+ const IntPercentageToDoublePolicyHandler&) = delete;
+ IntPercentageToDoublePolicyHandler& operator=(
+ const IntPercentageToDoublePolicyHandler&) = delete;
+ ~IntPercentageToDoublePolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ // Name of the pref to write.
+ const char* pref_path_;
+};
+
+// Like TypeCheckingPolicyHandler, but validates against a schema instead of a
+// single type. |schema| is the schema used for this policy, and |strategy| is
+// the strategy used for schema validation errors.
+class POLICY_EXPORT SchemaValidatingPolicyHandler : public NamedPolicyHandler {
+ public:
+ SchemaValidatingPolicyHandler(const char* policy_name,
+ Schema schema,
+ SchemaOnErrorStrategy strategy);
+ SchemaValidatingPolicyHandler(const SchemaValidatingPolicyHandler&) = delete;
+ SchemaValidatingPolicyHandler& operator=(
+ const SchemaValidatingPolicyHandler&) = delete;
+ ~SchemaValidatingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+
+ protected:
+ // Runs policy checks and returns the policy value if successful.
+ bool CheckAndGetValue(const PolicyMap& policies,
+ PolicyErrorMap* errors,
+ std::unique_ptr<base::Value>* output);
+
+ private:
+ const Schema schema_;
+ const SchemaOnErrorStrategy strategy_;
+};
+
+// Maps policy to pref like SimplePolicyHandler while ensuring that the value
+// set matches the schema. |schema| is the schema used for policies, and
+// |strategy| is the strategy used for schema validation errors.
+// The |recommended_permission| and |mandatory_permission| flags indicate the
+// levels at which the policy can be set. A value set at an unsupported level
+// will be ignored.
+class POLICY_EXPORT SimpleSchemaValidatingPolicyHandler
+ : public SchemaValidatingPolicyHandler {
+ public:
+ enum MandatoryPermission { MANDATORY_ALLOWED, MANDATORY_PROHIBITED };
+ enum RecommendedPermission { RECOMMENDED_ALLOWED, RECOMMENDED_PROHIBITED };
+
+ SimpleSchemaValidatingPolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ Schema schema,
+ SchemaOnErrorStrategy strategy,
+ RecommendedPermission recommended_permission,
+ MandatoryPermission mandatory_permission);
+ SimpleSchemaValidatingPolicyHandler(
+ const SimpleSchemaValidatingPolicyHandler&) = delete;
+ SimpleSchemaValidatingPolicyHandler& operator=(
+ const SimpleSchemaValidatingPolicyHandler&) = delete;
+ ~SimpleSchemaValidatingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ const char* pref_path_;
+ const bool allow_recommended_;
+ const bool allow_mandatory_;
+};
+
+// Maps policy to pref like SimplePolicyHandler. Ensures that the root value
+// of the policy is of the correct type (that is, a string, or a list, depending
+// on the policy). Apart from that, all policy values are accepted without
+// modification, but the |PolicyErrorMap| will be updated for every error
+// encountered - for instance, if the embedded JSON is unparsable or if it does
+// not match the validation schema.
+// NOTE: Do not store new policies using JSON strings! If your policy has a
+// complex schema, store it as a dict of that schema. This has some advantages:
+// - You don't have to parse JSON every time you read it from the pref store.
+// - Nested dicts are simple, but nested JSON strings are complicated.
+class POLICY_EXPORT SimpleJsonStringSchemaValidatingPolicyHandler
+ : public NamedPolicyHandler {
+ public:
+ SimpleJsonStringSchemaValidatingPolicyHandler(
+ const char* policy_name,
+ const char* pref_path,
+ Schema schema,
+ SimpleSchemaValidatingPolicyHandler::RecommendedPermission
+ recommended_permission,
+ SimpleSchemaValidatingPolicyHandler::MandatoryPermission
+ mandatory_permission);
+ SimpleJsonStringSchemaValidatingPolicyHandler(
+ const SimpleJsonStringSchemaValidatingPolicyHandler&) = delete;
+ SimpleJsonStringSchemaValidatingPolicyHandler& operator=(
+ const SimpleJsonStringSchemaValidatingPolicyHandler&) = delete;
+
+ ~SimpleJsonStringSchemaValidatingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ // Validates |root_value| as a string. Updates |errors| if it is not valid
+ // JSON or if it does not match the validation schema.
+ bool CheckSingleJsonString(const base::Value* root_value,
+ PolicyErrorMap* errors);
+
+ // Validates |root_value| as a list. Updates |errors| for each item that is
+ // not a string, is not valid JSON, or doesn't match the validation schema.
+ bool CheckListOfJsonStrings(const base::Value* root_value,
+ PolicyErrorMap* errors);
+
+ // Validates that the given JSON string matches the schema. |index| is used
+ // only in error messages, it is the index of the given string in the list
+ // if the root value is a list, and ignored otherwise. Adds any errors it
+ // finds to |errors|.
+ bool ValidateJsonString(const std::string& json_string,
+ PolicyErrorMap* errors,
+ int index);
+
+ // Returns a string describing where an error occurred - |index| is the index
+ // of the string where the error occurred if the root value is a list, and
+ // ignored otherwise. |json_error_path| describes where the error occurred
+ // inside a JSON string (this can be empty).
+ std::string ErrorPath(int index, std::string json_error_path);
+
+ // Record to UMA that this policy failed validation due to an error in one or
+ // more embedded JSON strings - either unparsable, or didn't match the schema.
+ void RecordJsonError();
+
+ // Returns true if the schema root is a list.
+ bool IsListSchema() const;
+
+ const Schema schema_;
+ const char* pref_path_;
+ const bool allow_recommended_;
+ const bool allow_mandatory_;
+};
+
+// A policy handler to deprecate multiple legacy policies with a new one.
+// This handler will completely ignore any of legacy policy values if the new
+// one is set.
+class POLICY_EXPORT LegacyPoliciesDeprecatingPolicyHandler
+ : public ConfigurationPolicyHandler {
+ public:
+ LegacyPoliciesDeprecatingPolicyHandler(
+ std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
+ legacy_policy_handlers,
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler);
+ LegacyPoliciesDeprecatingPolicyHandler(
+ const LegacyPoliciesDeprecatingPolicyHandler&) = delete;
+ LegacyPoliciesDeprecatingPolicyHandler& operator=(
+ const LegacyPoliciesDeprecatingPolicyHandler&) = delete;
+ ~LegacyPoliciesDeprecatingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettingsWithParameters(
+ const policy::PolicyMap& policies,
+ const policy::PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs) override;
+
+ protected:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ std::vector<std::unique_ptr<ConfigurationPolicyHandler>>
+ legacy_policy_handlers_;
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler_;
+};
+
+// A policy handler to deprecate a single policy with a new one. It will attempt
+// to use the new value if present and then try to use the legacy value instead.
+class POLICY_EXPORT SimpleDeprecatingPolicyHandler
+ : public ConfigurationPolicyHandler {
+ public:
+ SimpleDeprecatingPolicyHandler(
+ std::unique_ptr<NamedPolicyHandler> legacy_policy_handler,
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler);
+ SimpleDeprecatingPolicyHandler(const SimpleDeprecatingPolicyHandler&) =
+ delete;
+ SimpleDeprecatingPolicyHandler& operator=(
+ const SimpleDeprecatingPolicyHandler&) = delete;
+ ~SimpleDeprecatingPolicyHandler() override;
+
+ // ConfigurationPolicyHandler:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+
+ void ApplyPolicySettingsWithParameters(
+ const PolicyMap& policies,
+ const PolicyHandlerParameters& parameters,
+ PrefValueMap* prefs) override;
+
+ protected:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ std::unique_ptr<NamedPolicyHandler> legacy_policy_handler_;
+ std::unique_ptr<NamedPolicyHandler> new_policy_handler_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler_list.cc b/chromium/components/policy/core/browser/configuration_policy_handler_list.cc
new file mode 100644
index 00000000000..d33eecdd9cc
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler_list.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/configuration_policy_handler_parameters.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/values_util.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace {
+
+// Don't show errors for policies starting with that prefix.
+const char kPolicyCommentPrefix[] = "_comment";
+
+} // namespace
+
+ConfigurationPolicyHandlerList::ConfigurationPolicyHandlerList(
+ const PopulatePolicyHandlerParametersCallback& parameters_callback,
+ const GetChromePolicyDetailsCallback& details_callback,
+ bool allow_future_policies)
+ : parameters_callback_(parameters_callback),
+ details_callback_(details_callback),
+ allow_future_policies_(allow_future_policies) {}
+
+ConfigurationPolicyHandlerList::~ConfigurationPolicyHandlerList() {
+}
+
+void ConfigurationPolicyHandlerList::AddHandler(
+ std::unique_ptr<ConfigurationPolicyHandler> handler) {
+ handlers_.push_back(std::move(handler));
+}
+
+void ConfigurationPolicyHandlerList::ApplyPolicySettings(
+ const PolicyMap& policies,
+ PrefValueMap* prefs,
+ PolicyErrorMap* errors,
+ PoliciesSet* deprecated_policies,
+ PoliciesSet* future_policies) const {
+ if (deprecated_policies)
+ deprecated_policies->clear();
+ if (future_policies)
+ future_policies->clear();
+ // This function is used both to apply the policy settings, and to check them
+ // and list errors. As such it must get all the errors even if it isn't
+ // applying the policies.
+ PolicyMap filtered_policies = policies.Clone();
+ base::flat_set<std::string> enabled_future_policies =
+ allow_future_policies_
+ ? base::flat_set<std::string>()
+ : ValueToStringSet(policies.GetValue(key::kEnableExperimentalPolicies,
+ base::Value::Type::LIST));
+ filtered_policies.EraseMatching(base::BindRepeating(
+ &ConfigurationPolicyHandlerList::FilterOutUnsupportedPolicies,
+ base::Unretained(this), enabled_future_policies, future_policies));
+
+ PolicyErrorMap scoped_errors;
+ if (!errors)
+ errors = &scoped_errors;
+
+ PolicyHandlerParameters parameters;
+ if (parameters_callback_)
+ parameters_callback_.Run(&parameters);
+
+ for (const auto& handler : handlers_) {
+ if (handler->CheckPolicySettings(filtered_policies, errors) && prefs) {
+ handler->ApplyPolicySettingsWithParameters(filtered_policies, parameters,
+ prefs);
+ }
+ }
+
+ if (details_callback_ && deprecated_policies) {
+ for (const auto& key_value : filtered_policies) {
+ const PolicyDetails* details = details_callback_.Run(key_value.first);
+ if (details && details->is_deprecated)
+ deprecated_policies->insert(key_value.first);
+ }
+ }
+}
+
+void ConfigurationPolicyHandlerList::PrepareForDisplaying(
+ PolicyMap* policies) const {
+ for (const auto& handler : handlers_)
+ handler->PrepareForDisplaying(policies);
+}
+
+bool ConfigurationPolicyHandlerList::FilterOutUnsupportedPolicies(
+ const base::flat_set<std::string>& enabled_future_policies,
+ PoliciesSet* future_policies,
+ const PolicyMap::const_iterator iter) const {
+ // Callback might be missing in tests.
+ if (!details_callback_) {
+ return false;
+ }
+
+ const PolicyDetails* policy_details = details_callback_.Run(iter->first);
+ if (!policy_details) {
+ const std::string prefix(kPolicyCommentPrefix);
+ if (iter->first.compare(0, prefix.length(), prefix) != 0) {
+ DVLOG(1) << "Unknown policy: " << iter->first;
+ }
+ return false;
+ }
+
+ if (IsFuturePolicy(enabled_future_policies, *policy_details, iter)) {
+ if (future_policies)
+ future_policies->insert(iter->first);
+ return true;
+ }
+
+ return IsPlatformDevicePolicy(*policy_details, iter);
+}
+
+bool ConfigurationPolicyHandlerList::IsPlatformDevicePolicy(
+ const PolicyDetails& policy_details,
+ const PolicyMap::const_iterator iter) const {
+ if (iter->second.source == POLICY_SOURCE_PLATFORM &&
+ policy_details.is_device_policy) {
+ // Device Policy is only implemented as Cloud Policy (not Platform Policy).
+ LOG(WARNING) << "Ignoring device platform policy: " << iter->first;
+ return true;
+ }
+ return false;
+}
+
+bool ConfigurationPolicyHandlerList::IsFuturePolicy(
+ const base::flat_set<std::string>& enabled_future_policies,
+ const PolicyDetails& policy_details,
+ const PolicyMap::const_iterator iter) const {
+ return !allow_future_policies_ && policy_details.is_future &&
+ !enabled_future_policies.contains(iter->first);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler_list.h b/chromium/components/policy/core/browser/configuration_policy_handler_list.h
new file mode 100644
index 00000000000..593b29f7800
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler_list.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_LIST_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_LIST_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_export.h"
+
+class PrefValueMap;
+
+namespace policy {
+
+class ConfigurationPolicyHandler;
+struct PolicyDetails;
+class PolicyErrorMap;
+struct PolicyHandlerParameters;
+class Schema;
+
+// Converts policies to their corresponding preferences by applying a list of
+// ConfigurationPolicyHandler objects. This includes error checking and cleaning
+// up policy values for display.
+class POLICY_EXPORT ConfigurationPolicyHandlerList {
+ public:
+ typedef base::RepeatingCallback<void(PolicyHandlerParameters*)>
+ PopulatePolicyHandlerParametersCallback;
+
+ explicit ConfigurationPolicyHandlerList(
+ const PopulatePolicyHandlerParametersCallback& parameters_callback,
+ const GetChromePolicyDetailsCallback& details_callback,
+ bool allow_future_policies);
+ ConfigurationPolicyHandlerList(const ConfigurationPolicyHandlerList&) =
+ delete;
+ ConfigurationPolicyHandlerList& operator=(
+ const ConfigurationPolicyHandlerList&) = delete;
+ ~ConfigurationPolicyHandlerList();
+
+ // Adds a policy handler to the list.
+ void AddHandler(std::unique_ptr<ConfigurationPolicyHandler> handler);
+
+ // Translates |policies| to their corresponding preferences in |prefs|. Any
+ // errors found while processing the policies are stored in |errors|.
+ // All deprecated policies will be stored into |deprecated_policies|.
+ // All non-applying unreleased policies will be stored into |future_policies|.
+ // |prefs|, |deprecated_policies|, |future_policies| or |errors| can be
+ // nullptr, and won't be filled in that case.
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs,
+ PolicyErrorMap* errors,
+ PoliciesSet* deprecated_policies,
+ PoliciesSet* future_policies) const;
+
+ // Converts sensitive policy values to others more appropriate for displaying.
+ void PrepareForDisplaying(PolicyMap* policies) const;
+
+ private:
+ // Returns true if the policy |iter| shouldn't be passed to the |handlers_|.
+ // On Stable and Beta channel, future policies that are not in the
+ // |enabled_future_policies| will be filtered out and put into the
+ // |future_policies|.
+ bool FilterOutUnsupportedPolicies(
+ const base::flat_set<std::string>& enabled_future_policies,
+ PoliciesSet* future_policies,
+ const PolicyMap::const_iterator iter) const;
+
+ bool IsPlatformDevicePolicy(const PolicyDetails& policy_details,
+ const PolicyMap::const_iterator iter) const;
+ bool IsFuturePolicy(
+ const base::flat_set<std::string>& enabled_future_policies,
+ const PolicyDetails& policy_details,
+ const PolicyMap::const_iterator iter) const;
+
+ std::vector<std::unique_ptr<ConfigurationPolicyHandler>> handlers_;
+ const PopulatePolicyHandlerParametersCallback parameters_callback_;
+ const GetChromePolicyDetailsCallback details_callback_;
+
+ bool allow_future_policies_ = false;
+};
+
+// Callback with signature of BuildHandlerList(), to be used in constructor of
+// BrowserPolicyConnector.
+using HandlerListFactory =
+ base::RepeatingCallback<std::unique_ptr<ConfigurationPolicyHandlerList>(
+ const Schema&)>;
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_LIST_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler_list_unittest.cc b/chromium/components/policy/core/browser/configuration_policy_handler_list_unittest.cc
new file mode 100644
index 00000000000..aaf0602f82a
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler_list_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+
+#include <string>
+#include <tuple>
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kPolicyName[] = "PolicyName";
+const char kPolicyName2[] = "PolicyName2";
+const int kPolicyValue = 12;
+
+class StubPolicyHandler : public ConfigurationPolicyHandler {
+ public:
+ explicit StubPolicyHandler(const std::string& policy_name)
+ : policy_name_(policy_name) {}
+ ~StubPolicyHandler() override = default;
+
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override {
+ // It's safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ return policies.GetValueUnsafe(policy_name_);
+ }
+
+ protected:
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override {
+ prefs->SetInteger(
+ policy_name_,
+ policies.GetValue(policy_name_, base::Value::Type::INTEGER)->GetInt());
+ }
+
+ private:
+ std::string policy_name_;
+};
+
+} // namespace
+
+class ConfigurationPolicyHandlerListTest : public ::testing::Test {
+ public:
+ void SetUp() override { CreateHandlerList(); }
+
+ void AddSimplePolicy() {
+ AddPolicy(kPolicyName, /* is_cloud */ true, base::Value(kPolicyValue));
+ }
+
+ void AddPolicy(const std::string policy_name,
+ bool is_cloud,
+ base::Value value) {
+ policies_.Set(policy_name, PolicyLevel::POLICY_LEVEL_MANDATORY,
+ PolicyScope::POLICY_SCOPE_MACHINE,
+ is_cloud ? PolicySource::POLICY_SOURCE_CLOUD
+ : PolicySource::POLICY_SOURCE_PLATFORM,
+ std::move(value), nullptr);
+ if (policy_name != key::kEnableExperimentalPolicies) {
+ handler_list_->AddHandler(
+ std::make_unique<StubPolicyHandler>(policy_name));
+ }
+ }
+
+ void ApplySettings() {
+ handler_list_->ApplyPolicySettings(
+ policies_, &prefs_, &errors_, &deprecated_policies_, &future_policies_);
+ }
+
+ void CreateHandlerList(bool allow_all_future_policies = false) {
+ handler_list_ = std::make_unique<ConfigurationPolicyHandlerList>(
+ ConfigurationPolicyHandlerList::
+ PopulatePolicyHandlerParametersCallback(),
+ base::BindRepeating(
+ &ConfigurationPolicyHandlerListTest::GetPolicyDetails,
+ base::Unretained(this)),
+ allow_all_future_policies);
+ }
+
+ PrefValueMap* prefs() { return &prefs_; }
+
+ const PolicyDetails* GetPolicyDetails(const std::string& policy_name) {
+ return &details_;
+ }
+ PolicyDetails* details() { return &details_; }
+
+ void VerifyPolicyAndPref(const std::string& policy_name,
+ bool in_pref,
+ bool in_deprecated = false,
+ bool in_future = false) {
+ int pref_value;
+
+ ASSERT_EQ(in_pref, prefs_.GetInteger(policy_name, &pref_value));
+ if (in_pref)
+ EXPECT_EQ(kPolicyValue, pref_value);
+
+ // Pref filter never affects PolicyMap.
+ const base::Value* policy_value =
+ policies_.GetValue(policy_name, base::Value::Type::INTEGER);
+ ASSERT_TRUE(policy_value);
+ EXPECT_EQ(kPolicyValue, policy_value->GetInt());
+
+ EXPECT_EQ(in_deprecated, deprecated_policies_.find(policy_name) !=
+ deprecated_policies_.end());
+ EXPECT_EQ(in_future,
+ future_policies_.find(policy_name) != future_policies_.end());
+ }
+
+ private:
+ PrefValueMap prefs_;
+ PolicyErrorMap errors_;
+ PolicyMap policies_;
+ PoliciesSet deprecated_policies_;
+ PoliciesSet future_policies_;
+ PolicyDetails details_{false, false, false, 0, 0, {}};
+
+ std::unique_ptr<ConfigurationPolicyHandlerList> handler_list_;
+};
+
+TEST_F(ConfigurationPolicyHandlerListTest, ApplySettingsWithNormalPolicy) {
+ AddSimplePolicy();
+ ApplySettings();
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ true);
+}
+
+// Future policy will be filter out unless it's whitelisted by
+// kEnableExperimentalPolicies.
+TEST_F(ConfigurationPolicyHandlerListTest, ApplySettingsWithFuturePolicy) {
+ AddSimplePolicy();
+ details()->is_future = true;
+
+ ApplySettings();
+
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ false,
+ /* in_deprecated */ false, /* in_future */ true);
+
+ // Whitelist a different policy.
+ base::Value::ListStorage enabled_future_policies;
+ enabled_future_policies.push_back(base::Value(kPolicyName2));
+ AddPolicy(key::kEnableExperimentalPolicies, /* is_cloud */ true,
+ base::Value(enabled_future_policies));
+
+ ApplySettings();
+
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ false,
+ /* in_deprecated */ false, /* in_future */ true);
+
+ // Whitelist the policy.
+ enabled_future_policies.push_back(base::Value(kPolicyName));
+ AddPolicy(key::kEnableExperimentalPolicies, /* is_cloud */ true,
+ base::Value(enabled_future_policies));
+
+ ApplySettings();
+
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ true);
+}
+
+TEST_F(ConfigurationPolicyHandlerListTest,
+ ApplySettingsWithoutFutureFilterPolicy) {
+ CreateHandlerList(true);
+ AddSimplePolicy();
+ details()->is_future = true;
+
+ ApplySettings();
+
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ true);
+}
+
+// Device platform policy will be fitered out.
+TEST_F(ConfigurationPolicyHandlerListTest,
+ ApplySettingsWithPlatformDevicePolicy) {
+ AddSimplePolicy();
+ details()->is_device_policy = true;
+
+ ApplySettings();
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ true);
+
+ AddPolicy(kPolicyName2, /* is_cloud */ false, base::Value(kPolicyValue));
+
+ ApplySettings();
+ VerifyPolicyAndPref(kPolicyName2, /* in_pref */ false);
+}
+
+// Deprecated policy won't be filtered out.
+TEST_F(ConfigurationPolicyHandlerListTest, ApplySettingsWithDeprecatedPolicy) {
+ AddSimplePolicy();
+ details()->is_deprecated = true;
+
+ ApplySettings();
+ VerifyPolicyAndPref(kPolicyName, /* in_pref */ true, /* in_deprecated*/ true);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler_parameters.h b/chromium/components/policy/core/browser/configuration_policy_handler_parameters.h
new file mode 100644
index 00000000000..10799bd00e9
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler_parameters.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_PARAMETERS_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_PARAMETERS_H_
+
+#include <string>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// The parameters passed to the policy handlers. The structure can be extended
+// with more fields, if more parameters are needed by the policy handlers.
+struct POLICY_EXPORT PolicyHandlerParameters {
+ std::string user_id_hash;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_HANDLER_PARAMETERS_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_handler_unittest.cc b/chromium/components/policy/core/browser/configuration_policy_handler_unittest.cc
new file mode 100644
index 00000000000..cea69022f4e
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_handler_unittest.cc
@@ -0,0 +1,1092 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_handler.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "components/policy/core/browser/configuration_policy_handler_parameters.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/prefs/pref_value_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestPolicy[] = "unit_test.test_policy";
+const char kTestPref[] = "unit_test.test_pref";
+const char kPolicyName[] = "PolicyForTesting";
+
+constexpr char kValidationSchemaJson[] = R"(
+ {
+ "type": "object",
+ "properties": {
+ "PolicyForTesting": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "movie": { "type": "string" },
+ "min_age": { "type": "integer" }
+ }
+ }
+ }
+ }
+ })";
+
+constexpr char kPolicyMapJsonValid[] = R"(
+ {
+ "PolicyForTesting": [
+ "{ \"movie\": \"Talking Animals\", \"min_age\": 0, }",
+ "{ \"movie\": \"Five Cowboys\", \"min_age\": 12, }",
+ "{ \"movie\": \"Scary Horrors\", \"min_age\": 16, }",
+ ]
+ })";
+
+constexpr char kPolicyMapJsonInvalid[] = R"(
+ {
+ "PolicyForTesting": [
+ "{ \"movie\": \"Talking Animals\", \"min_age\": \"G\", }",
+ "{ \"movie\": \"Five Cowboys\", \"min_age\": \"PG\", }",
+ "{ \"movie\": \"Scary Horrors\", \"min_age\": \"R\", }",
+ ]
+ })";
+
+constexpr char kPolicyMapJsonUnparsable[] = R"(
+ {
+ "PolicyForTesting": [
+ "Talking Animals is rated G",
+ "Five Cowboys is rated PG",
+ "Scary Horrors is rated R",
+ ]
+ })";
+
+constexpr char kPolicyMapJsonWrongTypes[] = R"(
+ {
+ "PolicyForTesting": [ 1, 2, 3, ]
+ })";
+
+constexpr char kPolicyMapJsonWrongRootType[] = R"(
+ {
+ "PolicyForTesting": "test",
+ })";
+
+void GetIntegerTypeMap(
+ std::vector<std::unique_ptr<StringMappingListPolicyHandler::MappingEntry>>*
+ result) {
+ result->push_back(
+ std::make_unique<StringMappingListPolicyHandler::MappingEntry>(
+ "one", std::make_unique<base::Value>(1)));
+ result->push_back(
+ std::make_unique<StringMappingListPolicyHandler::MappingEntry>(
+ "two", std::make_unique<base::Value>(2)));
+}
+
+class TestSchemaValidatingPolicyHandler : public SchemaValidatingPolicyHandler {
+ public:
+ TestSchemaValidatingPolicyHandler(const Schema& schema,
+ SchemaOnErrorStrategy strategy)
+ : SchemaValidatingPolicyHandler(kPolicyName, schema, strategy) {}
+ ~TestSchemaValidatingPolicyHandler() override {}
+
+ void ApplyPolicySettings(const policy::PolicyMap&, PrefValueMap*) override {}
+
+ bool CheckAndGetValueForTest(const PolicyMap& policies,
+ std::unique_ptr<base::Value>* value) {
+ return SchemaValidatingPolicyHandler::CheckAndGetValue(policies, nullptr,
+ value);
+ }
+};
+
+// Simple implementation of ListPolicyHandler that assumes a string list and
+// sets the kTestPref pref to the filtered list.
+class StringListPolicyHandler : public ListPolicyHandler {
+ public:
+ StringListPolicyHandler(const char* policy_name, const char* pref_path)
+ : ListPolicyHandler(policy_name, base::Value::Type::STRING) {}
+
+ protected:
+ void ApplyList(base::Value filtered_list, PrefValueMap* prefs) override {
+ DCHECK(filtered_list.is_list());
+ prefs->SetValue(kTestPref, std::move(filtered_list));
+ }
+};
+
+std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler>
+JsonStringHandlerForTesting() {
+ std::string error;
+ Schema validation_schema = Schema::Parse(kValidationSchemaJson, &error);
+ return std::make_unique<SimpleJsonStringSchemaValidatingPolicyHandler>(
+ kPolicyName, kTestPref, validation_schema,
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED);
+}
+
+} // namespace
+
+TEST(ListPolicyHandlerTest, CheckPolicySettings) {
+ base::Value list(base::Value::Type::LIST);
+ base::Value dict(base::Value::Type::DICTIONARY);
+ policy::PolicyMap policy_map;
+ policy::PolicyErrorMap errors;
+ StringListPolicyHandler handler(kTestPolicy, kTestPref);
+
+ // No policy set is OK.
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ errors.Clear();
+
+ // Not a list is not OK.
+ policy_map.Set(kTestPolicy, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ dict.Clone(), nullptr);
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ errors.Clear();
+
+ // Empty list is OK.
+ policy_map.Set(kTestPolicy, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ list.Clone(), nullptr);
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ errors.Clear();
+
+ // List with an int is OK, but error is added.
+ list.Append(175); // hex af, 255's sake.
+ policy_map.Set(kTestPolicy, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ list.Clone(), nullptr);
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ list.ClearList();
+ errors.Clear();
+
+ // List with a string is OK.
+ list.Append("any_string");
+ policy_map.Set(kTestPolicy, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ list.Clone(), nullptr);
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ errors.Clear();
+}
+
+TEST(StringListPolicyHandlerTest, ApplyPolicySettings) {
+ base::Value list(base::Value::Type::LIST);
+ base::Value expected(base::Value::Type::LIST);
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ base::Value* value;
+ StringListPolicyHandler handler(kTestPolicy, kTestPref);
+
+ // Empty list applies as empty list.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+
+ // List with any string applies that string.
+ list.Append("any_string");
+ expected.Append("any_string");
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+ list.ClearList();
+ expected.ClearList();
+
+ // List with a string and an integer filters out the integer.
+ list.Append("any_string");
+ list.Append(42);
+ expected.Append("any_string");
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+ list.ClearList();
+ expected.ClearList();
+}
+
+TEST(StringToIntEnumListPolicyHandlerTest, CheckPolicySettings) {
+ base::Value list(base::Value::Type::LIST);
+ PolicyMap policy_map;
+ PolicyErrorMap errors;
+ StringMappingListPolicyHandler handler(
+ kTestPolicy, kTestPref, base::BindRepeating(GetIntegerTypeMap));
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ list.Append("one");
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ list.Append("invalid");
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ EXPECT_FALSE(errors.GetErrors(kTestPolicy).empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("no list"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ EXPECT_FALSE(errors.GetErrors(kTestPolicy).empty());
+}
+
+TEST(StringMappingListPolicyHandlerTest, ApplyPolicySettings) {
+ base::Value list(base::Value::Type::LIST);
+ base::Value expected(base::Value::Type::LIST);
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ base::Value* value;
+ StringMappingListPolicyHandler handler(
+ kTestPolicy, kTestPref, base::BindRepeating(GetIntegerTypeMap));
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+
+ list.Append("two");
+ expected.Append(2);
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+
+ list.Append("invalid");
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(expected, *value);
+}
+
+TEST(IntRangePolicyHandler, CheckPolicySettingsClamp) {
+ PolicyMap policy_map;
+ PolicyErrorMap errors;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntRangePolicyHandler handler(kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are not rejected.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ // Check that values lying outside the accepted range are not rejected
+ // (because clamping is enabled) but do yield a warning message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ // Check that an entirely invalid value is rejected and yields an error
+ // message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("invalid"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(IntRangePolicyHandler, CheckPolicySettingsDontClamp) {
+ PolicyMap policy_map;
+ PolicyErrorMap errors;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntRangePolicyHandler handler(kTestPolicy, kTestPref, 0, 10, false);
+
+ // Check that values lying in the accepted range are not rejected.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ // Check that values lying outside the accepted range are rejected and yield
+ // an error message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ // Check that an entirely invalid value is rejected and yields an error
+ // message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("invalid"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(IntRangePolicyHandler, ApplyPolicySettingsClamp) {
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ std::unique_ptr<base::Value> expected;
+ const base::Value* value;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntRangePolicyHandler handler(kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are written to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(5);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(10);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ // Check that values lying outside the accepted range are clamped and written
+ // to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(10);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+}
+
+TEST(IntRangePolicyHandler, ApplyPolicySettingsDontClamp) {
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ std::unique_ptr<base::Value> expected;
+ const base::Value* value;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntRangePolicyHandler handler(kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are written to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(5);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(10);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+}
+
+TEST(IntPercentageToDoublePolicyHandler, CheckPolicySettingsClamp) {
+ PolicyMap policy_map;
+ PolicyErrorMap errors;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntPercentageToDoublePolicyHandler handler(
+ kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are not rejected.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ // Check that values lying outside the accepted range are not rejected
+ // (because clamping is enabled) but do yield a warning message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ // Check that an entirely invalid value is rejected and yields an error
+ // message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("invalid"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(IntPercentageToDoublePolicyHandler, CheckPolicySettingsDontClamp) {
+ PolicyMap policy_map;
+ PolicyErrorMap errors;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntPercentageToDoublePolicyHandler handler(
+ kTestPolicy, kTestPref, 0, 10, false);
+
+ // Check that values lying in the accepted range are not rejected.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+
+ // Check that values lying outside the accepted range are rejected and yield
+ // an error message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+
+ // Check that an entirely invalid value is rejected and yields an error
+ // message.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("invalid"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(IntPercentageToDoublePolicyHandler, ApplyPolicySettingsClamp) {
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ std::unique_ptr<base::Value> expected;
+ const base::Value* value;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntPercentageToDoublePolicyHandler handler(
+ kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are written to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.05);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.1);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ // Check that values lying outside the accepted range are clamped and written
+ // to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(-5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(15), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.1);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+}
+
+TEST(IntPercentageToDoublePolicyHandler, ApplyPolicySettingsDontClamp) {
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ std::unique_ptr<base::Value> expected;
+ const base::Value* value;
+
+ // This tests needs to modify an int policy. The exact policy used and its
+ // semantics outside the test are irrelevant.
+ IntPercentageToDoublePolicyHandler handler(
+ kTestPolicy, kTestPref, 0, 10, true);
+
+ // Check that values lying in the accepted range are written to the pref.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.0);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(5), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.05);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(10), nullptr);
+ prefs.Clear();
+ handler.ApplyPolicySettings(policy_map, &prefs);
+ expected = std::make_unique<base::Value>(0.1);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+}
+
+TEST(SchemaValidatingPolicyHandlerTest, CheckAndGetValueInvalid) {
+ std::string error;
+ static const char kSchemaJson[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"OneToThree\": {"
+ " \"type\": \"integer\","
+ " \"minimum\": 1,"
+ " \"maximum\": 3"
+ " },"
+ " \"Colors\": {"
+ " \"type\": \"string\","
+ " \"enum\": [ \"Red\", \"Green\", \"Blue\" ]"
+ " }"
+ " }"
+ "}";
+ Schema schema = Schema::Parse(kSchemaJson, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ static const char kPolicyMapJson[] =
+ "{"
+ " \"PolicyForTesting\": {"
+ " \"OneToThree\": 2,"
+ " \"Colors\": \"White\""
+ " }"
+ "}";
+ base::JSONReader::ValueWithError parsed_json_or_error =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJson, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json_or_error.value) << parsed_json_or_error.error_message;
+
+ base::Value& parsed_json = parsed_json_or_error.value.value();
+ ASSERT_TRUE(parsed_json.is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(&base::Value::AsDictionaryValue(parsed_json),
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD);
+
+ TestSchemaValidatingPolicyHandler handler(schema, SCHEMA_ALLOW_UNKNOWN);
+ std::unique_ptr<base::Value> output_value;
+ EXPECT_FALSE(handler.CheckAndGetValueForTest(policy_map, &output_value));
+}
+
+TEST(SchemaValidatingPolicyHandlerTest, CheckAndGetValueUnknown) {
+ std::string error;
+ static const char kSchemaJson[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"OneToThree\": {"
+ " \"type\": \"integer\","
+ " \"minimum\": 1,"
+ " \"maximum\": 3"
+ " },"
+ " \"Colors\": {"
+ " \"type\": \"string\","
+ " \"enum\": [ \"Red\", \"Green\", \"Blue\" ]"
+ " }"
+ " }"
+ "}";
+ Schema schema = Schema::Parse(kSchemaJson, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ static const char kPolicyMapJson[] =
+ "{"
+ " \"PolicyForTesting\": {"
+ " \"OneToThree\": 2,"
+ " \"Apples\": \"Red\""
+ " }"
+ "}";
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJson, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ TestSchemaValidatingPolicyHandler handler(schema, SCHEMA_ALLOW_UNKNOWN);
+ std::unique_ptr<base::Value> output_value;
+ ASSERT_TRUE(handler.CheckAndGetValueForTest(policy_map, &output_value));
+ ASSERT_TRUE(output_value);
+ ASSERT_TRUE(output_value->is_dict());
+
+ // Test that CheckAndGetValue() actually dropped unknown properties.
+ absl::optional<int> one_two_three = output_value->FindIntKey("OneToThree");
+ ASSERT_TRUE(one_two_three);
+ int int_value = one_two_three.value();
+ EXPECT_EQ(2, int_value);
+ EXPECT_FALSE(output_value->FindKey("Apples"));
+}
+
+TEST(SimpleSchemaValidatingPolicyHandlerTest, CheckAndGetValue) {
+ static const char kSchemaJson[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"PolicyForTesting\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"OneToThree\": {"
+ " \"type\": \"integer\","
+ " \"minimum\": 1,"
+ " \"maximum\": 3"
+ " },"
+ " \"Colors\": {"
+ " \"type\": \"string\","
+ " \"enum\": [ \"Red\", \"Green\", \"Blue\" ]"
+ " }"
+ " }"
+ " }"
+ " }"
+ "}";
+ std::string error;
+ Schema schema = Schema::Parse(kSchemaJson, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ static const char kPolicyMapJson[] =
+ "{"
+ " \"PolicyForTesting\": {"
+ " \"OneToThree\": 2,"
+ " \"Colors\": \"Green\""
+ " }"
+ "}";
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJson, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map_recommended;
+ policy_map_recommended.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ PolicyMap policy_map_mandatory;
+ policy_map_mandatory.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ SimpleSchemaValidatingPolicyHandler handler_all(
+ kPolicyName, kTestPref, schema, SCHEMA_STRICT,
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_ALLOWED,
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED);
+
+ SimpleSchemaValidatingPolicyHandler handler_recommended(
+ kPolicyName, kTestPref, schema, SCHEMA_STRICT,
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_ALLOWED,
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_PROHIBITED);
+
+ SimpleSchemaValidatingPolicyHandler handler_mandatory(
+ kPolicyName, kTestPref, schema, SCHEMA_STRICT,
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED);
+
+ SimpleSchemaValidatingPolicyHandler handler_none(
+ kPolicyName, kTestPref, schema, SCHEMA_STRICT,
+ SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
+ SimpleSchemaValidatingPolicyHandler::MANDATORY_PROHIBITED);
+
+ const base::Value* value_expected_in_pref =
+ parsed_json.value->FindPath(kPolicyName);
+
+ PolicyErrorMap errors;
+ PrefValueMap prefs;
+ base::Value* value_set_in_pref;
+
+ EXPECT_TRUE(handler_all.CheckPolicySettings(policy_map_mandatory, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler_all.ApplyPolicySettings(policy_map_mandatory, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+
+ EXPECT_FALSE(
+ handler_recommended.CheckPolicySettings(policy_map_mandatory, &errors));
+ EXPECT_FALSE(errors.empty());
+ errors.Clear();
+
+ EXPECT_TRUE(
+ handler_mandatory.CheckPolicySettings(policy_map_mandatory, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler_mandatory.ApplyPolicySettings(policy_map_mandatory, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+
+ EXPECT_FALSE(handler_none.CheckPolicySettings(policy_map_mandatory, &errors));
+ EXPECT_FALSE(errors.empty());
+ errors.Clear();
+
+ EXPECT_TRUE(handler_all.CheckPolicySettings(policy_map_recommended, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler_all.ApplyPolicySettings(policy_map_mandatory, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+
+ EXPECT_FALSE(
+ handler_mandatory.CheckPolicySettings(policy_map_recommended, &errors));
+ EXPECT_FALSE(errors.empty());
+ errors.Clear();
+
+ EXPECT_TRUE(
+ handler_recommended.CheckPolicySettings(policy_map_recommended, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler_recommended.ApplyPolicySettings(policy_map_mandatory, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+
+ EXPECT_FALSE(
+ handler_none.CheckPolicySettings(policy_map_recommended, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(SimpleJsonStringSchemaValidatingPolicyHandlerTest, ValidEmbeddedJson) {
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJsonValid, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ const base::Value* value_expected_in_pref =
+ parsed_json.value->FindPath(kPolicyName);
+
+ PolicyErrorMap errors;
+ PrefValueMap prefs;
+ base::Value* value_set_in_pref;
+
+ // This value matches the schema - handler shouldn't record any errors.
+ std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler> handler =
+ JsonStringHandlerForTesting();
+ EXPECT_TRUE(handler->CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ handler->ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+}
+
+TEST(SimpleJsonStringSchemaValidatingPolicyHandlerTest, InvalidEmbeddedJson) {
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJsonInvalid, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ const base::Value* value_expected_in_pref =
+ parsed_json.value->FindPath(kPolicyName);
+
+ PolicyErrorMap errors;
+ PrefValueMap prefs;
+ base::Value* value_set_in_pref;
+
+ // Handler accepts JSON that doesn't match the schema, but records errors.
+ std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler> handler =
+ JsonStringHandlerForTesting();
+ EXPECT_TRUE(handler->CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ handler->ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+}
+
+TEST(SimpleJsonStringSchemaValidatingPolicyHandlerTest, UnparsableJson) {
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJsonUnparsable, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ const base::Value* value_expected_in_pref =
+ parsed_json.value->FindPath(kPolicyName);
+
+ PolicyErrorMap errors;
+ PrefValueMap prefs;
+ base::Value* value_set_in_pref;
+
+ // Handler accepts unparsable JSON, but records errors.
+ std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler> handler =
+ JsonStringHandlerForTesting();
+ EXPECT_TRUE(handler->CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ handler->ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+}
+
+TEST(SimpleJsonStringSchemaValidatingPolicyHandlerTest, WrongType) {
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJsonWrongTypes, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ const base::Value* value_expected_in_pref =
+ parsed_json.value->FindPath(kPolicyName);
+
+ PolicyErrorMap errors;
+ PrefValueMap prefs;
+ base::Value* value_set_in_pref;
+
+ // Handler allows wrong types (not at the root), but records errors.
+ std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler> handler =
+ JsonStringHandlerForTesting();
+ EXPECT_TRUE(handler->CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ handler->ApplyPolicySettings(policy_map, &prefs);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value_set_in_pref));
+ EXPECT_EQ(*value_expected_in_pref, *value_set_in_pref);
+}
+
+TEST(SimpleJsonStringSchemaValidatingPolicyHandlerTest, WrongRootType) {
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(
+ kPolicyMapJsonWrongRootType, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
+ ASSERT_TRUE(parsed_json.value->is_dict());
+
+ PolicyMap policy_map;
+ policy_map.LoadFrom(
+ &base::Value::AsDictionaryValue(parsed_json.value.value()),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD);
+
+ PolicyErrorMap errors;
+
+ // Handler rejects the wrong root type and records errors.
+ std::unique_ptr<SimpleJsonStringSchemaValidatingPolicyHandler> handler =
+ JsonStringHandlerForTesting();
+ EXPECT_FALSE(handler->CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST(SimpleDeprecatingPolicyHandlerTest, CheckDeprecatedUsedWhenNoNewValue) {
+ PolicyMap policy_map;
+ PrefValueMap prefs;
+ std::unique_ptr<base::Value> expected;
+ const base::Value* value;
+ PolicyErrorMap errors;
+ PolicyHandlerParameters params;
+ const char kLegacyPolicy[] = "legacy_policy";
+
+ SimpleDeprecatingPolicyHandler handler(
+ std::make_unique<SimplePolicyHandler>(kLegacyPolicy, kTestPref,
+ base::Value::Type::INTEGER),
+ std::make_unique<SimplePolicyHandler>(kTestPolicy, kTestPref,
+ base::Value::Type::INTEGER));
+
+ // Check that legacy value alone is used.
+ policy_map.Set(kLegacyPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(42), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler.ApplyPolicySettingsWithParameters(policy_map, params, &prefs);
+ expected = std::make_unique<base::Value>(42);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ // Set the new value as invalid and verify that the total result is invalid.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("0"), nullptr);
+ errors.Clear();
+ EXPECT_FALSE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ prefs.Clear();
+
+ // Set the new value and verify that it overrides the legacy.
+ policy_map.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1337), nullptr);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_FALSE(errors.empty());
+ prefs.Clear();
+ handler.ApplyPolicySettingsWithParameters(policy_map, params, &prefs);
+ expected = std::make_unique<base::Value>(1337);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+
+ // Erasing the legacy value should have no effect at this point.
+ policy_map.Erase(kLegacyPolicy);
+ errors.Clear();
+ EXPECT_TRUE(handler.CheckPolicySettings(policy_map, &errors));
+ EXPECT_TRUE(errors.empty());
+ prefs.Clear();
+ handler.ApplyPolicySettingsWithParameters(policy_map, params, &prefs);
+ expected = std::make_unique<base::Value>(1337);
+ EXPECT_TRUE(prefs.GetValue(kTestPref, &value));
+ EXPECT_EQ(*expected, *value);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_pref_store.cc b/chromium/components/policy/core/browser/configuration_policy_pref_store.cc
new file mode 100644
index 00000000000..88648aa055b
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_pref_store.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_pref_store.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/observer_list.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/policy/core/browser/browser_policy_connector_base.h"
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+void LogErrors(std::unique_ptr<PolicyErrorMap> errors,
+ PoliciesSet deprecated_policies,
+ PoliciesSet future_policies) {
+ DCHECK(errors->IsReady());
+ for (auto& pair : *errors) {
+ std::u16string policy = base::ASCIIToUTF16(pair.first);
+ DLOG(WARNING) << "Policy " << policy << ": " << pair.second;
+ }
+ for (const auto& policy : deprecated_policies) {
+ VLOG(1) << "Policy " << policy << " has been deprecated.";
+ }
+ for (const auto& policy : future_policies) {
+ VLOG(1) << "Policy " << policy << " has not been released yet.";
+ }
+}
+
+bool IsLevel(PolicyLevel level, const PolicyMap::const_iterator iter) {
+ return iter->second.level == level;
+}
+
+} // namespace
+
+ConfigurationPolicyPrefStore::ConfigurationPolicyPrefStore(
+ BrowserPolicyConnectorBase* policy_connector,
+ PolicyService* service,
+ const ConfigurationPolicyHandlerList* handler_list,
+ PolicyLevel level)
+ : policy_connector_(policy_connector),
+ policy_service_(service),
+ handler_list_(handler_list),
+ level_(level) {
+ // Read initial policy.
+ prefs_.reset(CreatePreferencesFromPolicies());
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, this);
+}
+
+void ConfigurationPolicyPrefStore::AddObserver(PrefStore::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ConfigurationPolicyPrefStore::RemoveObserver(
+ PrefStore::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool ConfigurationPolicyPrefStore::HasObservers() const {
+ return !observers_.empty();
+}
+
+bool ConfigurationPolicyPrefStore::IsInitializationComplete() const {
+ return policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME);
+}
+
+bool ConfigurationPolicyPrefStore::GetValue(const std::string& key,
+ const base::Value** value) const {
+ const base::Value* stored_value = nullptr;
+ if (!prefs_ || !prefs_->GetValue(key, &stored_value))
+ return false;
+
+ if (value)
+ *value = stored_value;
+ return true;
+}
+
+std::unique_ptr<base::DictionaryValue> ConfigurationPolicyPrefStore::GetValues()
+ const {
+ if (!prefs_)
+ return std::make_unique<base::DictionaryValue>();
+ return prefs_->AsDictionaryValue();
+}
+
+void ConfigurationPolicyPrefStore::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ DCHECK_EQ(POLICY_DOMAIN_CHROME, ns.domain);
+ DCHECK(ns.component_id.empty());
+ Refresh();
+}
+
+void ConfigurationPolicyPrefStore::OnPolicyServiceInitialized(
+ PolicyDomain domain) {
+ if (domain == POLICY_DOMAIN_CHROME) {
+ for (auto& observer : observers_)
+ observer.OnInitializationCompleted(true);
+ }
+}
+
+ConfigurationPolicyPrefStore::~ConfigurationPolicyPrefStore() {
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, this);
+}
+
+void ConfigurationPolicyPrefStore::Refresh() {
+ std::unique_ptr<PrefValueMap> new_prefs(CreatePreferencesFromPolicies());
+ std::vector<std::string> changed_prefs;
+ new_prefs->GetDifferingKeys(prefs_.get(), &changed_prefs);
+ prefs_.swap(new_prefs);
+
+ // Send out change notifications.
+ for (std::vector<std::string>::const_iterator pref(changed_prefs.begin());
+ pref != changed_prefs.end(); ++pref) {
+ for (auto& observer : observers_)
+ observer.OnPrefValueChanged(*pref);
+ }
+}
+
+PrefValueMap* ConfigurationPolicyPrefStore::CreatePreferencesFromPolicies() {
+ std::unique_ptr<PrefValueMap> prefs(new PrefValueMap);
+ PolicyMap filtered_policies =
+ policy_service_
+ ->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Clone();
+ filtered_policies.EraseNonmatching(base::BindRepeating(&IsLevel, level_));
+
+ std::unique_ptr<PolicyErrorMap> errors = std::make_unique<PolicyErrorMap>();
+
+ PoliciesSet deprecated_policies;
+ PoliciesSet future_policies;
+ handler_list_->ApplyPolicySettings(filtered_policies, prefs.get(),
+ errors.get(), &deprecated_policies,
+ &future_policies);
+
+ if (!errors->empty()) {
+ if (errors->IsReady()) {
+ LogErrors(std::move(errors), std::move(deprecated_policies),
+ std::move(future_policies));
+ } else if (policy_connector_) { // May be null in tests.
+ policy_connector_->NotifyWhenResourceBundleReady(base::BindOnce(
+ &LogErrors, std::move(errors), std::move(deprecated_policies),
+ std::move(future_policies)));
+ }
+ }
+
+ return prefs.release();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_pref_store.h b/chromium/components/policy/core/browser/configuration_policy_pref_store.h
new file mode 100644
index 00000000000..b5c12e0415b
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_pref_store.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+#include "components/prefs/pref_store.h"
+
+class PrefValueMap;
+
+namespace policy {
+
+class BrowserPolicyConnectorBase;
+class ConfigurationPolicyHandlerList;
+
+// An implementation of PrefStore that bridges policy settings as read from the
+// PolicyService to preferences. Converts POLICY_DOMAIN_CHROME policies a given
+// PolicyLevel to their corresponding preferences.
+class POLICY_EXPORT ConfigurationPolicyPrefStore
+ : public PrefStore,
+ public PolicyService::Observer {
+ public:
+ // Does not take ownership of |service| nor |handler_list|, which must outlive
+ // the store. Only policies of the given |level| will be mapped.
+ ConfigurationPolicyPrefStore(
+ BrowserPolicyConnectorBase* policy_connector,
+ PolicyService* service,
+ const ConfigurationPolicyHandlerList* handler_list,
+ PolicyLevel level);
+ ConfigurationPolicyPrefStore(const ConfigurationPolicyPrefStore&) = delete;
+ ConfigurationPolicyPrefStore& operator=(const ConfigurationPolicyPrefStore&) =
+ delete;
+
+ // PrefStore methods:
+ void AddObserver(PrefStore::Observer* observer) override;
+ void RemoveObserver(PrefStore::Observer* observer) override;
+ bool HasObservers() const override;
+ bool IsInitializationComplete() const override;
+ bool GetValue(const std::string& key,
+ const base::Value** result) const override;
+ std::unique_ptr<base::DictionaryValue> GetValues() const override;
+
+ // PolicyService::Observer methods:
+ void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) override;
+ void OnPolicyServiceInitialized(PolicyDomain domain) override;
+
+ private:
+ ~ConfigurationPolicyPrefStore() override;
+
+ // Refreshes policy information, rereading policy from the policy service and
+ // sending out change notifications as appropriate.
+ void Refresh();
+
+ // Returns a new PrefValueMap containing the preference values that correspond
+ // to the policies currently provided by the policy service.
+ PrefValueMap* CreatePreferencesFromPolicies();
+
+ // May be null in tests.
+ raw_ptr<BrowserPolicyConnectorBase> policy_connector_;
+
+ // The PolicyService from which policy settings are read.
+ raw_ptr<PolicyService> policy_service_;
+
+ // The policy handlers used to convert policies into their corresponding
+ // preferences.
+ raw_ptr<const ConfigurationPolicyHandlerList> handler_list_;
+
+ // The policy level that this PrefStore uses.
+ PolicyLevel level_;
+
+ // Current policy preferences.
+ std::unique_ptr<PrefValueMap> prefs_;
+
+ base::ObserverList<PrefStore::Observer, true>::Unchecked observers_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_pref_store_test.cc b/chromium/components/policy/core/browser/configuration_policy_pref_store_test.cc
new file mode 100644
index 00000000000..340d117e66f
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_pref_store_test.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_pref_store_test.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/run_loop.h"
+#include "components/policy/core/browser/configuration_policy_handler_parameters.h"
+#include "components/policy/core/browser/configuration_policy_pref_store.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::Return;
+using testing::_;
+
+namespace policy {
+
+ConfigurationPolicyPrefStoreTest::ConfigurationPolicyPrefStoreTest()
+ : handler_list_(base::BindRepeating(&ConfigurationPolicyPrefStoreTest::
+ PopulatePolicyHandlerParameters,
+ base::Unretained(this)),
+ GetChromePolicyDetailsCallback(),
+ /* allow_all_future_policies*/ true) {
+ provider_.SetDefaultReturns(false /* is_initialization_complete_return */,
+ false /* is_first_policy_load_complete_return */);
+ EXPECT_CALL(provider_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ provider_.Init();
+ providers_.push_back(&provider_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(providers_);
+ store_ = new ConfigurationPolicyPrefStore(
+ nullptr, policy_service_.get(), &handler_list_, POLICY_LEVEL_MANDATORY);
+}
+
+ConfigurationPolicyPrefStoreTest::~ConfigurationPolicyPrefStoreTest() {}
+
+void ConfigurationPolicyPrefStoreTest::PopulatePolicyHandlerParameters(
+ PolicyHandlerParameters* parameters) {}
+
+void ConfigurationPolicyPrefStoreTest::TearDown() {
+ provider_.Shutdown();
+}
+
+void ConfigurationPolicyPrefStoreTest::UpdateProviderPolicy(
+ const PolicyMap& policy) {
+ provider_.UpdateChromePolicy(policy);
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/configuration_policy_pref_store_test.h b/chromium/components/policy/core/browser/configuration_policy_pref_store_test.h
new file mode 100644
index 00000000000..98421dfee41
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_pref_store_test.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_TEST_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_TEST_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/test/task_environment.h"
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+struct PolicyHandlerParameters;
+class PolicyMap;
+class ConfigurationPolicyPrefStore;
+
+class ConfigurationPolicyPrefStoreTest : public testing::Test {
+ public:
+ ConfigurationPolicyPrefStoreTest(const ConfigurationPolicyPrefStoreTest&) =
+ delete;
+ ConfigurationPolicyPrefStoreTest& operator=(
+ const ConfigurationPolicyPrefStoreTest&) = delete;
+
+ protected:
+ ConfigurationPolicyPrefStoreTest();
+ ~ConfigurationPolicyPrefStoreTest() override;
+ void TearDown() override;
+ void UpdateProviderPolicy(const PolicyMap& policy);
+
+ // A unit test can override this method to populate the policy handler
+ // parameters as suited to its needs.
+ virtual void PopulatePolicyHandlerParameters(
+ PolicyHandlerParameters* parameters);
+
+ PolicyServiceImpl::Providers providers_;
+ ConfigurationPolicyHandlerList handler_list_;
+ testing::NiceMock<MockConfigurationPolicyProvider> provider_;
+ std::unique_ptr<PolicyServiceImpl> policy_service_;
+ scoped_refptr<ConfigurationPolicyPrefStore> store_;
+ base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_CONFIGURATION_POLICY_PREF_STORE_TEST_H_
diff --git a/chromium/components/policy/core/browser/configuration_policy_pref_store_unittest.cc b/chromium/components/policy/core/browser/configuration_policy_pref_store_unittest.cc
new file mode 100644
index 00000000000..abecdca8534
--- /dev/null
+++ b/chromium/components/policy/core/browser/configuration_policy_pref_store_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/configuration_policy_pref_store.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/configuration_policy_pref_store_test.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_service_impl.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/prefs/pref_store_observer_mock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+// Note: this file should move to components/policy/core/browser, but the
+// components_unittests runner does not load the ResourceBundle as
+// ChromeTestSuite::Initialize does, which leads to failures using
+// PolicyErrorMap.
+
+using testing::Mock;
+using testing::Return;
+using testing::_;
+
+namespace {
+
+const char kTestPolicy[] = "test.policy";
+const char kTestPref[] = "test.pref";
+
+} // namespace
+
+namespace policy {
+
+// Test cases for list-valued policy settings.
+class ConfigurationPolicyPrefStoreListTest
+ : public ConfigurationPolicyPrefStoreTest {
+ void SetUp() override {
+ handler_list_.AddHandler(
+ base::WrapUnique<ConfigurationPolicyHandler>(new SimplePolicyHandler(
+ kTestPolicy, kTestPref, base::Value::Type::LIST)));
+ }
+};
+
+TEST_F(ConfigurationPolicyPrefStoreListTest, GetDefault) {
+ EXPECT_FALSE(store_->GetValue(kTestPref, nullptr));
+}
+
+TEST_F(ConfigurationPolicyPrefStoreListTest, SetValue) {
+ base::Value in_value(base::Value::Type::LIST);
+ in_value.Append("test1");
+ in_value.Append("test2,");
+ PolicyMap policy;
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, in_value.Clone(), nullptr);
+ UpdateProviderPolicy(policy);
+ const base::Value* value = nullptr;
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ ASSERT_TRUE(value);
+ EXPECT_EQ(in_value, *value);
+}
+
+// Test cases for string-valued policy settings.
+class ConfigurationPolicyPrefStoreStringTest
+ : public ConfigurationPolicyPrefStoreTest {
+ void SetUp() override {
+ handler_list_.AddHandler(
+ base::WrapUnique<ConfigurationPolicyHandler>(new SimplePolicyHandler(
+ kTestPolicy, kTestPref, base::Value::Type::STRING)));
+ }
+};
+
+TEST_F(ConfigurationPolicyPrefStoreStringTest, GetDefault) {
+ EXPECT_FALSE(store_->GetValue(kTestPref, nullptr));
+}
+
+TEST_F(ConfigurationPolicyPrefStoreStringTest, SetValue) {
+ PolicyMap policy;
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("http://chromium.org"), nullptr);
+ UpdateProviderPolicy(policy);
+ const base::Value* value = nullptr;
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ ASSERT_TRUE(value);
+ EXPECT_EQ(base::Value("http://chromium.org"), *value);
+}
+
+// Test cases for boolean-valued policy settings.
+class ConfigurationPolicyPrefStoreBooleanTest
+ : public ConfigurationPolicyPrefStoreTest {
+ void SetUp() override {
+ handler_list_.AddHandler(
+ base::WrapUnique<ConfigurationPolicyHandler>(new SimplePolicyHandler(
+ kTestPolicy, kTestPref, base::Value::Type::BOOLEAN)));
+ }
+};
+
+TEST_F(ConfigurationPolicyPrefStoreBooleanTest, GetDefault) {
+ EXPECT_FALSE(store_->GetValue(kTestPref, nullptr));
+}
+
+TEST_F(ConfigurationPolicyPrefStoreBooleanTest, SetValue) {
+ PolicyMap policy;
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ UpdateProviderPolicy(policy);
+ const base::Value* value = nullptr;
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ ASSERT_TRUE(value);
+ ASSERT_TRUE(value->is_bool());
+ EXPECT_FALSE(value->GetBool());
+
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ UpdateProviderPolicy(policy);
+ value = nullptr;
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ ASSERT_TRUE(value->is_bool());
+ EXPECT_TRUE(value->GetBool());
+}
+
+// Test cases for integer-valued policy settings.
+class ConfigurationPolicyPrefStoreIntegerTest
+ : public ConfigurationPolicyPrefStoreTest {
+ void SetUp() override {
+ handler_list_.AddHandler(
+ base::WrapUnique<ConfigurationPolicyHandler>(new SimplePolicyHandler(
+ kTestPolicy, kTestPref, base::Value::Type::INTEGER)));
+ }
+};
+
+TEST_F(ConfigurationPolicyPrefStoreIntegerTest, GetDefault) {
+ EXPECT_FALSE(store_->GetValue(kTestPref, nullptr));
+}
+
+TEST_F(ConfigurationPolicyPrefStoreIntegerTest, SetValue) {
+ PolicyMap policy;
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(2), nullptr);
+ UpdateProviderPolicy(policy);
+ const base::Value* value = nullptr;
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ EXPECT_EQ(base::Value(2), *value);
+}
+
+// Exercises the policy refresh mechanism.
+class ConfigurationPolicyPrefStoreRefreshTest
+ : public ConfigurationPolicyPrefStoreTest {
+ protected:
+ void SetUp() override {
+ ConfigurationPolicyPrefStoreTest::SetUp();
+ store_->AddObserver(&observer_);
+ handler_list_.AddHandler(
+ base::WrapUnique<ConfigurationPolicyHandler>(new SimplePolicyHandler(
+ kTestPolicy, kTestPref, base::Value::Type::STRING)));
+ }
+
+ void TearDown() override {
+ store_->RemoveObserver(&observer_);
+ ConfigurationPolicyPrefStoreTest::TearDown();
+ }
+
+ PrefStoreObserverMock observer_;
+};
+
+TEST_F(ConfigurationPolicyPrefStoreRefreshTest, Refresh) {
+ const base::Value* value = nullptr;
+ EXPECT_FALSE(store_->GetValue(kTestPolicy, nullptr));
+
+ PolicyMap policy;
+ policy.Set(kTestPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("http://www.chromium.org"),
+ nullptr);
+ UpdateProviderPolicy(policy);
+ observer_.VerifyAndResetChangedKey(kTestPref);
+ EXPECT_TRUE(store_->GetValue(kTestPref, &value));
+ EXPECT_EQ(base::Value("http://www.chromium.org"), *value);
+
+ UpdateProviderPolicy(policy);
+ EXPECT_TRUE(observer_.changed_keys.empty());
+
+ policy.Erase(kTestPolicy);
+ UpdateProviderPolicy(policy);
+ observer_.VerifyAndResetChangedKey(kTestPref);
+ EXPECT_FALSE(store_->GetValue(kTestPref, nullptr));
+}
+
+TEST_F(ConfigurationPolicyPrefStoreRefreshTest, Initialization) {
+ EXPECT_FALSE(store_->IsInitializationComplete());
+ EXPECT_CALL(provider_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ PolicyMap policy;
+ UpdateProviderPolicy(policy);
+ EXPECT_TRUE(observer_.initialized);
+ EXPECT_TRUE(observer_.initialization_success);
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(store_->IsInitializationComplete());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_conversions.cc b/chromium/components/policy/core/browser/policy_conversions.cc
new file mode 100644
index 00000000000..2050fd9dfbb
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_conversions.cc
@@ -0,0 +1,258 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_conversions.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/strings/grit/components_strings.h"
+#include "extensions/buildflags/buildflags.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using base::Value;
+
+namespace policy {
+
+const webui::LocalizedString kPolicySources[POLICY_SOURCE_COUNT] = {
+ {"sourceEnterpriseDefault", IDS_POLICY_SOURCE_ENTERPRISE_DEFAULT},
+ {"commandLine", IDS_POLICY_SOURCE_COMMAND_LINE},
+ {"cloud", IDS_POLICY_SOURCE_CLOUD},
+ {"sourceActiveDirectory", IDS_POLICY_SOURCE_ACTIVE_DIRECTORY},
+ {"sourceDeviceLocalAccountOverrideDeprecated",
+ IDS_POLICY_SOURCE_DEVICE_LOCAL_ACCOUNT_OVERRIDE},
+ {"platform", IDS_POLICY_SOURCE_PLATFORM},
+ {"priorityCloud", IDS_POLICY_SOURCE_CLOUD},
+ {"merged", IDS_POLICY_SOURCE_MERGED},
+ {"cloud_from_ash", IDS_POLICY_SOURCE_CLOUD_FROM_ASH},
+ {"restrictedManagedGuestSessionOverride",
+ IDS_POLICY_SOURCE_RESTRICTED_MANAGED_GUEST_SESSION_OVERRIDE},
+};
+
+PolicyConversions::PolicyConversions(
+ std::unique_ptr<PolicyConversionsClient> client)
+ : client_(std::move(client)) {
+ DCHECK(client_.get());
+}
+
+PolicyConversions::~PolicyConversions() = default;
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+PolicyConversions& PolicyConversions::WithUpdaterPolicies(
+ std::unique_ptr<PolicyMap> policies) {
+ client()->SetUpdaterPolicies(std::move(policies));
+ return *this;
+}
+PolicyConversions& PolicyConversions::WithUpdaterPolicySchemas(
+ PolicyToSchemaMap schemas) {
+ client()->SetUpdaterPolicySchemas(std::move(schemas));
+ return *this;
+}
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+PolicyConversions& PolicyConversions::EnableConvertTypes(bool enabled) {
+ client_->EnableConvertTypes(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::EnableConvertValues(bool enabled) {
+ client_->EnableConvertValues(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::EnableDeviceLocalAccountPolicies(
+ bool enabled) {
+ client_->EnableDeviceLocalAccountPolicies(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::EnableDeviceInfo(bool enabled) {
+ client_->EnableDeviceInfo(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::EnablePrettyPrint(bool enabled) {
+ client_->EnablePrettyPrint(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::EnableUserPolicies(bool enabled) {
+ client_->EnableUserPolicies(enabled);
+ return *this;
+}
+
+PolicyConversions& PolicyConversions::SetDropDefaultValues(bool enabled) {
+ client_->SetDropDefaultValues(enabled);
+ return *this;
+}
+
+std::string PolicyConversions::ToJSON() {
+ return client_->ConvertValueToJSON(ToValue());
+}
+
+/**
+ * DictionaryPolicyConversions
+ */
+
+DictionaryPolicyConversions::DictionaryPolicyConversions(
+ std::unique_ptr<PolicyConversionsClient> client)
+ : PolicyConversions(std::move(client)) {}
+DictionaryPolicyConversions::~DictionaryPolicyConversions() = default;
+
+Value DictionaryPolicyConversions::ToValue() {
+ Value all_policies(Value::Type::DICTIONARY);
+
+ if (client()->HasUserPolicies()) {
+ all_policies.SetKey("chromePolicies", client()->GetChromePolicies());
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ all_policies.SetKey("extensionPolicies",
+ GetExtensionPolicies(POLICY_DOMAIN_EXTENSIONS));
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ }
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ if (client()->HasUpdaterPolicies())
+ all_policies.SetKey("updaterPolicies", client()->GetUpdaterPolicies());
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ all_policies.SetKey("loginScreenExtensionPolicies",
+ GetExtensionPolicies(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+#endif // BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ all_policies.SetKey("deviceLocalAccountPolicies",
+ GetDeviceLocalAccountPolicies());
+ Value identity_fields = client()->GetIdentityFields();
+ if (!identity_fields.is_none())
+ all_policies.MergeDictionary(&identity_fields);
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+ return all_policies;
+}
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+Value DictionaryPolicyConversions::GetDeviceLocalAccountPolicies() {
+ Value policies = client()->GetDeviceLocalAccountPolicies();
+ Value device_values(Value::Type::DICTIONARY);
+ for (auto&& policy : policies.GetListDeprecated()) {
+ device_values.SetKey(policy.FindKey("id")->GetString(),
+ std::move(*policy.FindKey("policies")));
+ }
+ return device_values;
+}
+#endif
+
+Value DictionaryPolicyConversions::GetExtensionPolicies(
+ PolicyDomain policy_domain) {
+ Value policies = client()->GetExtensionPolicies(policy_domain);
+ Value extension_values(Value::Type::DICTIONARY);
+ for (auto&& policy : policies.GetListDeprecated()) {
+ extension_values.SetKey(policy.FindKey("id")->GetString(),
+ std::move(*policy.FindKey("policies")));
+ }
+ return extension_values;
+}
+
+/**
+ * ArrayPolicyConversions
+ */
+
+ArrayPolicyConversions::ArrayPolicyConversions(
+ std::unique_ptr<PolicyConversionsClient> client)
+ : PolicyConversions(std::move(client)) {}
+ArrayPolicyConversions::~ArrayPolicyConversions() = default;
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+void ArrayPolicyConversions::WithAdditionalChromePolicies(Value&& policies) {
+ additional_chrome_policies_ = std::move(policies);
+}
+#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+Value ArrayPolicyConversions::ToValue() {
+ Value all_policies(Value::Type::LIST);
+
+ if (client()->HasUserPolicies()) {
+ all_policies.Append(GetChromePolicies());
+
+#if !BUILDFLAG(IS_CHROMEOS)
+ // Precedence policies do not apply to Chrome OS, so the Policy Precedence
+ // table is not shown in chrome://policy.
+ all_policies.Append(GetPrecedencePolicies());
+#endif // !BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ if (client()->HasUpdaterPolicies())
+ all_policies.Append(GetUpdaterPolicies());
+#endif
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ for (auto& policy : client()
+ ->GetExtensionPolicies(POLICY_DOMAIN_EXTENSIONS)
+ .TakeListDeprecated()) {
+ all_policies.Append(std::move(policy));
+ }
+#endif // BUILDFLAG(ENABLE_EXTENSIONS)
+ }
+
+#if BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ for (auto& policy :
+ client()
+ ->GetExtensionPolicies(POLICY_DOMAIN_SIGNIN_EXTENSIONS)
+ .TakeListDeprecated()) {
+ all_policies.Append(std::move(policy));
+ }
+#endif // BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ for (auto& device_policy :
+ client()->GetDeviceLocalAccountPolicies().TakeListDeprecated())
+ all_policies.Append(std::move(device_policy));
+
+ Value identity_fields = client()->GetIdentityFields();
+ if (!identity_fields.is_none())
+ all_policies.Append(std::move(identity_fields));
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+ return all_policies;
+}
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+Value ArrayPolicyConversions::GetUpdaterPolicies() {
+ Value chrome_policies_data(Value::Type::DICTIONARY);
+ chrome_policies_data.SetKey("name", Value("Google Update Policies"));
+ chrome_policies_data.SetKey("id", Value("updater"));
+ chrome_policies_data.SetKey("policies", client()->GetUpdaterPolicies());
+ return chrome_policies_data;
+}
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+Value ArrayPolicyConversions::GetChromePolicies() {
+ Value chrome_policies_data(Value::Type::DICTIONARY);
+ chrome_policies_data.SetKey("id", Value("chrome"));
+ chrome_policies_data.SetKey("name", Value("Chrome Policies"));
+ Value chrome_policies = client()->GetChromePolicies();
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ if (additional_chrome_policies_ != base::Value())
+ chrome_policies.MergeDictionary(&additional_chrome_policies_);
+#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
+ chrome_policies_data.SetKey("policies", std::move(chrome_policies));
+ return chrome_policies_data;
+}
+
+Value ArrayPolicyConversions::GetPrecedencePolicies() {
+ Value precedence_policies_data(Value::Type::DICTIONARY);
+ precedence_policies_data.SetKey("id", Value("precedence"));
+ precedence_policies_data.SetKey("name", Value("Policy Precedence"));
+ precedence_policies_data.SetKey("policies",
+ client()->GetPrecedencePolicies());
+ precedence_policies_data.SetKey("precedenceOrder",
+ client()->GetPrecedenceOrder());
+ return precedence_policies_data;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_conversions.h b/chromium/components/policy/core/browser/policy_conversions.h
new file mode 100644
index 00000000000..e0ba0154261
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_conversions.h
@@ -0,0 +1,139 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "base/values.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+#include "ui/base/webui/web_ui_util.h"
+
+namespace policy {
+
+class PolicyConversionsClient;
+
+extern const POLICY_EXPORT webui::LocalizedString
+ kPolicySources[POLICY_SOURCE_COUNT];
+
+// A convenience class to retrieve all policies values.
+class POLICY_EXPORT PolicyConversions {
+ public:
+ // Maps known policy names to their schema. If a policy is not present, it is
+ // not known (either through policy_templates.json or through an extension's
+ // managed storage schema).
+ using PolicyToSchemaMap = base::flat_map<std::string, Schema>;
+
+ // |client| provides embedder-specific policy information and must not be
+ // nullptr.
+ explicit PolicyConversions(std::unique_ptr<PolicyConversionsClient> client);
+ PolicyConversions(const PolicyConversions&) = delete;
+ PolicyConversions& operator=(const PolicyConversions&) = delete;
+ virtual ~PolicyConversions();
+
+ // Set to get policy types as human friendly string instead of enum integer.
+ // Policy types includes policy source, policy scope and policy level.
+ // Enabled by default.
+ PolicyConversions& EnableConvertTypes(bool enabled);
+ // Set to get dictionary policy value as JSON string.
+ // Disabled by default.
+ PolicyConversions& EnableConvertValues(bool enabled);
+ // Set to get device local account policies on ChromeOS.
+ // Disabled by default.
+ PolicyConversions& EnableDeviceLocalAccountPolicies(bool enabled);
+ // Set to get device basic information on ChromeOS.
+ // Disabled by default.
+ PolicyConversions& EnableDeviceInfo(bool enabled);
+ // Set to enable pretty print for all JSON string.
+ // Enabled by default.
+ PolicyConversions& EnablePrettyPrint(bool enabled);
+ // Set to get all user scope policies.
+ // Enabled by default.
+ PolicyConversions& EnableUserPolicies(bool enabled);
+ // Set to drop the policies of which value is a default one set by the policy
+ // provider. Disabled by default.
+ PolicyConversions& SetDropDefaultValues(bool enabled);
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // Sets the updater policies.
+ PolicyConversions& WithUpdaterPolicies(std::unique_ptr<PolicyMap> policies);
+
+ // Sets the updater policy schemas.
+ PolicyConversions& WithUpdaterPolicySchemas(PolicyToSchemaMap schemas);
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+ // Returns the policy data as a base::Value object.
+ virtual base::Value ToValue() = 0;
+
+ // Returns the policy data as a JSON string;
+ virtual std::string ToJSON();
+
+ protected:
+ PolicyConversionsClient* client() { return client_.get(); }
+
+ private:
+ std::unique_ptr<PolicyConversionsClient> client_;
+};
+
+class POLICY_EXPORT DictionaryPolicyConversions : public PolicyConversions {
+ public:
+ explicit DictionaryPolicyConversions(
+ std::unique_ptr<PolicyConversionsClient> client);
+ DictionaryPolicyConversions(const DictionaryPolicyConversions&) = delete;
+ DictionaryPolicyConversions& operator=(const DictionaryPolicyConversions&) =
+ delete;
+ ~DictionaryPolicyConversions() override;
+
+ base::Value ToValue() override;
+
+ private:
+ base::Value GetExtensionPolicies(PolicyDomain policy_domain);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ base::Value GetDeviceLocalAccountPolicies();
+#endif
+};
+
+class POLICY_EXPORT ArrayPolicyConversions : public PolicyConversions {
+ public:
+ explicit ArrayPolicyConversions(
+ std::unique_ptr<PolicyConversionsClient> client);
+ ArrayPolicyConversions(const ArrayPolicyConversions&) = delete;
+ ArrayPolicyConversions& operator=(const ArrayPolicyConversions&) = delete;
+ ~ArrayPolicyConversions() override;
+
+ base::Value ToValue() override;
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ // Additional Chrome policies that need to be displayed, though not available
+ // through policy service.
+ void WithAdditionalChromePolicies(base::Value&& policies);
+#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+ private:
+ base::Value GetChromePolicies();
+ base::Value GetPrecedencePolicies();
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ base::Value GetUpdaterPolicies();
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ base::Value additional_chrome_policies_;
+#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_H_
diff --git a/chromium/components/policy/core/browser/policy_conversions_client.cc b/chromium/components/policy/core/browser/policy_conversions_client.cc
new file mode 100644
index 00000000000..cf0e25c3570
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_conversions_client.cc
@@ -0,0 +1,448 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_conversions_client.h"
+
+#include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/policy/core/browser/configuration_policy_handler_list.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_merger.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/strings/grit/components_strings.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using base::Value;
+
+namespace policy {
+
+PolicyConversionsClient::PolicyConversionsClient() = default;
+PolicyConversionsClient::~PolicyConversionsClient() = default;
+
+void PolicyConversionsClient::EnableConvertTypes(bool enabled) {
+ convert_types_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::EnableConvertValues(bool enabled) {
+ convert_values_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::EnableDeviceLocalAccountPolicies(bool enabled) {
+ device_local_account_policies_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::EnableDeviceInfo(bool enabled) {
+ device_info_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::EnablePrettyPrint(bool enabled) {
+ pretty_print_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::EnableUserPolicies(bool enabled) {
+ user_policies_enabled_ = enabled;
+}
+
+void PolicyConversionsClient::SetDropDefaultValues(bool enabled) {
+ drop_default_values_enabled_ = enabled;
+}
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+void PolicyConversionsClient::SetUpdaterPolicies(
+ std::unique_ptr<PolicyMap> policies) {
+ updater_policies_ = std::move(policies);
+}
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+std::string PolicyConversionsClient::ConvertValueToJSON(
+ const Value& value) const {
+ std::string json_string;
+ base::JSONWriter::WriteWithOptions(
+ value,
+ (pretty_print_enabled_ ? base::JSONWriter::OPTIONS_PRETTY_PRINT : 0),
+ &json_string);
+ return json_string;
+}
+
+base::Value PolicyConversionsClient::GetChromePolicies() {
+ DCHECK(HasUserPolicies());
+
+ PolicyService* policy_service = GetPolicyService();
+
+ auto* schema_registry = GetPolicySchemaRegistry();
+ if (!schema_registry) {
+ LOG(ERROR) << "Cannot dump Chrome policies, no schema registry";
+ return Value(Value::Type::DICTIONARY);
+ }
+
+ const scoped_refptr<SchemaMap> schema_map = schema_registry->schema_map();
+ PolicyNamespace policy_namespace =
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Make a copy that can be modified, since some policy values are modified
+ // before being displayed.
+ PolicyMap map = policy_service->GetPolicies(policy_namespace).Clone();
+
+ // Get a list of all the errors in the policy values.
+ const ConfigurationPolicyHandlerList* handler_list = GetHandlerList();
+ PolicyErrorMap errors;
+ PoliciesSet deprecated_policies;
+ PoliciesSet future_policies;
+ handler_list->ApplyPolicySettings(map, nullptr, &errors, &deprecated_policies,
+ &future_policies);
+
+ // Convert dictionary values to strings for display.
+ handler_list->PrepareForDisplaying(&map);
+
+ return GetPolicyValues(map, &errors, deprecated_policies, future_policies,
+ GetKnownPolicies(schema_map, policy_namespace));
+}
+
+base::Value PolicyConversionsClient::GetPrecedencePolicies() {
+ DCHECK(HasUserPolicies());
+
+ PolicyNamespace policy_namespace =
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string());
+ const PolicyMap& chrome_policies =
+ GetPolicyService()->GetPolicies(policy_namespace);
+
+ auto* schema_registry = GetPolicySchemaRegistry();
+ if (!schema_registry) {
+ LOG(ERROR) << "Cannot dump Chrome precedence policies, no schema registry";
+ return Value(Value::Type::DICTIONARY);
+ }
+
+ base::Value values(base::Value::Type::DICTIONARY);
+ // Iterate through all precedence metapolicies and retrieve their value only
+ // if they are set in the PolicyMap.
+ for (auto* policy : metapolicy::kPrecedence) {
+ auto* entry = chrome_policies.Get(policy);
+
+ if (entry) {
+ values.SetKey(
+ policy, GetPolicyValue(policy, entry->DeepCopy(), PoliciesSet(),
+ PoliciesSet(), nullptr,
+ GetKnownPolicies(schema_registry->schema_map(),
+ policy_namespace)));
+ }
+ }
+
+ return values;
+}
+
+base::Value PolicyConversionsClient::GetPrecedenceOrder() {
+ DCHECK(HasUserPolicies());
+
+ PolicyNamespace policy_namespace =
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string());
+ const PolicyMap& chrome_policies =
+ GetPolicyService()->GetPolicies(policy_namespace);
+
+ bool cloud_machine_precedence =
+ chrome_policies.GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value::Type::BOOLEAN) &&
+ chrome_policies
+ .GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value::Type::BOOLEAN)
+ ->GetBool();
+ bool cloud_user_precedence =
+ chrome_policies.IsUserAffiliated() &&
+ chrome_policies.GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value::Type::BOOLEAN) &&
+ chrome_policies
+ .GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value::Type::BOOLEAN)
+ ->GetBool();
+
+ std::vector<int> precedence_order(4);
+ if (cloud_user_precedence) {
+ if (cloud_machine_precedence) {
+ precedence_order = {IDS_POLICY_PRECEDENCE_CLOUD_USER,
+ IDS_POLICY_PRECEDENCE_CLOUD_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_USER};
+ } else {
+ precedence_order = {IDS_POLICY_PRECEDENCE_PLATFORM_MACHINE,
+ IDS_POLICY_PRECEDENCE_CLOUD_USER,
+ IDS_POLICY_PRECEDENCE_CLOUD_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_USER};
+ }
+ } else {
+ if (cloud_machine_precedence) {
+ precedence_order = {IDS_POLICY_PRECEDENCE_CLOUD_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_USER,
+ IDS_POLICY_PRECEDENCE_CLOUD_USER};
+ } else {
+ precedence_order = {IDS_POLICY_PRECEDENCE_PLATFORM_MACHINE,
+ IDS_POLICY_PRECEDENCE_CLOUD_MACHINE,
+ IDS_POLICY_PRECEDENCE_PLATFORM_USER,
+ IDS_POLICY_PRECEDENCE_CLOUD_USER};
+ }
+ }
+
+ base::Value precedence_order_localized(base::Value::Type::LIST);
+ for (int label_id : precedence_order) {
+ precedence_order_localized.Append(
+ base::Value(l10n_util::GetStringUTF16(label_id)));
+ }
+
+ return precedence_order_localized;
+}
+
+Value PolicyConversionsClient::CopyAndMaybeConvert(
+ const Value& value,
+ const absl::optional<Schema>& schema) const {
+ Value value_copy = value.Clone();
+ if (schema.has_value())
+ schema->MaskSensitiveValues(&value_copy);
+ if (!convert_values_enabled_)
+ return value_copy;
+ if (value_copy.is_dict())
+ return Value(ConvertValueToJSON(value_copy));
+
+ if (!value_copy.is_list()) {
+ return value_copy;
+ }
+
+ Value result(Value::Type::LIST);
+ for (const auto& element : value_copy.GetListDeprecated()) {
+ if (element.is_dict()) {
+ result.Append(Value(ConvertValueToJSON(element)));
+ } else {
+ result.Append(element.Clone());
+ }
+ }
+ return result;
+}
+
+Value PolicyConversionsClient::GetPolicyValue(
+ const std::string& policy_name,
+ const PolicyMap::Entry& policy,
+ const PoliciesSet& deprecated_policies,
+ const PoliciesSet& future_policies,
+ PolicyErrorMap* errors,
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas) const {
+ absl::optional<Schema> known_policy_schema =
+ GetKnownPolicySchema(known_policy_schemas, policy_name);
+ Value value(Value::Type::DICTIONARY);
+ value.SetKey("value", CopyAndMaybeConvert(*policy.value_unsafe(),
+ known_policy_schema));
+ if (convert_types_enabled_) {
+ value.SetKey(
+ "scope",
+ Value((policy.scope == POLICY_SCOPE_USER) ? "user" : "machine"));
+ value.SetKey("level", Value(Value((policy.level == POLICY_LEVEL_RECOMMENDED)
+ ? "recommended"
+ : "mandatory")));
+ value.SetKey("source", Value(policy.IsDefaultValue()
+ ? "sourceDefault"
+ : kPolicySources[policy.source].name));
+ } else {
+ value.SetKey("scope", Value(policy.scope));
+ value.SetKey("level", Value(policy.level));
+ value.SetKey("source", Value(policy.source));
+ }
+
+ // Policies that have at least one source that could not be merged will
+ // still be treated as conflicted policies while policies that had all of
+ // their sources merged will not be considered conflicted anymore. Some
+ // policies have only one source but still appear as POLICY_SOURCE_MERGED
+ // because all policies that are listed as policies that should be merged are
+ // treated as merged regardless the number of sources. Those policies will not
+ // be treated as conflicted policies.
+ if (policy.source == POLICY_SOURCE_MERGED) {
+ bool policy_has_unmerged_source = false;
+ for (const auto& conflict : policy.conflicts) {
+ if (PolicyMerger::EntriesCanBeMerged(
+ conflict.entry(), policy,
+ /*is_user_cloud_merging_enabled=*/false))
+ continue;
+ policy_has_unmerged_source = true;
+ break;
+ }
+ value.SetKey("allSourcesMerged", Value(policy.conflicts.size() <= 1 ||
+ !policy_has_unmerged_source));
+ }
+
+ std::u16string error;
+ if (!known_policy_schema.has_value()) {
+ // We don't know what this policy is. This is an important error to
+ // show.
+ error = l10n_util::GetStringUTF16(IDS_POLICY_UNKNOWN);
+ } else {
+ // The PolicyMap contains errors about retrieving the policy, while the
+ // PolicyErrorMap contains validation errors. Concat the errors.
+ auto policy_map_errors = policy.GetLocalizedMessages(
+ PolicyMap::MessageType::kError,
+ base::BindRepeating(&l10n_util::GetStringUTF16));
+ auto error_map_errors =
+ errors ? errors->GetErrors(policy_name) : std::u16string();
+ if (policy_map_errors.empty())
+ error = error_map_errors;
+ else if (error_map_errors.empty())
+ error = policy_map_errors;
+ else
+ error = base::JoinString(
+ {policy_map_errors, errors->GetErrors(policy_name)}, u"\n");
+ }
+ if (!error.empty())
+ value.SetKey("error", Value(error));
+
+ std::u16string warning = policy.GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning,
+ base::BindRepeating(&l10n_util::GetStringUTF16));
+ if (!warning.empty())
+ value.SetKey("warning", Value(warning));
+
+ std::u16string info = policy.GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo,
+ base::BindRepeating(&l10n_util::GetStringUTF16));
+ if (!info.empty())
+ value.SetKey("info", Value(info));
+
+ if (policy.ignored())
+ value.SetBoolKey("ignored", true);
+
+ if (deprecated_policies.find(policy_name) != deprecated_policies.end())
+ value.SetBoolKey("deprecated", true);
+
+ if (future_policies.find(policy_name) != future_policies.end())
+ value.SetBoolKey("future", true);
+
+ if (!policy.conflicts.empty()) {
+ Value override_values(Value::Type::LIST);
+ Value supersede_values(Value::Type::LIST);
+
+ bool has_override_values = false;
+ bool has_supersede_values = false;
+ for (const auto& conflict : policy.conflicts) {
+ base::Value conflicted_policy_value =
+ GetPolicyValue(policy_name, conflict.entry(), deprecated_policies,
+ future_policies, errors, known_policy_schemas);
+ switch (conflict.conflict_type()) {
+ case PolicyMap::ConflictType::Supersede:
+ supersede_values.Append(std::move(conflicted_policy_value));
+ has_supersede_values = true;
+ break;
+ case PolicyMap::ConflictType::Override:
+ override_values.Append(std::move(conflicted_policy_value));
+ has_override_values = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (has_override_values) {
+ value.SetKey("conflicts", std::move(override_values));
+ }
+ if (has_supersede_values) {
+ value.SetKey("superseded", std::move(supersede_values));
+ }
+ }
+
+ return value;
+}
+
+Value PolicyConversionsClient::GetPolicyValues(
+ const PolicyMap& map,
+ PolicyErrorMap* errors,
+ const PoliciesSet& deprecated_policies,
+ const PoliciesSet& future_policies,
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas) const {
+ base::Value values(base::Value::Type::DICTIONARY);
+ for (const auto& entry : map) {
+ const std::string& policy_name = entry.first;
+ const PolicyMap::Entry& policy = entry.second;
+ if (policy.scope == POLICY_SCOPE_USER && !user_policies_enabled_)
+ continue;
+ if (policy.IsDefaultValue() && drop_default_values_enabled_)
+ continue;
+ base::Value value =
+ GetPolicyValue(policy_name, policy, deprecated_policies,
+ future_policies, errors, known_policy_schemas);
+ values.SetKey(policy_name, std::move(value));
+ }
+ return values;
+}
+
+absl::optional<Schema> PolicyConversionsClient::GetKnownPolicySchema(
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas,
+ const std::string& policy_name) const {
+ if (!known_policy_schemas.has_value())
+ return absl::nullopt;
+ auto known_policy_iterator = known_policy_schemas->find(policy_name);
+ if (known_policy_iterator == known_policy_schemas->end())
+ return absl::nullopt;
+ return known_policy_iterator->second;
+}
+
+absl::optional<PolicyConversions::PolicyToSchemaMap>
+PolicyConversionsClient::GetKnownPolicies(
+ const scoped_refptr<SchemaMap> schema_map,
+ const PolicyNamespace& policy_namespace) const {
+ const Schema* schema = schema_map->GetSchema(policy_namespace);
+ // There is no policy name verification without valid schema.
+ if (!schema || !schema->valid())
+ return absl::nullopt;
+
+ // Build a vector first and construct the PolicyToSchemaMap (which is a
+ // |flat_map|) from that. The reason is that insertion into a |flat_map| is
+ // O(n), which would make the loop O(n^2), but constructing from a
+ // pre-populated vector is less expensive.
+ std::vector<std::pair<std::string, Schema>> policy_to_schema_entries;
+ for (auto it = schema->GetPropertiesIterator(); !it.IsAtEnd(); it.Advance()) {
+ policy_to_schema_entries.push_back(std::make_pair(it.key(), it.schema()));
+ }
+ return PolicyConversions::PolicyToSchemaMap(
+ std::move(policy_to_schema_entries));
+}
+
+bool PolicyConversionsClient::GetDeviceLocalAccountPoliciesEnabled() const {
+ return device_local_account_policies_enabled_;
+}
+
+bool PolicyConversionsClient::GetDeviceInfoEnabled() const {
+ return device_info_enabled_;
+}
+
+bool PolicyConversionsClient::GetUserPoliciesEnabled() const {
+ return user_policies_enabled_;
+}
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+Value PolicyConversionsClient::GetUpdaterPolicies() {
+ return updater_policies_
+ ? GetPolicyValues(*updater_policies_, nullptr, PoliciesSet(),
+ PoliciesSet(), updater_policy_schemas_)
+ : base::Value(base::Value::Type::DICTIONARY);
+}
+
+bool PolicyConversionsClient::PolicyConversionsClient::HasUpdaterPolicies()
+ const {
+ return !!updater_policies_;
+}
+
+void PolicyConversionsClient::SetUpdaterPolicySchemas(
+ PolicyConversions::PolicyToSchemaMap schemas) {
+ updater_policy_schemas_ = std::move(schemas);
+}
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_conversions_client.h b/chromium/components/policy/core/browser/policy_conversions_client.h
new file mode 100644
index 00000000000..69fb45b8366
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_conversions_client.h
@@ -0,0 +1,190 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_CLIENT_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_CLIENT_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/values.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/browser/policy_conversions.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+class ConfigurationPolicyHandlerList;
+class PolicyErrorMap;
+class PolicyService;
+class Schema;
+class SchemaMap;
+class SchemaRegistry;
+
+using PoliciesSet = std::set<std::string>;
+
+// PolicyConversionsClient supplies embedder-specific information that is needed
+// by the PolicyConversions class. It also provides common utilities and
+// helpers needed to compute and format policy information. Embedders must
+// subclass PolicyConversionsClient and provide an instance to
+// PolicyConversions.
+class POLICY_EXPORT PolicyConversionsClient {
+ public:
+ PolicyConversionsClient();
+ virtual ~PolicyConversionsClient();
+
+ // Set to get policy types as human friendly string instead of enum integer.
+ // Policy types includes policy source, policy scope and policy level.
+ // Enabled by default.
+ void EnableConvertTypes(bool enabled);
+ // Set to get dictionary policy value as JSON string.
+ // Disabled by default.
+ void EnableConvertValues(bool enabled);
+ // Set to get device local account policies on ChromeOS.
+ // Disabled by default.
+ void EnableDeviceLocalAccountPolicies(bool enabled);
+ // Set to get device basic information on ChromeOS.
+ // Disabled by default.
+ void EnableDeviceInfo(bool enabled);
+ // Set to enable pretty print for all JSON string.
+ // Enabled by default.
+ void EnablePrettyPrint(bool enabled);
+ // Set to get all user scope policies.
+ // Enabled by default.
+ void EnableUserPolicies(bool enabled);
+ // Set to drop the policies of which value is a default one set by the policy
+ // provider. Disabled by default.
+ void SetDropDefaultValues(bool enabled);
+
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ // Sets the updater policies.
+ void SetUpdaterPolicies(std::unique_ptr<PolicyMap> policies);
+
+ // Returns true if this client is able to return information on the updater's
+ // policies.
+ bool HasUpdaterPolicies() const;
+ base::Value GetUpdaterPolicies();
+
+ // Sets the updater policy schemas.
+ void SetUpdaterPolicySchemas(PolicyConversions::PolicyToSchemaMap schemas);
+#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+
+ // Converts the given |value| to JSON, respecting the configuration
+ // preferences that were set on this client.
+ std::string ConvertValueToJSON(const base::Value& value) const;
+
+ // Returns policies for Chrome browser. Must only be called if
+ // |HasUserPolicies()| returns true.
+ base::Value GetChromePolicies();
+
+ // Returns precedence-related policies for Chrome browser. Must only be called
+ // if |HasUserPolicies()| returns true.
+ base::Value GetPrecedencePolicies();
+
+ // Returns an array containing the ordered precedence strings.
+ base::Value GetPrecedenceOrder();
+
+ // Returns true if this client is able to return information on user
+ // policies.
+ virtual bool HasUserPolicies() const = 0;
+
+ // Returns policies for Chrome extensions.
+ virtual base::Value GetExtensionPolicies(PolicyDomain policy_domain) = 0;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ // Returns policies for ChromeOS device.
+ virtual base::Value GetDeviceLocalAccountPolicies() = 0;
+ // Returns device specific information if this device is enterprise managed.
+ virtual base::Value GetIdentityFields() = 0;
+#endif
+
+ // Returns the embedder's PolicyService.
+ virtual PolicyService* GetPolicyService() const = 0;
+
+ // Returns the embedder's SchemaRegistry.
+ virtual SchemaRegistry* GetPolicySchemaRegistry() const = 0;
+
+ // Returns the embedder's ConfigurationPolicyHandlerList.
+ virtual const ConfigurationPolicyHandlerList* GetHandlerList() const = 0;
+
+ protected:
+ // Returns a copy of |value|. If necessary (which is specified by
+ // |convert_values_enabled_|), converts some values to a representation that
+ // i18n_template.js will display.
+ base::Value CopyAndMaybeConvert(const base::Value& value,
+ const absl::optional<Schema>& schema) const;
+
+ // Creates a description of the policy |policy_name| using |policy| and the
+ // optional errors in |errors| to determine the status of each policy.
+ // |known_policy_schemas| contains |Schema|s for known policies in the same
+ // policy namespace of |map|. |deprecated_policies| holds deprecated policies.
+ // |future_policies| holds unreleased policies. A policy without an entry in
+ // |known_policy_schemas| is an unknown policy.
+ base::Value GetPolicyValue(
+ const std::string& policy_name,
+ const PolicyMap::Entry& policy,
+ const PoliciesSet& deprecated_policies,
+ const PoliciesSet& future_policies,
+ PolicyErrorMap* errors,
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas) const;
+
+ // Returns a description of each policy in |map| as Value, using the
+ // optional errors in |errors| to determine the status of each policy.
+ // |known_policy_schemas| contains |Schema|s for known policies in the same
+ // policy namespace of |map|. |deprecated_policies| holds deprecated policies.
+ // |future_policies| holds unreleased policies. A policy in |map| but without
+ // an entry |known_policy_schemas| is an unknown policy.
+ base::Value GetPolicyValues(
+ const PolicyMap& map,
+ PolicyErrorMap* errors,
+ const PoliciesSet& deprecated_policies,
+ const PoliciesSet& future_policies,
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas) const;
+
+ // Returns the Schema for |policy_name| if that policy is known. If the policy
+ // is unknown, returns |absl::nullopt|.
+ absl::optional<Schema> GetKnownPolicySchema(
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas,
+ const std::string& policy_name) const;
+
+ absl::optional<PolicyConversions::PolicyToSchemaMap> GetKnownPolicies(
+ const scoped_refptr<SchemaMap> schema_map,
+ const PolicyNamespace& policy_namespace) const;
+
+ // Returns whether this client was configured to get device local account
+ // policies on ChromeOS.
+ bool GetDeviceLocalAccountPoliciesEnabled() const;
+ // Returns whether this client was configured to get device basic information
+ // on ChromeOS.
+ bool GetDeviceInfoEnabled() const;
+ // Returns whether this client was configured to get all user scope policies.
+ bool GetUserPoliciesEnabled() const;
+
+ private:
+ friend class PolicyConversionsClientTest;
+#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ std::unique_ptr<PolicyMap> updater_policies_;
+ absl::optional<PolicyConversions::PolicyToSchemaMap> updater_policy_schemas_;
+#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_WIN)
+
+ bool convert_types_enabled_ = true;
+ bool convert_values_enabled_ = false;
+ bool device_local_account_policies_enabled_ = false;
+ bool device_info_enabled_ = false;
+ bool pretty_print_enabled_ = true;
+ bool user_policies_enabled_ = true;
+ bool drop_default_values_enabled_ = false;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_POLICY_CONVERSIONS_CLIENT_H_
diff --git a/chromium/components/policy/core/browser/policy_conversions_client_unittest.cc b/chromium/components/policy/core/browser/policy_conversions_client_unittest.cc
new file mode 100644
index 00000000000..21c06ad3502
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_conversions_client_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_conversions_client.h"
+
+#include "components/policy/core/common/policy_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace {
+constexpr char kPolicyName1[] = "policy_a";
+constexpr char kPolicyName2[] = "policy_b";
+constexpr char kPolicyName3[] = "policy_c";
+} // namespace
+
+class MockPolicyConversionsClient : public PolicyConversionsClient {
+ public:
+ MockPolicyConversionsClient() = default;
+ MockPolicyConversionsClient(const MockPolicyConversionsClient&) = delete;
+ MockPolicyConversionsClient& operator=(const MockPolicyConversionsClient&) =
+ delete;
+ ~MockPolicyConversionsClient() override = default;
+
+ private:
+ // PolicyConversionsClient.
+ bool HasUserPolicies() const override { return false; }
+ base::Value GetExtensionPolicies(PolicyDomain policy_domain) override {
+ return base::Value();
+ }
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ base::Value GetDeviceLocalAccountPolicies() override { return base::Value(); }
+ base::Value GetIdentityFields() override { return base::Value(); }
+#endif
+ PolicyService* GetPolicyService() const override { return nullptr; }
+ SchemaRegistry* GetPolicySchemaRegistry() const override { return nullptr; }
+ const ConfigurationPolicyHandlerList* GetHandlerList() const override {
+ return nullptr;
+ }
+};
+
+class PolicyConversionsClientTest : public ::testing::Test {
+ public:
+ PolicyMap::Entry CreateEntry(bool set_is_default) const {
+ PolicyMap::Entry entry(policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_MACHINE,
+ policy::POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ base::Value(std::vector<base::Value>()), nullptr);
+ if (set_is_default)
+ entry.SetIsDefaultValue();
+ return entry;
+ }
+
+ base::Value GetPolicyValues(
+ const PolicyConversionsClient& client,
+ const PolicyMap& map,
+ const absl::optional<PolicyConversions::PolicyToSchemaMap>&
+ known_policy_schemas) const {
+ return client.GetPolicyValues(map, nullptr, PoliciesSet(), PoliciesSet(),
+ known_policy_schemas);
+ }
+};
+
+// Verify dropping default values option is working.
+TEST_F(PolicyConversionsClientTest, SetDropDefaultValues) {
+ policy::PolicyMap policy_map;
+ policy_map.Set(kPolicyName1, CreateEntry(false /* set_is_default */));
+ policy_map.Set(kPolicyName2, CreateEntry(true /* set_is_default */));
+ policy_map.Set(kPolicyName3, CreateEntry(false /* set_is_default */));
+
+ absl::optional<PolicyConversions::PolicyToSchemaMap> policy_schemas =
+ policy::PolicyConversions::PolicyToSchemaMap{
+ {{kPolicyName1, policy::Schema()},
+ {kPolicyName2, policy::Schema()},
+ {kPolicyName3, policy::Schema()}}};
+
+ MockPolicyConversionsClient client;
+
+ // All policies should exist because |drop_default_values_enabled_| is false
+ // by default.
+ base::Value policies1 = GetPolicyValues(client, policy_map, policy_schemas);
+ const base::Value::Dict& policies_dict1 = policies1.GetDict();
+ EXPECT_EQ(3u, policies_dict1.size());
+ EXPECT_NE(nullptr, policies_dict1.FindDict(kPolicyName1));
+ EXPECT_NE(nullptr, policies_dict1.FindDict(kPolicyName2));
+ EXPECT_NE(nullptr, policies_dict1.FindDict(kPolicyName3));
+
+ // Enable dropping default values.
+ client.SetDropDefaultValues(true);
+ base::Value policies2 = GetPolicyValues(client, policy_map, policy_schemas);
+
+ // A default valued policy should not exist.
+ const base::Value::Dict& policies_dict2 = policies2.GetDict();
+ EXPECT_EQ(2u, policies_dict2.size());
+ EXPECT_NE(nullptr, policies_dict2.FindDict(kPolicyName1));
+ EXPECT_NE(nullptr, policies_dict2.FindDict(kPolicyName3));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_error_map.cc b/chromium/components/policy/core/browser/policy_error_map.cc
new file mode 100644
index 00000000000..3f21746b719
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_error_map.cc
@@ -0,0 +1,301 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_error_map.h"
+
+#include <string>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace policy {
+
+class PolicyErrorMap::PendingError {
+ public:
+ explicit PendingError(const std::string& policy_name)
+ : policy_name_(policy_name) {}
+ PendingError(const PendingError&) = delete;
+ PendingError& operator=(const PendingError&) = delete;
+ virtual ~PendingError() = default;
+
+ const std::string& policy_name() const { return policy_name_; }
+
+ virtual std::u16string GetMessage() const = 0;
+
+ private:
+ std::string policy_name_;
+};
+
+namespace {
+
+class SimplePendingError : public PolicyErrorMap::PendingError {
+ public:
+ SimplePendingError(const std::string& policy_name, int message_id)
+ : SimplePendingError(policy_name,
+ message_id,
+ std::string(),
+ std::string()) {}
+ SimplePendingError(const std::string& policy_name,
+ int message_id,
+ const std::string& replacement_a)
+ : SimplePendingError(policy_name,
+ message_id,
+ replacement_a,
+ std::string()) {}
+
+ SimplePendingError(const std::string& policy_name,
+ int message_id,
+ const std::string& replacement_a,
+ const std::string& replacement_b)
+ : PendingError(policy_name),
+ message_id_(message_id),
+ replacement_a_(replacement_a),
+ replacement_b_(replacement_b) {
+ DCHECK(replacement_b.empty() || !replacement_a.empty());
+ }
+ SimplePendingError(const SimplePendingError&) = delete;
+ SimplePendingError& operator=(const SimplePendingError&) = delete;
+ ~SimplePendingError() override = default;
+
+ std::u16string GetMessage() const override {
+ if (message_id_ >= 0) {
+ if (replacement_a_.empty() && replacement_b_.empty())
+ return l10n_util::GetStringUTF16(message_id_);
+ if (replacement_b_.empty()) {
+ return l10n_util::GetStringFUTF16(message_id_,
+ base::ASCIIToUTF16(replacement_a_));
+ }
+ return l10n_util::GetStringFUTF16(message_id_,
+ base::ASCIIToUTF16(replacement_a_),
+ base::ASCIIToUTF16(replacement_b_));
+ }
+ return base::ASCIIToUTF16(replacement_a_);
+ }
+
+ private:
+ int message_id_;
+ std::string replacement_a_;
+ std::string replacement_b_;
+};
+
+class DictSubkeyPendingError : public SimplePendingError {
+ public:
+ DictSubkeyPendingError(const std::string& policy_name,
+ const std::string& subkey,
+ int message_id,
+ const std::string& replacement)
+ : SimplePendingError(policy_name, message_id, replacement),
+ subkey_(subkey) {}
+ DictSubkeyPendingError(const DictSubkeyPendingError&) = delete;
+ DictSubkeyPendingError& operator=(const DictSubkeyPendingError&) = delete;
+ ~DictSubkeyPendingError() override = default;
+
+ std::u16string GetMessage() const override {
+ return l10n_util::GetStringFUTF16(IDS_POLICY_SUBKEY_ERROR,
+ base::ASCIIToUTF16(subkey_),
+ SimplePendingError::GetMessage());
+ }
+
+ private:
+ std::string subkey_;
+};
+
+class ListItemPendingError : public SimplePendingError {
+ public:
+ ListItemPendingError(const std::string& policy_name,
+ int index,
+ int message_id,
+ const std::string& replacement)
+ : SimplePendingError(policy_name, message_id, replacement),
+ index_(index) {}
+ ListItemPendingError(const ListItemPendingError&) = delete;
+ ListItemPendingError& operator=(const ListItemPendingError&) = delete;
+ ~ListItemPendingError() override = default;
+
+ std::u16string GetMessage() const override {
+ return l10n_util::GetStringFUTF16(IDS_POLICY_LIST_ENTRY_ERROR,
+ base::NumberToString16(index_),
+ SimplePendingError::GetMessage());
+ }
+
+ private:
+ int index_;
+};
+
+class SchemaValidatingPendingError : public SimplePendingError {
+ public:
+ SchemaValidatingPendingError(const std::string& policy_name,
+ const std::string& error_path,
+ const std::string& replacement)
+ : SimplePendingError(policy_name, -1, replacement),
+ error_path_(error_path) {}
+ SchemaValidatingPendingError(const SchemaValidatingPendingError&) = delete;
+ SchemaValidatingPendingError& operator=(const SchemaValidatingPendingError&) =
+ delete;
+ ~SchemaValidatingPendingError() override = default;
+
+ std::u16string GetMessage() const override {
+ return l10n_util::GetStringFUTF16(IDS_POLICY_SCHEMA_VALIDATION_ERROR,
+ base::ASCIIToUTF16(error_path_),
+ SimplePendingError::GetMessage());
+ }
+
+ private:
+ std::string error_path_;
+};
+
+} // namespace
+
+PolicyErrorMap::PolicyErrorMap() = default;
+
+PolicyErrorMap::~PolicyErrorMap() = default;
+
+bool PolicyErrorMap::IsReady() const {
+ return ui::ResourceBundle::HasSharedInstance();
+}
+
+void PolicyErrorMap::AddError(const std::string& policy, int message_id) {
+ AddError(std::make_unique<SimplePendingError>(policy, message_id));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ const std::string& subkey,
+ int message_id) {
+ AddError(std::make_unique<DictSubkeyPendingError>(policy, subkey, message_id,
+ std::string()));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ int index,
+ int message_id) {
+ AddError(std::make_unique<ListItemPendingError>(policy, index, message_id,
+ std::string()));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ int message_id,
+ const std::string& replacement) {
+ AddError(
+ std::make_unique<SimplePendingError>(policy, message_id, replacement));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ int message_id,
+ const std::string& replacement_a,
+ const std::string& replacement_b) {
+ AddError(std::make_unique<SimplePendingError>(policy, message_id,
+ replacement_a, replacement_b));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ const std::string& subkey,
+ int message_id,
+ const std::string& replacement) {
+ AddError(std::make_unique<DictSubkeyPendingError>(policy, subkey, message_id,
+ replacement));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ int index,
+ int message_id,
+ const std::string& replacement) {
+ AddError(std::make_unique<ListItemPendingError>(policy, index, message_id,
+ replacement));
+}
+
+void PolicyErrorMap::AddError(const std::string& policy,
+ const std::string& error_path,
+ const std::string& message) {
+ AddError(std::make_unique<SchemaValidatingPendingError>(policy, error_path,
+ message));
+}
+
+bool PolicyErrorMap::HasError(const std::string& policy) {
+ if (IsReady()) {
+ CheckReadyAndConvert();
+ return map_.find(policy) != map_.end();
+ } else {
+ return std::find_if(pending_.begin(), pending_.end(),
+ [policy](const auto& error) {
+ return error->policy_name() == policy;
+ }) != pending_.end();
+ }
+}
+
+std::u16string PolicyErrorMap::GetErrors(const std::string& policy) {
+ CheckReadyAndConvert();
+ std::pair<const_iterator, const_iterator> range = map_.equal_range(policy);
+ std::vector<base::StringPiece16> list;
+ for (auto it = range.first; it != range.second; ++it)
+ list.push_back(it->second);
+ return base::JoinString(list, u"\n");
+}
+
+bool PolicyErrorMap::empty() const {
+ // This doesn't call CheckReadyAndConvert() to allow code to destroy empty
+ // PolicyErrorMaps rather than having to wait for ResourceBundle to be ready.
+ return pending_.empty() && map_.empty();
+}
+
+size_t PolicyErrorMap::size() {
+ CheckReadyAndConvert();
+ return map_.size();
+}
+
+PolicyErrorMap::const_iterator PolicyErrorMap::begin() {
+ CheckReadyAndConvert();
+ return map_.begin();
+}
+
+PolicyErrorMap::const_iterator PolicyErrorMap::end() {
+ CheckReadyAndConvert();
+ return map_.end();
+}
+
+void PolicyErrorMap::Clear() {
+ CheckReadyAndConvert();
+ map_.clear();
+ debug_infos_.clear();
+}
+
+void PolicyErrorMap::SetDebugInfo(const std::string& policy,
+ const std::string& debug_infos) {
+ debug_infos_[policy] = debug_infos;
+}
+
+const std::string PolicyErrorMap::GetDebugInfo(const std::string& policy) {
+ auto it = debug_infos_.find(policy);
+ if (it != debug_infos_.end())
+ return it->second;
+ return std::string();
+}
+
+void PolicyErrorMap::AddError(std::unique_ptr<PendingError> error) {
+ if (IsReady()) {
+ Convert(error.get());
+ // Allow error to be deleted as it exits function scope.
+ } else {
+ pending_.push_back(std::move(error));
+ }
+}
+
+void PolicyErrorMap::Convert(PendingError* error) {
+ map_.insert(std::make_pair(error->policy_name(), error->GetMessage()));
+}
+
+void PolicyErrorMap::CheckReadyAndConvert() {
+ DCHECK(IsReady());
+ for (size_t i = 0; i < pending_.size(); ++i) {
+ Convert(pending_[i].get());
+ }
+ pending_.clear();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_error_map.h b/chromium/components/policy/core/browser/policy_error_map.h
new file mode 100644
index 00000000000..250663f7185
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_error_map.h
@@ -0,0 +1,130 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_POLICY_ERROR_MAP_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_POLICY_ERROR_MAP_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Collects error messages and their associated policies.
+class POLICY_EXPORT PolicyErrorMap {
+ public:
+ typedef std::multimap<std::string, std::u16string> PolicyMapType;
+ typedef PolicyMapType::const_iterator const_iterator;
+
+ class PendingError;
+
+ PolicyErrorMap();
+ PolicyErrorMap(const PolicyErrorMap&) = delete;
+ PolicyErrorMap& operator=(const PolicyErrorMap&) = delete;
+ virtual ~PolicyErrorMap();
+
+ // Returns true when the errors logged are ready to be retrieved. It is always
+ // safe to call AddError, but the other methods are only allowed once
+ // IsReady is true. IsReady will be true once the UI message loop has started.
+ bool IsReady() const;
+
+ // Adds an entry with key |policy| and the error message corresponding to
+ // |message_id| in grit/generated_resources.h to the map.
+ void AddError(const std::string& policy, int message_id);
+
+ // Adds an entry with key |policy|, subkey |subkey|, and the error message
+ // corresponding to |message_id| in grit/generated_resources.h to the map.
+ void AddError(const std::string& policy,
+ const std::string& subkey,
+ int message_id);
+
+ // Adds an entry with key |policy|, list index |index|, and the error message
+ // corresponding to |message_id| in grit/generated_resources.h to the map.
+ void AddError(const std::string& policy, int index, int message_id);
+
+ // Adds an entry with key |policy| and the error message corresponding to
+ // |message_id| in grit/generated_resources.h to the map and replaces the
+ // placeholder within the error message with |replacement_string|.
+ void AddError(const std::string& policy,
+ int message_id,
+ const std::string& replacement_string);
+
+ // Same as AddError above but supports two replacement strings.
+ void AddError(const std::string& policy,
+ int message_id,
+ const std::string& replacement_a,
+ const std::string& replacement_b);
+
+ // Adds an entry with key |policy|, subkey |subkey| and the error message
+ // corresponding to |message_id| in grit/generated_resources.h to the map.
+ // Replaces the placeholder in the error message with
+ // |replacement_string|.
+ void AddError(const std::string& policy,
+ const std::string& subkey,
+ int message_id,
+ const std::string& replacement_string);
+
+ // Adds an entry with key |policy|, list index |index| and the error message
+ // corresponding to |message_id| in grit/generated_resources.h to the map.
+ // Replaces the placeholder in the error message with |replacement_string|.
+ void AddError(const std::string& policy,
+ int index,
+ int message_id,
+ const std::string& replacement_string);
+
+ // Adds an entry with key |policy|, the schema validation error location
+ // |error_path|, and detailed error |message|.
+ void AddError(const std::string& policy,
+ const std::string& error_path,
+ const std::string& message);
+
+ // Returns true if there is any error for |policy|.
+ bool HasError(const std::string& policy);
+
+ // Returns all the error messages stored for |policy|, separated by a white
+ // space. Returns an empty string if there are no errors for |policy|.
+ std::u16string GetErrors(const std::string& policy);
+
+ bool empty() const;
+ size_t size();
+
+ const_iterator begin();
+ const_iterator end();
+
+ void Clear();
+
+ // Sets the debug info |debug_info| for the policy with key |policy|.
+ // This is intended to be developer-friendly, non-localized detailed
+ // information from validation of |policy|.
+ void SetDebugInfo(const std::string& policy, const std::string& debug_info);
+
+ // Returns the debug info set for the key |policy| by |SetDebugInfo| or an
+ // empty string if no debug info was set.
+ const std::string GetDebugInfo(const std::string& policy);
+
+ private:
+ // Maps the error when ready, otherwise adds it to the pending errors list.
+ void AddError(std::unique_ptr<PendingError> error);
+
+ // Converts a PendingError into a |map_| entry.
+ void Convert(PendingError* error);
+
+ // Converts all pending errors to |map_| entries.
+ void CheckReadyAndConvert();
+
+ std::vector<std::unique_ptr<PendingError>> pending_;
+ PolicyMapType map_;
+
+ // Maps policy keys to debug infos set through |SetDebugInfo|.
+ std::map<std::string, std::string> debug_infos_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_POLICY_ERROR_MAP_H_
diff --git a/chromium/components/policy/core/browser/policy_error_map_unittest.cc b/chromium/components/policy/core/browser/policy_error_map_unittest.cc
new file mode 100644
index 00000000000..e67310df516
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_error_map_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_error_map.h"
+
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace {
+constexpr char kPolicyWithError[] = "policy-error";
+constexpr char kPolicyWithoutError[] = "policy";
+} // namespace
+
+using PolicyErrorMapTest = ::testing::Test;
+
+TEST_F(PolicyErrorMapTest, HasErrorWithoutResource) {
+ ui::ResourceBundle* original_resource_bundle =
+ ui::ResourceBundle::SwapSharedInstanceForTesting(nullptr);
+ PolicyErrorMap errors;
+ ASSERT_FALSE(errors.IsReady());
+ errors.AddError(kPolicyWithError, IDS_POLICY_BLOCKED);
+
+ EXPECT_TRUE(errors.HasError(kPolicyWithError));
+ EXPECT_FALSE(errors.HasError(kPolicyWithoutError));
+ ui::ResourceBundle::SwapSharedInstanceForTesting(original_resource_bundle);
+}
+TEST_F(PolicyErrorMapTest, HasErrorWithResource) {
+ PolicyErrorMap errors;
+ ASSERT_TRUE(errors.IsReady());
+ errors.AddError(kPolicyWithError, IDS_POLICY_BLOCKED);
+
+ EXPECT_TRUE(errors.HasError(kPolicyWithError));
+ EXPECT_FALSE(errors.HasError(kPolicyWithoutError));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_pref_mapping_test.cc b/chromium/components/policy/core/browser/policy_pref_mapping_test.cc
new file mode 100644
index 00000000000..a212bb23550
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_pref_mapping_test.cc
@@ -0,0 +1,697 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/policy_pref_mapping_test.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+namespace {
+
+// The name of the instructions key in policy_test_cases.json that does not need
+// to be parsed.
+const char kInstructionKeyName[] = "-- Instructions --";
+
+// The name of the switch to filter the testcases by
+// ${policy_name}[.optionalTestNameSuffix]. Several names could be passed
+// separated by colon. (For example --test_policy_to_pref_mappings_filter=\
+// AuthNegotiateDelegateByKdcPolicy:\
+// BuiltInDnsClientEnabled.FeatureEnabledByDefault
+const char kPolicyToPrefMappingsFilterSwitch[] =
+ "test_policy_to_pref_mappings_filter";
+
+enum class PrefLocation {
+ kUserProfile,
+ kSigninProfile,
+ kLocalState,
+};
+
+PrefLocation GetPrefLocation(const base::Value& settings) {
+ const std::string* location = settings.FindStringKey("location");
+ if (!location || *location == "user_profile")
+ return PrefLocation::kUserProfile;
+ if (*location == "local_state")
+ return PrefLocation::kLocalState;
+ if (*location == "signin_profile")
+ return PrefLocation::kSigninProfile;
+ ADD_FAILURE() << "Unknown pref location: " << *location;
+ return PrefLocation::kUserProfile;
+}
+
+std::string GetPolicyName(const std::string& policy_name_decorated) {
+ const size_t offset = policy_name_decorated.find('.');
+ if (offset != std::string::npos)
+ return policy_name_decorated.substr(0, offset);
+ return policy_name_decorated;
+}
+
+PrefService* GetPrefServiceForLocation(PrefLocation location,
+ PrefService* local_state,
+ PrefService* user_prefs,
+ PrefService* signin_profile_prefs) {
+ switch (location) {
+ case PrefLocation::kUserProfile:
+ return user_prefs;
+ case PrefLocation::kSigninProfile:
+ return signin_profile_prefs;
+ case PrefLocation::kLocalState:
+ return local_state;
+ default:
+ ADD_FAILURE() << "Unhandled pref location: "
+ << static_cast<int>(location);
+ }
+ return nullptr;
+}
+
+void CheckPrefHasValue(const PrefService::Preference* pref,
+ const base::Value* expected_value) {
+ ASSERT_TRUE(pref);
+
+ const base::Value* pref_value = pref->GetValue();
+ ASSERT_TRUE(pref->GetValue());
+ ASSERT_TRUE(expected_value);
+ EXPECT_EQ(*pref_value, *expected_value);
+}
+
+void CheckPrefHasDefaultValue(const PrefService::Preference* pref,
+ const base::Value* expected_value = nullptr) {
+ ASSERT_TRUE(pref);
+ EXPECT_TRUE(pref->IsDefaultValue());
+ EXPECT_TRUE(pref->IsUserModifiable());
+ EXPECT_FALSE(pref->IsUserControlled());
+ EXPECT_FALSE(pref->IsManaged());
+ EXPECT_FALSE(pref->IsRecommended());
+ if (expected_value)
+ CheckPrefHasValue(pref, expected_value);
+}
+
+void CheckPrefHasRecommendedValue(const PrefService::Preference* pref,
+ const base::Value* expected_value) {
+ ASSERT_TRUE(pref);
+ ASSERT_TRUE(expected_value);
+ EXPECT_FALSE(pref->IsDefaultValue());
+ EXPECT_TRUE(pref->IsUserModifiable());
+ EXPECT_FALSE(pref->IsUserControlled());
+ EXPECT_FALSE(pref->IsManaged());
+ EXPECT_TRUE(pref->IsRecommended());
+ CheckPrefHasValue(pref, expected_value);
+}
+
+void CheckPrefHasMandatoryValue(const PrefService::Preference* pref,
+ const base::Value* expected_value) {
+ ASSERT_TRUE(pref);
+ ASSERT_TRUE(expected_value);
+ EXPECT_FALSE(pref->IsDefaultValue());
+ EXPECT_FALSE(pref->IsUserModifiable());
+ EXPECT_FALSE(pref->IsUserControlled());
+ EXPECT_TRUE(pref->IsManaged());
+ EXPECT_FALSE(pref->IsRecommended());
+ CheckPrefHasValue(pref, expected_value);
+}
+
+// Contains the testing details for a single pref affected by one or multiple
+// policies. This is part of the data loaded from
+// chrome/test/data/policy/policy_test_cases.json.
+class PrefTestCase {
+ public:
+ explicit PrefTestCase(const std::string& name, const base::Value& settings) {
+ const base::Value* value = settings.FindKey("value");
+ const base::Value* default_value = settings.FindKey("default_value");
+ location_ = GetPrefLocation(settings);
+ check_for_mandatory_ =
+ settings.FindBoolKey("check_for_mandatory").value_or(true);
+ check_for_recommended_ =
+ settings.FindBoolKey("check_for_recommended").value_or(true);
+
+ pref_ = name;
+ if (value)
+ value_ = value->Clone();
+ if (default_value)
+ default_value_ = default_value->Clone();
+
+ if (value && default_value) {
+ ADD_FAILURE()
+ << "only one of |value| or |default_value| should be used for pref "
+ << name;
+ }
+ }
+
+ ~PrefTestCase() = default;
+ PrefTestCase(const PrefTestCase& other) = delete;
+ PrefTestCase& operator=(const PrefTestCase& other) = delete;
+
+ const std::string& pref() const { return pref_; }
+
+ const base::Value* value() const {
+ if (value_.is_none())
+ return nullptr;
+ return &value_;
+ }
+ const base::Value* default_value() const {
+ if (default_value_.is_none())
+ return nullptr;
+ return &default_value_;
+ }
+
+ PrefLocation location() const { return location_; }
+
+ bool check_for_mandatory() const { return check_for_mandatory_; }
+ bool check_for_recommended() const { return check_for_recommended_; }
+
+ private:
+ std::string pref_;
+ PrefLocation location_;
+ bool check_for_mandatory_;
+ bool check_for_recommended_;
+
+ // At most one of these will be set.
+ base::Value value_;
+ base::Value default_value_;
+};
+
+// Contains the testing details for a single pref affected by a policy. This is
+// part of the data loaded from chrome/test/data/policy/policy_test_cases.json.
+class PolicyPrefMappingTest {
+ public:
+ explicit PolicyPrefMappingTest(const base::Value& mapping)
+ : policies_(base::Value::Type::DICTIONARY),
+ policies_settings_(base::Value::Type::DICTIONARY) {
+ const base::Value* policies = mapping.FindDictKey("policies");
+ const base::Value* policies_settings =
+ mapping.FindDictKey("policies_settings");
+ const base::Value* prefs = mapping.FindDictKey("prefs");
+ if (policies)
+ policies_ = policies->Clone();
+ if (policies_settings)
+ policies_settings_ = policies_settings->Clone();
+ if (prefs) {
+ for (auto pref_setting : prefs->DictItems())
+ prefs_.push_back(std::make_unique<PrefTestCase>(pref_setting.first,
+ pref_setting.second));
+ }
+ if (prefs_.empty()) {
+ ADD_FAILURE() << "missing |prefs|";
+ }
+ const base::Value* required_preprocessor_macros_value =
+ mapping.FindListKey("required_preprocessor_macros");
+ if (required_preprocessor_macros_value) {
+ for (const auto& macro :
+ required_preprocessor_macros_value->GetListDeprecated())
+ required_preprocessor_macros_.push_back(macro.GetString());
+ }
+ }
+ ~PolicyPrefMappingTest() = default;
+ PolicyPrefMappingTest(const PolicyPrefMappingTest& other) = delete;
+ PolicyPrefMappingTest& operator=(const PolicyPrefMappingTest& other) = delete;
+
+ const base::Value& policies() const { return policies_; }
+ const base::Value& policies_settings() const { return policies_settings_; }
+
+ const std::vector<std::unique_ptr<PrefTestCase>>& prefs() const {
+ return prefs_;
+ }
+
+ const std::vector<std::string>& required_preprocessor_macros() const {
+ return required_preprocessor_macros_;
+ }
+
+ private:
+ base::Value policies_;
+ base::Value policies_settings_;
+ const std::string pref_;
+ std::vector<std::unique_ptr<PrefTestCase>> prefs_;
+ std::vector<std::string> required_preprocessor_macros_;
+};
+
+// Populates preprocessor macros as strings that policy pref mapping test cases
+// can depend on and implements a check if such a test case should run according
+// to the preprocessor macros.
+class PreprocessorMacrosChecker {
+ public:
+ PreprocessorMacrosChecker() {
+ // If you are adding a macro mapping here, please also add it to the
+ // documentation of 'required_preprocessor_macros' in
+ // policy_test_cases.json.
+#if defined(USE_CUPS)
+ enabled_preprocessor_macros.insert("USE_CUPS");
+#endif
+ }
+ ~PreprocessorMacrosChecker() = default;
+ PreprocessorMacrosChecker(const PreprocessorMacrosChecker& other) = delete;
+ PreprocessorMacrosChecker& operator=(const PreprocessorMacrosChecker& other) =
+ delete;
+
+ // Returns true if |test| may be executed based on its reuquired preprocessor
+ // macros.
+ bool SupportsTest(const PolicyPrefMappingTest* test) const {
+ for (const std::string& required_macro :
+ test->required_preprocessor_macros()) {
+ if (enabled_preprocessor_macros.find(required_macro) ==
+ enabled_preprocessor_macros.end()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ std::set<std::string> enabled_preprocessor_macros;
+};
+
+// Contains the testing details for a single policy. This is part of the data
+// loaded from chrome/test/data/policy/policy_test_cases.json.
+class PolicyTestCase {
+ public:
+ PolicyTestCase(const std::string& name, const base::Value& test_case)
+ : name_(name) {
+ is_official_only_ = test_case.FindBoolKey("official_only").value_or(false);
+ can_be_recommended_ =
+ test_case.FindBoolKey("can_be_recommended").value_or(false);
+ has_reason_for_missing_test_ =
+ test_case.FindStringKey("reason_for_missing_test") != nullptr;
+
+ const base::Value* os_list = test_case.FindListKey("os");
+ if (os_list) {
+ for (const auto& os : os_list->GetListDeprecated()) {
+ if (os.is_string())
+ supported_os_.push_back(os.GetString());
+ }
+ }
+
+ const base::Value* policy_pref_mapping_tests =
+ test_case.FindListKey("policy_pref_mapping_tests");
+ if (policy_pref_mapping_tests) {
+ for (const auto& mapping :
+ policy_pref_mapping_tests->GetListDeprecated()) {
+ if (mapping.is_dict()) {
+ policy_pref_mapping_tests_.push_back(
+ std::make_unique<PolicyPrefMappingTest>(mapping));
+ }
+ }
+ }
+ }
+
+ ~PolicyTestCase() = default;
+ PolicyTestCase(const PolicyTestCase& other) = delete;
+ PolicyTestCase& operator=(const PolicyTestCase& other) = delete;
+
+ const std::string& name() const { return name_; }
+
+ bool is_official_only() const { return is_official_only_; }
+
+ bool can_be_recommended() const { return can_be_recommended_; }
+
+ bool has_reason_for_missing_test() const {
+ return has_reason_for_missing_test_;
+ }
+
+ bool IsOsSupported() const {
+#if BUILDFLAG(IS_ANDROID)
+ const std::string os("android");
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
+ const std::string os("chromeos_ash");
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+ const std::string os("chromeos_lacros");
+#elif BUILDFLAG(IS_IOS)
+ const std::string os("ios");
+#elif BUILDFLAG(IS_LINUX)
+ const std::string os("linux");
+#elif BUILDFLAG(IS_MAC)
+ const std::string os("mac");
+#elif BUILDFLAG(IS_WIN)
+ const std::string os("win");
+#elif BUILDFLAG(IS_FUCHSIA)
+ const std::string os("fuchsia");
+#else
+#error "Unknown platform"
+#endif
+ return base::Contains(supported_os_, os);
+ }
+
+ bool IsOsCovered() const {
+#if BUILDFLAG(IS_CHROMEOS)
+ return base::Contains(supported_os_, "chromeos_ash") ||
+ base::Contains(supported_os_, "chromeos_lacros");
+#else
+ return IsOsSupported();
+#endif
+ }
+
+ bool IsSupported() const {
+#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
+ if (is_official_only())
+ return false;
+#endif
+ return IsOsSupported();
+ }
+
+ const std::vector<std::unique_ptr<PolicyPrefMappingTest>>&
+ policy_pref_mapping_tests() const {
+ return policy_pref_mapping_tests_;
+ }
+
+ bool HasSupportedOs() const { return !supported_os_.empty(); }
+
+ private:
+ std::string name_;
+ bool is_official_only_;
+ bool can_be_recommended_;
+ bool has_reason_for_missing_test_;
+ std::vector<std::string> supported_os_;
+ std::vector<std::unique_ptr<PolicyPrefMappingTest>>
+ policy_pref_mapping_tests_;
+};
+
+// Parses all policy test cases and makes them available in a map.
+class PolicyTestCases {
+ public:
+ typedef std::vector<std::unique_ptr<PolicyTestCase>> PolicyTestCaseVector;
+ typedef std::map<std::string, PolicyTestCaseVector> PolicyTestCaseMap;
+ typedef PolicyTestCaseMap::const_iterator iterator;
+
+ explicit PolicyTestCases(const base::FilePath& test_case_path) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ std::string json;
+ if (!base::ReadFileToString(test_case_path, &json)) {
+ ADD_FAILURE() << "Error reading: " << test_case_path;
+ return;
+ }
+ base::DictionaryValue* dict = nullptr;
+ base::JSONReader::ValueWithError parsed_json =
+ base::JSONReader::ReadAndReturnValueWithError(json);
+ if (!parsed_json.value || !parsed_json.value->GetAsDictionary(&dict)) {
+ ADD_FAILURE() << "Error parsing policy_test_cases.json: "
+ << parsed_json.error_message;
+ return;
+ }
+ for (auto it : dict->DictItems()) {
+ const std::string policy_name = GetPolicyName(it.first);
+ if (policy_name == kInstructionKeyName)
+ continue;
+ auto policy_test_case =
+ std::make_unique<PolicyTestCase>(it.first, it.second);
+ policy_test_cases_[policy_name].push_back(std::move(policy_test_case));
+ }
+ }
+
+ ~PolicyTestCases() = default;
+ PolicyTestCases(const PolicyTestCases& other) = delete;
+ PolicyTestCases& operator=(const PolicyTestCases& other) = delete;
+
+ const PolicyTestCaseVector* Get(const std::string& name) const {
+ const iterator it = policy_test_cases_.find(name);
+ return it == end() ? nullptr : &it->second;
+ }
+
+ const PolicyTestCaseMap& map() const { return policy_test_cases_; }
+ iterator begin() const { return policy_test_cases_.begin(); }
+ iterator end() const { return policy_test_cases_.end(); }
+
+ private:
+ PolicyTestCaseMap policy_test_cases_;
+};
+
+struct PolicySettings {
+ PolicySource source = PolicySource::POLICY_SOURCE_CLOUD;
+ PolicyScope scope = PolicyScope::POLICY_SCOPE_USER;
+};
+
+PolicySettings GetPolicySettings(const std::string& policy,
+ const base::Value& policies_settings) {
+ PolicySettings settings;
+ const base::Value* settings_value = policies_settings.FindPath(policy);
+ if (!settings_value)
+ return settings;
+ const std::string* source = settings_value->FindStringKey("source");
+ if (source) {
+ if (*source == "enterprise_default")
+ settings.source = POLICY_SOURCE_ENTERPRISE_DEFAULT;
+ else if (*source == "command_line")
+ settings.source = POLICY_SOURCE_COMMAND_LINE;
+ else if (*source == "cloud")
+ settings.source = POLICY_SOURCE_CLOUD;
+ else if (*source == "active_directory")
+ settings.source = POLICY_SOURCE_ACTIVE_DIRECTORY;
+ else if (*source == "local_account_override")
+ settings.source = POLICY_SOURCE_DEVICE_LOCAL_ACCOUNT_OVERRIDE_DEPRECATED;
+ else if (*source == "platform")
+ settings.source = POLICY_SOURCE_PLATFORM;
+ else if (*source == "merged")
+ settings.source = POLICY_SOURCE_MERGED;
+ else if (*source == "cloud_from_ash")
+ settings.source = POLICY_SOURCE_CLOUD_FROM_ASH;
+ }
+
+ const std::string* scope = settings_value->FindStringKey("scope");
+ if (scope) {
+ if (*scope == "user")
+ settings.scope = POLICY_SCOPE_USER;
+ else if (*scope == "machine")
+ settings.scope = POLICY_SCOPE_MACHINE;
+ }
+
+ return settings;
+}
+
+void SetProviderPolicy(MockConfigurationPolicyProvider* provider,
+ const base::Value& policies,
+ const base::Value& policies_settings,
+ PolicyLevel level) {
+ PolicyMap policy_map;
+#if BUILDFLAG(IS_CHROMEOS)
+ SetEnterpriseUsersDefaults(&policy_map);
+#endif // BUILDFLAG(IS_CHROMEOS)
+ for (auto it : policies.DictItems()) {
+ const PolicyDetails* policy_details = GetChromePolicyDetails(it.first);
+ const PolicySettings policy_settings =
+ GetPolicySettings(it.first, policies_settings);
+ ASSERT_TRUE(policy_details);
+ policy_map.Set(
+ it.first, level, policy_settings.scope, policy_settings.source,
+ it.second.Clone(),
+ policy_details->max_external_data_size
+ ? std::make_unique<ExternalDataFetcher>(nullptr, it.first)
+ : nullptr);
+ }
+ provider->UpdateChromePolicy(policy_map);
+}
+
+absl::optional<base::flat_set<std::string>> GetTestFilter() {
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ kPolicyToPrefMappingsFilterSwitch)) {
+ return absl::nullopt;
+ }
+
+ std::string value =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kPolicyToPrefMappingsFilterSwitch);
+ auto list = base::SplitString(value, ":", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ if (list.empty())
+ return absl::nullopt;
+
+ return base::flat_set<std::string>(std::move(list));
+}
+
+} // namespace
+
+void VerifyAllPoliciesHaveATestCase(const base::FilePath& test_case_path) {
+ // Verifies that all known policies have a test case in the JSON file.
+ // This test fails when a policy is added to
+ // components/policy/resources/policy_templates.json but a test case is not
+ // added to chrome/test/data/policy/policy_test_cases.json.
+ Schema chrome_schema = Schema::Wrap(GetChromeSchemaData());
+ ASSERT_TRUE(chrome_schema.valid());
+
+ PolicyTestCases policy_test_cases(test_case_path);
+ for (Schema::Iterator it = chrome_schema.GetPropertiesIterator();
+ !it.IsAtEnd(); it.Advance()) {
+ auto policy = policy_test_cases.map().find(it.key());
+ if (policy == policy_test_cases.map().end()) {
+ ADD_FAILURE() << "Missing policy test case for: " << it.key();
+ continue;
+ }
+
+ bool has_test_case_or_reason_for_this_os = false;
+ bool has_reason_for_all_os = false;
+ for (const auto& test_case : policy->second) {
+ EXPECT_TRUE(test_case->has_reason_for_missing_test() ||
+ !test_case->policy_pref_mapping_tests().empty())
+ << "Test case " << test_case->name()
+ << " has empty list of test cases (policy_pref_mapping_tests). Add "
+ "tests or use reason_for_missing_test.";
+
+ if (test_case->HasSupportedOs()) {
+ has_test_case_or_reason_for_this_os |= test_case->IsOsCovered();
+ } else {
+ has_reason_for_all_os |= test_case->has_reason_for_missing_test();
+ }
+ }
+ EXPECT_TRUE(has_test_case_or_reason_for_this_os || has_reason_for_all_os)
+ << "Policy " << policy->first
+ << " should either provide a test case for all supported operating "
+ "systems (see policy_templates.json) or provide a "
+ "reason_for_missing_test.";
+ }
+}
+
+// Verifies that policies make their corresponding preferences become managed,
+// and that the user can't override that setting.
+void VerifyPolicyToPrefMappings(const base::FilePath& test_case_path,
+ PrefService* local_state,
+ PrefService* user_prefs,
+ PrefService* signin_profile_prefs,
+ MockConfigurationPolicyProvider* provider) {
+ Schema chrome_schema = Schema::Wrap(GetChromeSchemaData());
+ ASSERT_TRUE(chrome_schema.valid());
+
+ const PreprocessorMacrosChecker preprocessor_macros_checker;
+ const PolicyTestCases test_cases(test_case_path);
+
+ auto test_filter = GetTestFilter();
+
+ for (const auto& policy : test_cases) {
+ SCOPED_TRACE(::testing::Message() << "Policy name: " << policy.first);
+ for (const auto& test_case : policy.second) {
+ if (test_filter.has_value() &&
+ !base::Contains(test_filter.value(), test_case->name())) {
+ // Skip policy based on the filter.
+ continue;
+ }
+
+ if (!chrome_schema.GetKnownProperty(policy.first).valid() &&
+ test_case->IsSupported()) {
+ // Print warning message if a deprecated policy is still supported by
+ // the test file.
+ LOG(WARNING)
+ << "Policy " << policy.first
+ << " is marked as supported on this OS but does not exist in the "
+ << "Chrome policy schema.";
+ continue;
+ }
+
+ if (!test_case->IsSupported() ||
+ test_case->has_reason_for_missing_test()) {
+ continue;
+ }
+
+ for (size_t i = 0; i < test_case->policy_pref_mapping_tests().size();
+ ++i) {
+ const auto& pref_mapping = test_case->policy_pref_mapping_tests()[i];
+ SCOPED_TRACE(::testing::Message() << "Mapping test index " << i);
+
+ EXPECT_FALSE(pref_mapping->prefs().empty())
+ << "Test #" << i << " for " << test_case->name()
+ << " is missing pref values to check for";
+
+ if (!preprocessor_macros_checker.SupportsTest(pref_mapping.get())) {
+ LOG(INFO) << "Test #" << i << " for " << test_case->name()
+ << " skipped due to preprocessor macros";
+ continue;
+ }
+
+ for (const auto& pref_case : pref_mapping->prefs()) {
+ SCOPED_TRACE(::testing::Message() << "Pref: " << pref_case->pref());
+ PrefService* prefs =
+ GetPrefServiceForLocation(pref_case->location(), local_state,
+ user_prefs, signin_profile_prefs);
+ // Skip preference mapping if required PrefService was not provided.
+ if (!prefs)
+ continue;
+
+ const bool check_recommended = test_case->can_be_recommended() &&
+ pref_case->check_for_recommended();
+ const bool check_mandatory = pref_case->check_for_mandatory();
+
+ LOG(INFO) << "policy: " << test_case->name()
+ << "\t test_case_index: " << i
+ << "\t pref_name: " << pref_case->pref()
+ << "\t check_mandatory: " << check_mandatory
+ << "\t check_recommended: " << check_recommended;
+
+ EXPECT_TRUE(check_recommended || check_mandatory)
+ << "pref has to be checked for recommended and/or mandatory "
+ "values";
+
+ // The preference must have been registered.
+ const PrefService::Preference* pref =
+ prefs->FindPreference(pref_case->pref());
+ ASSERT_TRUE(pref)
+ << "Pref " << pref_case->pref() << " not registered";
+
+ provider->UpdateChromePolicy(PolicyMap());
+ prefs->ClearPref(pref_case->pref());
+ CheckPrefHasDefaultValue(pref);
+
+ const base::Value& policies = pref_mapping->policies();
+
+ const base::Value* expected_value = pref_case->value();
+ bool expect_value_to_be_default = false;
+ if (!expected_value && pref_case->default_value()) {
+ expected_value = pref_case->default_value();
+ expect_value_to_be_default = true;
+ }
+ if (!expected_value && policies.DictSize() == 1) {
+ // If no value/default value is specified, fall back to the policy
+ // value (if only one policy is set).
+ expected_value = &policies.DictItems().begin()->second;
+ expect_value_to_be_default = false;
+ }
+ ASSERT_TRUE(expected_value);
+
+ if (check_recommended) {
+ ASSERT_NO_FATAL_FAILURE(SetProviderPolicy(
+ provider, policies, pref_mapping->policies_settings(),
+ POLICY_LEVEL_RECOMMENDED));
+ if (expect_value_to_be_default) {
+ CheckPrefHasDefaultValue(pref, expected_value);
+ } else {
+ CheckPrefHasRecommendedValue(pref, expected_value);
+ }
+ }
+
+ if (check_mandatory) {
+ ASSERT_NO_FATAL_FAILURE(SetProviderPolicy(
+ provider, policies, pref_mapping->policies_settings(),
+ POLICY_LEVEL_MANDATORY));
+ if (expect_value_to_be_default) {
+ CheckPrefHasDefaultValue(pref, expected_value);
+ } else {
+ CheckPrefHasMandatoryValue(pref, expected_value);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/policy_pref_mapping_test.h b/chromium/components/policy/core/browser/policy_pref_mapping_test.h
new file mode 100644
index 00000000000..a1aef8a134f
--- /dev/null
+++ b/chromium/components/policy/core/browser/policy_pref_mapping_test.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_POLICY_PREF_MAPPING_TEST_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_POLICY_PREF_MAPPING_TEST_H_
+
+namespace base {
+class FilePath;
+}
+
+namespace policy {
+class MockConfigurationPolicyProvider;
+}
+
+class PrefService;
+
+namespace policy {
+
+// Verifies that all of the policies have a test case listed in the JSON file at
+// |test_case_path|.
+void VerifyAllPoliciesHaveATestCase(const base::FilePath& test_case_path);
+
+// Verifies that policies make their corresponding preferences become managed,
+// and that the user can't override that setting. Loads test cases from the
+// JSON file at |test_case_path| and updates policies using the given
+// |provider|.
+// |local_state|, |user_prefs| or |signin_profile_prefs| can be nullptr, in
+// which case the mappings into the respective location are skipped.
+// TODO(https://crbug.com/809991) Policies mapping into CrosSettings are always
+// skipped.
+void VerifyPolicyToPrefMappings(const base::FilePath& test_case_path,
+ PrefService* local_state,
+ PrefService* user_prefs,
+ PrefService* signin_profile_prefs,
+ MockConfigurationPolicyProvider* provider);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_POLICY_PREF_MAPPING_TEST_H_
diff --git a/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.cc b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.cc
new file mode 100644
index 00000000000..9c876536b8a
--- /dev/null
+++ b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.cc
@@ -0,0 +1,223 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h"
+
+#include <set>
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/location.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/proto/secure_connect.pb.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "google_apis/gaia/core_account_id.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "net/base/load_flags.h"
+#include "net/base/url_util.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace policy {
+
+namespace {
+
+const char kAuthorizationHeaderFormat[] = "Bearer %s";
+const char kJsonContentType[] = "application/json";
+const char kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl[] =
+ "https://secureconnect-pa.clients6.google.com/"
+ "v1:getManagedAccountsSigninRestriction";
+
+std::unique_ptr<network::SimpleURLLoader> CreateUrlLoader(
+ const GURL& url,
+ const std::string& access_token,
+ net::NetworkTrafficAnnotationTag annotation) {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+
+ resource_request->url = url;
+ resource_request->method = net::HttpRequestHeaders::kGetMethod;
+ resource_request->load_flags = net::LOAD_DISABLE_CACHE;
+ resource_request->headers.SetHeader(
+ net::HttpRequestHeaders::kAuthorization,
+ base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
+ resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
+ kJsonContentType);
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ auto url_loader =
+ network::SimpleURLLoader::Create(std::move(resource_request), annotation);
+ return url_loader;
+}
+
+} // namespace
+
+UserCloudSigninRestrictionPolicyFetcher::
+ UserCloudSigninRestrictionPolicyFetcher(
+ policy::BrowserPolicyConnector* browser_policy_connector,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+ : browser_policy_connector_(browser_policy_connector),
+ url_loader_factory_(url_loader_factory) {}
+
+UserCloudSigninRestrictionPolicyFetcher::
+ ~UserCloudSigninRestrictionPolicyFetcher() = default;
+
+void UserCloudSigninRestrictionPolicyFetcher::
+ GetManagedAccountsSigninRestriction(
+ signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceCallback<void(const std::string&)> callback) {
+ if (!base::FeatureList::IsEnabled(
+ features::kEnableUserCloudSigninRestrictionPolicyFetcher)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+ return;
+ }
+ // base::Unretained is safe here because the callback is called in the
+ // lifecycle of `this`.
+ FetchAccessToken(
+ identity_manager, account_id,
+ base::BindOnce(&UserCloudSigninRestrictionPolicyFetcher::
+ GetManagedAccountsSigninRestrictionInternal,
+ base::Unretained(this), std::move(callback)));
+}
+
+void UserCloudSigninRestrictionPolicyFetcher::FetchAccessToken(
+ signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceCallback<void(const std::string&)> callback) {
+ DCHECK(callback);
+ DCHECK(!account_id.empty());
+ DCHECK(identity_manager->HasAccountWithRefreshToken(account_id));
+ DCHECK(!access_token_fetcher_);
+
+ // base::Unretained is safe here because `access_token_fetcher_` is owned by
+ // `this`.
+ access_token_fetcher_ = identity_manager->CreateAccessTokenFetcherForAccount(
+ account_id, /*oauth_consumer_name=*/"cloud_policy", /*scopes=*/
+ {GaiaConstants::kSecureConnectOAuth2Scope},
+ base::BindOnce(
+ &UserCloudSigninRestrictionPolicyFetcher::OnFetchAccessTokenResult,
+ base::Unretained(this), std::move(callback)),
+ signin::AccessTokenFetcher::Mode::kImmediate);
+}
+
+void UserCloudSigninRestrictionPolicyFetcher::OnFetchAccessTokenResult(
+ base::OnceCallback<void(const std::string&)> callback,
+ GoogleServiceAuthError error,
+ signin::AccessTokenInfo token_info) {
+ std::move(callback).Run(
+ error.state() == GoogleServiceAuthError::NONE ? token_info.token : "");
+}
+
+void UserCloudSigninRestrictionPolicyFetcher::
+ GetManagedAccountsSigninRestrictionInternal(
+ base::OnceCallback<void(const std::string&)> callback,
+ const std::string& access_token) {
+ net::NetworkTrafficAnnotationTag annotation =
+ net::DefineNetworkTrafficAnnotation(
+ "managed_acccount_signin_restrictions_secure_connect", R"(
+ semantics {
+ sender: "SecureConnect Service"
+ description:
+ "A request to the SecureConnect API to retrieve the value of the "
+ "ManagedAccountsSigninRestriction policy for the signed in user."
+ trigger:
+ "After a user signs into a managed account and if they have not "
+ "explicitely accepted to use a managed profile and no value for the "
+ "profile separation is not enforced by any machine level policy."
+ data:
+ "Gaia access token."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ chrome_policy {
+ SigninInterceptionEnabled {
+ SigninInterceptionEnabled: false
+ }
+ }
+ })");
+
+ // Each url loader can only be used for one request.
+ url_loader_ =
+ CreateUrlLoader(GetSecureConnectApiGetAccountSigninRestrictionUrl(),
+ access_token, annotation);
+
+ // base::Unretained is safe here because `url_loader_` is owned by `this`.
+ url_loader_->DownloadToString(
+ url_loader_factory_for_testing_ == nullptr
+ ? url_loader_factory_.get()
+ : url_loader_factory_for_testing_.get(),
+ base::BindOnce(&UserCloudSigninRestrictionPolicyFetcher::
+ OnManagedAccountsSigninRestrictionResult,
+ base::Unretained(this), std::move(callback)),
+ 1024 * 1024 /* 1 MiB */);
+}
+
+void UserCloudSigninRestrictionPolicyFetcher::
+ OnManagedAccountsSigninRestrictionResult(
+ base::OnceCallback<void(const std::string&)> callback,
+ std::unique_ptr<std::string> response_body) {
+ std::string restriction;
+ std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(url_loader_);
+
+ GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
+ absl::optional<int> response_code;
+ if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers)
+ response_code = url_loader->ResponseInfo()->headers->response_code();
+
+ if (response_code)
+ base::UmaHistogramSparse(
+ "Enterprise.ProfileSeparation.DasherPolicyFetch.HttpResponse",
+ response_code.value());
+
+ base::UmaHistogramSparse(
+ "Enterprise.ProfileSeparation.DasherPolicyFetch.NetworkError",
+ url_loader->NetError());
+ if (url_loader->NetError() != net::OK) {
+ if (response_code) {
+ LOG(WARNING)
+ << "ManagedAccountsSigninRestriction request failed with HTTP code: "
+ << response_code.value();
+ } else {
+ error =
+ GoogleServiceAuthError::FromConnectionError(url_loader->NetError());
+ LOG(WARNING)
+ << "ManagedAccountsSigninRestriction request failed with error: "
+ << url_loader->NetError();
+ }
+ }
+
+ if (error.state() == GoogleServiceAuthError::NONE && response_body) {
+ auto result = base::JSONReader::Read(*response_body, base::JSON_PARSE_RFC);
+ if (result && result->FindStringKey("policyValue"))
+ restriction = *result->FindStringKey("policyValue");
+ else
+ LOG(WARNING) << "Failed to ManagedAccountsSigninRestriction response";
+ }
+
+ std::move(callback).Run(std::move(restriction));
+}
+
+GURL UserCloudSigninRestrictionPolicyFetcher::
+ GetSecureConnectApiGetAccountSigninRestrictionUrl() const {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ std::string url =
+ command_line->HasSwitch(policy::switches::kSecureConnectApiUrl) &&
+ browser_policy_connector_ &&
+ browser_policy_connector_->IsCommandLineSwitchSupported()
+ ? command_line->GetSwitchValueASCII(
+ policy::switches::kSecureConnectApiUrl)
+ : kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl;
+ return GURL(url);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h
new file mode 100644
index 00000000000..5c9d09fc8d8
--- /dev/null
+++ b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h
@@ -0,0 +1,97 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/policy_export.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+struct CoreAccountId;
+
+namespace network {
+class SimpleURLLoader;
+}
+
+namespace signin {
+class AccessTokenFetcher;
+class IdentityManager;
+} // namespace signin
+
+namespace policy {
+
+class BrowserPolicyConnector;
+
+class POLICY_EXPORT UserCloudSigninRestrictionPolicyFetcher {
+ public:
+ UserCloudSigninRestrictionPolicyFetcher(
+ policy::BrowserPolicyConnector* browser_policy_connector,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+ ~UserCloudSigninRestrictionPolicyFetcher();
+
+ UserCloudSigninRestrictionPolicyFetcher(
+ const UserCloudSigninRestrictionPolicyFetcher&) = delete;
+ UserCloudSigninRestrictionPolicyFetcher& operator=(
+ const UserCloudSigninRestrictionPolicyFetcher&) = delete;
+
+ // Fetched the value of the ManagedAccountsSigninRestriction set at the
+ // account level for `account_id` and calls `callback` with the resulting
+ // value. If the policy was not set or we were not able to retrieve it, the
+ // result will be an empty string.
+ void GetManagedAccountsSigninRestriction(
+ signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceCallback<void(const std::string&)> callback);
+
+ void SetURLLoaderFactoryForTesting(
+ network::mojom::URLLoaderFactory* factory) {
+ url_loader_factory_for_testing_ = factory;
+ }
+
+ private:
+ // Fetches an access token for `accound_id` and calls `callback` with the
+ // access token.
+ void FetchAccessToken(signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceCallback<void(const std::string&)> callback);
+
+ void OnFetchAccessTokenResult(
+ base::OnceCallback<void(const std::string&)> callback,
+ GoogleServiceAuthError error,
+ signin::AccessTokenInfo token_info);
+
+ // Calls the SecureConnect API to get the ManagedAccountsSigninRestriction
+ // policy using `access_token` for the authentication. Calls
+ // `OnManagedAccountsSigninRestrictionResult` with the result from the API.
+ void GetManagedAccountsSigninRestrictionInternal(
+ base::OnceCallback<void(const std::string&)> callback,
+ const std::string& access_token);
+
+ // Retrieves the policy value from `response_body` and calls `callback` with
+ // that value.
+ void OnManagedAccountsSigninRestrictionResult(
+ base::OnceCallback<void(const std::string&)> callback,
+ std::unique_ptr<std::string> response_body);
+
+ GURL GetSecureConnectApiGetAccountSigninRestrictionUrl() const;
+
+ const raw_ptr<policy::BrowserPolicyConnector> browser_policy_connector_;
+ std::unique_ptr<signin::AccessTokenFetcher> access_token_fetcher_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ raw_ptr<network::mojom::URLLoaderFactory> url_loader_factory_for_testing_ =
+ nullptr;
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_SIGNIN_USER_CLOUD_SIGNIN_RESTRICTION_POLICY_FETCHER_H_
diff --git a/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher_unittest.cc b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher_unittest.cc
new file mode 100644
index 00000000000..0004cc6e0b8
--- /dev/null
+++ b/chromium/components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/features.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl[] =
+ "https://secureconnect-pa.clients6.google.com/"
+ "v1:getManagedAccountsSigninRestriction";
+}
+
+namespace policy {
+
+class UserCloudSigninRestrictionPolicyFetcherTest : public ::testing::Test {
+ public:
+ UserCloudSigninRestrictionPolicyFetcherTest()
+ : policy_fetcher_(nullptr, nullptr) {
+ feature_list_.InitAndEnableFeature(
+ policy::features::kEnableUserCloudSigninRestrictionPolicyFetcher);
+ }
+
+ UserCloudSigninRestrictionPolicyFetcher* policy_fetcher() {
+ return &policy_fetcher_;
+ }
+ signin::IdentityTestEnvironment* identity_test_env() {
+ return &identity_test_env_;
+ }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+ base::test::TaskEnvironment task_env_;
+ UserCloudSigninRestrictionPolicyFetcher policy_fetcher_;
+ signin::IdentityTestEnvironment identity_test_env_;
+};
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherTest, ReturnsValueFromBody) {
+ network::TestURLLoaderFactory url_loader_factory;
+ base::Value expected_response(base::Value::Type::DICTIONARY);
+ expected_response.SetStringKey("policyValue", "primary_account");
+ std::string response;
+ JSONStringValueSerializer serializer(&response);
+ ASSERT_TRUE(serializer.Serialize(expected_response));
+ url_loader_factory.AddResponse(
+ kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl,
+ std::move(response));
+
+ identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
+ AccountInfo account_info =
+ identity_test_env()->MakeAccountAvailable("alice@example.com");
+
+ std::string result;
+ policy_fetcher()->SetURLLoaderFactoryForTesting(&url_loader_factory);
+ policy_fetcher()->GetManagedAccountsSigninRestriction(
+ identity_test_env()->identity_manager(), account_info.account_id,
+ base::BindLambdaForTesting(
+ [&result](const std::string& res) { result = res; }));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ("primary_account", result);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherTest,
+ ReturnsEmptyValueIfNetworkError) {
+ network::TestURLLoaderFactory url_loader_factory;
+ auto head = network::mojom::URLResponseHead::New();
+ head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
+ url_loader_factory.AddResponse(
+ GURL(kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl),
+ /*head=*/std::move(head), /*content=*/"",
+ network::URLLoaderCompletionStatus(net::ERR_INTERNET_DISCONNECTED),
+ network::TestURLLoaderFactory::Redirects(),
+ network::TestURLLoaderFactory::ResponseProduceFlags::
+ kSendHeadersOnNetworkError);
+
+ identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
+ AccountInfo account_info =
+ identity_test_env()->MakeAccountAvailable("alice@example.com");
+
+ std::string result;
+ policy_fetcher()->SetURLLoaderFactoryForTesting(&url_loader_factory);
+ policy_fetcher()->GetManagedAccountsSigninRestriction(
+ identity_test_env()->identity_manager(), account_info.account_id,
+ base::BindLambdaForTesting(
+ [&result](const std::string& res) { result = res; }));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(std::string(), result);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherTest,
+ ReturnsEmptyValueIfHTTPError) {
+ network::TestURLLoaderFactory url_loader_factory;
+ url_loader_factory.AddResponse(
+ kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl, std::string(),
+ net::HTTP_BAD_GATEWAY);
+
+ identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
+ AccountInfo account_info =
+ identity_test_env()->MakeAccountAvailable("alice@example.com");
+
+ std::string result;
+ policy_fetcher()->SetURLLoaderFactoryForTesting(&url_loader_factory);
+ policy_fetcher()->GetManagedAccountsSigninRestriction(
+ identity_test_env()->identity_manager(), account_info.account_id,
+ base::BindLambdaForTesting(
+ [&result](const std::string& res) { result = res; }));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(std::string(), result);
+}
+
+TEST_F(UserCloudSigninRestrictionPolicyFetcherTest,
+ ReturnsEmptyValueInResponseNotParsable) {
+ network::TestURLLoaderFactory url_loader_factory;
+ url_loader_factory.AddResponse(
+ kSecureConnectApiGetManagedAccountsSigninRestrictionsUrl, "bad");
+
+ identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
+ AccountInfo account_info =
+ identity_test_env()->MakeAccountAvailable("alice@example.com");
+
+ std::string result;
+ policy_fetcher()->SetURLLoaderFactoryForTesting(&url_loader_factory);
+ policy_fetcher()->GetManagedAccountsSigninRestriction(
+ identity_test_env()->identity_manager(), account_info.account_id,
+ base::BindLambdaForTesting(
+ [&result](const std::string& res) { result = res; }));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(std::string(), result);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_allowlist_policy_handler.cc b/chromium/components/policy/core/browser/url_allowlist_policy_handler.cc
new file mode 100644
index 00000000000..3ff2ac2faa1
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_allowlist_policy_handler.cc
@@ -0,0 +1,104 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_allowlist_policy_handler.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+
+namespace policy {
+
+URLAllowlistPolicyHandler::URLAllowlistPolicyHandler(const char* policy_name)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST) {}
+
+URLAllowlistPolicyHandler::~URLAllowlistPolicyHandler() = default;
+
+bool URLAllowlistPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ if (!policies.IsPolicySet(policy_name()))
+ return true;
+
+ const base::Value* url_allowlist =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ if (!url_allowlist) {
+ errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::LIST));
+ return true;
+ }
+
+ // Filters more than |policy::kMaxUrlFiltersPerPolicy| are ignored, add a
+ // warning message.
+ if (url_allowlist->GetListDeprecated().size() > kMaxUrlFiltersPerPolicy) {
+ errors->AddError(policy_name(),
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString(kMaxUrlFiltersPerPolicy));
+ }
+
+ bool type_error = false;
+ std::string policy;
+ std::vector<std::string> invalid_policies;
+ for (const auto& policy_iter : url_allowlist->GetListDeprecated()) {
+ if (!policy_iter.is_string()) {
+ type_error = true;
+ continue;
+ }
+
+ policy = policy_iter.GetString();
+ if (!ValidatePolicy(policy))
+ invalid_policies.push_back(policy);
+ }
+
+ if (type_error) {
+ errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ }
+
+ if (invalid_policies.size()) {
+ errors->AddError(policy_name(), IDS_POLICY_PROTO_PARSING_ERROR,
+ base::JoinString(invalid_policies, ","));
+ }
+
+ return true;
+}
+
+void URLAllowlistPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ const base::Value* url_allowlist =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ if (!url_allowlist) {
+ return;
+ }
+
+ std::vector<base::Value> filtered_url_allowlist;
+ for (const auto& entry : url_allowlist->GetListDeprecated()) {
+ if (entry.is_string())
+ filtered_url_allowlist.push_back(entry.Clone());
+ }
+
+ prefs->SetValue(policy_prefs::kUrlAllowlist,
+ base::Value(std::move(filtered_url_allowlist)));
+}
+
+bool URLAllowlistPolicyHandler::ValidatePolicy(const std::string& policy) {
+ url_matcher::util::FilterComponents components;
+ return url_matcher::util::FilterToComponents(
+ policy, &components.scheme, &components.host,
+ &components.match_subdomains, &components.port, &components.path,
+ &components.query);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_allowlist_policy_handler.h b/chromium/components/policy/core/browser/url_allowlist_policy_handler.h
new file mode 100644
index 00000000000..062dd791b68
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_allowlist_policy_handler.h
@@ -0,0 +1,37 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_URL_ALLOWLIST_POLICY_HANDLER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_URL_ALLOWLIST_POLICY_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Handles URLAllowlist policy.
+class POLICY_EXPORT URLAllowlistPolicyHandler
+ : public TypeCheckingPolicyHandler {
+ public:
+ URLAllowlistPolicyHandler(const URLAllowlistPolicyHandler&) = delete;
+ void operator=(const URLAllowlistPolicyHandler&) = delete;
+
+ explicit URLAllowlistPolicyHandler(const char* policy_name);
+ ~URLAllowlistPolicyHandler() override;
+
+ // Validates that policy follows official pattern
+ // https://www.chromium.org/administrators/url-blocklist-filter-format
+ bool ValidatePolicy(const std::string& policy);
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_URL_ALLOWLIST_POLICY_HANDLER_H_
diff --git a/chromium/components/policy/core/browser/url_allowlist_policy_handler_unittest.cc b/chromium/components/policy/core/browser/url_allowlist_policy_handler_unittest.cc
new file mode 100644
index 00000000000..36657a69bb1
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_allowlist_policy_handler_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_allowlist_policy_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestAllowlistValue[] = "kTestAllowlistValue";
+
+} // namespace
+
+class URLAllowlistPolicyHandlerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ handler_ = std::make_unique<URLAllowlistPolicyHandler>(key::kURLAllowlist);
+ }
+
+ protected:
+ void SetPolicy(const std::string& key, base::Value value) {
+ policies_.Set(key, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(value), nullptr);
+ }
+ bool CheckPolicy(const std::string& key, base::Value value) {
+ SetPolicy(key, std::move(value));
+ return handler_->CheckPolicySettings(policies_, &errors_);
+ }
+ void ApplyPolicies() { handler_->ApplyPolicySettings(policies_, &prefs_); }
+ bool ValidatePolicy(const std::string& policy) {
+ return handler_->ValidatePolicy(policy);
+ }
+ base::Value GetURLAllowlistPolicyValueWithEntries(size_t len) {
+ std::vector<base::Value> allowlist(len);
+ for (auto& entry : allowlist)
+ entry = base::Value(kTestAllowlistValue);
+ return base::Value(std::move(allowlist));
+ }
+
+ std::unique_ptr<URLAllowlistPolicyHandler> handler_;
+ PolicyErrorMap errors_;
+ PolicyMap policies_;
+ PrefValueMap prefs_;
+};
+
+TEST_F(URLAllowlistPolicyHandlerTest, CheckPolicySettings_WrongType) {
+ // The policy expects a list. Give it a boolean.
+ EXPECT_TRUE(CheckPolicy(key::kURLAllowlist, base::Value(false)));
+ EXPECT_EQ(1U, errors_.size());
+ const std::string expected = key::kURLAllowlist;
+ const std::string actual = errors_.begin()->first;
+ EXPECT_EQ(expected, actual);
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ApplyPolicySettings_NothingSpecified) {
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(policy_prefs::kUrlAllowlist, nullptr));
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ApplyPolicySettings_WrongType) {
+ // The policy expects a list. Give it a boolean.
+ SetPolicy(key::kURLAllowlist, base::Value(false));
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(policy_prefs::kUrlAllowlist, nullptr));
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ApplyPolicySettings_Empty) {
+ SetPolicy(key::kURLAllowlist, base::Value(base::Value::Type::LIST));
+ ApplyPolicies();
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlAllowlist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ApplyPolicySettings_WrongElementType) {
+ // The policy expects string-valued elements. Give it booleans.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(false);
+ SetPolicy(key::kURLAllowlist, std::move(in));
+ ApplyPolicies();
+
+ // The element should be skipped.
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlAllowlist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ApplyPolicySettings_Successful) {
+ base::Value in_url_allowlist(base::Value::Type::LIST);
+ in_url_allowlist.Append(kTestAllowlistValue);
+ SetPolicy(key::kURLAllowlist, std::move(in_url_allowlist));
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlAllowlist, &out));
+ ASSERT_TRUE(out->is_list());
+ ASSERT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestAllowlistValue, *out_string);
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitOK) {
+ size_t max_filters_per_policy = policy::kMaxUrlFiltersPerPolicy;
+ base::Value urls =
+ GetURLAllowlistPolicyValueWithEntries(max_filters_per_policy);
+
+ EXPECT_TRUE(CheckPolicy(key::kURLAllowlist, std::move(urls)));
+ EXPECT_EQ(0U, errors_.size());
+
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlAllowlist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(max_filters_per_policy, out->GetListDeprecated().size());
+}
+
+// Test that the warning message, mapped to
+// |IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING|, is added to
+// |errors_| when URLAllowlist entries exceed the max filters per policy limit.
+TEST_F(URLAllowlistPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded) {
+ size_t max_filters_per_policy = policy::kMaxUrlFiltersPerPolicy;
+ base::Value urls =
+ GetURLAllowlistPolicyValueWithEntries(max_filters_per_policy + 1);
+
+ EXPECT_TRUE(CheckPolicy(key::kURLAllowlist, std::move(urls)));
+ EXPECT_EQ(1U, errors_.size());
+
+ ApplyPolicies();
+
+ auto error_str = errors_.GetErrors(key::kURLAllowlist);
+ auto expected_str = l10n_util::GetStringFUTF16(
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString16(max_filters_per_policy));
+ EXPECT_TRUE(error_str.find(expected_str) != std::wstring::npos);
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlAllowlist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(max_filters_per_policy + 1, out->GetListDeprecated().size());
+}
+
+TEST_F(URLAllowlistPolicyHandlerTest, ValidatePolicy) {
+ EXPECT_TRUE(ValidatePolicy("http://*"));
+ EXPECT_TRUE(ValidatePolicy("http:*"));
+
+ EXPECT_TRUE(ValidatePolicy("ws://example.org/component.js"));
+ EXPECT_FALSE(ValidatePolicy("wsgi:///rancom,org/"));
+
+ EXPECT_TRUE(ValidatePolicy("127.0.0.1:1"));
+ EXPECT_TRUE(ValidatePolicy("127.0.0.1:65535"));
+ EXPECT_FALSE(ValidatePolicy("127.0.0.1:65536"));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_blocklist_manager.cc b/chromium/components/policy/core/browser/url_blocklist_manager.cc
new file mode 100644
index 00000000000..b00a452af06
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_manager.cc
@@ -0,0 +1,311 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_blocklist_manager.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <limits>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/task/task_runner_util.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/browser/url_blocklist_policy_handler.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/url_formatter/url_fixer.h"
+#include "net/base/filename_util.h"
+#include "net/base/net_errors.h"
+#include "url/third_party/mozilla/url_parse.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+using url_matcher::URLMatcher;
+using url_matcher::URLMatcherCondition;
+using url_matcher::URLMatcherConditionFactory;
+using url_matcher::URLMatcherConditionSet;
+using url_matcher::URLMatcherPortFilter;
+using url_matcher::URLMatcherSchemeFilter;
+using url_matcher::URLQueryElementMatcherCondition;
+
+namespace policy {
+
+using url_matcher::util::CreateConditionSet;
+using url_matcher::util::FilterComponents;
+using url_matcher::util::FilterToComponents;
+
+namespace {
+
+// List of schemes of URLs that should not be blocked by the "*" wildcard in
+// the blocklist. Note that URLs with these schemes can still be blocked with
+// a more specific filter e.g. "chrome-extension://*".
+// The schemes are hardcoded here to avoid dependencies on //extensions and
+// //chrome.
+const char* kBypassBlocklistWildcardForSchemes[] = {
+ // For internal extension URLs e.g. the Bookmark Manager and the File
+ // Manager on Chrome OS.
+ "chrome-extension",
+
+ // NTP on Android.
+ "chrome-native",
+
+ // NTP on other platforms.
+ "chrome-search",
+};
+
+#if BUILDFLAG(IS_IOS)
+// The two schemes used on iOS for the NTP.
+constexpr char kIosNtpAboutScheme[] = "about";
+constexpr char kIosNtpChromeScheme[] = "chrome";
+// The host string used on iOS for the NTP.
+constexpr char kIosNtpHost[] = "newtab";
+#endif
+
+// Returns a blocklist based on the given |block| and |allow| pattern lists.
+std::unique_ptr<URLBlocklist> BuildBlocklist(const base::Value* block,
+ const base::Value* allow) {
+ auto blocklist = std::make_unique<URLBlocklist>();
+ if (block)
+ blocklist->Block(&base::Value::AsListValue(*block));
+ if (allow)
+ blocklist->Allow(&base::Value::AsListValue(*allow));
+ return blocklist;
+}
+
+const base::Value* GetPrefValue(PrefService* pref_service,
+ absl::optional<std::string> pref_path) {
+ DCHECK(pref_service);
+
+ if (!pref_path)
+ return nullptr;
+
+ DCHECK(!pref_path->empty());
+
+ return pref_service->HasPrefPath(*pref_path)
+ ? pref_service->GetList(*pref_path)
+ : nullptr;
+}
+
+bool BypassBlocklistWildcardForURL(const GURL& url) {
+ const std::string& scheme = url.scheme();
+ for (const char* bypass_scheme : kBypassBlocklistWildcardForSchemes) {
+ if (scheme == bypass_scheme)
+ return true;
+ }
+#if BUILDFLAG(IS_IOS)
+ // Compare the chrome scheme and host against the chrome://newtab version of
+ // the NTP URL.
+ if (scheme == kIosNtpChromeScheme && url.host() == kIosNtpHost) {
+ return true;
+ }
+ // Compare the URL scheme and path to the about:newtab version of the NTP URL.
+ // Leading and trailing slashes must be removed because the host name is
+ // parsed as the URL path (which may contain slashes).
+ base::StringPiece trimmed_path =
+ base::TrimString(url.path_piece(), "/", base::TrimPositions::TRIM_ALL);
+ if (scheme == kIosNtpAboutScheme && trimmed_path == kIosNtpHost) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+} // namespace
+
+URLBlocklist::URLBlocklist() : url_matcher_(new URLMatcher) {}
+
+URLBlocklist::~URLBlocklist() = default;
+
+void URLBlocklist::Block(const base::ListValue* filters) {
+ url_matcher::util::AddFilters(url_matcher_.get(), false, &id_, filters,
+ &filters_);
+}
+
+void URLBlocklist::Allow(const base::ListValue* filters) {
+ url_matcher::util::AddFilters(url_matcher_.get(), true, &id_, filters,
+ &filters_);
+}
+
+bool URLBlocklist::IsURLBlocked(const GURL& url) const {
+ return URLBlocklist::GetURLBlocklistState(url) ==
+ URLBlocklist::URLBlocklistState::URL_IN_BLOCKLIST;
+}
+
+URLBlocklist::URLBlocklistState URLBlocklist::GetURLBlocklistState(
+ const GURL& url) const {
+ std::set<URLMatcherConditionSet::ID> matching_ids =
+ url_matcher_->MatchURL(url);
+
+ const FilterComponents* max = nullptr;
+ for (auto id = matching_ids.begin(); id != matching_ids.end(); ++id) {
+ auto it = filters_.find(*id);
+ DCHECK(it != filters_.end());
+ const FilterComponents& filter = it->second;
+ if (!max || FilterTakesPrecedence(filter, *max))
+ max = &filter;
+ }
+
+ // Default neutral.
+ if (!max)
+ return URLBlocklist::URLBlocklistState::URL_NEUTRAL_STATE;
+
+ // Some of the internal Chrome URLs are not affected by the "*" in the
+ // blocklist. Note that the "*" is the lowest priority filter possible, so
+ // any higher priority filter will be applied first.
+ if (!max->allow && max->IsWildcard() && BypassBlocklistWildcardForURL(url))
+ return URLBlocklist::URLBlocklistState::URL_IN_ALLOWLIST;
+
+ return max->allow ? URLBlocklist::URLBlocklistState::URL_IN_ALLOWLIST
+ : URLBlocklist::URLBlocklistState::URL_IN_BLOCKLIST;
+}
+
+size_t URLBlocklist::Size() const {
+ return filters_.size();
+}
+
+// static
+bool URLBlocklist::FilterTakesPrecedence(const FilterComponents& lhs,
+ const FilterComponents& rhs) {
+ // The "*" wildcard in the blocklist is the lowest priority filter.
+ if (!rhs.allow && rhs.IsWildcard())
+ return true;
+
+ if (lhs.match_subdomains && !rhs.match_subdomains)
+ return false;
+ if (!lhs.match_subdomains && rhs.match_subdomains)
+ return true;
+
+ size_t host_length = lhs.host.length();
+ size_t other_host_length = rhs.host.length();
+ if (host_length != other_host_length)
+ return host_length > other_host_length;
+
+ size_t path_length = lhs.path.length();
+ size_t other_path_length = rhs.path.length();
+ if (path_length != other_path_length)
+ return path_length > other_path_length;
+
+ if (lhs.number_of_url_matching_conditions !=
+ rhs.number_of_url_matching_conditions)
+ return lhs.number_of_url_matching_conditions >
+ rhs.number_of_url_matching_conditions;
+
+ if (lhs.allow && !rhs.allow)
+ return true;
+
+ return false;
+}
+
+URLBlocklistManager::URLBlocklistManager(
+ PrefService* pref_service,
+ absl::optional<std::string> blocklist_pref_path,
+ absl::optional<std::string> allowlist_pref_path)
+ : pref_service_(pref_service),
+ blocklist_pref_path_(std::move(blocklist_pref_path)),
+ allowlist_pref_path_(std::move(allowlist_pref_path)),
+ blocklist_(new URLBlocklist) {
+ DCHECK(blocklist_pref_path_ || allowlist_pref_path_);
+
+ // This class assumes that it is created on the same thread that
+ // |pref_service_| lives on.
+ ui_task_runner_ = base::SequencedTaskRunnerHandle::Get();
+ background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+ {base::TaskPriority::BEST_EFFORT});
+
+ pref_change_registrar_.Init(pref_service_);
+ base::RepeatingClosure callback = base::BindRepeating(
+ &URLBlocklistManager::ScheduleUpdate, base::Unretained(this));
+ if (blocklist_pref_path_)
+ pref_change_registrar_.Add(*blocklist_pref_path_, callback);
+ if (allowlist_pref_path_)
+ pref_change_registrar_.Add(*allowlist_pref_path_, callback);
+
+ // Start enforcing the policies without a delay when they are present at
+ // startup.
+ const base::Value* block = GetPrefValue(pref_service_, blocklist_pref_path_);
+ const base::Value* allow = GetPrefValue(pref_service_, allowlist_pref_path_);
+ if (block || allow)
+ SetBlocklist(BuildBlocklist(block, allow));
+}
+
+URLBlocklistManager::~URLBlocklistManager() {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+ pref_change_registrar_.RemoveAll();
+}
+
+void URLBlocklistManager::ScheduleUpdate() {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+ // Cancel pending updates, if any. This can happen if two preferences that
+ // change the blocklist are updated in one message loop cycle. In those cases,
+ // only rebuild the blocklist after all the preference updates are processed.
+ ui_weak_ptr_factory_.InvalidateWeakPtrs();
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&URLBlocklistManager::Update,
+ ui_weak_ptr_factory_.GetWeakPtr()));
+}
+
+void URLBlocklistManager::Update() {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+
+ // The URLBlocklist is built in the background. Once it's ready, it is passed
+ // to the URLBlocklistManager back on ui_task_runner_.
+ const base::Value* block = GetPrefValue(pref_service_, blocklist_pref_path_);
+ const base::Value* allow = GetPrefValue(pref_service_, allowlist_pref_path_);
+ base::PostTaskAndReplyWithResult(
+ background_task_runner_.get(), FROM_HERE,
+ base::BindOnce(
+ &BuildBlocklist,
+ base::Owned(block ? base::Value::ToUniquePtrValue(block->Clone())
+ : nullptr),
+ base::Owned(allow ? base::Value::ToUniquePtrValue(allow->Clone())
+ : nullptr)),
+ base::BindOnce(&URLBlocklistManager::SetBlocklist,
+ ui_weak_ptr_factory_.GetWeakPtr()));
+}
+
+void URLBlocklistManager::SetBlocklist(
+ std::unique_ptr<URLBlocklist> blocklist) {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+ blocklist_ = std::move(blocklist);
+}
+
+bool URLBlocklistManager::IsURLBlocked(const GURL& url) const {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+ // Ignore blob scheme for two reasons:
+ // 1) Its used to deliver the response to the renderer.
+ // 2) A page on the allowlist can use blob URLs internally.
+ return !url.SchemeIs(url::kBlobScheme) && blocklist_->IsURLBlocked(url);
+}
+
+URLBlocklist::URLBlocklistState URLBlocklistManager::GetURLBlocklistState(
+ const GURL& url) const {
+ DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+ return blocklist_->GetURLBlocklistState(url);
+}
+
+// static
+void URLBlocklistManager::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterListPref(policy_prefs::kUrlBlocklist);
+ registry->RegisterListPref(policy_prefs::kUrlAllowlist);
+ registry->RegisterIntegerPref(
+ policy_prefs::kSafeSitesFilterBehavior,
+ static_cast<int>(SafeSitesFilterBehavior::kSafeSitesFilterDisabled));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_blocklist_manager.h b/chromium/components/policy/core/browser/url_blocklist_manager.h
new file mode 100644
index 00000000000..35ee99c852e
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_manager.h
@@ -0,0 +1,146 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_MANAGER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/policy_export.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/url_matcher/url_matcher.h"
+#include "components/url_matcher/url_util.h"
+#include "url/gurl.h"
+
+class PrefService;
+
+namespace base {
+class ListValue;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace policy {
+
+// Contains a set of filters to block and allow certain URLs, and matches GURLs
+// against this set. The filters are currently kept in memory.
+class POLICY_EXPORT URLBlocklist {
+ public:
+ // Indicates if the URL matches a pattern defined in blocklist, in allowlist
+ // or doesn't match anything in either list as defined in URLBlocklist and
+ // URLAllowlist policies.
+ enum URLBlocklistState {
+ URL_IN_ALLOWLIST,
+ URL_IN_BLOCKLIST,
+ URL_NEUTRAL_STATE,
+ };
+
+ URLBlocklist();
+ URLBlocklist(const URLBlocklist&) = delete;
+ URLBlocklist& operator=(const URLBlocklist&) = delete;
+ virtual ~URLBlocklist();
+
+ // URLs matching one of the |filters| will be blocked. The filter format is
+ // documented at
+ // http://www.chromium.org/administrators/url-blocklist-filter-format.
+ void Block(const base::ListValue* filters);
+
+ // URLs matching one of the |filters| will be allowed. If a URL is both
+ // Blocked and Allowed, Allow takes precedence.
+ void Allow(const base::ListValue* filters);
+
+ // Returns true if the URL is blocked.
+ bool IsURLBlocked(const GURL& url) const;
+
+ URLBlocklistState GetURLBlocklistState(const GURL& url) const;
+
+ // Returns the number of items in the list.
+ size_t Size() const;
+
+ private:
+ // Returns true if |lhs| takes precedence over |rhs|.
+ static bool FilterTakesPrecedence(
+ const url_matcher::util::FilterComponents& lhs,
+ const url_matcher::util::FilterComponents& rhs);
+
+ url_matcher::URLMatcherConditionSet::ID id_ = 0;
+ std::map<url_matcher::URLMatcherConditionSet::ID,
+ url_matcher::util::FilterComponents>
+ filters_;
+ std::unique_ptr<url_matcher::URLMatcher> url_matcher_;
+};
+
+// Tracks the blocklist policies for a given profile, and updates it on changes.
+class POLICY_EXPORT URLBlocklistManager {
+ public:
+ // Must be constructed on the UI thread and either of |blocklist_pref_path| or
+ // |allowlist_pref_path| should be valid.
+ URLBlocklistManager(PrefService* pref_service,
+ absl::optional<std::string> blocklist_pref_path,
+ absl::optional<std::string> allowlist_pref_path);
+ URLBlocklistManager(const URLBlocklistManager&) = delete;
+ URLBlocklistManager& operator=(const URLBlocklistManager&) = delete;
+ virtual ~URLBlocklistManager();
+
+ // Returns true if |url| is blocked by the current blocklist.
+ bool IsURLBlocked(const GURL& url) const;
+
+ URLBlocklist::URLBlocklistState GetURLBlocklistState(const GURL& url) const;
+
+ // Replaces the current blocklist.
+ // Virtual for testing.
+ virtual void SetBlocklist(std::unique_ptr<URLBlocklist> blocklist);
+
+ // Registers the preferences related to blocklisting in the given PrefService.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ protected:
+ // Used to delay updating the blocklist while the preferences are
+ // changing, and execute only one update per simultaneous prefs changes.
+ void ScheduleUpdate();
+
+ // Updates the blocklist using the current preference values.
+ // Virtual for testing.
+ virtual void Update();
+
+ private:
+ // Used to track the policies and update the blocklist on changes.
+ PrefChangeRegistrar pref_change_registrar_;
+ raw_ptr<PrefService> pref_service_; // Weak.
+
+ const absl::optional<std::string> blocklist_pref_path_;
+ const absl::optional<std::string> allowlist_pref_path_;
+
+ // Used to post tasks to a background thread.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ // Used to schedule tasks on the main loop to avoid rebuilding the blocklist
+ // multiple times during a message loop process. This can happen if two
+ // preferences that change the blocklist are updated in one message loop
+ // cycle. In addition, we use this task runner to ensure that the
+ // URLBlocklistManager is only access from the thread call the constructor for
+ // data accesses.
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
+
+ // The current blocklist.
+ std::unique_ptr<URLBlocklist> blocklist_;
+
+ // Used to post update tasks to the UI thread.
+ base::WeakPtrFactory<URLBlocklistManager> ui_weak_ptr_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_MANAGER_H_
diff --git a/chromium/components/policy/core/browser/url_blocklist_manager_unittest.cc b/chromium/components/policy/core/browser/url_blocklist_manager_unittest.cc
new file mode 100644
index 00000000000..52cc1851228
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_manager_unittest.cc
@@ -0,0 +1,649 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_blocklist_manager.h"
+
+#include <stdint.h>
+#include <memory>
+#include <ostream>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/url_formatter/url_fixer.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/load_flags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+namespace {
+
+class TestingURLBlocklistManager : public URLBlocklistManager {
+ public:
+ explicit TestingURLBlocklistManager(PrefService* pref_service)
+ : URLBlocklistManager(pref_service,
+ policy_prefs::kUrlBlocklist,
+ policy_prefs::kUrlAllowlist),
+ update_called_(0),
+ set_blocklist_called_(false) {}
+ TestingURLBlocklistManager(const TestingURLBlocklistManager&) = delete;
+ TestingURLBlocklistManager& operator=(const TestingURLBlocklistManager&) =
+ delete;
+
+ ~TestingURLBlocklistManager() override = default;
+
+ // Make this method public for testing.
+ using URLBlocklistManager::ScheduleUpdate;
+
+ // URLBlocklistManager overrides:
+ void SetBlocklist(std::unique_ptr<URLBlocklist> blocklist) override {
+ set_blocklist_called_ = true;
+ URLBlocklistManager::SetBlocklist(std::move(blocklist));
+ }
+
+ void Update() override {
+ update_called_++;
+ URLBlocklistManager::Update();
+ }
+
+ int update_called() const { return update_called_; }
+ bool set_blocklist_called() const { return set_blocklist_called_; }
+
+ private:
+ int update_called_;
+ bool set_blocklist_called_;
+};
+
+class URLBlocklistManagerTest : public testing::Test {
+ protected:
+ URLBlocklistManagerTest() = default;
+
+ void SetUp() override {
+ pref_service_.registry()->RegisterListPref(policy_prefs::kUrlBlocklist);
+ pref_service_.registry()->RegisterListPref(policy_prefs::kUrlAllowlist);
+ blocklist_manager_ =
+ std::make_unique<TestingURLBlocklistManager>(&pref_service_);
+ task_environment_.RunUntilIdle();
+ }
+
+ void TearDown() override {
+ if (blocklist_manager_)
+ task_environment_.RunUntilIdle();
+ blocklist_manager_.reset();
+ }
+
+ TestingPrefServiceSimple pref_service_;
+ std::unique_ptr<TestingURLBlocklistManager> blocklist_manager_;
+
+ base::test::TaskEnvironment task_environment_;
+};
+
+} // namespace
+
+// Returns whether |url| matches the |pattern|.
+bool IsMatch(const std::string& pattern, const std::string& url) {
+ URLBlocklist blocklist;
+
+ // Add the pattern to blocklist.
+ base::Value blocked(base::Value::Type::LIST);
+ blocked.Append(pattern);
+ blocklist.Block(&base::Value::AsListValue(blocked));
+
+ return blocklist.IsURLBlocked(GURL(url));
+}
+
+// Returns the state from blocklist after adding |pattern| to be blocked or
+// allowed depending on |use_allowlist| and checking |url|.
+policy::URLBlocklist::URLBlocklistState GetMatch(const std::string& pattern,
+ const std::string& url,
+ const bool use_allowlist) {
+ URLBlocklist blocklist;
+
+ // Add the pattern to list.
+ base::Value blocked(base::Value::Type::LIST);
+ blocked.Append(pattern);
+
+ if (use_allowlist) {
+ blocklist.Allow(&base::Value::AsListValue(blocked));
+ } else {
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ }
+
+ return blocklist.GetURLBlocklistState(GURL(url));
+}
+
+TEST_F(URLBlocklistManagerTest, LoadBlocklistOnCreate) {
+ base::Value list(base::Value::Type::LIST);
+ list.Append("example.com");
+ pref_service_.SetManagedPref(policy_prefs::kUrlBlocklist,
+ base::Value::ToUniquePtrValue(std::move(list)));
+ auto manager = std::make_unique<URLBlocklistManager>(
+ &pref_service_, policy_prefs::kUrlBlocklist, policy_prefs::kUrlAllowlist);
+ task_environment_.RunUntilIdle();
+ EXPECT_EQ(URLBlocklist::URL_IN_BLOCKLIST,
+ manager->GetURLBlocklistState(GURL("http://example.com")));
+}
+
+TEST_F(URLBlocklistManagerTest, LoadAllowlistOnCreate) {
+ base::Value list(base::Value::Type::LIST);
+ list.Append("example.com");
+ pref_service_.SetManagedPref(policy_prefs::kUrlAllowlist,
+ base::Value::ToUniquePtrValue(std::move(list)));
+ auto manager = std::make_unique<URLBlocklistManager>(
+ &pref_service_, policy_prefs::kUrlBlocklist, policy_prefs::kUrlAllowlist);
+ task_environment_.RunUntilIdle();
+ EXPECT_EQ(URLBlocklist::URL_IN_ALLOWLIST,
+ manager->GetURLBlocklistState(GURL("http://example.com")));
+}
+
+TEST_F(URLBlocklistManagerTest, SingleUpdateForTwoPrefChanges) {
+ base::Value blocklist(base::Value::Type::LIST);
+ blocklist.Append("*.google.com");
+ base::Value allowlist(base::Value::Type::LIST);
+ allowlist.Append("mail.google.com");
+ pref_service_.SetManagedPref(
+ policy_prefs::kUrlBlocklist,
+ base::Value::ToUniquePtrValue(std::move(blocklist)));
+ pref_service_.SetManagedPref(
+ policy_prefs::kUrlBlocklist,
+ base::Value::ToUniquePtrValue(std::move(allowlist)));
+ task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(1, blocklist_manager_->update_called());
+}
+
+TEST_F(URLBlocklistManagerTest, Filtering) {
+ URLBlocklist blocklist;
+
+ // Block domain and all subdomains, for any filtered scheme.
+ EXPECT_TRUE(IsMatch("google.com", "http://google.com"));
+ EXPECT_TRUE(IsMatch("google.com", "http://google.com/"));
+ EXPECT_TRUE(IsMatch("google.com", "http://google.com/whatever"));
+ EXPECT_TRUE(IsMatch("google.com", "https://google.com/"));
+ EXPECT_FALSE(IsMatch("google.com", "bogus://google.com/"));
+ EXPECT_FALSE(IsMatch("google.com", "http://notgoogle.com/"));
+ EXPECT_TRUE(IsMatch("google.com", "http://mail.google.com"));
+ EXPECT_TRUE(IsMatch("google.com", "http://x.mail.google.com"));
+ EXPECT_TRUE(IsMatch("google.com", "https://x.mail.google.com/"));
+ EXPECT_TRUE(IsMatch("google.com", "http://x.y.google.com/a/b"));
+ EXPECT_FALSE(IsMatch("google.com", "http://youtube.com/"));
+
+ // Filter only http, ftp and ws schemes.
+ EXPECT_TRUE(IsMatch("http://secure.com", "http://secure.com"));
+ EXPECT_TRUE(IsMatch("http://secure.com", "http://secure.com/whatever"));
+ EXPECT_TRUE(IsMatch("ftp://secure.com", "ftp://secure.com/"));
+ EXPECT_TRUE(IsMatch("ws://secure.com", "ws://secure.com"));
+ EXPECT_FALSE(IsMatch("http://secure.com", "https://secure.com/"));
+ EXPECT_FALSE(IsMatch("ws://secure.com", "wss://secure.com"));
+ EXPECT_TRUE(IsMatch("http://secure.com", "http://www.secure.com"));
+ EXPECT_FALSE(IsMatch("http://secure.com", "https://www.secure.com"));
+ EXPECT_FALSE(IsMatch("ws://secure.com", "wss://www.secure.com"));
+
+ // Filter only a certain path prefix.
+ EXPECT_TRUE(IsMatch("path.to/ruin", "http://path.to/ruin"));
+ EXPECT_TRUE(IsMatch("path.to/ruin", "https://path.to/ruin"));
+ EXPECT_TRUE(IsMatch("path.to/ruin", "http://path.to/ruins"));
+ EXPECT_TRUE(IsMatch("path.to/ruin", "http://path.to/ruin/signup"));
+ EXPECT_TRUE(IsMatch("path.to/ruin", "http://www.path.to/ruin"));
+ EXPECT_FALSE(IsMatch("path.to/ruin", "http://path.to/fortune"));
+
+ // Filter only a certain path prefix and scheme.
+ EXPECT_TRUE(IsMatch("https://s.aaa.com/path", "https://s.aaa.com/path"));
+ EXPECT_TRUE(IsMatch("https://s.aaa.com/path", "https://s.aaa.com/path/bbb"));
+ EXPECT_FALSE(IsMatch("https://s.aaa.com/path", "http://s.aaa.com/path"));
+ EXPECT_FALSE(IsMatch("https://s.aaa.com/path", "https://aaa.com/path"));
+ EXPECT_FALSE(IsMatch("https://s.aaa.com/path", "https://x.aaa.com/path"));
+ EXPECT_FALSE(IsMatch("https://s.aaa.com/path", "https://s.aaa.com/bbb"));
+ EXPECT_FALSE(IsMatch("https://s.aaa.com/path", "https://s.aaa.com/"));
+
+ // Filter only ws and wss schemes.
+ EXPECT_TRUE(IsMatch("ws://ws.aaa.com", "ws://ws.aaa.com"));
+ EXPECT_TRUE(IsMatch("wss://ws.aaa.com", "wss://ws.aaa.com"));
+ EXPECT_FALSE(IsMatch("ws://ws.aaa.com", "http://ws.aaa.com"));
+ EXPECT_FALSE(IsMatch("ws://ws.aaa.com", "https://ws.aaa.com"));
+ EXPECT_FALSE(IsMatch("ws://ws.aaa.com", "ftp://ws.aaa.com"));
+
+ // Block an ip address.
+ EXPECT_TRUE(IsMatch("123.123.123.123", "http://123.123.123.123/"));
+ EXPECT_FALSE(IsMatch("123.123.123.123", "http://123.123.123.124/"));
+
+ // Test exceptions to path prefixes, and most specific matches.
+ base::Value blocked(base::Value::Type::LIST);
+ base::Value allowed(base::Value::Type::LIST);
+ blocked.Append("s.xxx.com/a");
+ allowed.Append("s.xxx.com/a/b");
+ blocked.Append("https://s.xxx.com/a/b/c");
+ allowed.Append("https://s.xxx.com/a/b/c/d");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/x")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/x")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/b")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/b")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/b/x")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/b/c")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/b/c")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/b/c/x")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/b/c/d")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/b/c/d")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://s.xxx.com/a/b/c/d/x")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.xxx.com/a/b/c/d/x")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://xxx.com/a")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://xxx.com/a/b")));
+
+ // Open an exception.
+ blocked.ClearList();
+ blocked.Append("google.com");
+ allowed.ClearList();
+ allowed.Append("plus.google.com");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.google.com/")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://plus.google.com/")));
+
+ // Open an exception only when using https for mail.
+ allowed.ClearList();
+ allowed.Append("https://mail.google.com");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://mail.google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://www.google.com/")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://mail.google.com/")));
+
+ // Match exactly "google.com", only for http. Subdomains without exceptions
+ // are still blocked.
+ allowed.ClearList();
+ allowed.Append("http://.google.com");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://google.com/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.google.com/")));
+
+ // A smaller path match in an exact host overrides a longer path for hosts
+ // that also match subdomains.
+ blocked.ClearList();
+ blocked.Append("yyy.com/aaa");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ allowed.ClearList();
+ allowed.Append(".yyy.com/a");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://yyy.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://yyy.com/aaa")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://yyy.com/aaa2")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://www.yyy.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.yyy.com/aaa")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.yyy.com/aaa2")));
+
+ // If the exact entry is both allowed and blocked, allowing takes precedence.
+ blocked.ClearList();
+ blocked.Append("example.com");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ allowed.ClearList();
+ allowed.Append("example.com");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://example.com")));
+
+ // Devtools should not be blocked.
+ blocked.ClearList();
+ blocked.Append("*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ allowed.ClearList();
+ allowed.Append("devtools://*");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("devtools://something.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://something.com")));
+}
+
+TEST_F(URLBlocklistManagerTest, QueryParameters) {
+ URLBlocklist blocklist;
+ base::Value blocked(base::Value::Type::LIST);
+ base::Value allowed(base::Value::Type::LIST);
+
+ // Block domain and all subdomains, for any filtered scheme.
+ blocked.Append("youtube.com");
+ allowed.Append("youtube.com/watch?v=XYZ");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?v=123")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?v=123&v=XYZ")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?v=XYZ&v=123")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?v=XYZ")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?v=XYZ&foo=bar")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?foo=bar&v=XYZ")));
+
+ allowed.ClearList();
+ allowed.Append("youtube.com/watch?av=XYZ&ag=123");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?av=123")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?av=XYZ")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?av=123&ag=XYZ")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?ag=XYZ&av=123")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?av=XYZ&ag=123")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?ag=123&av=XYZ")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(
+ GURL("http://youtube.com/watch?av=XYZ&ag=123&av=123")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(
+ GURL("http://youtube.com/watch?av=XYZ&ag=123&ag=1234")));
+
+ allowed.ClearList();
+ allowed.Append("youtube.com/watch?foo=bar*&vid=2*");
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?vid=2")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube.com/watch?foo=bar")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?vid=2&foo=bar")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?vid=2&foo=bar1")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube.com/watch?vid=234&foo=bar")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube.com/watch?vid=234&foo=bar23")));
+
+ blocked.ClearList();
+ blocked.Append("youtube1.com/disallow?v=44678");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube1.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube1.com?v=123")));
+ // Path does not match
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube1.com?v=44678")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube1.com/disallow?v=44678")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube1.com/disallow?v=4467")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube1.com/disallow?v=4467&v=123")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(
+ GURL("http://youtube1.com/disallow?v=4467&v=123&v=44678")));
+
+ blocked.ClearList();
+ blocked.Append("youtube1.com/disallow?g=*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube1.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube1.com?ag=123")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube1.com/disallow?g=123")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube1.com/disallow?ag=13&g=123")));
+
+ blocked.ClearList();
+ blocked.Append("youtube2.com/disallow?a*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube2.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(
+ GURL("http://youtube2.com/disallow?b=123&a21=467")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube2.com/disallow?abba=true")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube2.com/disallow?baba=true")));
+
+ allowed.ClearList();
+ blocked.ClearList();
+ blocked.Append("youtube3.com");
+ allowed.Append("youtube3.com/watch?fo*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube3.com")));
+ EXPECT_TRUE(
+ blocklist.IsURLBlocked(GURL("http://youtube3.com/watch?b=123&a21=467")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube3.com/watch?b=123&a21=467&foo1")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube3.com/watch?b=123&a21=467&foo=bar")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube3.com/watch?b=123&a21=467&fo=ba")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("http://youtube3.com/watch?foreign=true")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube3.com/watch?fold")));
+
+ allowed.ClearList();
+ blocked.ClearList();
+ blocked.Append("youtube4.com");
+ allowed.Append("youtube4.com?*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube4.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube4.com/?hello")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube4.com/?foo")));
+
+ allowed.ClearList();
+ blocked.ClearList();
+ blocked.Append("youtube5.com?foo=bar");
+ allowed.Append("youtube5.com?foo1=bar1&foo2=bar2&");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://youtube5.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://youtube5.com/?foo=bar&a=b")));
+ // More specific filter is given precedence.
+ EXPECT_FALSE(blocklist.IsURLBlocked(
+ GURL("http://youtube5.com/?a=b&foo=bar&foo1=bar1&foo2=bar2")));
+}
+
+TEST_F(URLBlocklistManagerTest, BlockAllWithExceptions) {
+ URLBlocklist blocklist;
+
+ base::Value blocked(base::Value::Type::LIST);
+ base::Value allowed(base::Value::Type::LIST);
+ blocked.Append("*");
+ allowed.Append(".www.google.com");
+ allowed.Append("plus.google.com");
+ allowed.Append("https://mail.google.com");
+ allowed.Append("https://very.safe/path");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://random.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://google.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://s.www.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://www.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://plus.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("http://s.plus.google.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://mail.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://mail.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://s.mail.google.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("https://very.safe/")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://very.safe/path")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("https://very.safe/path")));
+}
+
+TEST_F(URLBlocklistManagerTest, DefaultBlocklistExceptions) {
+ URLBlocklist blocklist;
+ base::Value blocked(base::Value::Type::LIST);
+
+ // Blocklist everything:
+ blocked.Append("*");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+
+ // Internal NTP and extension URLs are not blocked by the "*":
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.google.com")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("chrome-extension://xyz")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("chrome-search://most-visited/title.html")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("chrome-native://ntp")));
+#if BUILDFLAG(IS_IOS)
+ // Ensure that the NTP is not blocked on iOS by "*".
+ // TODO(crbug.com/1073291): On iOS, the NTP can not be blocked even by
+ // explicitly listing it as a blocked URL. This is due to the usage of
+ // "about:newtab" as its URL which is not recognized and filtered by the
+ // URLBlocklist code.
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("about:newtab")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("about://newtab/")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("chrome://newtab")));
+#endif
+
+ // Unless they are explicitly on the blocklist:
+ blocked.Append("chrome-extension://*");
+ base::Value allowed(base::Value::Type::LIST);
+ allowed.Append("chrome-extension://abc");
+ blocklist.Block(&base::Value::AsListValue(blocked));
+ blocklist.Allow(&base::Value::AsListValue(allowed));
+
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("http://www.google.com")));
+ EXPECT_TRUE(blocklist.IsURLBlocked(GURL("chrome-extension://xyz")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("chrome-extension://abc")));
+ EXPECT_FALSE(
+ blocklist.IsURLBlocked(GURL("chrome-search://most-visited/title.html")));
+ EXPECT_FALSE(blocklist.IsURLBlocked(GURL("chrome-native://ntp")));
+}
+
+TEST_F(URLBlocklistManagerTest, BlocklistBasicCoverage) {
+ // Tests to cover the documentation from
+ // http://www.chromium.org/administrators/url-blocklist-filter-format
+
+ // [scheme://][.]host[:port][/path][@query]
+ // Scheme can be http, https, ftp, chrome, etc. This field is optional, and
+ // must be followed by '://'.
+ EXPECT_TRUE(IsMatch("file://*", "file:///abc.txt"));
+ EXPECT_TRUE(IsMatch("file:*", "file:///usr/local/boot.txt"));
+ EXPECT_TRUE(IsMatch("https://*", "https:///abc.txt"));
+ EXPECT_TRUE(IsMatch("ftp://*", "ftp://ftp.txt"));
+ EXPECT_TRUE(IsMatch("chrome://*", "chrome:policy"));
+ EXPECT_TRUE(IsMatch("noscheme", "http://noscheme"));
+ // Filter custom schemes.
+ EXPECT_TRUE(IsMatch("custom://*", "custom://example_app"));
+ EXPECT_TRUE(IsMatch("custom:*", "custom:example2_app"));
+ EXPECT_FALSE(IsMatch("custom://*", "customs://example_apps"));
+ EXPECT_FALSE(IsMatch("custom://*", "cust*://example_ap"));
+ EXPECT_FALSE(IsMatch("custom://*", "ecustom:example_app"));
+ EXPECT_TRUE(IsMatch("custom://*", "custom:///abc.txt"));
+ // Tests for custom scheme patterns that are not supported.
+ EXPECT_FALSE(IsMatch("wrong://app", "wrong://app"));
+ EXPECT_FALSE(IsMatch("wrong ://*", "wrong ://app"));
+ EXPECT_FALSE(IsMatch(" wrong:*", " wrong://app"));
+
+ // Ommitting the scheme matches most standard schemes.
+ EXPECT_TRUE(IsMatch("example.com", "chrome:example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "chrome://example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "file://example.com/"));
+ EXPECT_TRUE(IsMatch("example.com", "ftp://example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "http://example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "https://example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "ws://example.com"));
+ EXPECT_TRUE(IsMatch("example.com", "wss://example.com"));
+
+ // Some schemes are not matched when the scheme is ommitted.
+ EXPECT_FALSE(IsMatch("example.com", "about://example.com"));
+ EXPECT_FALSE(IsMatch("example.com", "about:example.com"));
+ EXPECT_FALSE(IsMatch("example.com/*", "filesystem:///something"));
+ EXPECT_FALSE(IsMatch("example.com", "custom://example.com"));
+ EXPECT_FALSE(IsMatch("example", "custom://example"));
+ EXPECT_FALSE(IsMatch("example.com", "gopher://example.com"));
+
+ // An optional '.' (dot) can prefix the host field to disable subdomain
+ // matching, see below for details.
+ EXPECT_TRUE(IsMatch(".example.com", "http://example.com/path"));
+ EXPECT_FALSE(IsMatch(".example.com", "http://mail.example.com/path"));
+ EXPECT_TRUE(IsMatch("example.com", "http://mail.example.com/path"));
+ EXPECT_TRUE(IsMatch("ftp://.ftp.file", "ftp://ftp.file"));
+ EXPECT_FALSE(IsMatch("ftp://.ftp.file", "ftp://sub.ftp.file"));
+
+ // The host field is required, and is a valid hostname or an IP address. It
+ // can also take the special '*' value, see below for details.
+ EXPECT_TRUE(IsMatch("*", "http://anything"));
+ EXPECT_TRUE(IsMatch("*", "ftp://anything"));
+ EXPECT_TRUE(IsMatch("*", "custom://anything"));
+ EXPECT_TRUE(IsMatch("host", "http://host:8080"));
+ EXPECT_FALSE(IsMatch("host", "file:///host"));
+ EXPECT_TRUE(IsMatch("10.1.2.3", "http://10.1.2.3:8080/path"));
+ // No host, will match nothing.
+ EXPECT_FALSE(IsMatch(":8080", "http://host:8080"));
+ EXPECT_FALSE(IsMatch(":8080", "http://:8080"));
+
+ // An optional port can come after the host. It must be a valid port value
+ // from 1 to 65535.
+ EXPECT_TRUE(IsMatch("host:8080", "http://host:8080/path"));
+ EXPECT_TRUE(IsMatch("host:1", "http://host:1/path"));
+ // Out of range port.
+ EXPECT_FALSE(IsMatch("host:65536", "http://host:65536/path"));
+ // Star is not allowed in port numbers.
+ EXPECT_FALSE(IsMatch("example.com:*", "http://example.com"));
+ EXPECT_FALSE(IsMatch("example.com:*", "http://example.com:8888"));
+
+ // An optional path can come after port.
+ EXPECT_TRUE(IsMatch("host/path", "http://host:8080/path"));
+ EXPECT_TRUE(IsMatch("host/path/path2", "http://host/path/path2"));
+ EXPECT_TRUE(IsMatch("host/path", "http://host/path/path2"));
+
+ // An optional query can come in the end, which is a set of key-value and
+ // key-only tokens delimited by '&'. The key-value tokens are separated
+ // by '='. A query token can optionally end with a '*' to indicate prefix
+ // match. Token order is ignored during matching.
+ EXPECT_TRUE(IsMatch("host?q1=1&q2=2", "http://host?q2=2&q1=1"));
+ EXPECT_FALSE(IsMatch("host?q1=1&q2=2", "http://host?q2=1&q1=2"));
+ EXPECT_FALSE(IsMatch("host?q1=1&q2=2", "http://host?Q2=2&Q1=1"));
+ EXPECT_TRUE(IsMatch("host?q1=1&q2=2", "http://host?q2=2&q1=1&q3=3"));
+ EXPECT_TRUE(IsMatch("host?q1=1&q2=2*", "http://host?q2=21&q1=1&q3=3"));
+
+ // user:pass fields can be included but will be ignored
+ // (e.g. http://user:pass@ftp.example.com/pub/bigfile.iso).
+ EXPECT_TRUE(IsMatch("host.com/path", "http://user:pass@host.com:8080/path"));
+ EXPECT_TRUE(
+ IsMatch("ftp://host.com/path", "ftp://user:pass@host.com:8080/path"));
+
+ // Case sensitivity.
+ // Scheme is case insensitive.
+ EXPECT_TRUE(IsMatch("suPPort://*", "support:example"));
+ EXPECT_TRUE(IsMatch("FILE://*", "file:example"));
+ EXPECT_TRUE(IsMatch("FILE://*", "FILE://example"));
+ EXPECT_TRUE(IsMatch("FtP:*", "ftp://example"));
+ EXPECT_TRUE(IsMatch("http://example.com", "HTTP://example.com"));
+ EXPECT_TRUE(IsMatch("HTTP://example.com", "http://example.com"));
+ // Host is case insensitive.
+ EXPECT_TRUE(IsMatch("http://EXAMPLE.COM", "http://example.com"));
+ EXPECT_TRUE(IsMatch("Example.com", "http://examplE.com/Path?Query=1"));
+ // Path is case sensitive.
+ EXPECT_FALSE(IsMatch("example.com/Path", "http://example.com/path"));
+ EXPECT_TRUE(IsMatch("http://example.com/aB", "http://example.com/aB"));
+ EXPECT_FALSE(IsMatch("http://example.com/aB", "http://example.com/Ab"));
+ EXPECT_FALSE(IsMatch("http://example.com/aB", "http://example.com/ab"));
+ EXPECT_FALSE(IsMatch("http://example.com/aB", "http://example.com/AB"));
+ // Query is case sensitive.
+ EXPECT_FALSE(IsMatch("host/path?Query=1", "http://host/path?query=1"));
+}
+
+// Test for GetURLBlocklistState method.
+TEST_F(URLBlocklistManagerTest, UseBlocklistState) {
+ const policy::URLBlocklist::URLBlocklistState in_blocklist =
+ policy::URLBlocklist::URLBlocklistState::URL_IN_BLOCKLIST;
+ const policy::URLBlocklist::URLBlocklistState in_allowlist =
+ policy::URLBlocklist::URLBlocklistState::URL_IN_ALLOWLIST;
+ const policy::URLBlocklist::URLBlocklistState neutral_state =
+ policy::URLBlocklist::URLBlocklistState::URL_NEUTRAL_STATE;
+
+ // Test allowlist states.
+ EXPECT_EQ(in_allowlist, GetMatch("example.com", "http://example.com", true));
+ EXPECT_EQ(in_allowlist, GetMatch("http://*", "http://example.com", true));
+ EXPECT_EQ(in_allowlist, GetMatch("custom://*", "custom://app", true));
+ EXPECT_EQ(in_allowlist, GetMatch("custom:*", "custom://app/play", true));
+ EXPECT_EQ(in_allowlist, GetMatch("custom:*", "custom://app:8080", true));
+ // Test blocklist states.
+ EXPECT_EQ(in_blocklist, GetMatch("ftp:*", "ftp://server", false));
+ // Test neutral states.
+ EXPECT_EQ(neutral_state, GetMatch("file:*", "http://example.com", true));
+ EXPECT_EQ(neutral_state, GetMatch("https://*", "http://example.com", false));
+}
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_blocklist_policy_handler.cc b/chromium/components/policy/core/browser/url_blocklist_policy_handler.cc
new file mode 100644
index 00000000000..d701df8642c
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_policy_handler.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_blocklist_policy_handler.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+URLBlocklistPolicyHandler::URLBlocklistPolicyHandler(const char* policy_name)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST) {}
+
+URLBlocklistPolicyHandler::~URLBlocklistPolicyHandler() = default;
+
+bool URLBlocklistPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ size_t disabled_schemes_entries = 0;
+ // It is safe to use `GetValueUnsafe()` because type checking is performed
+ // before the value is used.
+ // This policy is deprecated but still supported so check it first.
+ const base::Value* disabled_schemes =
+ policies.GetValueUnsafe(key::kDisabledSchemes);
+ if (disabled_schemes) {
+ if (!disabled_schemes->is_list()) {
+ errors->AddError(key::kDisabledSchemes, IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::LIST));
+ } else {
+ disabled_schemes_entries = disabled_schemes->GetListDeprecated().size();
+ }
+ }
+
+ if (!policies.IsPolicySet(policy_name()))
+ return true;
+ const base::Value* url_blocklist =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+
+ if (!url_blocklist) {
+ errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::LIST));
+
+ return true;
+ }
+
+ // Filters more than |url_util::kMaxFiltersPerPolicy| are ignored, add a
+ // warning message.
+ if (url_blocklist->GetListDeprecated().size() + disabled_schemes_entries >
+ kMaxUrlFiltersPerPolicy) {
+ errors->AddError(policy_name(),
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString(kMaxUrlFiltersPerPolicy));
+ }
+
+ bool type_error = false;
+ std::string policy;
+ std::vector<std::string> invalid_policies;
+ for (const auto& policy_iter : url_blocklist->GetListDeprecated()) {
+ if (!policy_iter.is_string()) {
+ type_error = true;
+ continue;
+ }
+
+ policy = policy_iter.GetString();
+ if (!ValidatePolicy(policy))
+ invalid_policies.push_back(policy);
+ }
+
+ if (type_error) {
+ errors->AddError(policy_name(), IDS_POLICY_TYPE_ERROR,
+ base::Value::GetTypeName(base::Value::Type::STRING));
+ }
+
+ if (invalid_policies.size()) {
+ errors->AddError(policy_name(), IDS_POLICY_PROTO_PARSING_ERROR,
+ base::JoinString(invalid_policies, ","));
+ }
+
+ return true;
+}
+
+void URLBlocklistPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ const base::Value* url_blocklist_policy =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ const base::Value* disabled_schemes_policy =
+ policies.GetValue(key::kDisabledSchemes, base::Value::Type::LIST);
+
+ absl::optional<std::vector<base::Value>> merged_url_blocklist;
+
+ // We start with the DisabledSchemes because we have size limit when
+ // handling URLBlocklists.
+ if (disabled_schemes_policy) {
+ merged_url_blocklist = std::vector<base::Value>();
+ for (const auto& entry : disabled_schemes_policy->GetListDeprecated()) {
+ if (entry.is_string()) {
+ merged_url_blocklist->emplace_back(
+ base::StrCat({entry.GetString(), "://*"}));
+ }
+ }
+ }
+
+ if (url_blocklist_policy) {
+ if (!merged_url_blocklist)
+ merged_url_blocklist = std::vector<base::Value>();
+
+ for (const auto& entry : url_blocklist_policy->GetListDeprecated()) {
+ if (entry.is_string())
+ merged_url_blocklist->push_back(entry.Clone());
+ }
+ }
+
+ if (merged_url_blocklist) {
+ prefs->SetValue(policy_prefs::kUrlBlocklist,
+ base::Value(std::move(merged_url_blocklist.value())));
+ }
+}
+
+bool URLBlocklistPolicyHandler::ValidatePolicy(const std::string& policy) {
+ url_matcher::util::FilterComponents components;
+ return url_matcher::util::FilterToComponents(
+ policy, &components.scheme, &components.host,
+ &components.match_subdomains, &components.port, &components.path,
+ &components.query);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_blocklist_policy_handler.h b/chromium/components/policy/core/browser/url_blocklist_policy_handler.h
new file mode 100644
index 00000000000..afd8bed5cd0
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_policy_handler.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_POLICY_HANDLER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_POLICY_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Possible values for kSafeSitesFilterBehavior pref from policy. Values must
+// coincide with SafeSitesFilterBehavior from policy_templates.json.
+enum class SafeSitesFilterBehavior {
+ kSafeSitesFilterDisabled = 0,
+ kSafeSitesFilterEnabled = 1,
+};
+
+// Handles URLBlocklist policies.
+class POLICY_EXPORT URLBlocklistPolicyHandler
+ : public TypeCheckingPolicyHandler {
+ public:
+ explicit URLBlocklistPolicyHandler(const char* policy_name);
+ URLBlocklistPolicyHandler(const URLBlocklistPolicyHandler&) = delete;
+ URLBlocklistPolicyHandler& operator=(const URLBlocklistPolicyHandler&) =
+ delete;
+ ~URLBlocklistPolicyHandler() override;
+
+ // Validates that policy follows official pattern
+ // https://www.chromium.org/administrators/url-blocklist-filter-format
+ bool ValidatePolicy(const std::string& policy);
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_URL_BLOCKLIST_POLICY_HANDLER_H_
diff --git a/chromium/components/policy/core/browser/url_blocklist_policy_handler_unittest.cc b/chromium/components/policy/core/browser/url_blocklist_policy_handler_unittest.cc
new file mode 100644
index 00000000000..b693f460dd2
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_blocklist_policy_handler_unittest.cc
@@ -0,0 +1,317 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_blocklist_policy_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+// Note: this file should move to components/policy/core/browser, but the
+// components_unittests runner does not load the ResourceBundle as
+// ChromeTestSuite::Initialize does, which leads to failures using
+// PolicyErrorMap.
+
+namespace policy {
+
+namespace {
+
+const char kTestDisabledScheme[] = "kTestDisabledScheme";
+const char kTestBlocklistValue[] = "kTestBlocklistValue";
+
+} // namespace
+
+class URLBlocklistPolicyHandlerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ handler_ = std::make_unique<URLBlocklistPolicyHandler>(key::kURLBlocklist);
+ }
+
+ protected:
+ void SetPolicy(const std::string& key, base::Value value) {
+ policies_.Set(key, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(value), nullptr);
+ }
+ bool CheckPolicy(const std::string& key, base::Value value) {
+ SetPolicy(key, std::move(value));
+ return handler_->CheckPolicySettings(policies_, &errors_);
+ }
+ void ApplyPolicies() { handler_->ApplyPolicySettings(policies_, &prefs_); }
+ bool ValidatePolicy(const std::string& policy) {
+ return handler_->ValidatePolicy(policy);
+ }
+ base::Value GetURLBlocklistPolicyValueWithEntries(size_t len) {
+ std::vector<base::Value> blocklist(len);
+ for (auto& entry : blocklist)
+ entry = base::Value(kTestBlocklistValue);
+ return base::Value(std::move(blocklist));
+ }
+
+ std::unique_ptr<URLBlocklistPolicyHandler> handler_;
+ PolicyErrorMap errors_;
+ PolicyMap policies_;
+ PrefValueMap prefs_;
+};
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ CheckPolicySettings_DisabledSchemesUnspecified) {
+ EXPECT_TRUE(
+ CheckPolicy(key::kURLBlocklist, base::Value(base::Value::Type::LIST)));
+ EXPECT_EQ(0U, errors_.size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ CheckPolicySettings_URLBlocklistUnspecified) {
+ EXPECT_TRUE(
+ CheckPolicy(key::kDisabledSchemes, base::Value(base::Value::Type::LIST)));
+ EXPECT_EQ(0U, errors_.size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ CheckPolicySettings_DisabledSchemesWrongType) {
+ // The policy expects a list. Give it a boolean.
+ EXPECT_TRUE(CheckPolicy(key::kDisabledSchemes, base::Value(false)));
+ EXPECT_EQ(1U, errors_.size());
+ const std::string expected = key::kDisabledSchemes;
+ const std::string actual = errors_.begin()->first;
+ EXPECT_EQ(expected, actual);
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ CheckPolicySettings_URLBlocklistWrongType) {
+ // The policy expects a list. Give it a boolean.
+ EXPECT_TRUE(CheckPolicy(key::kURLBlocklist, base::Value(false)));
+ EXPECT_EQ(1U, errors_.size());
+ const std::string expected = key::kURLBlocklist;
+ const std::string actual = errors_.begin()->first;
+ EXPECT_EQ(expected, actual);
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest, ApplyPolicySettings_NothingSpecified) {
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(policy_prefs::kUrlBlocklist, nullptr));
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_DisabledSchemesWrongType) {
+ // The policy expects a list. Give it a boolean.
+ SetPolicy(key::kDisabledSchemes, base::Value(false));
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(policy_prefs::kUrlBlocklist, nullptr));
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_URLBlocklistWrongType) {
+ // The policy expects a list. Give it a boolean.
+ SetPolicy(key::kURLBlocklist, base::Value(false));
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(policy_prefs::kUrlBlocklist, nullptr));
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_DisabledSchemesEmpty) {
+ SetPolicy(key::kDisabledSchemes, base::Value(base::Value::Type::LIST));
+ ApplyPolicies();
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest, ApplyPolicySettings_URLBlocklistEmpty) {
+ SetPolicy(key::kURLBlocklist, base::Value(base::Value::Type::LIST));
+ ApplyPolicies();
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_DisabledSchemesWrongElementType) {
+ // The policy expects string-valued elements. Give it booleans.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(false);
+ SetPolicy(key::kDisabledSchemes, std::move(in));
+ ApplyPolicies();
+
+ // The element should be skipped.
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_URLBlocklistWrongElementType) {
+ // The policy expects string-valued elements. Give it booleans.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(false);
+ SetPolicy(key::kURLBlocklist, std::move(in));
+ ApplyPolicies();
+
+ // The element should be skipped.
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(0U, out->GetListDeprecated().size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_DisabledSchemesSuccessful) {
+ base::Value in_disabled_schemes(base::Value::Type::LIST);
+ in_disabled_schemes.Append(kTestDisabledScheme);
+ SetPolicy(key::kDisabledSchemes, std::move(in_disabled_schemes));
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestDisabledScheme + std::string("://*"), *out_string);
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_URLBlocklistSuccessful) {
+ base::Value in_url_blocklist(base::Value::Type::LIST);
+ in_url_blocklist.Append(kTestBlocklistValue);
+ SetPolicy(key::kURLBlocklist, std::move(in_url_blocklist));
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestBlocklistValue, *out_string);
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest, ApplyPolicySettings_MergeSuccessful) {
+ base::Value in_disabled_schemes(base::Value::Type::LIST);
+ in_disabled_schemes.Append(kTestDisabledScheme);
+ SetPolicy(key::kDisabledSchemes, std::move(in_disabled_schemes));
+
+ base::Value in_url_blocklist(base::Value::Type::LIST);
+ in_url_blocklist.Append(kTestBlocklistValue);
+ SetPolicy(key::kURLBlocklist, std::move(in_url_blocklist));
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ ASSERT_EQ(2U, out->GetListDeprecated().size());
+
+ const std::string* out_string1 = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string1);
+ EXPECT_EQ(kTestDisabledScheme + std::string("://*"), *out_string1);
+
+ const std::string* out_string2 = out->GetListDeprecated()[1].GetIfString();
+ ASSERT_TRUE(out_string2);
+ EXPECT_EQ(kTestBlocklistValue, *out_string2);
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitOK) {
+ size_t max_filters_per_policy = policy::kMaxUrlFiltersPerPolicy;
+ base::Value urls =
+ GetURLBlocklistPolicyValueWithEntries(max_filters_per_policy);
+
+ EXPECT_TRUE(CheckPolicy(key::kURLBlocklist, std::move(urls)));
+ EXPECT_EQ(0U, errors_.size());
+
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(max_filters_per_policy, out->GetListDeprecated().size());
+}
+
+// Test that the warning message, mapped to
+// |IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING|, is added to
+// |errors_| when URLBlocklist entries exceed the max filters per policy limit.
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded_1) {
+ size_t max_filters_per_policy = policy::kMaxUrlFiltersPerPolicy;
+ base::Value urls =
+ GetURLBlocklistPolicyValueWithEntries(max_filters_per_policy + 1);
+
+ EXPECT_TRUE(CheckPolicy(key::kURLBlocklist, std::move(urls)));
+ EXPECT_EQ(1U, errors_.size());
+
+ ApplyPolicies();
+
+ auto error_str = errors_.GetErrors(key::kURLBlocklist);
+ auto expected_str = l10n_util::GetStringFUTF16(
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString16(max_filters_per_policy));
+ EXPECT_TRUE(error_str.find(expected_str) != std::wstring::npos);
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(max_filters_per_policy + 1, out->GetListDeprecated().size());
+}
+
+// Test that the warning message, mapped to
+// |IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING|, is added to
+// |errors_| when URLBlocklist + DisabledScheme entries exceed the max filters
+// per policy limit.
+TEST_F(URLBlocklistPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded_2) {
+ base::Value in_disabled_schemes(base::Value::Type::LIST);
+ in_disabled_schemes.Append(kTestDisabledScheme);
+ SetPolicy(key::kDisabledSchemes, std::move(in_disabled_schemes));
+
+ size_t max_filters_per_policy = policy::kMaxUrlFiltersPerPolicy;
+ base::Value urls =
+ GetURLBlocklistPolicyValueWithEntries(max_filters_per_policy);
+
+ EXPECT_TRUE(CheckPolicy(key::kURLBlocklist, std::move(urls)));
+ EXPECT_EQ(1U, errors_.size());
+
+ ApplyPolicies();
+
+ auto error_str = errors_.GetErrors(key::kURLBlocklist);
+ auto expected_str = l10n_util::GetStringFUTF16(
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString16(max_filters_per_policy));
+ EXPECT_TRUE(error_str.find(expected_str) != std::wstring::npos);
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(policy_prefs::kUrlBlocklist, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(max_filters_per_policy + 1, out->GetListDeprecated().size());
+}
+
+TEST_F(URLBlocklistPolicyHandlerTest, ValidatePolicy) {
+ EXPECT_TRUE(ValidatePolicy("http://*"));
+ EXPECT_TRUE(ValidatePolicy("http:*"));
+
+ EXPECT_TRUE(ValidatePolicy("ws://example.org/component.js"));
+ EXPECT_FALSE(ValidatePolicy("wsgi:///rancom,org/"));
+
+ EXPECT_TRUE(ValidatePolicy("127.0.0.1:1"));
+ EXPECT_TRUE(ValidatePolicy("127.0.0.1:65535"));
+ EXPECT_FALSE(ValidatePolicy("127.0.0.1:65536"));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_scheme_list_policy_handler.cc b/chromium/components/policy/core/browser/url_scheme_list_policy_handler.cc
new file mode 100644
index 00000000000..5b6f4dc67e4
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_scheme_list_policy_handler.cc
@@ -0,0 +1,93 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_scheme_list_policy_handler.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+
+namespace policy {
+
+URLSchemeListPolicyHandler::URLSchemeListPolicyHandler(const char* policy_name,
+ const char* pref_path)
+ : TypeCheckingPolicyHandler(policy_name, base::Value::Type::LIST),
+ pref_path_(pref_path) {}
+
+URLSchemeListPolicyHandler::~URLSchemeListPolicyHandler() = default;
+
+bool URLSchemeListPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) {
+ if (!TypeCheckingPolicyHandler::CheckPolicySettings(policies, errors))
+ return false;
+
+ const base::Value* schemes =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ if (!schemes || schemes->GetListDeprecated().empty())
+ return true;
+
+ // Filters more than |url_util::kMaxFiltersPerPolicy| are ignored, add a
+ // warning message.
+ if (schemes->GetListDeprecated().size() > policy::kMaxUrlFiltersPerPolicy) {
+ errors->AddError(policy_name(),
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString(policy::kMaxUrlFiltersPerPolicy));
+ }
+
+ std::vector<std::string> invalid_policies;
+ for (const auto& entry : schemes->GetListDeprecated()) {
+ if (!ValidatePolicyEntry(entry.GetIfString()))
+ invalid_policies.push_back(entry.GetString());
+ }
+
+ if (!invalid_policies.empty()) {
+ errors->AddError(policy_name(), IDS_POLICY_PROTO_PARSING_ERROR,
+ base::JoinString(invalid_policies, ","));
+ }
+
+ return invalid_policies.size() < schemes->GetListDeprecated().size();
+}
+
+void URLSchemeListPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) {
+ const base::Value* schemes =
+ policies.GetValue(policy_name(), base::Value::Type::LIST);
+ if (!schemes)
+ return;
+ std::vector<base::Value> filtered_schemes;
+ for (const auto& entry : schemes->GetListDeprecated()) {
+ if (ValidatePolicyEntry(entry.GetIfString()))
+ filtered_schemes.push_back(entry.Clone());
+ }
+ if (filtered_schemes.size() > policy::kMaxUrlFiltersPerPolicy)
+ filtered_schemes.resize(policy::kMaxUrlFiltersPerPolicy);
+
+ prefs->SetValue(pref_path_, base::Value(std::move(filtered_schemes)));
+}
+
+// Validates that policy follows official pattern
+// https://www.chromium.org/administrators/url-blocklist-filter-format
+bool URLSchemeListPolicyHandler::ValidatePolicyEntry(
+ const std::string* policy) {
+ url_matcher::util::FilterComponents components;
+ return policy && url_matcher::util::FilterToComponents(
+ *policy, &components.scheme, &components.host,
+ &components.match_subdomains, &components.port,
+ &components.path, &components.query);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/url_scheme_list_policy_handler.h b/chromium/components/policy/core/browser/url_scheme_list_policy_handler.h
new file mode 100644
index 00000000000..74329f149f0
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_scheme_list_policy_handler.h
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_URL_SCHEME_LIST_POLICY_HANDLER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_URL_SCHEME_LIST_POLICY_HANDLER_H_
+
+#include "base/compiler_specific.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Maps policy to pref like TypeCheckingPolicyHandler while ensuring that the
+// value is a list of urls that follow the url format which documented at
+// http://www.chromium.org/administrators/url-blocklist-filter-format
+class POLICY_EXPORT URLSchemeListPolicyHandler
+ : public TypeCheckingPolicyHandler {
+ public:
+ URLSchemeListPolicyHandler(const char* policy_name, const char* pref_path);
+ URLSchemeListPolicyHandler(const URLSchemeListPolicyHandler&) = delete;
+ URLSchemeListPolicyHandler& operator=(const URLSchemeListPolicyHandler&) =
+ delete;
+ ~URLSchemeListPolicyHandler() override;
+
+ // ConfigurationPolicyHandler methods:
+ bool CheckPolicySettings(const PolicyMap& policies,
+ PolicyErrorMap* errors) override;
+ void ApplyPolicySettings(const PolicyMap& policies,
+ PrefValueMap* prefs) override;
+
+ private:
+ bool ValidatePolicyEntry(const std::string* policy);
+ const char* pref_path_;
+
+ FRIEND_TEST_ALL_PREFIXES(URLSchemeListPolicyHandlerTest, ValidatePolicyEntry);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_URL_SCHEME_LIST_POLICY_HANDLER_H_
diff --git a/chromium/components/policy/core/browser/url_scheme_list_policy_handler_unittest.cc b/chromium/components/policy/core/browser/url_scheme_list_policy_handler_unittest.cc
new file mode 100644
index 00000000000..619921018ee
--- /dev/null
+++ b/chromium/components/policy/core/browser/url_scheme_list_policy_handler_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/url_scheme_list_policy_handler.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_matcher/url_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestPolicyName[] = "kTestPolicyName";
+const char kTestPrefName[] = "kTestPrefName";
+const char kTestUrl[] = "https://www.example.com";
+const char kNotAUrl[] = "htttps:///abce.NotAUrl..fgh";
+
+} // namespace
+
+class URLSchemeListPolicyHandlerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ std::string error;
+ DCHECK(error.empty());
+ handler_ = std::make_unique<URLSchemeListPolicyHandler>(kTestPolicyName,
+ kTestPrefName);
+ }
+
+ protected:
+ void SetPolicy(const std::string& key, base::Value value) {
+ policies_.Set(key, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(value), nullptr);
+ }
+ bool CheckPolicy(const std::string& key, absl::optional<base::Value> value) {
+ if (value)
+ SetPolicy(key, value.value().Clone());
+ return handler_->CheckPolicySettings(policies_, &errors_);
+ }
+ void ApplyPolicies() { handler_->ApplyPolicySettings(policies_, &prefs_); }
+ base::Value GetPolicyValueWithEntries(size_t len) {
+ std::vector<base::Value> blocklist(len);
+ for (auto& entry : blocklist)
+ entry = base::Value(kTestUrl);
+ return base::Value(std::move(blocklist));
+ }
+
+ std::unique_ptr<URLSchemeListPolicyHandler> handler_;
+ PolicyErrorMap errors_;
+ PolicyMap policies_;
+ PrefValueMap prefs_;
+};
+
+TEST_F(URLSchemeListPolicyHandlerTest, CheckPolicySettings_EmptyPolicy) {
+ EXPECT_TRUE(
+ CheckPolicy(kTestPolicyName, base::Value(base::Value::Type::LIST)));
+ EXPECT_TRUE(errors_.empty());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, CheckPolicySettings_WrongType) {
+ // The policy expects a list. Give it a boolean.
+ EXPECT_FALSE(CheckPolicy(kTestPolicyName, base::Value(false)));
+ EXPECT_EQ(1U, errors_.size());
+ const std::string expected = kTestPolicyName;
+ const std::string actual = errors_.begin()->first;
+ EXPECT_EQ(expected, actual);
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, CheckPolicySettings_NoPolicy) {
+ // The policy expects a list. Give it a boolean.
+ EXPECT_TRUE(CheckPolicy(kTestPolicyName, absl::nullopt));
+ EXPECT_TRUE(errors_.empty());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, CheckPolicySettings_OneBadValue) {
+ // The policy expects a list. Give it a boolean.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(kNotAUrl);
+ in.Append(kTestUrl);
+ EXPECT_TRUE(CheckPolicy(kTestPolicyName, std::move(in)));
+ EXPECT_EQ(1U, errors_.size());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, CheckPolicySettings_SingleBadValue) {
+ // The policy expects a list. Give it a boolean.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(kNotAUrl);
+ EXPECT_FALSE(CheckPolicy(kTestPolicyName, std::move(in)));
+ EXPECT_EQ(1U, errors_.size());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_NothingSpecified) {
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(kTestPolicyName, nullptr));
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_WrongType) {
+ // The policy expects a list. Give it a boolean.
+ SetPolicy(kTestPolicyName, base::Value(false));
+ ApplyPolicies();
+ EXPECT_FALSE(prefs_.GetValue(kTestPrefName, nullptr));
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_Empty) {
+ SetPolicy(kTestPolicyName, base::Value(base::Value::Type::LIST));
+ ApplyPolicies();
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_TRUE(out->GetListDeprecated().empty());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_WrongElementType) {
+ // The policy expects string-valued elements. Give it booleans.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(kNotAUrl);
+ in.Append(kTestUrl);
+ SetPolicy(kTestPolicyName, std::move(in));
+ ApplyPolicies();
+
+ // The element should be skipped.
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestUrl, *out_string);
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_BadUrl) {
+ // The policy expects a gvalid url schema.
+ base::Value in(base::Value::Type::LIST);
+ in.Append(false);
+ in.Append(kTestUrl);
+ SetPolicy(kTestPolicyName, std::move(in));
+ ApplyPolicies();
+
+ // The element should be skipped.
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestUrl, *out_string);
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ApplyPolicySettings_Successful) {
+ base::Value in_url_blocklist(base::Value::Type::LIST);
+ in_url_blocklist.Append(kTestUrl);
+ SetPolicy(kTestPolicyName, std::move(in_url_blocklist));
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(1U, out->GetListDeprecated().size());
+
+ const std::string* out_string = out->GetListDeprecated()[0].GetIfString();
+ ASSERT_TRUE(out_string);
+ EXPECT_EQ(kTestUrl, *out_string);
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitOK) {
+ base::Value urls = GetPolicyValueWithEntries(policy::kMaxUrlFiltersPerPolicy);
+
+ EXPECT_TRUE(CheckPolicy(kTestPolicyName, std::move(urls)));
+ EXPECT_TRUE(errors_.empty());
+
+ ApplyPolicies();
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(policy::kMaxUrlFiltersPerPolicy, out->GetListDeprecated().size());
+}
+
+// Test that the warning message, mapped to
+// |IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING|, is added to
+// |errors_| when URLBlocklist entries exceed the max filters per policy limit.
+TEST_F(URLSchemeListPolicyHandlerTest,
+ ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded) {
+ base::Value urls =
+ GetPolicyValueWithEntries(policy::kMaxUrlFiltersPerPolicy + 1);
+
+ EXPECT_TRUE(CheckPolicy(kTestPolicyName, std::move(urls)));
+ EXPECT_EQ(1U, errors_.size());
+
+ ApplyPolicies();
+
+ auto error_str = errors_.GetErrors(kTestPolicyName);
+ auto expected_str = l10n_util::GetStringFUTF16(
+ IDS_POLICY_URL_ALLOW_BLOCK_LIST_MAX_FILTERS_LIMIT_WARNING,
+ base::NumberToString16(policy::kMaxUrlFiltersPerPolicy));
+ EXPECT_TRUE(error_str.find(expected_str) != std::wstring::npos);
+
+ base::Value* out;
+ EXPECT_TRUE(prefs_.GetValue(kTestPrefName, &out));
+ ASSERT_TRUE(out->is_list());
+ EXPECT_EQ(policy::kMaxUrlFiltersPerPolicy, out->GetListDeprecated().size());
+}
+
+TEST_F(URLSchemeListPolicyHandlerTest, ValidatePolicyEntry) {
+ std::vector<std::string> good{"http://*", "http:*",
+ "ws://example.org/component.js", "127.0.0.1:1",
+ "127.0.0.1:65535"};
+ for (const auto& it : good)
+ EXPECT_TRUE(handler_->ValidatePolicyEntry(&it));
+
+ std::vector<std::string> bad{"wsgi:///rancom,org/", "127.0.0.1:65536"};
+ for (const auto& it : bad)
+ EXPECT_FALSE(handler_->ValidatePolicyEntry(&it));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/webui/json_generation.cc b/chromium/components/policy/core/browser/webui/json_generation.cc
new file mode 100644
index 00000000000..994a3a3908e
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/json_generation.cc
@@ -0,0 +1,66 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/webui/json_generation.h"
+
+#include <memory>
+
+#include "base/json/json_writer.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/policy/core/browser/policy_conversions.h"
+#include "components/policy/core/browser/policy_conversions_client.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/version_info/version_info.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+JsonGenerationParams::JsonGenerationParams() = default;
+JsonGenerationParams::~JsonGenerationParams() = default;
+
+std::string GenerateJson(std::unique_ptr<PolicyConversionsClient> client,
+ base::Value status,
+ const JsonGenerationParams& params) {
+ base::Value chrome_metadata(base::Value::Type::DICTIONARY);
+ chrome_metadata.SetKey("application", base::Value(params.application_name));
+
+ std::string version = base::StringPrintf(
+ "%s (%s)%s %s%s", version_info::GetVersionNumber().c_str(),
+ l10n_util::GetStringUTF8(version_info::IsOfficialBuild()
+ ? IDS_VERSION_UI_OFFICIAL
+ : IDS_VERSION_UI_UNOFFICIAL)
+ .c_str(),
+ (params.channel_name.empty() ? "" : " " + params.channel_name).c_str(),
+ params.processor_variation.c_str(),
+ params.cohort_name ? params.cohort_name->c_str() : "");
+
+ chrome_metadata.SetKey("version", base::Value(version));
+
+ if (params.os_name && !params.os_name->empty()) {
+ chrome_metadata.SetKey("OS", base::Value(params.os_name.value()));
+ }
+
+ if (params.platform_name && !params.platform_name->empty()) {
+ chrome_metadata.SetKey("platform",
+ base::Value(params.platform_name.value()));
+ }
+
+ chrome_metadata.SetKey("revision",
+ base::Value(version_info::GetLastChange()));
+
+ base::Value dict =
+ policy::DictionaryPolicyConversions(std::move(client)).ToValue();
+
+ dict.SetKey("chromeMetadata", std::move(chrome_metadata));
+ dict.SetKey("status", std::move(status));
+
+ std::string json_policies;
+ base::JSONWriter::WriteWithOptions(
+ dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_policies);
+
+ return json_policies;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/webui/json_generation.h b/chromium/components/policy/core/browser/webui/json_generation.h
new file mode 100644
index 00000000000..3bdcabe2780
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/json_generation.h
@@ -0,0 +1,78 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_WEBUI_JSON_GENERATION_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_WEBUI_JSON_GENERATION_H_
+
+#include <string>
+
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+class PolicyConversionsClient;
+
+// Simple object containing parameters used to generate a string of JSON from
+// a set of policies.
+struct POLICY_EXPORT JsonGenerationParams {
+ explicit JsonGenerationParams();
+ ~JsonGenerationParams();
+
+ JsonGenerationParams& with_application_name(
+ const std::string& other_application_name) {
+ application_name = other_application_name;
+ return *this;
+ }
+
+ JsonGenerationParams& with_channel_name(
+ const std::string& other_channel_name) {
+ channel_name = other_channel_name;
+ return *this;
+ }
+
+ JsonGenerationParams& with_processor_variation(
+ const std::string& other_processor_variation) {
+ processor_variation = other_processor_variation;
+ return *this;
+ }
+
+ JsonGenerationParams& with_cohort_name(const std::string& other_cohort_name) {
+ cohort_name = other_cohort_name;
+ return *this;
+ }
+
+ JsonGenerationParams& with_os_name(const std::string& other_os_name) {
+ os_name = other_os_name;
+ return *this;
+ }
+
+ JsonGenerationParams& with_platform_name(
+ const std::string& other_platform_name) {
+ platform_name = other_platform_name;
+ return *this;
+ }
+
+ std::string application_name;
+ std::string channel_name;
+ std::string processor_variation;
+ absl::optional<std::string> cohort_name;
+ absl::optional<std::string> os_name;
+ absl::optional<std::string> platform_name;
+};
+
+// Generates a string of JSON containing the currently applied policies along
+// with additional metadata about the current device/build, based both on what
+// is stored in |params| and also information that is statically available.
+POLICY_EXPORT std::string GenerateJson(
+ std::unique_ptr<PolicyConversionsClient> client,
+ base::Value status,
+ const JsonGenerationParams& params);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_WEBUI_JSON_GENERATION_H_
diff --git a/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc b/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc
new file mode 100644
index 00000000000..95ad65c435c
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h"
+
+#include <string>
+
+#include "base/i18n/time_formatting.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "ui/base/l10n/time_format.h"
+
+namespace policy {
+
+MachineLevelUserCloudPolicyStatusProvider::
+ MachineLevelUserCloudPolicyStatusProvider(
+ CloudPolicyCore* core,
+ MachineLevelUserCloudPolicyContext* context)
+ : core_(core), context_(context) {
+ if (core_->store())
+ core_->store()->AddObserver(this);
+}
+
+MachineLevelUserCloudPolicyStatusProvider::
+ ~MachineLevelUserCloudPolicyStatusProvider() {
+ if (core_->store())
+ core_->store()->RemoveObserver(this);
+}
+
+void MachineLevelUserCloudPolicyStatusProvider::GetStatus(
+ base::DictionaryValue* dict) {
+ CloudPolicyRefreshScheduler* refresh_scheduler = core_->refresh_scheduler();
+
+ dict->SetStringKey(
+ "refreshInterval",
+ ui::TimeFormat::Simple(
+ ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_SHORT,
+ base::Milliseconds(
+ refresh_scheduler
+ ? refresh_scheduler->GetActualRefreshDelay()
+ : CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs)));
+ dict->SetBoolKey(
+ "policiesPushAvailable",
+ refresh_scheduler ? refresh_scheduler->invalidations_available() : false);
+
+ if (!context_->enrollmentToken.empty())
+ dict->SetStringKey("enrollmentToken", context_->enrollmentToken);
+
+ if (!context_->deviceId.empty())
+ dict->SetStringKey("deviceId", context_->deviceId);
+
+ CloudPolicyStore* store = core_->store();
+ if (store) {
+ std::u16string status = GetPolicyStatusFromStore(store, core_->client());
+
+ dict->SetStringKey("status", status);
+
+ const enterprise_management::PolicyData* policy = store->policy();
+ if (policy) {
+ dict->SetStringKey(
+ "timeSinceLastRefresh",
+ GetTimeSinceLastActionString(refresh_scheduler
+ ? refresh_scheduler->last_refresh()
+ : base::Time()));
+ dict->SetStringKey("domain", gaia::ExtractDomainName(policy->username()));
+ }
+ }
+ dict->SetStringKey("machine", GetMachineName());
+
+ if (!context_->lastCloudReportSent.is_null()) {
+ dict->SetStringKey("lastCloudReportSentTimestamp",
+ base::TimeFormatShortDateAndTimeWithTimeZone(
+ context_->lastCloudReportSent));
+ dict->SetStringKey(
+ "timeSinceLastCloudReportSent",
+ GetTimeSinceLastActionString(context_->lastCloudReportSent));
+ }
+}
+
+void MachineLevelUserCloudPolicyStatusProvider::OnStoreLoaded(
+ CloudPolicyStore* store) {
+ NotifyStatusChange();
+}
+
+void MachineLevelUserCloudPolicyStatusProvider::OnStoreError(
+ CloudPolicyStore* store) {
+ NotifyStatusChange();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h b/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h
new file mode 100644
index 00000000000..05f0540d267
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_WEBUI_MACHINE_LEVEL_USER_CLOUD_POLICY_STATUS_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_WEBUI_MACHINE_LEVEL_USER_CLOUD_POLICY_STATUS_PROVIDER_H_
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
+#include "components/policy/core/browser/webui/policy_status_provider.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace policy {
+class CloudPolicyCore;
+
+struct POLICY_EXPORT MachineLevelUserCloudPolicyContext {
+ std::string enrollmentToken;
+ std::string deviceId;
+ base::Time lastCloudReportSent;
+};
+
+class POLICY_EXPORT MachineLevelUserCloudPolicyStatusProvider
+ : public PolicyStatusProvider,
+ public CloudPolicyStore::Observer {
+ public:
+ MachineLevelUserCloudPolicyStatusProvider(
+ CloudPolicyCore* core,
+ MachineLevelUserCloudPolicyContext* context);
+ MachineLevelUserCloudPolicyStatusProvider(
+ const MachineLevelUserCloudPolicyStatusProvider&) = delete;
+ MachineLevelUserCloudPolicyStatusProvider& operator=(
+ const MachineLevelUserCloudPolicyStatusProvider&) = delete;
+ ~MachineLevelUserCloudPolicyStatusProvider() override;
+
+ // PolicyStatusProvider implementation.
+ void GetStatus(base::DictionaryValue* dict) override;
+
+ // CloudPolicyStore::Observer implementation.
+ void OnStoreLoaded(CloudPolicyStore* store) override;
+ void OnStoreError(CloudPolicyStore* store) override;
+
+ private:
+ raw_ptr<CloudPolicyCore> core_;
+ raw_ptr<MachineLevelUserCloudPolicyContext> context_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_WEBUI_MACHINE_LEVEL_USER_CLOUD_POLICY_STATUS_PROVIDER_H_
diff --git a/chromium/components/policy/core/browser/webui/policy_status_provider.cc b/chromium/components/policy/core/browser/webui/policy_status_provider.cc
new file mode 100644
index 00000000000..826c8d7f3a1
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/policy_status_provider.cc
@@ -0,0 +1,183 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/browser/webui/policy_status_provider.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/no_destructor.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "components/policy/core/browser/cloud/message_util.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+// Formats the association state indicated by |data|. If |data| is NULL, the
+// state is considered to be UNMANAGED.
+std::u16string FormatAssociationState(const em::PolicyData* data) {
+ if (data) {
+ switch (data->state()) {
+ case em::PolicyData::ACTIVE:
+ return l10n_util::GetStringUTF16(IDS_POLICY_ASSOCIATION_STATE_ACTIVE);
+ case em::PolicyData::UNMANAGED:
+ return l10n_util::GetStringUTF16(
+ IDS_POLICY_ASSOCIATION_STATE_UNMANAGED);
+ case em::PolicyData::DEPROVISIONED:
+ return l10n_util::GetStringUTF16(
+ IDS_POLICY_ASSOCIATION_STATE_DEPROVISIONED);
+ }
+ NOTREACHED() << "Unknown state " << data->state();
+ }
+
+ // Default to UNMANAGED for the case of missing policy or bad state enum.
+ return l10n_util::GetStringUTF16(IDS_POLICY_ASSOCIATION_STATE_UNMANAGED);
+}
+
+base::Clock* clock_for_testing_ = nullptr;
+
+const base::Clock* GetClock() {
+ if (clock_for_testing_)
+ return clock_for_testing_;
+ return base::DefaultClock::GetInstance();
+}
+
+} // namespace
+
+PolicyStatusProvider::PolicyStatusProvider() = default;
+
+PolicyStatusProvider::~PolicyStatusProvider() = default;
+
+void PolicyStatusProvider::SetStatusChangeCallback(
+ const base::RepeatingClosure& callback) {
+ callback_ = callback;
+}
+
+// static
+void PolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
+ // This method is called when the client is not enrolled.
+ // Thus leaving the dict without any changes.
+}
+
+void PolicyStatusProvider::NotifyStatusChange() {
+ if (callback_)
+ callback_.Run();
+}
+
+// static
+void PolicyStatusProvider::GetStatusFromCore(const CloudPolicyCore* core,
+ base::DictionaryValue* dict) {
+ const CloudPolicyStore* store = core->store();
+ const CloudPolicyClient* client = core->client();
+ const CloudPolicyRefreshScheduler* refresh_scheduler =
+ core->refresh_scheduler();
+
+ const std::u16string status = GetPolicyStatusFromStore(store, client);
+
+ const em::PolicyData* policy = store->policy();
+ GetStatusFromPolicyData(policy, dict);
+
+ base::TimeDelta refresh_interval = base::Milliseconds(
+ refresh_scheduler ? refresh_scheduler->GetActualRefreshDelay()
+ : CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs);
+
+ bool no_error = store->status() == CloudPolicyStore::STATUS_OK && client &&
+ client->status() == DM_STATUS_SUCCESS;
+ dict->SetBoolKey("error", !no_error);
+ dict->SetBoolKey(
+ "policiesPushAvailable",
+ refresh_scheduler ? refresh_scheduler->invalidations_available() : false);
+ dict->SetStringKey("status", status);
+ dict->SetStringKey(
+ "refreshInterval",
+ ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION,
+ ui::TimeFormat::LENGTH_SHORT, refresh_interval));
+ base::Time last_refresh_time =
+ policy && policy->has_timestamp()
+ ? base::Time::FromJavaTime(policy->timestamp())
+ : base::Time();
+ dict->SetStringKey("timeSinceLastRefresh",
+ GetTimeSinceLastActionString(last_refresh_time));
+
+ // In case state_keys aren't available, we have no scheduler. See also
+ // DeviceCloudPolicyInitializer::TryToCreateClient and b/181140445.
+ base::Time last_fetch_attempted_time =
+ refresh_scheduler ? refresh_scheduler->last_refresh() : base::Time();
+ dict->SetStringKey("timeSinceLastFetchAttempt",
+ GetTimeSinceLastActionString(last_fetch_attempted_time));
+}
+
+// static
+void PolicyStatusProvider::GetStatusFromPolicyData(
+ const em::PolicyData* policy,
+ base::DictionaryValue* dict) {
+ std::string client_id = policy ? policy->device_id() : std::string();
+ std::string username = policy ? policy->username() : std::string();
+
+ if (policy && policy->has_annotated_asset_id())
+ dict->SetStringKey("assetId", policy->annotated_asset_id());
+ if (policy && policy->has_annotated_location())
+ dict->SetStringKey("location", policy->annotated_location());
+ if (policy && policy->has_directory_api_id())
+ dict->SetStringKey("directoryApiId", policy->directory_api_id());
+ if (policy && policy->has_gaia_id())
+ dict->SetStringKey("gaiaId", policy->gaia_id());
+
+ dict->SetStringKey("clientId", client_id);
+ dict->SetStringKey("username", username);
+}
+
+// CloudPolicyStore errors take precedence to show in the status message.
+// Other errors (such as transient policy fetching problems) get displayed
+// only if CloudPolicyStore is in STATUS_OK.
+// static
+std::u16string PolicyStatusProvider::GetPolicyStatusFromStore(
+ const CloudPolicyStore* store,
+ const CloudPolicyClient* client) {
+ if (store->status() == CloudPolicyStore::STATUS_OK) {
+ if (client && client->status() != DM_STATUS_SUCCESS)
+ return FormatDeviceManagementStatus(client->status());
+ else if (!store->is_managed())
+ return FormatAssociationState(store->policy());
+ }
+
+ return FormatStoreStatus(store->status(), store->validation_status());
+}
+
+// static
+std::u16string PolicyStatusProvider::GetTimeSinceLastActionString(
+ base::Time last_action_time) {
+ if (last_action_time.is_null())
+ return l10n_util::GetStringUTF16(IDS_POLICY_NEVER_FETCHED);
+ base::Time now = GetClock()->Now();
+ base::TimeDelta elapsed_time;
+ if (now > last_action_time)
+ elapsed_time = now - last_action_time;
+ return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_ELAPSED,
+ ui::TimeFormat::LENGTH_SHORT, elapsed_time);
+}
+
+// static
+base::ScopedClosureRunner PolicyStatusProvider::OverrideClockForTesting(
+ base::Clock* clock_for_testing) {
+ CHECK(!clock_for_testing_);
+ clock_for_testing_ = clock_for_testing;
+ return base::ScopedClosureRunner(
+ base::BindOnce([]() { clock_for_testing_ = nullptr; }));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/browser/webui/policy_status_provider.h b/chromium/components/policy/core/browser/webui/policy_status_provider.h
new file mode 100644
index 00000000000..f1577045dae
--- /dev/null
+++ b/chromium/components/policy/core/browser/webui/policy_status_provider.h
@@ -0,0 +1,69 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_BROWSER_WEBUI_POLICY_STATUS_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_BROWSER_WEBUI_POLICY_STATUS_PROVIDER_H_
+
+#include <memory>
+
+#include "base/callback_helpers.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class DictionaryValue;
+class Time;
+}
+
+namespace enterprise_management {
+class PolicyData;
+}
+
+namespace policy {
+class CloudPolicyClient;
+class CloudPolicyCore;
+class CloudPolicyStore;
+
+// An interface for querying the status of a policy provider. It surfaces
+// things like last fetch time or status of the backing store, but not the
+// actual policies themselves.
+class POLICY_EXPORT PolicyStatusProvider {
+ public:
+ PolicyStatusProvider();
+ PolicyStatusProvider(const PolicyStatusProvider&) = delete;
+ PolicyStatusProvider& operator=(const PolicyStatusProvider&) = delete;
+ virtual ~PolicyStatusProvider();
+
+ // Sets a callback to invoke upon status changes.
+ virtual void SetStatusChangeCallback(const base::RepeatingClosure& callback);
+
+ // Fills the passed dictionary with metadata about policies.
+ // The passed base::DictionaryValue should be empty.
+ virtual void GetStatus(base::DictionaryValue* dict);
+
+ static void GetStatusFromCore(const CloudPolicyCore* core,
+ base::DictionaryValue* dict);
+ static void GetStatusFromPolicyData(
+ const enterprise_management::PolicyData* policy,
+ base::DictionaryValue* dict);
+
+ // Overrides clock in tests. Returned closure removes the override when
+ // destroyed.
+ static base::ScopedClosureRunner OverrideClockForTesting(
+ base::Clock* clock_for_testing);
+
+ protected:
+ void NotifyStatusChange();
+ static std::u16string GetPolicyStatusFromStore(const CloudPolicyStore*,
+ const CloudPolicyClient*);
+ static std::u16string GetTimeSinceLastActionString(base::Time);
+
+ private:
+ base::RepeatingClosure callback_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_BROWSER_WEBUI_POLICY_STATUS_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/DEPS b/chromium/components/policy/core/common/DEPS
new file mode 100644
index 00000000000..0c402716601
--- /dev/null
+++ b/chromium/components/policy/core/common/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+chromeos/crosapi",
+ "+chromeos/lacros",
+ "+chromeos/startup",
+ "+chromeos/system",
+ "+components/account_id",
+ "+components/reporting",
+ "-components/policy/core/browser",
+ "+components/signin/public",
+ "+components/strings",
+ "+extensions/buildflags",
+ "+net/http",
+ "+net/traffic_annotation",
+ "+services/network/test",
+ "+services/network/public/mojom",
+ "+third_party/libxml/chromium",
+ "+ui/base/l10n",
+]
diff --git a/chromium/components/policy/core/common/android/android_combined_policy_provider.cc b/chromium/components/policy/core/common/android/android_combined_policy_provider.cc
new file mode 100644
index 00000000000..415cc1fbe6a
--- /dev/null
+++ b/chromium/components/policy/core/common/android/android_combined_policy_provider.cc
@@ -0,0 +1,73 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/android_combined_policy_provider.h"
+
+#include <memory>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "components/policy/android/jni_headers/CombinedPolicyProvider_jni.h"
+#include "components/policy/core/common/android/policy_converter.h"
+
+using base::android::AttachCurrentThread;
+using base::android::JavaParamRef;
+
+namespace {
+
+bool g_wait_for_policies = false;
+
+} // namespace
+
+namespace policy {
+namespace android {
+
+AndroidCombinedPolicyProvider::AndroidCombinedPolicyProvider(
+ SchemaRegistry* registry)
+ : initialized_(!g_wait_for_policies) {
+ PolicyNamespace ns(POLICY_DOMAIN_CHROME, std::string());
+ const Schema* schema = registry->schema_map()->GetSchema(ns);
+ policy_converter_ =
+ std::make_unique<policy::android::PolicyConverter>(schema);
+ java_combined_policy_provider_.Reset(Java_CombinedPolicyProvider_linkNative(
+ AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
+ policy_converter_->GetJavaObject()));
+}
+
+AndroidCombinedPolicyProvider::~AndroidCombinedPolicyProvider() {
+ Java_CombinedPolicyProvider_linkNative(AttachCurrentThread(), 0, nullptr);
+ java_combined_policy_provider_.Reset();
+}
+
+void AndroidCombinedPolicyProvider::RefreshPolicies() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_CombinedPolicyProvider_refreshPolicies(env,
+ java_combined_policy_provider_);
+}
+
+void AndroidCombinedPolicyProvider::FlushPolicies(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& obj) {
+ initialized_ = true;
+ UpdatePolicy(policy_converter_->GetPolicyBundle());
+}
+
+// static
+void AndroidCombinedPolicyProvider::SetShouldWaitForPolicy(
+ bool should_wait_for_policy) {
+ g_wait_for_policies = should_wait_for_policy;
+}
+
+bool AndroidCombinedPolicyProvider::IsInitializationComplete(
+ PolicyDomain domain) const {
+ return initialized_;
+}
+
+bool AndroidCombinedPolicyProvider::IsFirstPolicyLoadComplete(
+ PolicyDomain domain) const {
+ return IsInitializationComplete(domain);
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/android_combined_policy_provider.h b/chromium/components/policy/core/common/android/android_combined_policy_provider.h
new file mode 100644
index 00000000000..03d1e32bca8
--- /dev/null
+++ b/chromium/components/policy/core/common/android/android_combined_policy_provider.h
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ANDROID_ANDROID_COMBINED_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ANDROID_ANDROID_COMBINED_POLICY_PROVIDER_H_
+
+#include <jni.h>
+
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class SchemaRegistry;
+
+namespace android {
+
+class PolicyConverter;
+
+class POLICY_EXPORT AndroidCombinedPolicyProvider
+ : public ConfigurationPolicyProvider {
+ public:
+ explicit AndroidCombinedPolicyProvider(SchemaRegistry* registry);
+ AndroidCombinedPolicyProvider(const AndroidCombinedPolicyProvider&) = delete;
+ AndroidCombinedPolicyProvider& operator=(
+ const AndroidCombinedPolicyProvider&) = delete;
+
+ ~AndroidCombinedPolicyProvider() override;
+
+ // Push the polices updated by the Java policy providers to the core policy
+ // system
+ void FlushPolicies(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj);
+
+ // Call this method to tell the policy system whether it should wait for
+ // policies to be loaded by this provider. If this method is called,
+ // IsInitializationComplete() will only return true after SetPolicies() has
+ // been called at least once, otherwise it will return true immediately.
+ static void SetShouldWaitForPolicy(bool should_wait_for_policy);
+
+ // ConfigurationPolicyProvider:
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+ void RefreshPolicies() override;
+
+ // For testing
+ PolicyConverter* GetPolicyConverterForTesting() {
+ return policy_converter_.get();
+ }
+
+ private:
+ bool initialized_;
+ std::unique_ptr<policy::android::PolicyConverter> policy_converter_;
+ base::android::ScopedJavaGlobalRef<jobject> java_combined_policy_provider_;
+};
+
+} // namespace android
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ANDROID_ANDROID_COMBINED_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/android/android_combined_policy_provider_unittest.cc b/chromium/components/policy/core/common/android/android_combined_policy_provider_unittest.cc
new file mode 100644
index 00000000000..9fe5919703e
--- /dev/null
+++ b/chromium/components/policy/core/common/android/android_combined_policy_provider_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/values.h"
+#include "components/policy/core/common/android/android_combined_policy_provider.h"
+#include "components/policy/core/common/android/policy_converter.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+
+namespace policy {
+
+namespace android {
+class AndroidCombinedPolicyProviderTest : public ::testing::Test {
+ void TearDown() override;
+};
+
+void AndroidCombinedPolicyProviderTest::TearDown() {
+ AndroidCombinedPolicyProvider::SetShouldWaitForPolicy(false);
+}
+
+TEST_F(AndroidCombinedPolicyProviderTest, InitializationCompleted) {
+ SchemaRegistry registry;
+ AndroidCombinedPolicyProvider manager(&registry);
+ EXPECT_TRUE(manager.IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ // If the manager is deleted (by going out of scope) without being shutdown
+ // first it DCHECKs.
+ manager.Shutdown();
+}
+
+TEST_F(AndroidCombinedPolicyProviderTest, SetShouldWaitForPolicy) {
+ AndroidCombinedPolicyProvider::SetShouldWaitForPolicy(true);
+ SchemaRegistry registry;
+ AndroidCombinedPolicyProvider manager(&registry);
+ EXPECT_TRUE(manager.IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ manager.FlushPolicies(nullptr, nullptr);
+ EXPECT_TRUE(manager.IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ // If the manager is deleted (by going out of scope) without being shutdown
+ // first it DCHECKs.
+ manager.Shutdown();
+}
+
+TEST_F(AndroidCombinedPolicyProviderTest, FlushPolices) {
+ const char kSchemaTemplate[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " }"
+ "}";
+
+ PolicyNamespace ns(POLICY_DOMAIN_CHROME, std::string());
+ std::string error;
+ Schema schema = Schema::Parse(kSchemaTemplate, &error);
+ SchemaRegistry registry;
+ registry.RegisterComponent(ns, schema);
+ AndroidCombinedPolicyProvider manager(&registry);
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> jpolicy =
+ ConvertUTF8ToJavaString(env, "TestPolicy");
+ ScopedJavaLocalRef<jstring> jvalue =
+ ConvertUTF8ToJavaString(env, "TestValue");
+ manager.GetPolicyConverterForTesting()->SetPolicyString(env, nullptr, jpolicy,
+ jvalue);
+ manager.FlushPolicies(env, nullptr);
+ const PolicyBundle& bundle = manager.policies();
+ const PolicyMap& map = bundle.Get(ns);
+ const base::Value* value =
+ map.GetValue("TestPolicy", base::Value::Type::STRING);
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(base::Value::Type::STRING, value->type());
+ ASSERT_TRUE(value->is_string());
+ EXPECT_EQ("TestValue", value->GetString());
+ // If the manager is deleted (by going out of scope) without being shutdown
+ // first it DCHECKs.
+ manager.Shutdown();
+}
+
+} // namespace android
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_converter.cc b/chromium/components/policy/core/common/android/policy_converter.cc
new file mode 100644
index 00000000000..42ed096dfe1
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_converter.cc
@@ -0,0 +1,208 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/policy_converter.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/check_op.h"
+#include "base/json/json_reader.h"
+#include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "components/policy/android/jni_headers/PolicyConverter_jni.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::JavaRef;
+
+namespace policy {
+namespace android {
+
+PolicyConverter::PolicyConverter(const Schema* policy_schema)
+ : policy_schema_(policy_schema), policy_bundle_(new PolicyBundle) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ java_obj_.Reset(
+ env,
+ Java_PolicyConverter_create(env, reinterpret_cast<intptr_t>(this)).obj());
+ DCHECK(!java_obj_.is_null());
+}
+
+PolicyConverter::~PolicyConverter() {
+ Java_PolicyConverter_onNativeDestroyed(base::android::AttachCurrentThread(),
+ java_obj_);
+}
+
+std::unique_ptr<PolicyBundle> PolicyConverter::GetPolicyBundle() {
+ std::unique_ptr<PolicyBundle> filled_bundle(std::move(policy_bundle_));
+ policy_bundle_ = std::make_unique<PolicyBundle>();
+ return filled_bundle;
+}
+
+base::android::ScopedJavaLocalRef<jobject> PolicyConverter::GetJavaObject() {
+ return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
+}
+
+void PolicyConverter::SetPolicyBoolean(JNIEnv* env,
+ const JavaRef<jobject>& obj,
+ const JavaRef<jstring>& policyKey,
+ jboolean value) {
+ SetPolicyValue(ConvertJavaStringToUTF8(env, policyKey),
+ base::Value(static_cast<bool>(value)));
+}
+
+void PolicyConverter::SetPolicyInteger(JNIEnv* env,
+ const JavaRef<jobject>& obj,
+ const JavaRef<jstring>& policyKey,
+ jint value) {
+ SetPolicyValue(ConvertJavaStringToUTF8(env, policyKey),
+ base::Value(static_cast<int>(value)));
+}
+
+void PolicyConverter::SetPolicyString(JNIEnv* env,
+ const JavaRef<jobject>& obj,
+ const JavaRef<jstring>& policyKey,
+ const JavaRef<jstring>& value) {
+ SetPolicyValue(ConvertJavaStringToUTF8(env, policyKey),
+ base::Value(ConvertJavaStringToUTF8(env, value)));
+}
+
+void PolicyConverter::SetPolicyStringArray(JNIEnv* env,
+ const JavaRef<jobject>& obj,
+ const JavaRef<jstring>& policyKey,
+ const JavaRef<jobjectArray>& array) {
+ SetPolicyValue(ConvertJavaStringToUTF8(env, policyKey),
+ ConvertJavaStringArrayToListValue(env, array));
+}
+
+// static
+base::Value PolicyConverter::ConvertJavaStringArrayToListValue(
+ JNIEnv* env,
+ const JavaRef<jobjectArray>& array) {
+ DCHECK(!array.is_null());
+ base::android::JavaObjectArrayReader<jstring> array_reader(array);
+ DCHECK_GE(array_reader.size(), 0)
+ << "Invalid array length: " << array_reader.size();
+
+ base::Value list_value(base::Value::Type::LIST);
+ for (auto j_str : array_reader)
+ list_value.Append(ConvertJavaStringToUTF8(env, j_str));
+
+ return list_value;
+}
+
+// static
+absl::optional<base::Value> PolicyConverter::ConvertValueToSchema(
+ base::Value value,
+ const Schema& schema) {
+ if (!schema.valid())
+ return value;
+
+ switch (schema.type()) {
+ case base::Value::Type::NONE:
+ return base::Value();
+
+ case base::Value::Type::BOOLEAN: {
+ std::string string_value;
+ if (value.is_string()) {
+ const std::string& string_value = value.GetString();
+ if (string_value.compare("true") == 0)
+ return base::Value(true);
+
+ if (string_value.compare("false") == 0)
+ return base::Value(false);
+
+ return value;
+ }
+ if (value.is_int())
+ return base::Value(value.GetInt() != 0);
+
+ return value;
+ }
+
+ case base::Value::Type::INTEGER: {
+ if (value.is_string()) {
+ const std::string& string_value = value.GetString();
+ int int_value = 0;
+ if (base::StringToInt(string_value, &int_value))
+ return base::Value(int_value);
+ }
+ return value;
+ }
+
+ case base::Value::Type::DOUBLE: {
+ if (value.is_string()) {
+ const std::string& string_value = value.GetString();
+ double double_value = 0;
+ if (base::StringToDouble(string_value, &double_value))
+ return base::Value(double_value);
+ }
+ return value;
+ }
+
+ // String can't be converted from other types.
+ case base::Value::Type::STRING: {
+ return value;
+ }
+
+ // Binary is not a valid schema type.
+ case base::Value::Type::BINARY: {
+ NOTREACHED();
+ return base::Value();
+ }
+
+ // Complex types have to be deserialized from JSON.
+ case base::Value::Type::DICTIONARY:
+ case base::Value::Type::LIST: {
+ if (value.is_string()) {
+ const std::string str_value = value.GetString();
+ // Do not try to convert empty string to list/dictionaries, since most
+ // likely the value was not simply not set by the UEM.
+ if (str_value.empty())
+ return absl::nullopt;
+ absl::optional<base::Value> decoded_value = base::JSONReader::Read(
+ str_value, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ if (decoded_value.has_value())
+ return decoded_value;
+ }
+ return value;
+ }
+ }
+
+ NOTREACHED();
+ return absl::nullopt;
+}
+
+void PolicyConverter::SetPolicyValueForTesting(const std::string& key,
+ base::Value value) {
+ PolicyConverter::SetPolicyValue(key, std::move(value));
+}
+
+void PolicyConverter::SetPolicyValue(const std::string& key,
+ base::Value value) {
+ const Schema schema = policy_schema_->GetKnownProperty(key);
+ const PolicyNamespace ns(POLICY_DOMAIN_CHROME, std::string());
+ absl::optional<base::Value> converted_value =
+ ConvertValueToSchema(std::move(value), schema);
+ if (converted_value) {
+ // Do not set list/dictionary policies that are sent as empty strings from
+ // the UEM. This is common on Android when the UEM pushes the policy with
+ // managed configurations.
+ policy_bundle_->Get(ns).Set(key, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(converted_value), nullptr);
+ }
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_converter.h b/chromium/components/policy/core/common/android/policy_converter.h
new file mode 100644
index 00000000000..402840ab3b4
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_converter.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_CONVERTER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_CONVERTER_H_
+
+#include <jni.h>
+
+#include <memory>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+
+class Value;
+
+} // namespace base
+
+namespace policy {
+
+class PolicyBundle;
+class Schema;
+
+namespace android {
+
+// Populates natives policies from Java key/value pairs. With its associated
+// java classes, allows transforming Android |Bundle|s into |PolicyBundle|s.
+class POLICY_EXPORT PolicyConverter {
+ public:
+ explicit PolicyConverter(const Schema* policy_schema);
+ PolicyConverter(const PolicyConverter&) = delete;
+ PolicyConverter& operator=(const PolicyConverter&) = delete;
+ ~PolicyConverter();
+
+ // Returns a policy bundle containing all policies collected since the last
+ // call to this method.
+ std::unique_ptr<PolicyBundle> GetPolicyBundle();
+
+ base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
+
+ // To be called from Java:
+ void SetPolicyBoolean(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jstring>& policyKey,
+ jboolean value);
+ void SetPolicyInteger(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jstring>& policyKey,
+ jint value);
+ void SetPolicyString(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jstring>& policyKey,
+ const base::android::JavaRef<jstring>& value);
+ void SetPolicyStringArray(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jstring>& policyKey,
+ const base::android::JavaRef<jobjectArray>& value);
+
+ // Converts the passed in value to the type desired by the schema. If the
+ // value is not convertible, it is returned unchanged, so the policy system
+ // can report the error.
+ // Note that this method will only look at the type of the schema, not at any
+ // additional restrictions, or the schema for value's items or properties in
+ // the case of a list or dictionary value.
+ // Public for testing.
+ static absl::optional<base::Value> ConvertValueToSchema(base::Value value,
+ const Schema& schema);
+
+ // Public for testing.
+ static base::Value ConvertJavaStringArrayToListValue(
+ JNIEnv* env,
+ const base::android::JavaRef<jobjectArray>& array);
+
+ // Exposes `SetPolicyValue` for testing purposes.
+ void SetPolicyValueForTesting(const std::string& key, base::Value raw_value);
+
+ private:
+ const raw_ptr<const Schema> policy_schema_;
+
+ std::unique_ptr<PolicyBundle> policy_bundle_;
+
+ base::android::ScopedJavaGlobalRef<jobject> java_obj_;
+
+ void SetPolicyValue(const std::string& key, base::Value raw_value);
+};
+
+} // namespace android
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_CONVERTER_H_
diff --git a/chromium/components/policy/core/common/android/policy_converter_unittest.cc b/chromium/components/policy/core/common/android/policy_converter_unittest.cc
new file mode 100644
index 00000000000..dd496913d19
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_converter_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "components/policy/core/common/android/policy_converter.h"
+#include "components/policy/core/common/schema.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+using base::android::JavaRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace policy {
+namespace android {
+
+class PolicyConverterTest : public testing::Test {
+ public:
+ void SetUp() override {
+ const char kSchemaTemplate[] =
+ R"({
+ "type": "object",
+ "properties": {
+ "string": { "type": "string" },
+ "int": { "type": "integer" },
+ "bool": { "type": "boolean" },
+ "double": { "type": "number" },
+ "list": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "dict": { "type": "object" }
+ }
+ })";
+
+ std::string error;
+ schema_ = Schema::Parse(kSchemaTemplate, &error);
+ ASSERT_TRUE(schema_.valid()) << error;
+ }
+
+ protected:
+ // Converts the passed in value to the passed in schema, and serializes the
+ // result to JSON, to make it easier to compare with EXPECT_EQ.
+ std::string Convert(Value value, const Schema& value_schema) {
+ absl::optional<base::Value> converted_value =
+ PolicyConverter::ConvertValueToSchema(std::move(value), value_schema);
+ EXPECT_TRUE(converted_value.has_value());
+
+ std::string json_string;
+ EXPECT_TRUE(base::JSONWriter::Write(converted_value.value(), &json_string));
+ return json_string;
+ }
+
+ // Uses|PolicyConverter::ConvertJavaStringArrayToListValue| to convert the
+ // passed in java array and serializes the result to JSON, to make it easier
+ // to compare with EXPECT_EQ.
+ std::string ConvertJavaStringArrayToListValue(
+ JNIEnv* env,
+ const JavaRef<jobjectArray>& java_array) {
+ base::Value list =
+ PolicyConverter::ConvertJavaStringArrayToListValue(env, java_array);
+
+ std::string json_string;
+ EXPECT_TRUE(base::JSONWriter::Write(list, &json_string));
+
+ return json_string;
+ }
+
+ // Converts the passed in values to a java string array
+ ScopedJavaLocalRef<jobjectArray> MakeJavaStringArray(
+ JNIEnv* env,
+ std::vector<std::string> values) {
+ jobjectArray java_array = (jobjectArray)env->NewObjectArray(
+ values.size(), env->FindClass("java/lang/String"), nullptr);
+ for (size_t i = 0; i < values.size(); i++) {
+ env->SetObjectArrayElement(
+ java_array, i,
+ base::android::ConvertUTF8ToJavaString(env, values[i]).obj());
+ }
+
+ return ScopedJavaLocalRef<jobjectArray>(env, java_array);
+ }
+
+ Schema schema_;
+};
+
+TEST_F(PolicyConverterTest, ConvertToBoolValue) {
+ Schema bool_schema = schema_.GetKnownProperty("bool");
+ ASSERT_TRUE(bool_schema.valid());
+
+ EXPECT_EQ("true", Convert(Value(true), bool_schema));
+ EXPECT_EQ("false", Convert(Value(false), bool_schema));
+ EXPECT_EQ("true", Convert(Value("true"), bool_schema));
+ EXPECT_EQ("false", Convert(Value("false"), bool_schema));
+ EXPECT_EQ("\"narf\"", Convert(Value("narf"), bool_schema));
+ EXPECT_EQ("false", Convert(Value(0), bool_schema));
+ EXPECT_EQ("true", Convert(Value(1), bool_schema));
+ EXPECT_EQ("true", Convert(Value(42), bool_schema));
+ EXPECT_EQ("true", Convert(Value(-1), bool_schema));
+ EXPECT_EQ("\"1\"", Convert(Value("1"), bool_schema));
+ EXPECT_EQ("{}", Convert(Value(Value::Type::DICTIONARY), bool_schema));
+}
+
+TEST_F(PolicyConverterTest, ConvertToIntValue) {
+ Schema int_schema = schema_.GetKnownProperty("int");
+ ASSERT_TRUE(int_schema.valid());
+
+ EXPECT_EQ("23", Convert(Value(23), int_schema));
+ EXPECT_EQ("42", Convert(Value("42"), int_schema));
+ EXPECT_EQ("-1", Convert(Value("-1"), int_schema));
+ EXPECT_EQ("\"poit\"", Convert(Value("poit"), int_schema));
+ EXPECT_EQ("false", Convert(Value(false), int_schema));
+}
+
+TEST_F(PolicyConverterTest, ConvertToDoubleValue) {
+ Schema double_schema = schema_.GetKnownProperty("double");
+ ASSERT_TRUE(double_schema.valid());
+
+ EXPECT_EQ("3", Convert(Value(3), double_schema));
+ EXPECT_EQ("3.14", Convert(Value(3.14), double_schema));
+ EXPECT_EQ("2.71", Convert(Value("2.71"), double_schema));
+ EXPECT_EQ("\"zort\"", Convert(Value("zort"), double_schema));
+ EXPECT_EQ("true", Convert(Value(true), double_schema));
+}
+
+TEST_F(PolicyConverterTest, ConvertToStringValue) {
+ Schema string_schema = schema_.GetKnownProperty("string");
+ ASSERT_TRUE(string_schema.valid());
+
+ EXPECT_EQ("\"troz\"", Convert(Value("troz"), string_schema));
+ EXPECT_EQ("4711", Convert(Value(4711), string_schema));
+}
+
+TEST_F(PolicyConverterTest, ConvertToListValue) {
+ Schema list_schema = schema_.GetKnownProperty("list");
+ ASSERT_TRUE(list_schema.valid());
+
+ Value list = Value(Value::Type::LIST);
+ list.Append("foo");
+ list.Append("bar");
+ EXPECT_EQ("[\"foo\",\"bar\"]", Convert(std::move(list), list_schema));
+ EXPECT_EQ("[\"baz\",\"blurp\"]",
+ Convert(Value("[\"baz\", \"blurp\"]"), list_schema));
+ EXPECT_EQ("\"hurz\"", Convert(Value("hurz"), list_schema));
+ EXPECT_EQ("19", Convert(Value(19), list_schema));
+
+ EXPECT_FALSE(PolicyConverter::ConvertValueToSchema(Value(""), list_schema)
+ .has_value());
+}
+
+TEST_F(PolicyConverterTest, ConvertFromJavaListToListValue) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ EXPECT_EQ("[\"foo\",\"bar\",\"baz\"]",
+ ConvertJavaStringArrayToListValue(
+ env, MakeJavaStringArray(env, {"foo", "bar", "baz"})));
+ EXPECT_EQ("[]", ConvertJavaStringArrayToListValue(
+ env, MakeJavaStringArray(env, {})));
+}
+
+TEST_F(PolicyConverterTest, ConvertToDictionaryValue) {
+ Schema dict_schema = schema_.GetKnownProperty("dict");
+ ASSERT_TRUE(dict_schema.valid());
+
+ Value dict = Value(Value::Type::DICTIONARY);
+ dict.SetIntKey("thx", 1138);
+ EXPECT_EQ("{\"thx\":1138}", Convert(std::move(dict), dict_schema));
+ EXPECT_EQ("{\"moose\":true}",
+ Convert(Value("{\"moose\": true}"), dict_schema));
+ EXPECT_EQ("\"fnord\"", Convert(Value("fnord"), dict_schema));
+ EXPECT_EQ("1729", Convert(Value(1729), dict_schema));
+
+ EXPECT_FALSE(PolicyConverter::ConvertValueToSchema(Value(""), dict_schema)
+ .has_value());
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_map_android.cc b/chromium/components/policy/core/common/android/policy_map_android.cc
new file mode 100644
index 00000000000..a88cbf40554
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_map_android.cc
@@ -0,0 +1,117 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/policy_map_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "components/policy/android/jni_headers/PolicyMap_jni.h"
+
+namespace policy {
+namespace android {
+
+PolicyMapAndroid::PolicyMapAndroid(const PolicyMap& policy_map)
+ : policy_map_(policy_map) {}
+
+PolicyMapAndroid::~PolicyMapAndroid() = default;
+
+jboolean PolicyMapAndroid::HasValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ return GetValue(env, policy) != nullptr;
+}
+
+jint PolicyMapAndroid::GetIntValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ const base::Value* value = GetValue(env, policy);
+ DCHECK(value && value->is_int())
+ << "The policy must exist and be stored as integer.";
+ return value->GetInt();
+}
+
+jboolean PolicyMapAndroid::GetBooleanValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ const base::Value* value = GetValue(env, policy);
+ DCHECK(value && value->is_bool())
+ << "The policy must exist and be stored as boolean.";
+ return value->GetBool();
+}
+
+base::android::ScopedJavaLocalRef<jstring> PolicyMapAndroid::GetStringValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ const base::Value* value = GetValue(env, policy);
+ if (!value)
+ return nullptr;
+ DCHECK(value->is_string()) << "The policy must be stored as string.";
+ return base::android::ConvertUTF8ToJavaString(env, value->GetString());
+}
+
+base::android::ScopedJavaLocalRef<jstring> PolicyMapAndroid::GetListValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ return GetListOrDictValue(env, policy, /* is_dict */ false);
+}
+
+base::android::ScopedJavaLocalRef<jstring> PolicyMapAndroid::GetDictValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const {
+ return GetListOrDictValue(env, policy, /* is_dict */ true);
+}
+
+jboolean PolicyMapAndroid::Equals(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ jlong other) const {
+ return policy_map_.Equals(
+ reinterpret_cast<PolicyMapAndroid*>(other)->policy_map_);
+}
+
+base::android::ScopedJavaLocalRef<jobject> PolicyMapAndroid::GetJavaObject() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (!java_ref_) {
+ java_ref_.Reset(
+ Java_PolicyMap_Constructor(env, reinterpret_cast<intptr_t>(this)));
+ }
+ return base::android::ScopedJavaLocalRef<jobject>(java_ref_);
+}
+
+base::android::ScopedJavaLocalRef<jstring> PolicyMapAndroid::GetListOrDictValue(
+ JNIEnv* env,
+ const base::android::JavaRef<jstring>& policy,
+ bool is_dict) const {
+ const base::Value* value = GetValue(env, policy);
+ if (!value)
+ return nullptr;
+#if DCHECK_IS_ON()
+ if (is_dict)
+ DCHECK(value->is_dict()) << "The policy must be stored as dictionary.";
+ else
+ DCHECK(value->is_list()) << "The policy must be stored as list.";
+#endif // DCHECK_IS_ON()
+ std::string json_string;
+ base::JSONWriter::Write(*value, &json_string);
+ return base::android::ConvertUTF8ToJavaString(env, json_string);
+}
+
+const base::Value* PolicyMapAndroid::GetValue(
+ JNIEnv* env,
+ const base::android::JavaRef<jstring>& policy) const {
+ // It is safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ return policy_map_.GetValueUnsafe(
+ base::android::ConvertJavaStringToUTF8(env, policy));
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_map_android.h b/chromium/components/policy/core/common/android/policy_map_android.h
new file mode 100644
index 00000000000..5268dfd3e0f
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_map_android.h
@@ -0,0 +1,79 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_MAP_ANDROID_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_MAP_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+namespace android {
+
+// PolicyMap bridge class that is used for Android.
+class POLICY_EXPORT PolicyMapAndroid {
+ public:
+ explicit PolicyMapAndroid(const PolicyMap& policy_map);
+ PolicyMapAndroid(const PolicyMapAndroid&) = delete;
+ PolicyMapAndroid& operator=(const PolicyMapAndroid&) = delete;
+
+ ~PolicyMapAndroid();
+
+ jboolean HasValue(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ jint GetIntValue(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ jboolean GetBooleanValue(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ base::android::ScopedJavaLocalRef<jstring> GetStringValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ base::android::ScopedJavaLocalRef<jstring> GetListValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ base::android::ScopedJavaLocalRef<jstring> GetDictValue(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ jboolean Equals(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller,
+ jlong other) const;
+
+ base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
+
+ private:
+ base::android::ScopedJavaLocalRef<jstring> GetListOrDictValue(
+ JNIEnv* env,
+ const base::android::JavaRef<jstring>& policy,
+ bool is_dict) const;
+
+ const base::Value* GetValue(
+ JNIEnv* env,
+ const base::android::JavaRef<jstring>& policy) const;
+
+ const PolicyMap& policy_map_;
+
+ base::android::ScopedJavaGlobalRef<jobject> java_ref_;
+};
+
+} // namespace android
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_MAP_ANDROID_H_
diff --git a/chromium/components/policy/core/common/android/policy_map_android_unittest.cc b/chromium/components/policy/core/common/android/policy_map_android_unittest.cc
new file mode 100644
index 00000000000..1413bef65b0
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_map_android_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/policy_map_android.h"
+
+#include <vector>
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "base/values.h"
+#include "components/policy/android/test_jni_headers/PolicyMapTestSupporter_jni.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace android {
+namespace {
+
+constexpr char kPolicyName[] = "policy-name";
+
+} // namespace
+
+class PolicyMapAndroidTest : public ::testing::Test {
+ public:
+ PolicyMapAndroidTest() = default;
+ ~PolicyMapAndroidTest() override = default;
+
+ void SetPolicy(base::Value&& value) {
+ policy_map_.Set(kPolicyName, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, std::move(value), nullptr);
+ }
+
+ raw_ptr<JNIEnv> env_ = base::android::AttachCurrentThread();
+ PolicyMap policy_map_;
+ PolicyMapAndroid policy_map_android_{policy_map_};
+ base::android::ScopedJavaLocalRef<jobject> j_support_ =
+ Java_PolicyMapTestSupporter_Constructor(
+ env_,
+ policy_map_android_.GetJavaObject());
+ base::android::ScopedJavaLocalRef<jstring> policy_name_android_ =
+ base::android::ConvertUTF8ToJavaString(env_, kPolicyName);
+};
+
+TEST_F(PolicyMapAndroidTest, IntPolicy) {
+ Java_PolicyMapTestSupporter_verifyIntPolicy(env_, j_support_,
+ policy_name_android_, false, 0);
+ int value = 42;
+ SetPolicy(base::Value(value));
+ Java_PolicyMapTestSupporter_verifyIntPolicy(
+ env_, j_support_, policy_name_android_, true, value);
+
+ value = -42;
+ SetPolicy(base::Value(value));
+ Java_PolicyMapTestSupporter_verifyIntPolicy(
+ env_, j_support_, policy_name_android_, true, value);
+}
+
+TEST_F(PolicyMapAndroidTest, BooleanPolicy) {
+ Java_PolicyMapTestSupporter_verifyBooleanPolicy(
+ env_, j_support_, policy_name_android_, false, false);
+ bool value = true;
+ SetPolicy(base::Value(value));
+ Java_PolicyMapTestSupporter_verifyBooleanPolicy(
+ env_, j_support_, policy_name_android_, true, value);
+}
+
+TEST_F(PolicyMapAndroidTest, StringPolicy) {
+ Java_PolicyMapTestSupporter_verifyStringPolicy(env_, j_support_,
+ policy_name_android_, nullptr);
+ std::string value = "policy-value";
+ SetPolicy(base::Value(value));
+ Java_PolicyMapTestSupporter_verifyStringPolicy(
+ env_, j_support_, policy_name_android_,
+ base::android::ConvertUTF8ToJavaString(env_, value));
+}
+
+TEST_F(PolicyMapAndroidTest, DictPolicy) {
+ Java_PolicyMapTestSupporter_verifyDictPolicy(env_, j_support_,
+ policy_name_android_, nullptr);
+ base::Value value(base::Value::Type::DICTIONARY);
+ value.SetIntPath("key", 42);
+ SetPolicy(std::move(value));
+ Java_PolicyMapTestSupporter_verifyDictPolicy(
+ env_, j_support_, policy_name_android_,
+ base::android::ConvertUTF8ToJavaString(env_, R"({"key":42})"));
+}
+
+TEST_F(PolicyMapAndroidTest, ListPolicy) {
+ Java_PolicyMapTestSupporter_verifyListPolicy(env_, j_support_,
+ policy_name_android_, nullptr);
+ std::vector<base::Value> value;
+ value.push_back(base::Value("value-1"));
+ value.push_back(base::Value("value-2"));
+ SetPolicy(base::Value(value));
+ Java_PolicyMapTestSupporter_verifyListPolicy(
+ env_, j_support_, policy_name_android_,
+ base::android::ConvertUTF8ToJavaString(env_, R"(["value-1","value-2"])"));
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_service_android.cc b/chromium/components/policy/core/common/android/policy_service_android.cc
new file mode 100644
index 00000000000..8491b41010d
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_service_android.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/policy_service_android.h"
+
+#include "base/android/jni_android.h"
+#include "components/policy/android/jni_headers/PolicyService_jni.h"
+
+namespace policy {
+namespace android {
+
+PolicyServiceAndroid::PolicyServiceAndroid(PolicyService* policy_service)
+ : policy_service_(policy_service),
+ policy_map_(policy_service->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))) {}
+PolicyServiceAndroid::~PolicyServiceAndroid() = default;
+
+void PolicyServiceAndroid::AddObserver(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller) {
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, this);
+}
+
+void PolicyServiceAndroid::RemoveObserver(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller) {
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, this);
+}
+
+void PolicyServiceAndroid::OnPolicyServiceInitialized(PolicyDomain domain) {
+ DCHECK_EQ(POLICY_DOMAIN_CHROME, domain);
+ DCHECK(java_ref_);
+ Java_PolicyService_onPolicyServiceInitialized(
+ base::android::AttachCurrentThread(),
+ base::android::ScopedJavaLocalRef<jobject>(java_ref_));
+}
+
+void PolicyServiceAndroid::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ DCHECK_EQ(POLICY_DOMAIN_CHROME, ns.domain);
+ DCHECK(java_ref_);
+ PolicyMapAndroid previous_android(previous);
+ PolicyMapAndroid current_android(current);
+ Java_PolicyService_onPolicyUpdated(
+ base::android::AttachCurrentThread(),
+ base::android::ScopedJavaLocalRef<jobject>(java_ref_),
+ previous_android.GetJavaObject(), current_android.GetJavaObject());
+}
+
+bool PolicyServiceAndroid::IsInitializationComplete(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller) const {
+ return policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME);
+}
+
+base::android::ScopedJavaLocalRef<jobject> PolicyServiceAndroid::GetPolicies(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller) {
+ return policy_map_.GetJavaObject();
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+PolicyServiceAndroid::GetJavaObject() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (!java_ref_) {
+ java_ref_.Reset(
+ Java_PolicyService_Constructor(env, reinterpret_cast<intptr_t>(this)));
+ }
+ return base::android::ScopedJavaLocalRef<jobject>(java_ref_);
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/android/policy_service_android.h b/chromium/components/policy/core/common/android/policy_service_android.h
new file mode 100644
index 00000000000..5403f79ffc8
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_service_android.h
@@ -0,0 +1,69 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_SERVICE_ANDROID_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_SERVICE_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/core/common/android/policy_map_android.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace android {
+
+// PolicyService bridge class that is used for Android. It's owned by the main
+// PolicyService instance.
+// Note that it only support the Chrome policy domain but not any extension
+// policy.
+class POLICY_EXPORT PolicyServiceAndroid : public PolicyService::Observer {
+ public:
+ explicit PolicyServiceAndroid(PolicyService* policy_service);
+ PolicyServiceAndroid(const PolicyServiceAndroid&) = delete;
+ PolicyServiceAndroid& operator=(const PolicyServiceAndroid&) = delete;
+
+ ~PolicyServiceAndroid() override;
+
+ void AddObserver(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller);
+
+ void RemoveObserver(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller);
+
+ bool IsInitializationComplete(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller) const;
+
+ base::android::ScopedJavaLocalRef<jobject> GetPolicies(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& caller);
+
+ // PolicyService::Observer implementation.
+ // Pass the event to the Java observers.
+ void OnPolicyServiceInitialized(PolicyDomain domain) override;
+ void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) override;
+
+ base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
+
+ private:
+ raw_ptr<PolicyService> policy_service_;
+
+ // Contains all Chrome policies. The PolicyBundle is not used as there is only
+ // one policy namespace supported on Android.
+ PolicyMapAndroid policy_map_;
+
+ base::android::ScopedJavaGlobalRef<jobject> java_ref_;
+};
+
+} // namespace android
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ANDROID_POLICY_SERVICE_ANDROID_H_
diff --git a/chromium/components/policy/core/common/android/policy_service_android_unittest.cc b/chromium/components/policy/core/common/android/policy_service_android_unittest.cc
new file mode 100644
index 00000000000..91802671a1d
--- /dev/null
+++ b/chromium/components/policy/core/common/android/policy_service_android_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/android/policy_service_android.h"
+
+#include <jni.h>
+
+#include "base/android/java_exception_reporter.h"
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/android/test_jni_headers/PolicyServiceTestSupporter_jni.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+using ::testing::Return;
+using ::testing::ReturnRef;
+
+namespace policy {
+namespace android {
+
+// Tests the bridge class PolicyServiceAndroid. It uses the Java helper class in
+// //components/policy/android/javatest/.../test/PolicyServiceTestSupporter.java
+class PolicyServiceAndroidTest : public ::testing::Test {
+ public:
+ PolicyServiceAndroidTest() {
+ EXPECT_CALL(policy_service_, GetPolicies(PolicyNamespace(
+ POLICY_DOMAIN_CHROME, std::string())))
+ .WillOnce(ReturnRef(policies));
+ policy_service_android_ =
+ std::make_unique<PolicyServiceAndroid>(&policy_service_);
+ j_support_ = Java_PolicyServiceTestSupporter_Constructor(
+ env_, policy_service_android_->GetJavaObject());
+ }
+ ~PolicyServiceAndroidTest() override {
+ Java_PolicyServiceTestSupporter_verifyNoMoreInteractions(env_, j_support_);
+ }
+
+ raw_ptr<JNIEnv> env_ = base::android::AttachCurrentThread();
+ MockPolicyService policy_service_;
+ policy::PolicyMap policies;
+ std::unique_ptr<PolicyServiceAndroid> policy_service_android_;
+ base::android::ScopedJavaLocalRef<jobject> j_support_;
+};
+
+TEST_F(PolicyServiceAndroidTest, IsInitializationComplete) {
+ EXPECT_CALL(policy_service_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .Times(2)
+ .WillOnce(Return(false))
+ .WillOnce(Return(true));
+ EXPECT_CALL(policy_service_,
+ IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .Times(0);
+ EXPECT_CALL(policy_service_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .Times(0);
+ Java_PolicyServiceTestSupporter_verifyIsInitalizationComplete(
+ env_, j_support_, false);
+ Java_PolicyServiceTestSupporter_verifyIsInitalizationComplete(
+ env_, j_support_, true);
+
+ ::testing::Mock::VerifyAndClearExpectations(&policy_service_);
+}
+
+TEST_F(PolicyServiceAndroidTest, OneObserver) {
+ EXPECT_CALL(policy_service_,
+ AddObserver(POLICY_DOMAIN_CHROME, policy_service_android_.get()))
+ .Times(1);
+ int observer_id =
+ Java_PolicyServiceTestSupporter_addObserver(env_, j_support_);
+
+ policy_service_android_->OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME);
+ Java_PolicyServiceTestSupporter_verifyInitializationEvent(
+ env_, j_support_, /*index*/ 0, /*times*/ 1);
+
+ policy_service_android_->OnPolicyUpdated(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), PolicyMap(),
+ PolicyMap());
+ Java_PolicyServiceTestSupporter_verifyPolicyUpdatedEvent(
+ env_, j_support_, /*index*/ 0, /*times*/ 1);
+
+ EXPECT_CALL(policy_service_, RemoveObserver(POLICY_DOMAIN_CHROME,
+ policy_service_android_.get()))
+ .Times(1);
+ Java_PolicyServiceTestSupporter_removeObserver(env_, j_support_, observer_id);
+ ::testing::Mock::VerifyAndClearExpectations(&policy_service_);
+}
+
+TEST_F(PolicyServiceAndroidTest, MultipleObservers) {
+ // When multiple observers are added in Java, only one observer will be
+ // created in C++.
+ EXPECT_CALL(policy_service_,
+ AddObserver(POLICY_DOMAIN_CHROME, policy_service_android_.get()))
+ .Times(1);
+ int observer1 = Java_PolicyServiceTestSupporter_addObserver(env_, j_support_);
+ int observer2 = Java_PolicyServiceTestSupporter_addObserver(env_, j_support_);
+
+ // And we still observing the PolicyService as long as there is one Java
+ // observer.
+ Java_PolicyServiceTestSupporter_removeObserver(env_, j_support_, observer2);
+
+ ::testing::Mock::VerifyAndClearExpectations(&policy_service_);
+
+ // Trigger the event and only the activated Java observer get notified.
+ policy_service_android_->OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME);
+ Java_PolicyServiceTestSupporter_verifyInitializationEvent(
+ env_, j_support_, observer1, /*times*/ 1);
+ Java_PolicyServiceTestSupporter_verifyInitializationEvent(
+ env_, j_support_, observer2, /*times*/ 0);
+
+ // Remove the last Java observers and triggers the C++ observer cleanup too.
+ EXPECT_CALL(policy_service_, RemoveObserver(POLICY_DOMAIN_CHROME,
+ policy_service_android_.get()))
+ .Times(1);
+ Java_PolicyServiceTestSupporter_removeObserver(env_, j_support_, observer1);
+ ::testing::Mock::VerifyAndClearExpectations(&policy_service_);
+}
+
+TEST_F(PolicyServiceAndroidTest, PolicyUpdateEvent) {
+ EXPECT_CALL(policy_service_,
+ AddObserver(POLICY_DOMAIN_CHROME, policy_service_android_.get()))
+ .Times(1);
+ int observer_id =
+ Java_PolicyServiceTestSupporter_addObserver(env_, j_support_);
+
+ PolicyMap previous;
+ PolicyMap current;
+ previous.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(1), nullptr);
+ current.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(2), nullptr);
+
+ // PolicyMapAndroid needs to be valid until the verification is over.
+ PolicyMapAndroid previous_android(previous);
+ PolicyMapAndroid current_android(current);
+
+ Java_PolicyServiceTestSupporter_setupPolicyUpdatedEventWithValues(
+ env_, j_support_, /*index*/ 0, previous_android.GetJavaObject(),
+ current_android.GetJavaObject());
+ policy_service_android_->OnPolicyUpdated(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), previous, current);
+ Java_PolicyServiceTestSupporter_verifyPolicyUpdatedEventWithValues(
+ env_, j_support_, /*index*/ 0, /*times*/ 1);
+
+ EXPECT_CALL(policy_service_, RemoveObserver(POLICY_DOMAIN_CHROME,
+ policy_service_android_.get()))
+ .Times(1);
+ Java_PolicyServiceTestSupporter_removeObserver(env_, j_support_, observer_id);
+ ::testing::Mock::VerifyAndClearExpectations(&policy_service_);
+}
+
+} // namespace android
+} // namespace policy
diff --git a/chromium/components/policy/core/common/async_policy_loader.cc b/chromium/components/policy/core/common/async_policy_loader.cc
new file mode 100644
index 00000000000..7d40e9f3bf2
--- /dev/null
+++ b/chromium/components/policy/core/common/async_policy_loader.cc
@@ -0,0 +1,205 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_loader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/management/management_service.h"
+#include "components/policy/core/common/management/platform_management_service.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+using base::Time;
+
+namespace policy {
+
+namespace {
+
+// Amount of time to wait for the files on disk to settle before trying to load
+// them. This alleviates the problem of reading partially written files and
+// makes it possible to batch quasi-simultaneous changes.
+constexpr base::TimeDelta kSettleInterval = base::Seconds(5);
+
+// The time interval for rechecking policy. This is the fallback in case the
+// implementation never detects changes.
+constexpr base::TimeDelta kReloadInterval = base::Minutes(15);
+
+} // namespace
+
+AsyncPolicyLoader::AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ bool periodic_updates)
+ : task_runner_(task_runner),
+ management_service_(nullptr),
+ periodic_updates_(periodic_updates) {}
+
+AsyncPolicyLoader::AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ ManagementService* management_service,
+ bool periodic_updates)
+ : task_runner_(task_runner),
+ management_service_(management_service),
+ periodic_updates_(periodic_updates) {}
+
+AsyncPolicyLoader::~AsyncPolicyLoader() {}
+
+Time AsyncPolicyLoader::LastModificationTime() {
+ return Time();
+}
+
+void AsyncPolicyLoader::Reload(bool force) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ base::TimeDelta delay;
+ Time now = Time::Now();
+ // Check if there was a recent modification to the underlying files.
+ if (!force && !IsSafeToReload(now, &delay)) {
+ ScheduleNextReload(delay);
+ return;
+ }
+
+ // `management_service_` must be called on the main thread.
+ // base::Unretained is okay here since `management_service_` is an instance of
+ // PlatformManagementService which is a singleton that outlives this class.
+ if (!platform_management_trustworthiness_.has_value() &&
+ management_service_) {
+ DCHECK_EQ(management_service_, PlatformManagementService::GetInstance());
+ ui_thread_task_runner_->PostTaskAndReplyWithResult(
+ FROM_HERE,
+ base::BindOnce(
+ &ManagementService::GetManagementAuthorityTrustworthiness,
+ base::Unretained(management_service_)),
+ base::BindOnce(
+ &AsyncPolicyLoader::SetPlatformManagementTrustworthinessAndReload,
+ weak_factory_.GetWeakPtr(), force));
+ return;
+ }
+
+ std::unique_ptr<PolicyBundle> bundle(Load());
+
+ // Reset so that we get the latest management trustworthiness at the next
+ // reload.
+ platform_management_trustworthiness_.reset();
+
+ // Check if there was a modification while reading.
+ if (!force && !IsSafeToReload(now, &delay)) {
+ ScheduleNextReload(delay);
+ return;
+ }
+
+ // Filter out mismatching policies.
+ schema_map_->FilterBundle(bundle.get(),
+ /*drop_invalid_component_policies=*/true);
+
+ update_callback_.Run(std::move(bundle));
+ if (periodic_updates_) {
+ ScheduleNextReload(kReloadInterval);
+ }
+}
+
+bool AsyncPolicyLoader::ShouldFilterSensitivePolicies() {
+#if BUILDFLAG(IS_WIN)
+ DCHECK(platform_management_trustworthiness_);
+
+ return *platform_management_trustworthiness_ <
+ ManagementAuthorityTrustworthiness::TRUSTED;
+#else
+ return false;
+#endif
+}
+
+void AsyncPolicyLoader::SetPlatformManagementTrustworthinessAndReload(
+ bool force,
+ ManagementAuthorityTrustworthiness trustworthiness) {
+ platform_management_trustworthiness_ = trustworthiness;
+ Reload(force);
+}
+
+std::unique_ptr<PolicyBundle> AsyncPolicyLoader::InitialLoad(
+ const scoped_refptr<SchemaMap>& schema_map) {
+ // This is the first load, early during startup. Use this to record the
+ // initial |last_modification_time_|, so that potential changes made before
+ // installing the watches can be detected.
+ last_modification_time_ = LastModificationTime();
+ schema_map_ = schema_map;
+ if (management_service_) {
+ DCHECK_EQ(management_service_, PlatformManagementService::GetInstance());
+ platform_management_trustworthiness_ =
+ management_service_->GetManagementAuthorityTrustworthiness();
+ }
+ std::unique_ptr<PolicyBundle> bundle(Load());
+ platform_management_trustworthiness_.reset();
+ // Filter out mismatching policies.
+ schema_map_->FilterBundle(bundle.get(),
+ /*drop_invalid_component_policies=*/true);
+ return bundle;
+}
+
+void AsyncPolicyLoader::Init(
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
+ const UpdateCallback& update_callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(update_callback_.is_null());
+ DCHECK(!update_callback.is_null());
+ update_callback_ = update_callback;
+ ui_thread_task_runner_ = ui_thread_task_runner;
+
+ InitOnBackgroundThread();
+
+ // There might have been changes to the underlying files since the initial
+ // load and before the watchers have been created.
+ if (LastModificationTime() != last_modification_time_)
+ Reload(false);
+
+ // Start periodic refreshes.
+ if (periodic_updates_) {
+ ScheduleNextReload(kReloadInterval);
+ }
+}
+
+void AsyncPolicyLoader::RefreshPolicies(scoped_refptr<SchemaMap> schema_map) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ schema_map_ = schema_map;
+ Reload(true);
+}
+
+void AsyncPolicyLoader::ScheduleNextReload(base::TimeDelta delay) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ weak_factory_.InvalidateWeakPtrs();
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AsyncPolicyLoader::Reload, weak_factory_.GetWeakPtr(),
+ false /* force */),
+ delay);
+}
+
+bool AsyncPolicyLoader::IsSafeToReload(const Time& now,
+ base::TimeDelta* delay) {
+ Time last_modification = LastModificationTime();
+ if (last_modification.is_null())
+ return true;
+
+ // If there was a change since the last recorded modification, wait some more.
+ if (last_modification != last_modification_time_) {
+ last_modification_time_ = last_modification;
+ last_modification_clock_ = now;
+ *delay = kSettleInterval;
+ return false;
+ }
+
+ // Check whether the settle interval has elapsed.
+ const base::TimeDelta age = now - last_modification_clock_;
+ if (age < kSettleInterval) {
+ *delay = kSettleInterval - age;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/async_policy_loader.h b/chromium/components/policy/core/common/async_policy_loader.h
new file mode 100644
index 00000000000..6d749af68a2
--- /dev/null
+++ b/chromium/components/policy/core/common/async_policy_loader.h
@@ -0,0 +1,159 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/management/management_service.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class ManagementService;
+class PolicyBundle;
+
+// Base implementation for platform-specific policy loaders. Together with the
+// AsyncPolicyProvider, this base implementation takes care of the initial load,
+// refreshing policies and object lifetime. Also if the object has
+// |period_updates_| set to true it takes care of periodic reloads and watching
+// file changes.
+//
+// All methods are invoked on the background |task_runner_|, including the
+// destructor. The only exceptions are the constructor (which may be called on
+// any thread), InitialLoad() which is called on the thread that owns the
+// provider and the calls of Load() and LastModificationTime() during the
+// initial load.
+// Also, during tests the destructor may be called on the main thread.
+class POLICY_EXPORT AsyncPolicyLoader {
+ public:
+ explicit AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ bool periodic_updates);
+ explicit AsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ ManagementService* management_service,
+ bool periodic_updates);
+ AsyncPolicyLoader(const AsyncPolicyLoader&) = delete;
+ AsyncPolicyLoader& operator=(const AsyncPolicyLoader&) = delete;
+ virtual ~AsyncPolicyLoader();
+
+ // Gets a SequencedTaskRunner backed by the background thread.
+ base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
+
+ // Returns the currently configured policies. Load() is always invoked on
+ // the background thread, except for the initial Load() at startup which is
+ // invoked from the thread that owns the provider.
+ virtual std::unique_ptr<PolicyBundle> Load() = 0;
+
+ // Allows implementations to finalize their initialization on the background
+ // thread (e.g. setup file watchers).
+ virtual void InitOnBackgroundThread() = 0;
+
+ // Implementations should return the time of the last modification detected,
+ // or base::Time() if it doesn't apply, which is the default.
+ virtual base::Time LastModificationTime();
+
+ // Used by the AsyncPolicyProvider to do the initial Load(). The first load
+ // is also used to initialize |last_modification_time_| and
+ // |schema_map_|.
+ std::unique_ptr<PolicyBundle> InitialLoad(
+ const scoped_refptr<SchemaMap>& schemas);
+
+ // Implementations should invoke Reload() when a change is detected. This
+ // must be invoked from the background thread and will trigger a Load(),
+ // and pass the returned bundle to the provider.
+ // The load is immediate when |force| is true. Otherwise, the loader
+ // reschedules the reload until the LastModificationTime() is a couple of
+ // seconds in the past. This mitigates the problem of reading files that are
+ // currently being written to, and whose contents are incomplete.
+ // When |periodic_updates_| is true a reload is posted periodically, if it
+ // hasn't been triggered recently. This makes sure the policies are reloaded
+ // if the update events aren't triggered.
+ void Reload(bool force);
+
+ // Returns `true` and only if the platform is not managed by a trusted source.
+ bool ShouldFilterSensitivePolicies();
+ void SetPlatformManagementTrustworthinessAndReload(
+ bool force,
+ ManagementAuthorityTrustworthiness trustworthiness);
+
+ const scoped_refptr<SchemaMap>& schema_map() const { return schema_map_; }
+
+ private:
+ // Allow AsyncPolicyProvider to call Init().
+ friend class AsyncPolicyProvider;
+
+ typedef base::RepeatingCallback<void(std::unique_ptr<PolicyBundle>)>
+ UpdateCallback;
+
+ void ReloadInternal(bool force);
+
+ // Used by the AsyncPolicyProvider to install the |update_callback_|.
+ // Invoked on the background thread.
+ void Init(scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
+ const UpdateCallback& update_callback);
+
+ // Used by the AsyncPolicyProvider to reload with an updated SchemaMap.
+ void RefreshPolicies(scoped_refptr<SchemaMap> schema_map);
+
+ // Cancels any pending periodic reload and posts one |delay| time units from
+ // now.
+ void ScheduleNextReload(base::TimeDelta delay);
+
+ // Checks if the underlying files haven't changed recently, by checking the
+ // LastModificationTime(). |delay| is updated with a suggested time to wait
+ // before retrying when this returns false.
+ bool IsSafeToReload(const base::Time& now, base::TimeDelta* delay);
+
+ // Task runner for running background jobs.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Task runner for running foregroud jobs.
+ scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner_;
+
+ bool loading_management_trustworhiness_ = false;
+ absl::optional<ManagementAuthorityTrustworthiness>
+ platform_management_trustworthiness_;
+
+ raw_ptr<ManagementService> management_service_;
+
+ // Whether the loader will schedule periodic updates for policy data.
+ const bool periodic_updates_;
+
+ // Callback for updates, passed in Init().
+ UpdateCallback update_callback_;
+
+ // Records last known modification timestamp.
+ base::Time last_modification_time_;
+
+ // The wall clock time at which the last modification timestamp was
+ // recorded. It's better to not assume the file notification time and the
+ // wall clock times come from the same source, just in case there is some
+ // non-local filesystem involved.
+ base::Time last_modification_clock_;
+
+ // The current policy schemas that this provider should load.
+ scoped_refptr<SchemaMap> schema_map_;
+
+ // Used to get WeakPtrs for the periodic reload task.
+ base::WeakPtrFactory<AsyncPolicyLoader> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_LOADER_H_
diff --git a/chromium/components/policy/core/common/async_policy_provider.cc b/chromium/components/policy/core/common/async_policy_provider.cc
new file mode 100644
index 00000000000..327c9f5caaf
--- /dev/null
+++ b/chromium/components/policy/core/common/async_policy_provider.cc
@@ -0,0 +1,130 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_provider.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/schema_registry.h"
+
+namespace policy {
+
+AsyncPolicyProvider::AsyncPolicyProvider(
+ SchemaRegistry* registry,
+ std::unique_ptr<AsyncPolicyLoader> loader)
+ : loader_(std::move(loader)), first_policies_loaded_(false) {
+ // Make an immediate synchronous load on startup.
+ OnLoaderReloaded(loader_->InitialLoad(registry->schema_map()));
+}
+
+AsyncPolicyProvider::~AsyncPolicyProvider() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void AsyncPolicyProvider::Init(SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ConfigurationPolicyProvider::Init(registry);
+
+ if (!loader_)
+ return;
+
+ AsyncPolicyLoader::UpdateCallback callback = base::BindRepeating(
+ &AsyncPolicyProvider::LoaderUpdateCallback,
+ base::ThreadTaskRunnerHandle::Get(), weak_factory_.GetWeakPtr());
+ bool post = loader_->task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AsyncPolicyLoader::Init, base::Unretained(loader_.get()),
+ base::ThreadTaskRunnerHandle::Get(), callback));
+ DCHECK(post) << "AsyncPolicyProvider::Init() called with threads not running";
+}
+
+void AsyncPolicyProvider::Shutdown() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Note on the lifetime of |loader_|:
+ // The |loader_| lives on the background thread, and is deleted from here.
+ // This means that posting tasks on the |loader_| to the background thread
+ // from the AsyncPolicyProvider is always safe, since a potential DeleteSoon()
+ // is only posted from here. The |loader_| posts back to the
+ // AsyncPolicyProvider through the |update_callback_|, which has a WeakPtr to
+ // |this|.
+ // If threads are spinning, delete the loader on the thread it lives on. If
+ // there are no threads, kill it immediately.
+ AsyncPolicyLoader* loader_to_delete = loader_.release();
+ if (!loader_to_delete->task_runner()->DeleteSoon(FROM_HERE, loader_to_delete))
+ delete loader_to_delete;
+ ConfigurationPolicyProvider::Shutdown();
+}
+
+void AsyncPolicyProvider::RefreshPolicies() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Subtle: RefreshPolicies() has a contract that requires the next policy
+ // update notification (triggered from UpdatePolicy()) to reflect any changes
+ // made before this call. So if a caller has modified the policy settings and
+ // invoked RefreshPolicies(), then by the next notification these policies
+ // should already be provided.
+ // However, it's also possible that an asynchronous Reload() is in progress
+ // and just posted OnLoaderReloaded(). Therefore a task is posted to the
+ // background thread before posting the next Reload, to prevent a potential
+ // concurrent Reload() from triggering a notification too early. If another
+ // refresh task has been posted, it is invalidated now.
+ if (!loader_)
+ return;
+ refresh_callback_.Reset(
+ base::BindOnce(&AsyncPolicyProvider::ReloadAfterRefreshSync,
+ weak_factory_.GetWeakPtr()));
+ loader_->task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+ refresh_callback_.callback());
+}
+
+bool AsyncPolicyProvider::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return first_policies_loaded_;
+}
+
+void AsyncPolicyProvider::ReloadAfterRefreshSync() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // There can't be another refresh callback pending now, since its creation
+ // in RefreshPolicies() would have cancelled the current execution. So it's
+ // safe to cancel the |refresh_callback_| now, so that OnLoaderReloaded()
+ // sees that there is no refresh pending.
+ refresh_callback_.Cancel();
+
+ if (!loader_)
+ return;
+
+ loader_->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&AsyncPolicyLoader::RefreshPolicies,
+ base::Unretained(loader_.get()), schema_map()));
+}
+
+void AsyncPolicyProvider::OnLoaderReloaded(
+ std::unique_ptr<PolicyBundle> bundle) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ first_policies_loaded_ = true;
+ // Only propagate policy updates if there are no pending refreshes, and if
+ // Shutdown() hasn't been called yet.
+ if (refresh_callback_.IsCancelled() && loader_)
+ UpdatePolicy(std::move(bundle));
+}
+
+// static
+void AsyncPolicyProvider::LoaderUpdateCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ base::WeakPtr<AsyncPolicyProvider> weak_this,
+ std::unique_ptr<PolicyBundle> bundle) {
+ runner->PostTask(FROM_HERE,
+ base::BindOnce(&AsyncPolicyProvider::OnLoaderReloaded,
+ weak_this, std::move(bundle)));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/async_policy_provider.h b/chromium/components/policy/core/common/async_policy_provider.h
new file mode 100644
index 00000000000..723dab5851f
--- /dev/null
+++ b/chromium/components/policy/core/common/async_policy_provider.h
@@ -0,0 +1,82 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
+
+#include <memory>
+
+#include "base/cancelable_callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace policy {
+
+class AsyncPolicyLoader;
+class PolicyBundle;
+class SchemaRegistry;
+
+// A policy provider that loads its policies asynchronously on a background
+// thread. Platform-specific providers are created by passing an implementation
+// of AsyncPolicyLoader to a new AsyncPolicyProvider.
+class POLICY_EXPORT AsyncPolicyProvider : public ConfigurationPolicyProvider {
+ public:
+ // The AsyncPolicyProvider does a synchronous load in its constructor, and
+ // therefore it needs the |registry| at construction time. The same |registry|
+ // should be passed later to Init().
+ AsyncPolicyProvider(SchemaRegistry* registry,
+ std::unique_ptr<AsyncPolicyLoader> loader);
+ AsyncPolicyProvider(const AsyncPolicyProvider&) = delete;
+ AsyncPolicyProvider& operator=(const AsyncPolicyProvider&) = delete;
+ ~AsyncPolicyProvider() override;
+
+ // ConfigurationPolicyProvider implementation.
+ void Init(SchemaRegistry* registry) override;
+ void Shutdown() override;
+ void RefreshPolicies() override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+
+ private:
+ // Helper for RefreshPolicies().
+ void ReloadAfterRefreshSync();
+
+ // Invoked with the latest bundle loaded by the |loader_|.
+ void OnLoaderReloaded(std::unique_ptr<PolicyBundle> bundle);
+
+ // Callback passed to the loader that it uses to pass back the current policy
+ // bundle to the provider. This is invoked on the background thread and
+ // forwards to OnLoaderReloaded() on the runner that owns the provider,
+ // if |weak_this| is still valid.
+ static void LoaderUpdateCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ base::WeakPtr<AsyncPolicyProvider> weak_this,
+ std::unique_ptr<PolicyBundle> bundle);
+
+ // The |loader_| that does the platform-specific policy loading. It lives
+ // on the background thread but is owned by |this|.
+ std::unique_ptr<AsyncPolicyLoader> loader_;
+
+ // Callback used to synchronize RefreshPolicies() calls with the background
+ // thread. See the implementation for the details.
+ base::CancelableOnceClosure refresh_callback_;
+
+ bool first_policies_loaded_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Used to get a WeakPtr to |this| for the update callback given to the
+ // loader.
+ base::WeakPtrFactory<AsyncPolicyProvider> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_ASYNC_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/async_policy_provider_unittest.cc b/chromium/components/policy/core/common/async_policy_provider_unittest.cc
new file mode 100644
index 00000000000..64279bf968a
--- /dev/null
+++ b/chromium/components/policy/core/common/async_policy_provider_unittest.cc
@@ -0,0 +1,229 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/async_policy_provider.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::Sequence;
+
+namespace policy {
+
+namespace {
+
+// Helper to write a policy in |bundle| with less code.
+void SetPolicy(PolicyBundle* bundle,
+ const std::string& name,
+ const std::string& value) {
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(value), nullptr);
+}
+
+class MockPolicyLoader : public AsyncPolicyLoader {
+ public:
+ explicit MockPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ MockPolicyLoader(const MockPolicyLoader&) = delete;
+ MockPolicyLoader& operator=(const MockPolicyLoader&) = delete;
+ ~MockPolicyLoader() override;
+
+ // Load() returns a std::unique_ptr<PolicyBundle> but it can't be mocked
+ // because std::unique_ptr is moveable but not copyable. This override
+ // forwards the call to MockLoad() which returns a PolicyBundle*, and returns
+ // a copy wrapped in a std::unique_ptr.
+ std::unique_ptr<PolicyBundle> Load() override;
+
+ MOCK_METHOD0(MockLoad, const PolicyBundle*());
+ MOCK_METHOD0(InitOnBackgroundThread, void());
+ MOCK_METHOD0(LastModificationTime, base::Time());
+};
+
+MockPolicyLoader::MockPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/true) {}
+
+MockPolicyLoader::~MockPolicyLoader() {}
+
+std::unique_ptr<PolicyBundle> MockPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> bundle;
+ const PolicyBundle* loaded = MockLoad();
+ if (loaded) {
+ bundle = std::make_unique<PolicyBundle>();
+ bundle->CopyFrom(*loaded);
+ }
+ return bundle;
+}
+
+} // namespace
+
+class AsyncPolicyProviderTest : public testing::Test {
+ public:
+ AsyncPolicyProviderTest(const AsyncPolicyProviderTest&) = delete;
+ AsyncPolicyProviderTest& operator=(const AsyncPolicyProviderTest&) = delete;
+
+ protected:
+ AsyncPolicyProviderTest();
+ ~AsyncPolicyProviderTest() override;
+
+ void SetUp() override;
+ void TearDown() override;
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ SchemaRegistry schema_registry_;
+ PolicyBundle initial_bundle_;
+ raw_ptr<MockPolicyLoader> loader_;
+ std::unique_ptr<AsyncPolicyProvider> provider_;
+};
+
+AsyncPolicyProviderTest::AsyncPolicyProviderTest() {}
+
+AsyncPolicyProviderTest::~AsyncPolicyProviderTest() {}
+
+void AsyncPolicyProviderTest::SetUp() {
+ SetPolicy(&initial_bundle_, "policy", "initial");
+ loader_ = new MockPolicyLoader(base::ThreadTaskRunnerHandle::Get());
+ EXPECT_CALL(*loader_, LastModificationTime())
+ .WillRepeatedly(Return(base::Time()));
+ EXPECT_CALL(*loader_, InitOnBackgroundThread()).Times(1);
+ EXPECT_CALL(*loader_, MockLoad()).WillOnce(Return(&initial_bundle_));
+
+ provider_ = std::make_unique<AsyncPolicyProvider>(
+ &schema_registry_, std::unique_ptr<AsyncPolicyLoader>(loader_));
+ provider_->Init(&schema_registry_);
+ // Verify that the initial load is done synchronously:
+ EXPECT_TRUE(provider_->policies().Equals(initial_bundle_));
+
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(loader_);
+
+ EXPECT_CALL(*loader_, LastModificationTime())
+ .WillRepeatedly(Return(base::Time()));
+}
+
+void AsyncPolicyProviderTest::TearDown() {
+ if (provider_) {
+ provider_->Shutdown();
+ provider_.reset();
+ }
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPolicies) {
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+ EXPECT_CALL(*loader_, MockLoad()).WillOnce(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPoliciesTwice) {
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+ EXPECT_CALL(*loader_, MockLoad()).WillRepeatedly(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ // Doesn't refresh before going through the background thread.
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Doesn't refresh if another RefreshPolicies request is made.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ Mock::VerifyAndClearExpectations(&observer);
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, RefreshPoliciesDuringReload) {
+ PolicyBundle reloaded_bundle;
+ SetPolicy(&reloaded_bundle, "policy", "reloaded");
+ PolicyBundle refreshed_bundle;
+ SetPolicy(&refreshed_bundle, "policy", "refreshed");
+
+ Sequence load_sequence;
+ // Reload.
+ EXPECT_CALL(*loader_, MockLoad()).InSequence(load_sequence)
+ .WillOnce(Return(&reloaded_bundle));
+ // RefreshPolicies.
+ EXPECT_CALL(*loader_, MockLoad()).InSequence(load_sequence)
+ .WillOnce(Return(&refreshed_bundle));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+
+ // A Reload is triggered before RefreshPolicies, and it shouldn't trigger
+ // notifications.
+ loader_->Reload(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Doesn't refresh before going through the background thread.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ base::RunLoop().RunUntilIdle();
+ // The refreshed policies are now provided, and the |reloaded_bundle| was
+ // dropped.
+ EXPECT_TRUE(provider_->policies().Equals(refreshed_bundle));
+ Mock::VerifyAndClearExpectations(&observer);
+ provider_->RemoveObserver(&observer);
+}
+
+TEST_F(AsyncPolicyProviderTest, Shutdown) {
+ EXPECT_CALL(*loader_, MockLoad()).WillRepeatedly(Return(&initial_bundle_));
+
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+
+ // Though there is a pending Reload, the provider and the loader can be
+ // deleted at any time.
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ loader_->Reload(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(0);
+ provider_->Shutdown();
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ provider_->RemoveObserver(&observer);
+ provider_.reset();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/chrome_schema.cc b/chromium/components/policy/core/common/chrome_schema.cc
new file mode 100644
index 00000000000..e13ffd4611d
--- /dev/null
+++ b/chromium/components/policy/core/common/chrome_schema.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/chrome_schema.h"
+
+#include "base/no_destructor.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+
+namespace policy {
+
+const Schema& GetChromeSchema() {
+ static const base::NoDestructor<Schema> chrome_schema_(
+ Schema::Wrap(GetChromeSchemaData()));
+ return *chrome_schema_;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/chrome_schema.h b/chromium/components/policy/core/common/chrome_schema.h
new file mode 100644
index 00000000000..7db7a29bcb7
--- /dev/null
+++ b/chromium/components/policy/core/common/chrome_schema.h
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CHROME_SCHEMA_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CHROME_SCHEMA_H_
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class Schema;
+
+// Returns the policy Schema generated from policy_templates.json
+// Takes up very little memory, never destroyed.
+POLICY_EXPORT const Schema& GetChromeSchema();
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CHROME_SCHEMA_H_
diff --git a/chromium/components/policy/core/common/cloud/DEPS b/chromium/components/policy/core/common/cloud/DEPS
new file mode 100644
index 00000000000..b12c25683b1
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+components/signin/public",
+ "+components/user_manager",
+]
diff --git a/chromium/components/policy/core/common/cloud/OWNERS b/chromium/components/policy/core/common/cloud/OWNERS
new file mode 100644
index 00000000000..d44b0ee4618
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/OWNERS
@@ -0,0 +1,3 @@
+per-file machine_level_user_cloud_policy*=rogerta@chromium.org
+per-file machine_level_user_cloud_policy*=zmin@chromium.org
+per-file encrypted*=file://components/reporting/OWNERS
diff --git a/chromium/components/policy/core/common/cloud/affiliation.cc b/chromium/components/policy/core/common/cloud/affiliation.cc
new file mode 100644
index 00000000000..5f07deea13e
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/affiliation.cc
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/affiliation.h"
+
+namespace policy {
+
+bool IsAffiliated(const base::flat_set<std::string>& user_ids,
+ const base::flat_set<std::string>& device_ids) {
+ for (const std::string& device_id : device_ids) {
+ if (user_ids.count(device_id))
+ return true;
+ }
+ return false;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/affiliation.h b/chromium/components/policy/core/common/cloud/affiliation.h
new file mode 100644
index 00000000000..10dc324e013
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/affiliation.h
@@ -0,0 +1,24 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_AFFILIATION_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_AFFILIATION_H_
+
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Returns true if the user and browser are managed by the same customer
+// (affiliated). This is determined by comparing affiliation IDs obtained in the
+// policy fetching response. If either policies has no affiliation IDs, this
+// function returns false.
+POLICY_EXPORT bool IsAffiliated(const base::flat_set<std::string>& user_ids,
+ const base::flat_set<std::string>& device_ids);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_AFFILIATION_H_
diff --git a/chromium/components/policy/core/common/cloud/affiliation_unittest.cc b/chromium/components/policy/core/common/cloud/affiliation_unittest.cc
new file mode 100644
index 00000000000..b63e6cd986c
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/affiliation_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/affiliation.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+constexpr char kAffiliationId1[] = "abc";
+constexpr char kAffiliationId2[] = "def";
+} // namespace
+
+TEST(CloudManagementAffiliationTest, Affiliated) {
+ base::flat_set<std::string> user_ids;
+ user_ids.insert(kAffiliationId1);
+ user_ids.insert(kAffiliationId2);
+
+ base::flat_set<std::string> device_ids;
+ device_ids.insert(kAffiliationId1);
+
+ EXPECT_TRUE(policy::IsAffiliated(user_ids, device_ids));
+}
+
+TEST(CloudManagementAffiliationTest, Unaffiliated) {
+ base::flat_set<std::string> user_ids;
+ user_ids.insert(kAffiliationId1);
+
+ base::flat_set<std::string> device_ids;
+ user_ids.insert(kAffiliationId2);
+
+ EXPECT_FALSE(IsAffiliated(user_ids, device_ids));
+}
+
+TEST(CloudManagementAffiliationTest, UserIdsEmpty) {
+ base::flat_set<std::string> user_ids;
+ base::flat_set<std::string> device_ids;
+ user_ids.insert(kAffiliationId1);
+
+ EXPECT_FALSE(IsAffiliated(user_ids, device_ids));
+}
+
+TEST(CloudManagementAffiliationTest, DeviceIdsEmpty) {
+ base::flat_set<std::string> user_ids;
+ user_ids.insert(kAffiliationId1);
+ base::flat_set<std::string> device_ids;
+
+ EXPECT_FALSE(IsAffiliated(user_ids, device_ids));
+}
+
+TEST(CloudManagementAffiliationTest, BothIdsEmpty) {
+ base::flat_set<std::string> user_ids;
+ base::flat_set<std::string> device_ids;
+
+ EXPECT_FALSE(IsAffiliated(user_ids, device_ids));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/chrome_browser_cloud_management_metrics.h b/chromium/components/policy/core/common/cloud/chrome_browser_cloud_management_metrics.h
new file mode 100644
index 00000000000..62ffe003c9a
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/chrome_browser_cloud_management_metrics.h
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CHROME_BROWSER_CLOUD_MANAGEMENT_METRICS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CHROME_BROWSER_CLOUD_MANAGEMENT_METRICS_H_
+
+namespace policy {
+
+// This enum is used for recording the metrics. It must match the
+// MachineLevelUserCloudPolicyEnrollmentResult in enums.xml and should not be
+// reordered. |kMaxValue| must be assigned to the last entry of the enum.
+enum class ChromeBrowserCloudManagementEnrollmentResult {
+ kSuccess = 0,
+ kFailedToFetch = 1,
+ kFailedToStore = 2,
+ kMaxValue = kFailedToStore,
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CHROME_BROWSER_CLOUD_MANAGEMENT_METRICS_H_
diff --git a/chromium/components/policy/core/common/cloud/client_data_delegate.h b/chromium/components/policy/core/common/cloud/client_data_delegate.h
new file mode 100644
index 00000000000..cb3358eaefc
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/client_data_delegate.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLIENT_DATA_DELEGATE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLIENT_DATA_DELEGATE_H_
+
+#include "base/callback_forward.h"
+
+namespace enterprise_management {
+class RegisterBrowserRequest;
+} // namespace enterprise_management
+
+namespace policy {
+
+// Sets platform-specific fields in request protos for the DMServer.
+class ClientDataDelegate {
+ public:
+ ClientDataDelegate() = default;
+ ClientDataDelegate(const ClientDataDelegate&) = delete;
+ ClientDataDelegate& operator=(const ClientDataDelegate&) = delete;
+ virtual ~ClientDataDelegate() = default;
+
+ virtual void FillRegisterBrowserRequest(
+ enterprise_management::RegisterBrowserRequest* request,
+ base::OnceClosure callback) const = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLIENT_DATA_DELEGATE_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_external_data_manager.cc b/chromium/components/policy/core/common/cloud/cloud_external_data_manager.cc
new file mode 100644
index 00000000000..cf1e57c4422
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_external_data_manager.cc
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "crypto/sha2.h"
+
+namespace policy {
+
+CloudExternalDataManager::MetadataEntry::MetadataEntry() {
+}
+
+CloudExternalDataManager::MetadataEntry::MetadataEntry(const std::string& url,
+ const std::string& hash)
+ : url(url),
+ hash(hash) {
+}
+
+bool CloudExternalDataManager::MetadataEntry::operator!=(
+ const MetadataEntry& other) const {
+ return url != other.url || hash != other.hash;
+}
+
+CloudExternalDataManager::MetadataKey::MetadataKey() = default;
+
+CloudExternalDataManager::MetadataKey::MetadataKey(const std::string& policy)
+ : policy(policy) {}
+
+CloudExternalDataManager::MetadataKey::MetadataKey(
+ const std::string& policy,
+ const std::string& field_name)
+ : policy(policy), field_name(field_name) {}
+
+bool CloudExternalDataManager::MetadataKey::operator<(
+ const MetadataKey& other) const {
+ return policy < other.policy ||
+ (policy == other.policy && field_name < other.field_name);
+}
+
+// Hashing to avoid future parsing of this string
+std::string CloudExternalDataManager::MetadataKey::ToString() const {
+ return base::StrCat(
+ {crypto::SHA256HashString(policy), crypto::SHA256HashString(field_name)});
+}
+
+CloudExternalDataManager::CloudExternalDataManager() : policy_store_(nullptr) {}
+
+CloudExternalDataManager::~CloudExternalDataManager() {
+}
+
+void CloudExternalDataManager::SetPolicyStore(CloudPolicyStore* policy_store) {
+ weak_factory_.InvalidateWeakPtrs();
+ policy_store_ = policy_store;
+ if (policy_store_)
+ policy_store_->SetExternalDataManager(weak_factory_.GetWeakPtr());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_external_data_manager.h b/chromium/components/policy/core/common/cloud/cloud_external_data_manager.h
new file mode 100644
index 00000000000..30e11520abb
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_external_data_manager.h
@@ -0,0 +1,92 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/policy_export.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class CloudPolicyStore;
+
+// Downloads, verifies, caches and retrieves external data referenced by
+// policies.
+// This a common base class used by cloud policy implementations and mocks.
+class POLICY_EXPORT CloudExternalDataManager : public ExternalDataManager {
+ public:
+ struct POLICY_EXPORT MetadataEntry {
+ MetadataEntry();
+ MetadataEntry(const std::string& url, const std::string& hash);
+
+ bool operator!=(const MetadataEntry& other) const;
+
+ std::string url;
+ std::string hash;
+ };
+
+ struct POLICY_EXPORT MetadataKey {
+ MetadataKey();
+ explicit MetadataKey(const std::string& policy);
+ MetadataKey(const std::string& policy, const std::string& field_name);
+
+ bool operator<(const MetadataKey& other) const;
+
+ // For situations where you need an opaque identifier for the key, e.g.
+ // the ExternalPolicyDataUpdater or the CloudExternalDataStore.
+ // Do not use in situation where you might need to parse this.
+ std::string ToString() const;
+
+ std::string policy;
+ std::string field_name;
+ };
+
+ // Maps from policy names to the metadata specifying the external data that
+ // each of the policies references.
+ typedef std::map<MetadataKey, MetadataEntry> Metadata;
+
+ CloudExternalDataManager();
+ CloudExternalDataManager(const CloudExternalDataManager&) = delete;
+ CloudExternalDataManager& operator=(const CloudExternalDataManager&) = delete;
+ virtual ~CloudExternalDataManager();
+
+ // Sets the source of external data references to |policy_store|. The manager
+ // will start observing |policy_store| so that when external data references
+ // change, obsolete data can be deleted and new data can be downloaded. If the
+ // |policy_store| is destroyed before the manager, the connection must be
+ // severed first by calling SetPolicyStore(NULL).
+ virtual void SetPolicyStore(CloudPolicyStore* policy_store);
+
+ // Called by the |policy_store_| when policy changes.
+ virtual void OnPolicyStoreLoaded() = 0;
+
+ // Allows the manager to download external data by constructing URLLoaders
+ // from |url_loader_factory|.
+ virtual void Connect(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) = 0;
+
+ // Prevents further external data downloads and aborts any downloads currently
+ // in progress.
+ virtual void Disconnect() = 0;
+
+ protected:
+ raw_ptr<CloudPolicyStore> policy_store_; // Not owned.
+
+ base::WeakPtrFactory<CloudExternalDataManager> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_MANAGER_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_external_data_store.cc b/chromium/components/policy/core/common/cloud/cloud_external_data_store.cc
new file mode 100644
index 00000000000..8e759bb82d8
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_external_data_store.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_external_data_store.h"
+
+#include <set>
+
+#include "base/check.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "crypto/sha2.h"
+
+namespace policy {
+
+namespace {
+
+// Encodes (key, hash) into a single string.
+std::string GetSubkey(const std::string& key, const std::string& hash) {
+ DCHECK(!key.empty());
+ DCHECK(!hash.empty());
+ return base::NumberToString(key.size()) + ":" +
+ base::NumberToString(hash.size()) + ":" + key + hash;
+}
+
+} // namespace
+
+CloudExternalDataStore::CloudExternalDataStore(
+ const std::string& cache_key,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ResourceCache* cache)
+ : cache_key_(cache_key), task_runner_(task_runner), cache_(cache) {}
+
+CloudExternalDataStore::~CloudExternalDataStore() {
+ // No RunsTasksInCurrentSequence() check to avoid unit tests failures.
+ // In unit tests the browser process instance is deleted only after test ends
+ // and test task scheduler is shutted down. Therefore we need to delete some
+ // components of BrowserPolicyConnector (ResourceCache and
+ // CloudExternalDataManagerBase::Backend) manually when task runner doesn't
+ // accept new tasks (DeleteSoon in this case). This leads to the situation
+ // when this destructor is called not on |task_runner|.
+}
+
+void CloudExternalDataStore::Prune(const PruningData& key_hash_pairs_to_keep) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ std::set<std::string> subkeys_to_keep;
+ for (const auto& it : key_hash_pairs_to_keep) {
+ subkeys_to_keep.insert(GetSubkey(it.first, it.second));
+ }
+ cache_->PurgeOtherSubkeys(cache_key_, subkeys_to_keep);
+}
+
+base::FilePath CloudExternalDataStore::Store(const std::string& key,
+ const std::string& hash,
+ const std::string& data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return cache_->Store(cache_key_, GetSubkey(key, hash), data);
+}
+
+base::FilePath CloudExternalDataStore::Load(const std::string& key,
+ const std::string& hash,
+ size_t max_size,
+ std::string* data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ const std::string subkey = GetSubkey(key, hash);
+ base::FilePath file_path = cache_->Load(cache_key_, subkey, data);
+ if (!file_path.empty()) {
+ if (data->size() <= max_size && crypto::SHA256HashString(*data) == hash)
+ return file_path;
+ // If the data is larger than allowed or does not match the expected hash,
+ // delete the entry.
+ cache_->Delete(cache_key_, subkey);
+ data->clear();
+ }
+ return base::FilePath();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_external_data_store.h b/chromium/components/policy/core/common/cloud/cloud_external_data_store.h
new file mode 100644
index 00000000000..b63ceb97cc7
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_external_data_store.h
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_STORE_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class ResourceCache;
+
+// Stores external data referenced by policies. Data is keyed by (key, hash),
+// where |key| is an opaque string that comes from the policy referencing the
+// data, and |hash| is the data's SHA256 hash. Outdated entries are removed by
+// calling Prune() with the list of (key, hash) entries that are to be kept.
+// Instances of this class may be created on any thread and may share the same
+// cache, however:
+// * After creation, the cache and all stores using it must always be accessed
+// via the same |task_runner| only.
+// * Stores sharing a cache must use different cache_keys to avoid namespace
+// overlaps.
+// * The cache must outlive all stores using it.
+class POLICY_EXPORT CloudExternalDataStore {
+ public:
+ CloudExternalDataStore(const std::string& cache_key,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ResourceCache* cache);
+ CloudExternalDataStore(const CloudExternalDataStore&) = delete;
+ CloudExternalDataStore& operator=(const CloudExternalDataStore&) = delete;
+ ~CloudExternalDataStore();
+
+ // Removes all entries from the store whose (policy, hash) pair is not found
+ // in |metadata|.
+ using PruningData = std::vector<std::pair<std::string, std::string>>;
+ void Prune(const PruningData& key_hash_pairs_to_keep);
+
+ // Stores |data| under (key, hash). Returns file path if the store
+ // succeeded, and empty FilePath otherwise.
+ base::FilePath Store(const std::string& key,
+ const std::string& hash,
+ const std::string& data);
+
+ // Loads the entry at (key, hash) into |data|, verifies that it does not
+ // exceed |max_size| and matches the expected |hash|, then returns true.
+ // Returns empty FilePath if no entry is found at (key, hash), there is a
+ // problem during the load, the entry exceeds |max_size| or does not match
+ // |hash|, and file path otherwise.
+ base::FilePath Load(const std::string& key,
+ const std::string& hash,
+ size_t max_size,
+ std::string* data);
+
+ private:
+ std::string cache_key_;
+
+ // Task runner that |this| runs on.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ raw_ptr<ResourceCache> cache_; // Not owned.
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_EXTERNAL_DATA_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_external_data_store_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_external_data_store_unittest.cc
new file mode 100644
index 00000000000..a324aa1f1fb
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_external_data_store_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_external_data_store.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/test_simple_task_runner.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "crypto/sha2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+namespace {
+
+const char kKey1[] = "Key 1";
+const char kKey2[] = "Key 2";
+const char kPolicy1[] = "Test policy 1";
+const char kPolicy2[] = "Test policy 2";
+std::string kPolicy1Key =
+ CloudExternalDataManager::MetadataKey(kPolicy1).ToString();
+std::string kPolicy2Key =
+ CloudExternalDataManager::MetadataKey(kPolicy2).ToString();
+const char kData1[] = "Testing data 1";
+const char kData2[] = "Testing data 2";
+const size_t kMaxSize = 100;
+
+} // namespace
+
+class CloudExternalDataStoreTest : public testing::Test {
+ public:
+ CloudExternalDataStoreTest();
+ CloudExternalDataStoreTest(const CloudExternalDataStoreTest&) = delete;
+ CloudExternalDataStoreTest& operator=(const CloudExternalDataStoreTest&) =
+ delete;
+
+ void SetUp() override;
+
+ protected:
+ const std::string kData1Hash;
+ const std::string kData2Hash;
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ScopedTempDir temp_dir_;
+ std::unique_ptr<ResourceCache> resource_cache_;
+};
+
+CloudExternalDataStoreTest::CloudExternalDataStoreTest()
+ : kData1Hash(crypto::SHA256HashString(kData1)),
+ kData2Hash(crypto::SHA256HashString(kData2)),
+ task_runner_(new base::TestSimpleTaskRunner) {}
+
+void CloudExternalDataStoreTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ resource_cache_ =
+ std::make_unique<ResourceCache>(temp_dir_.GetPath(), task_runner_,
+ /* max_cache_size */ absl::nullopt);
+}
+
+TEST_F(CloudExternalDataStoreTest, StoreAndLoad) {
+ // Write an entry to a store.
+ CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData1).empty());
+
+ // Check that loading and verifying the entry against an invalid hash fails.
+ std::string data;
+ EXPECT_TRUE(store.Load(kPolicy1, kData2Hash, kMaxSize, &data).empty());
+
+ // Check that loading and verifying the entry against its hash succeeds.
+ EXPECT_FALSE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
+ EXPECT_EQ(kData1, data);
+}
+
+TEST_F(CloudExternalDataStoreTest, StoreTooLargeAndLoad) {
+ // Write an entry to a store.
+ CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData2).empty());
+
+ // Check that the entry has been written to the resource cache backing the
+ // store.
+ std::map<std::string, std::string> contents;
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData2, contents.begin()->second);
+
+ // Check that loading the entry fails when the maximum allowed data size is
+ // smaller than the entry size.
+ std::string data;
+ EXPECT_TRUE(store.Load(kPolicy1, kData1Hash, 1, &data).empty());
+
+ // Verify that the oversized entry has been detected and removed from the
+ // resource cache.
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ EXPECT_TRUE(contents.empty());
+}
+
+TEST_F(CloudExternalDataStoreTest, StoreInvalidAndLoad) {
+ // Construct a store entry whose hash and contents do not match.
+ CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData2).empty());
+
+ // Check that the entry has been written to the resource cache backing the
+ // store.
+ std::map<std::string, std::string> contents;
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData2, contents.begin()->second);
+
+ // Check that loading and verifying the entry against its hash fails.
+ std::string data;
+ EXPECT_TRUE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
+
+ // Verify that the corrupted entry has been detected and removed from the
+ // resource cache.
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ EXPECT_TRUE(contents.empty());
+}
+
+TEST_F(CloudExternalDataStoreTest, Prune) {
+ // Write two entries to a store.
+ CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData1).empty());
+ EXPECT_FALSE(store.Store(kPolicy2, kData2Hash, kData2).empty());
+
+ // Check that loading and verifying the entries against their hashes succeeds.
+ std::string data;
+ EXPECT_FALSE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
+ EXPECT_EQ(kData1, data);
+ EXPECT_FALSE(store.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
+ EXPECT_EQ(kData2, data);
+
+ // Prune the store, allowing only an entry for the first policy with its
+ // current hash to be kept.
+ std::vector<std::pair<std::string, std::string>> prune_data;
+ prune_data.emplace_back(kPolicy1, kData1Hash);
+ store.Prune(prune_data);
+
+ // Check that the entry for the second policy has been removed from the
+ // resource cache backing the store.
+ std::map<std::string, std::string> contents;
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData1, contents.begin()->second);
+
+ // Prune the store, allowing only an entry for the first policy with a
+ // different hash to be kept.
+ prune_data.clear();
+ prune_data.emplace_back(kPolicy1, kData2Hash);
+ store.Prune(prune_data);
+
+ // Check that the entry for the first policy has been removed from the
+ // resource cache.
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ EXPECT_TRUE(contents.empty());
+}
+
+TEST_F(CloudExternalDataStoreTest, SharedCache) {
+ // Write entries to two stores for two different cache_keys sharing a cache.
+ CloudExternalDataStore store1(kKey1, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store1.Store(kPolicy1, kData1Hash, kData1).empty());
+ CloudExternalDataStore store2(kKey2, task_runner_, resource_cache_.get());
+ EXPECT_FALSE(store2.Store(kPolicy2, kData2Hash, kData2).empty());
+
+ // Check that the entries have been assigned to the correct keys in the
+ // resource cache backing the stores.
+ std::map<std::string, std::string> contents;
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData1, contents.begin()->second);
+ resource_cache_->LoadAllSubkeys(kKey2, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData2, contents.begin()->second);
+
+ // Check that each entry can be loaded from the correct store.
+ std::string data;
+ EXPECT_FALSE(store1.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
+ EXPECT_EQ(kData1, data);
+ EXPECT_TRUE(store1.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
+
+ EXPECT_TRUE(store2.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
+ EXPECT_FALSE(store2.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
+ EXPECT_EQ(kData2, data);
+
+ // Prune the first store, allowing no entries to be kept.
+ std::vector<std::pair<std::string, std::string>> prune_data;
+ store1.Prune(prune_data);
+
+ // Check that the part of the resource cache backing the first store is empty.
+ resource_cache_->LoadAllSubkeys(kKey1, &contents);
+ EXPECT_TRUE(contents.empty());
+
+ // Check that the part of the resource cache backing the second store is
+ // unaffected.
+ resource_cache_->LoadAllSubkeys(kKey2, &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(kData2, contents.begin()->second);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_client.cc b/chromium/components/policy/core/common/cloud/cloud_policy_client.cc
new file mode 100644
index 00000000000..2c622efdef1
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_client.cc
@@ -0,0 +1,1723 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/guid.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/observer_list.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/client_data_delegate.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/dmserver_job_configurations.h"
+#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
+#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
+#include "components/policy/core/common/cloud/signing_service.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+// An enum for PSM execution result values.
+using PsmExecutionResult = em::DeviceRegisterRequest::PsmExecutionResult;
+
+// The type for variables containing an error from DM Server response.
+using CertProvisioningResponseErrorType =
+ enterprise_management::ClientCertificateProvisioningResponse::Error;
+// The namespace that contains convenient aliases for error values, e.g.
+// UNDEFINED, TIMED_OUT, IDENTITY_VERIFICATION_ERROR, CA_ERROR.
+using CertProvisioningResponseError =
+ enterprise_management::ClientCertificateProvisioningResponse;
+
+namespace policy {
+
+namespace {
+
+// Translates the DeviceRegisterResponse::DeviceMode |mode| to the enum used
+// internally to represent different device modes.
+DeviceMode TranslateProtobufDeviceMode(
+ em::DeviceRegisterResponse::DeviceMode mode) {
+ switch (mode) {
+ case em::DeviceRegisterResponse::ENTERPRISE:
+ return DEVICE_MODE_ENTERPRISE;
+ case em::DeviceRegisterResponse::RETAIL_DEPRECATED:
+ return DEPRECATED_DEVICE_MODE_LEGACY_RETAIL_MODE;
+ case em::DeviceRegisterResponse::CHROME_AD:
+ return DEVICE_MODE_ENTERPRISE_AD;
+ case em::DeviceRegisterResponse::DEMO:
+ return DEVICE_MODE_DEMO;
+ }
+ LOG(ERROR) << "Unknown enrollment mode in registration response: " << mode;
+ return DEVICE_MODE_NOT_SET;
+}
+
+bool IsChromePolicy(const std::string& type) {
+ return type == dm_protocol::kChromeDevicePolicyType ||
+ type == dm_protocol::kChromeUserPolicyType ||
+ IsMachineLevelUserCloudPolicyType(type);
+}
+
+em::PolicyValidationReportRequest::ValidationResultType
+TranslatePolicyValidationResult(CloudPolicyValidatorBase::Status status) {
+ using report = em::PolicyValidationReportRequest;
+ using policyValidationStatus = CloudPolicyValidatorBase::Status;
+ switch (status) {
+ case policyValidationStatus::VALIDATION_OK:
+ return report::VALIDATION_RESULT_TYPE_SUCCESS;
+ case policyValidationStatus::VALIDATION_BAD_INITIAL_SIGNATURE:
+ return report::VALIDATION_RESULT_TYPE_BAD_INITIAL_SIGNATURE;
+ case policyValidationStatus::VALIDATION_BAD_SIGNATURE:
+ return report::VALIDATION_RESULT_TYPE_BAD_SIGNATURE;
+ case policyValidationStatus::VALIDATION_ERROR_CODE_PRESENT:
+ return report::VALIDATION_RESULT_TYPE_ERROR_CODE_PRESENT;
+ case policyValidationStatus::VALIDATION_PAYLOAD_PARSE_ERROR:
+ return report::VALIDATION_RESULT_TYPE_PAYLOAD_PARSE_ERROR;
+ case policyValidationStatus::VALIDATION_WRONG_POLICY_TYPE:
+ return report::VALIDATION_RESULT_TYPE_WRONG_POLICY_TYPE;
+ case policyValidationStatus::VALIDATION_WRONG_SETTINGS_ENTITY_ID:
+ return report::VALIDATION_RESULT_TYPE_WRONG_SETTINGS_ENTITY_ID;
+ case policyValidationStatus::VALIDATION_BAD_TIMESTAMP:
+ return report::VALIDATION_RESULT_TYPE_BAD_TIMESTAMP;
+ case policyValidationStatus::VALIDATION_BAD_DM_TOKEN:
+ return report::VALIDATION_RESULT_TYPE_BAD_DM_TOKEN;
+ case policyValidationStatus::VALIDATION_BAD_DEVICE_ID:
+ return report::VALIDATION_RESULT_TYPE_BAD_DEVICE_ID;
+ case policyValidationStatus::VALIDATION_BAD_USER:
+ return report::VALIDATION_RESULT_TYPE_BAD_USER;
+ case policyValidationStatus::VALIDATION_POLICY_PARSE_ERROR:
+ return report::VALIDATION_RESULT_TYPE_POLICY_PARSE_ERROR;
+ case policyValidationStatus::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE:
+ return report::VALIDATION_RESULT_TYPE_BAD_KEY_VERIFICATION_SIGNATURE;
+ case policyValidationStatus::VALIDATION_VALUE_WARNING:
+ return report::VALIDATION_RESULT_TYPE_VALUE_WARNING;
+ case policyValidationStatus::VALIDATION_VALUE_ERROR:
+ return report::VALIDATION_RESULT_TYPE_VALUE_ERROR;
+ case policyValidationStatus::VALIDATION_STATUS_SIZE:
+ return report::VALIDATION_RESULT_TYPE_ERROR_UNSPECIFIED;
+ }
+ return report::VALIDATION_RESULT_TYPE_ERROR_UNSPECIFIED;
+}
+
+em::PolicyValueValidationIssue::ValueValidationIssueSeverity
+TranslatePolicyValidationResultSeverity(
+ ValueValidationIssue::Severity severity) {
+ using issue = em::PolicyValueValidationIssue;
+ switch (severity) {
+ case ValueValidationIssue::Severity::kWarning:
+ return issue::VALUE_VALIDATION_ISSUE_SEVERITY_WARNING;
+ case ValueValidationIssue::Severity::kError:
+ return issue::VALUE_VALIDATION_ISSUE_SEVERITY_ERROR;
+ }
+ NOTREACHED();
+ return issue::VALUE_VALIDATION_ISSUE_SEVERITY_UNSPECIFIED;
+}
+
+template <typename T>
+std::vector<T> ToVector(
+ const google::protobuf::RepeatedPtrField<T>& proto_container) {
+ return std::vector<T>(proto_container.begin(), proto_container.end());
+}
+
+std::tuple<DeviceManagementStatus, std::vector<em::SignedData>>
+DecodeRemoteCommands(DeviceManagementStatus status,
+ const em::DeviceManagementResponse& response) {
+ using MakeTuple =
+ std::tuple<DeviceManagementStatus, std::vector<em::SignedData>>;
+
+ if (status != DM_STATUS_SUCCESS) {
+ return MakeTuple(status, {});
+ }
+ if (!response.remote_command_response().commands().empty()) {
+ // Unsigned remote commands are no longer supported.
+ return MakeTuple(DM_STATUS_RESPONSE_DECODING_ERROR, {});
+ }
+
+ return MakeTuple(
+ DM_STATUS_SUCCESS,
+ ToVector(response.remote_command_response().secure_commands()));
+}
+
+} // namespace
+
+CloudPolicyClient::RegistrationParameters::RegistrationParameters(
+ em::DeviceRegisterRequest::Type registration_type,
+ em::DeviceRegisterRequest::Flavor flavor)
+ : registration_type(registration_type), flavor(flavor) {}
+
+CloudPolicyClient::RegistrationParameters::~RegistrationParameters() = default;
+
+void CloudPolicyClient::RegistrationParameters::SetPsmExecutionResult(
+ absl::optional<
+ enterprise_management::DeviceRegisterRequest::PsmExecutionResult>
+ new_psm_result) {
+ psm_execution_result = new_psm_result;
+}
+
+void CloudPolicyClient::RegistrationParameters::SetPsmDeterminationTimestamp(
+ absl::optional<int64_t> new_psm_timestamp) {
+ psm_determination_timestamp = new_psm_timestamp;
+}
+
+void CloudPolicyClient::RegistrationParameters::SetLicenseType(
+ em::LicenseType_LicenseTypeEnum license_type) {
+ license_type_ = license_type;
+}
+
+CloudPolicyClient::Observer::~Observer() {}
+
+CloudPolicyClient::CloudPolicyClient(
+ const std::string& machine_id,
+ const std::string& machine_model,
+ const std::string& brand_code,
+ const std::string& attested_device_id,
+ const std::string& ethernet_mac_address,
+ const std::string& dock_mac_address,
+ const std::string& manufacture_date,
+ DeviceManagementService* service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceDMTokenCallback device_dm_token_callback)
+ : machine_id_(machine_id),
+ machine_model_(machine_model),
+ brand_code_(brand_code),
+ attested_device_id_(attested_device_id),
+ ethernet_mac_address_(ethernet_mac_address),
+ dock_mac_address_(dock_mac_address),
+ manufacture_date_(manufacture_date),
+ service_(service), // Can be null for unit tests.
+ device_dm_token_callback_(device_dm_token_callback),
+ url_loader_factory_(url_loader_factory) {}
+
+CloudPolicyClient::CloudPolicyClient(
+ DeviceManagementService* service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceDMTokenCallback device_dm_token_callback)
+ : service_(service), // Can be null for unit tests.
+ device_dm_token_callback_(device_dm_token_callback),
+ url_loader_factory_(url_loader_factory) {}
+
+CloudPolicyClient::~CloudPolicyClient() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CloudPolicyClient::SetupRegistration(
+ const std::string& dm_token,
+ const std::string& client_id,
+ const std::vector<std::string>& user_affiliation_ids) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!dm_token.empty());
+ DCHECK(!client_id.empty());
+ DCHECK(!is_registered());
+
+ dm_token_ = dm_token;
+ client_id_ = client_id;
+ request_jobs_.clear();
+ app_install_report_request_job_ = nullptr;
+ extension_install_report_request_job_ = nullptr;
+ unique_request_job_.reset();
+ responses_.clear();
+ if (device_dm_token_callback_) {
+ device_dm_token_ = device_dm_token_callback_.Run(user_affiliation_ids);
+ }
+
+ NotifyRegistrationStateChanged();
+}
+
+// Sets the client ID or generate a new one. A new one is intentionally
+// generated on each new registration request in order to preserve privacy.
+// Reusing IDs would mean the server could track clients by their registration
+// attempts.
+void CloudPolicyClient::SetClientId(const std::string& client_id) {
+ client_id_ = client_id.empty() ? base::GenerateGUID() : client_id;
+}
+
+void CloudPolicyClient::Register(const RegistrationParameters& parameters,
+ const std::string& client_id,
+ const std::string& oauth_token) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(service_);
+ DCHECK(!oauth_token.empty());
+ DCHECK(!is_registered());
+
+ SetClientId(client_id);
+
+ std::unique_ptr<RegistrationJobConfiguration> config =
+ std::make_unique<RegistrationJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_REGISTRATION, this,
+ DMAuth::NoAuth(), oauth_token,
+ base::BindOnce(&CloudPolicyClient::OnRegisterCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ em::DeviceRegisterRequest* request =
+ config->request()->mutable_register_request();
+ CreateDeviceRegisterRequest(parameters, client_id, request);
+
+ if (requires_reregistration())
+ request->set_reregistration_dm_token(reregistration_dm_token_);
+
+ unique_request_job_ = service_->CreateJob(std::move(config));
+}
+
+void CloudPolicyClient::RegisterWithCertificate(
+ const RegistrationParameters& parameters,
+ const std::string& client_id,
+ const std::string& pem_certificate_chain,
+ const std::string& sub_organization,
+ SigningService* signing_service) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(signing_service);
+ DCHECK(service_);
+ DCHECK(!is_registered());
+
+ SetClientId(client_id);
+
+ em::CertificateBasedDeviceRegistrationData data;
+ data.set_certificate_type(em::CertificateBasedDeviceRegistrationData::
+ ENTERPRISE_ENROLLMENT_CERTIFICATE);
+ data.set_device_certificate(pem_certificate_chain);
+
+ em::DeviceRegisterRequest* request = data.mutable_device_register_request();
+ CreateDeviceRegisterRequest(parameters, client_id, request);
+ if (!sub_organization.empty()) {
+ em::DeviceRegisterConfiguration* configuration =
+ data.mutable_device_register_configuration();
+ configuration->set_device_owner(sub_organization);
+ }
+
+ signing_service->SignData(
+ data.SerializeAsString(),
+ base::BindOnce(&CloudPolicyClient::OnRegisterWithCertificateRequestSigned,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CloudPolicyClient::RegisterWithToken(
+ const std::string& token,
+ const std::string& client_id,
+ const ClientDataDelegate& client_data_delegate) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(service_);
+ DCHECK(!token.empty());
+ DCHECK(!client_id.empty());
+ DCHECK(!is_registered());
+
+ SetClientId(client_id);
+
+ std::unique_ptr<RegistrationJobConfiguration> config =
+ std::make_unique<RegistrationJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_TOKEN_ENROLLMENT,
+ this, DMAuth::FromEnrollmentToken(token),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnRegisterCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ enterprise_management::RegisterBrowserRequest* request =
+ config->request()->mutable_register_browser_request();
+ client_data_delegate.FillRegisterBrowserRequest(
+ request, base::BindOnce(&CloudPolicyClient::CreateUniqueRequestJob,
+ base::Unretained(this), std::move(config)));
+}
+
+void CloudPolicyClient::OnRegisterWithCertificateRequestSigned(
+ bool success,
+ em::SignedData signed_data) {
+ if (!success) {
+ const em::DeviceManagementResponse response;
+ OnRegisterCompleted(nullptr, DM_STATUS_CANNOT_SIGN_REQUEST, 0, response);
+ return;
+ }
+
+ std::unique_ptr<RegistrationJobConfiguration> config = std::make_unique<
+ RegistrationJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_BASED_REGISTRATION,
+ this, DMAuth::NoAuth(),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnRegisterCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ em::SignedData* signed_request =
+ config->request()
+ ->mutable_certificate_based_register_request()
+ ->mutable_signed_request();
+ signed_request->set_data(signed_data.data());
+ signed_request->set_signature(signed_data.signature());
+ signed_request->set_extra_data_bytes(signed_data.extra_data_bytes());
+
+ unique_request_job_ = service_->CreateJob(std::move(config));
+}
+
+void CloudPolicyClient::SetInvalidationInfo(int64_t version,
+ const std::string& payload) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ invalidation_version_ = version;
+ invalidation_payload_ = payload;
+}
+
+void CloudPolicyClient::SetOAuthTokenAsAdditionalAuth(
+ const std::string& oauth_token) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ oauth_token_ = oauth_token;
+}
+
+void CloudPolicyClient::FetchPolicy() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ CHECK(is_registered());
+ CHECK(!types_to_fetch_.empty());
+
+ VLOG(2) << "Policy fetch starting";
+ for (const auto& type : types_to_fetch_) {
+ VLOG(2) << "Fetching policy type: " << type.first << " -> " << type.second;
+ }
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH, this,
+ /*critical=*/true, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/oauth_token_,
+ base::BindOnce(&CloudPolicyClient::OnPolicyFetchCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ em::DeviceManagementRequest* request = config->request();
+
+ // Build policy fetch requests.
+ em::DevicePolicyRequest* policy_request = request->mutable_policy_request();
+ for (const auto& type_to_fetch : types_to_fetch_) {
+ em::PolicyFetchRequest* fetch_request = policy_request->add_requests();
+ fetch_request->set_policy_type(type_to_fetch.first);
+ if (!type_to_fetch.second.empty())
+ fetch_request->set_settings_entity_id(type_to_fetch.second);
+
+ // Request signed policy blobs to help prevent tampering on the client.
+ fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA);
+ if (public_key_version_valid_)
+ fetch_request->set_public_key_version(public_key_version_);
+
+ fetch_request->set_verification_key_hash(kPolicyVerificationKeyHash);
+
+ // These fields are included only in requests for chrome policy.
+ if (IsChromePolicy(type_to_fetch.first)) {
+ if (!device_dm_token_.empty())
+ fetch_request->set_device_dm_token(device_dm_token_);
+ if (!last_policy_timestamp_.is_null())
+ fetch_request->set_timestamp(last_policy_timestamp_.ToJavaTime());
+ if (!invalidation_payload_.empty()) {
+ fetch_request->set_invalidation_version(invalidation_version_);
+ fetch_request->set_invalidation_payload(invalidation_payload_);
+ }
+ }
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+ // Only set browser device identifier for CBCM Chrome cloud policy on
+ // desktop.
+ if (base::FeatureList::IsEnabled(
+ features::kUploadBrowserDeviceIdentifier) &&
+ type_to_fetch.first ==
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType) {
+ fetch_request->set_allocated_browser_device_identifier(
+ GetBrowserDeviceIdentifier().release());
+ }
+#endif
+ }
+
+ // Add device state keys.
+ if (!state_keys_to_upload_.empty()) {
+ em::DeviceStateKeyUpdateRequest* key_update_request =
+ request->mutable_device_state_key_update_request();
+ for (std::vector<std::string>::const_iterator key(
+ state_keys_to_upload_.begin());
+ key != state_keys_to_upload_.end(); ++key) {
+ key_update_request->add_server_backed_state_keys(*key);
+ }
+ }
+
+ // Set the fetched invalidation version to the latest invalidation version
+ // since it is now the invalidation version used for the latest fetch.
+ fetched_invalidation_version_ = invalidation_version_;
+
+ unique_request_job_ = service_->CreateJob(std::move(config));
+}
+
+void CloudPolicyClient::UploadPolicyValidationReport(
+ CloudPolicyValidatorBase::Status status,
+ const std::vector<ValueValidationIssue>& value_validation_issues,
+ const std::string& policy_type,
+ const std::string& policy_token) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ StatusCallback callback = base::DoNothing();
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_POLICY_VALIDATION_REPORT,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::DeviceManagementRequest* request = config->request();
+ em::PolicyValidationReportRequest* policy_validation_report_request =
+ request->mutable_policy_validation_report_request();
+
+ policy_validation_report_request->set_policy_type(policy_type);
+ policy_validation_report_request->set_policy_token(policy_token);
+ policy_validation_report_request->set_validation_result_type(
+ TranslatePolicyValidationResult(status));
+
+ for (const ValueValidationIssue& issue : value_validation_issues) {
+ em::PolicyValueValidationIssue* proto_result =
+ policy_validation_report_request->add_policy_value_validation_issues();
+ proto_result->set_policy_name(issue.policy_name);
+ proto_result->set_severity(
+ TranslatePolicyValidationResultSeverity(issue.severity));
+ proto_result->set_debug_message(issue.message);
+ }
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::FetchRobotAuthCodes(
+ DMAuth auth,
+ enterprise_management::DeviceServiceApiAccessRequest::DeviceType
+ device_type,
+ const std::set<std::string>& oauth_scopes,
+ RobotAuthCodeCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ DCHECK(auth.has_dm_token());
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+ this,
+ /*critical=*/false, std::move(auth),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnFetchRobotAuthCodesCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::DeviceServiceApiAccessRequest* request =
+ config->request()->mutable_service_api_access_request();
+ request->set_oauth2_client_id(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id());
+
+ for (const auto& scope : oauth_scopes)
+ request->add_auth_scopes(scope);
+
+ request->set_device_type(device_type);
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::Unregister() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(service_);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION, this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnUnregisterCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ config->request()->mutable_unregister_request();
+
+ unique_request_job_ = service_->CreateJob(std::move(config));
+}
+
+void CloudPolicyClient::UploadEnterpriseMachineCertificate(
+ const std::string& certificate_data,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ UploadCertificate(certificate_data,
+ em::DeviceCertUploadRequest::ENTERPRISE_MACHINE_CERTIFICATE,
+ std::move(callback));
+}
+
+void CloudPolicyClient::UploadEnterpriseEnrollmentCertificate(
+ const std::string& certificate_data,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ UploadCertificate(
+ certificate_data,
+ em::DeviceCertUploadRequest::ENTERPRISE_ENROLLMENT_CERTIFICATE,
+ std::move(callback));
+}
+
+void CloudPolicyClient::UploadEnterpriseEnrollmentId(
+ const std::string& enrollment_id,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ CreateCertUploadJobConfiguration(std::move(callback));
+ em::DeviceManagementRequest* request = config->request();
+ em::DeviceCertUploadRequest* upload_request =
+ request->mutable_cert_upload_request();
+ upload_request->set_enrollment_id(enrollment_id);
+ ExecuteCertUploadJob(std::move(config));
+}
+
+void CloudPolicyClient::UploadDeviceStatus(
+ const em::DeviceStatusReportRequest* device_status,
+ const em::SessionStatusReportRequest* session_status,
+ const em::ChildStatusReportRequest* child_status,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ // Should pass in at least one type of status.
+ DCHECK(device_status || session_status || child_status);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS, this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/oauth_token_,
+ base::BindOnce(&CloudPolicyClient::OnReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::DeviceManagementRequest* request = config->request();
+ if (device_status)
+ *request->mutable_device_status_report_request() = *device_status;
+ if (session_status)
+ *request->mutable_session_status_report_request() = *session_status;
+ if (child_status)
+ *request->mutable_child_status_report_request() = *child_status;
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadChromeDesktopReport(
+ std::unique_ptr<em::ChromeDesktopReportRequest> chrome_desktop_report,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ DCHECK(chrome_desktop_report);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_DESKTOP_REPORT,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->request()->set_allocated_chrome_desktop_report_request(
+ chrome_desktop_report.release());
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadChromeOsUserReport(
+ std::unique_ptr<em::ChromeOsUserReportRequest> chrome_os_user_report,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ DCHECK(chrome_os_user_report);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_OS_USER_REPORT,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->request()->set_allocated_chrome_os_user_report_request(
+ chrome_os_user_report.release());
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadChromeProfileReport(
+ std::unique_ptr<em::ChromeProfileReportRequest> chrome_profile_report,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ DCHECK(chrome_profile_report);
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_PROFILE_REPORT,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->request()->set_allocated_chrome_profile_report_request(
+ chrome_profile_report.release());
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadSecurityEventReport(
+ content::BrowserContext* context,
+ bool include_device_info,
+ base::Value::Dict report,
+ StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ CreateNewRealtimeReportingJob(
+ std::move(report),
+ service()->configuration()->GetReportingConnectorServerUrl(context),
+ include_device_info, add_connector_url_params_, std::move(callback));
+}
+
+void CloudPolicyClient::UploadEncryptedReport(
+ base::Value::Dict merging_payload,
+ absl::optional<base::Value::Dict> context,
+ ResponseCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!is_registered()) {
+ std::move(callback).Run(absl::nullopt);
+ return;
+ }
+
+ std::unique_ptr<EncryptedReportingJobConfiguration> config =
+ std::make_unique<EncryptedReportingJobConfiguration>(
+ this, service()->configuration()->GetEncryptedReportingServerUrl(),
+ std::move(merging_payload),
+ base::BindOnce(&CloudPolicyClient::OnEncryptedReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+ if (context.has_value()) {
+ config->UpdateContext(std::move(context.value()));
+ }
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadAppInstallReport(base::Value::Dict report,
+ StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ CancelAppInstallReportUpload();
+ app_install_report_request_job_ = CreateNewRealtimeReportingJob(
+ std::move(report),
+ service()->configuration()->GetRealtimeReportingServerUrl(),
+ /* include_device_info */ true, /* add_connector_url_params=*/false,
+ std::move(callback));
+ DCHECK(app_install_report_request_job_);
+}
+
+void CloudPolicyClient::CancelAppInstallReportUpload() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (app_install_report_request_job_) {
+ RemoveJob(app_install_report_request_job_);
+ DCHECK_EQ(app_install_report_request_job_, nullptr);
+ }
+}
+
+void CloudPolicyClient::UploadExtensionInstallReport(base::Value::Dict report,
+ StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ CancelExtensionInstallReportUpload();
+ extension_install_report_request_job_ = CreateNewRealtimeReportingJob(
+ std::move(report),
+ service()->configuration()->GetRealtimeReportingServerUrl(),
+ /* include_device_info */ true,
+ /* add_connector_url_params=*/false, std::move(callback));
+ DCHECK(extension_install_report_request_job_);
+}
+
+void CloudPolicyClient::CancelExtensionInstallReportUpload() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (extension_install_report_request_job_) {
+ RemoveJob(extension_install_report_request_job_);
+ DCHECK_EQ(extension_install_report_request_job_, nullptr);
+ }
+}
+
+void CloudPolicyClient::FetchRemoteCommands(
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
+ const std::vector<em::RemoteCommandResult>& command_results,
+ RemoteCommandCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_REMOTE_COMMANDS, this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnRemoteCommandsFetched,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::DeviceRemoteCommandRequest* const request =
+ config->request()->mutable_remote_command_request();
+
+ if (last_command_id)
+ request->set_last_command_unique_id(*last_command_id);
+
+ for (const auto& command_result : command_results)
+ *request->add_command_results() = command_result;
+
+ request->set_send_secure_commands(true);
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+DeviceManagementService::Job* CloudPolicyClient::CreateNewRealtimeReportingJob(
+ base::Value::Dict report,
+ const std::string& server_url,
+ bool include_device_info,
+ bool add_connector_url_params,
+ StatusCallback callback) {
+ std::unique_ptr<RealtimeReportingJobConfiguration> config =
+ std::make_unique<RealtimeReportingJobConfiguration>(
+ this, server_url, include_device_info, add_connector_url_params,
+ base::BindOnce(&CloudPolicyClient::OnRealtimeReportUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->AddReport(std::move(report));
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+ return request_jobs_.back().get();
+}
+
+void CloudPolicyClient::GetDeviceAttributeUpdatePermission(
+ DMAuth auth,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ // This request only works with an OAuth token identifying a user, because
+ // DMServer will resolve that user and check if they have permissions to
+ // update the device's attributes.
+ DCHECK(auth.has_oauth_token());
+
+ const bool has_oauth_token = auth.has_oauth_token();
+ const std::string oauth_token =
+ has_oauth_token ? auth.oauth_token() : std::string();
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::
+ TYPE_ATTRIBUTE_UPDATE_PERMISSION,
+ this,
+ /*critical=*/false,
+ !has_oauth_token ? std::move(auth) : DMAuth::NoAuth(), oauth_token,
+ base::BindOnce(
+ &CloudPolicyClient::OnDeviceAttributeUpdatePermissionCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->request()->mutable_device_attribute_update_permission_request();
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UpdateDeviceAttributes(
+ DMAuth auth,
+ const std::string& asset_id,
+ const std::string& location,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+ DCHECK(auth.has_oauth_token() || auth.has_enrollment_token());
+
+ const bool has_oauth_token = auth.has_oauth_token();
+ const std::string oauth_token =
+ has_oauth_token ? auth.oauth_token() : std::string();
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_ATTRIBUTE_UPDATE,
+ this,
+ /*critical=*/false,
+ !has_oauth_token ? std::move(auth) : DMAuth::NoAuth(), oauth_token,
+ base::BindOnce(&CloudPolicyClient::OnDeviceAttributeUpdated,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::DeviceAttributeUpdateRequest* request =
+ config->request()->mutable_device_attribute_update_request();
+
+ request->set_asset_id(asset_id);
+ request->set_location(location);
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UpdateGcmId(
+ const std::string& gcm_id,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_GCM_ID_UPDATE, this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnGcmIdUpdated,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::GcmIdUpdateRequest* const request =
+ config->request()->mutable_gcm_id_update_request();
+
+ request->set_gcm_id(gcm_id);
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UploadEuiccInfo(
+ std::unique_ptr<enterprise_management::UploadEuiccInfoRequest> request,
+ CloudPolicyClient::StatusCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config =
+ std::make_unique<DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_EUICC_INFO,
+ /*client=*/this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(&CloudPolicyClient::OnEuiccInfoUploaded,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ config->request()->set_allocated_upload_euicc_info_request(request.release());
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::OnEuiccInfoUploaded(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS)
+ NotifyClientError();
+
+ std::move(callback).Run(status == DM_STATUS_SUCCESS);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::ClientCertProvisioningStartCsr(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningStartCsrCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config = std::make_unique<
+ DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(
+ &CloudPolicyClient::OnClientCertProvisioningStartCsrResponse,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::ClientCertificateProvisioningRequest* request =
+ config->request()->mutable_client_certificate_provisioning_request();
+
+ request->set_certificate_scope(cert_scope);
+ request->set_cert_profile_id(cert_profile_id);
+ request->set_policy_version(cert_profile_version);
+ request->set_public_key(public_key);
+ if (!device_dm_token_.empty()) {
+ request->set_device_dm_token(device_dm_token_);
+ }
+ // Sets the request type, no actual data is required.
+ request->mutable_start_csr_request();
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::ClientCertProvisioningFinishCsr(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ const std::string& va_challenge_response,
+ const std::string& signature,
+ ClientCertProvisioningFinishCsrCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config = std::make_unique<
+ DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(
+ &CloudPolicyClient::OnClientCertProvisioningFinishCsrResponse,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::ClientCertificateProvisioningRequest* const request =
+ config->request()->mutable_client_certificate_provisioning_request();
+
+ request->set_certificate_scope(cert_scope);
+ request->set_cert_profile_id(cert_profile_id);
+ request->set_policy_version(cert_profile_version);
+ request->set_public_key(public_key);
+ if (!device_dm_token_.empty()) {
+ request->set_device_dm_token(device_dm_token_);
+ }
+
+ em::FinishCsrRequest* finish_csr_request =
+ request->mutable_finish_csr_request();
+ if (!va_challenge_response.empty()) {
+ finish_csr_request->set_va_challenge_response(va_challenge_response);
+ }
+ finish_csr_request->set_signature(signature);
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::ClientCertProvisioningDownloadCert(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningDownloadCertCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(is_registered());
+
+ std::unique_ptr<DMServerJobConfiguration> config = std::make_unique<
+ DMServerJobConfiguration>(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ this,
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt,
+ base::BindOnce(
+ &CloudPolicyClient::OnClientCertProvisioningDownloadCertResponse,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ em::ClientCertificateProvisioningRequest* const request =
+ config->request()->mutable_client_certificate_provisioning_request();
+
+ request->set_certificate_scope(cert_scope);
+ request->set_cert_profile_id(cert_profile_id);
+ request->set_policy_version(cert_profile_version);
+ request->set_public_key(public_key);
+ if (!device_dm_token_.empty()) {
+ request->set_device_dm_token(device_dm_token_);
+ }
+ // Sets the request type, no actual data is required.
+ request->mutable_download_cert_request();
+
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::UpdateServiceAccount(const std::string& account_email) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ NotifyServiceAccountSet(account_email);
+}
+
+void CloudPolicyClient::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.AddObserver(observer);
+}
+
+void CloudPolicyClient::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.RemoveObserver(observer);
+}
+
+void CloudPolicyClient::AddPolicyTypeToFetch(
+ const std::string& policy_type,
+ const std::string& settings_entity_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ types_to_fetch_.insert(std::make_pair(policy_type, settings_entity_id));
+}
+
+void CloudPolicyClient::RemovePolicyTypeToFetch(
+ const std::string& policy_type,
+ const std::string& settings_entity_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ types_to_fetch_.erase(std::make_pair(policy_type, settings_entity_id));
+}
+
+void CloudPolicyClient::SetStateKeysToUpload(
+ const std::vector<std::string>& keys) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ state_keys_to_upload_ = keys;
+}
+
+const em::PolicyFetchResponse* CloudPolicyClient::GetPolicyFor(
+ const std::string& policy_type,
+ const std::string& settings_entity_id) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ auto it = responses_.find(std::make_pair(policy_type, settings_entity_id));
+ return it == responses_.end() ? nullptr : &it->second;
+}
+
+scoped_refptr<network::SharedURLLoaderFactory>
+CloudPolicyClient::GetURLLoaderFactory() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ return url_loader_factory_;
+}
+
+int CloudPolicyClient::GetActiveRequestCountForTest() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ return request_jobs_.size();
+}
+
+void CloudPolicyClient::SetURLLoaderFactoryForTesting(
+ scoped_refptr<network::SharedURLLoaderFactory> factory) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ url_loader_factory_ = factory;
+}
+
+void CloudPolicyClient::UploadCertificate(
+ const std::string& certificate_data,
+ em::DeviceCertUploadRequest::CertificateType certificate_type,
+ CloudPolicyClient::StatusCallback callback) {
+ std::unique_ptr<DMServerJobConfiguration> config =
+ CreateCertUploadJobConfiguration(std::move(callback));
+ PrepareCertUploadRequest(config.get(), certificate_data, certificate_type);
+ ExecuteCertUploadJob(std::move(config));
+}
+
+void CloudPolicyClient::PrepareCertUploadRequest(
+ DMServerJobConfiguration* config,
+ const std::string& certificate_data,
+ enterprise_management::DeviceCertUploadRequest::CertificateType
+ certificate_type) {
+ em::DeviceManagementRequest* request = config->request();
+ em::DeviceCertUploadRequest* upload_request =
+ request->mutable_cert_upload_request();
+ upload_request->set_device_certificate(certificate_data);
+ upload_request->set_certificate_type(certificate_type);
+}
+
+std::unique_ptr<DMServerJobConfiguration>
+CloudPolicyClient::CreateCertUploadJobConfiguration(
+ CloudPolicyClient::StatusCallback callback) {
+ CHECK(is_registered());
+ return std::make_unique<DMServerJobConfiguration>(
+ service_,
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ client_id(),
+ /*critical=*/false, DMAuth::FromDMToken(dm_token_),
+ /*oauth_token=*/absl::nullopt, GetURLLoaderFactory(),
+ base::BindOnce(&CloudPolicyClient::OnCertificateUploadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void CloudPolicyClient::ExecuteCertUploadJob(
+ std::unique_ptr<DMServerJobConfiguration> config) {
+ request_jobs_.push_back(service_->CreateJob(std::move(config)));
+}
+
+void CloudPolicyClient::OnRegisterCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ if (status == DM_STATUS_SUCCESS) {
+ if (!response.has_register_response() ||
+ !response.register_response().has_device_management_token()) {
+ LOG(WARNING) << "Invalid registration response.";
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ } else if (!reregistration_dm_token_.empty() &&
+ reregistration_dm_token_ !=
+ response.register_response().device_management_token()) {
+ LOG(WARNING) << "Reregistration DMToken mismatch.";
+ status = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+ }
+ }
+
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS) {
+ dm_token_ = response.register_response().device_management_token();
+ reregistration_dm_token_.clear();
+ if (response.register_response().has_configuration_seed()) {
+ configuration_seed_ =
+ base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
+ response.register_response().configuration_seed(),
+ base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS));
+ if (!configuration_seed_)
+ LOG(ERROR) << "Failed to parse configuration seed";
+ }
+ DVLOG(1) << "Client registration complete - DMToken = " << dm_token_;
+
+ // Device mode is only relevant for device policy really, it's the
+ // responsibility of the consumer of the field to check validity.
+ device_mode_ = DEVICE_MODE_NOT_SET;
+ if (response.register_response().has_enrollment_type()) {
+ device_mode_ = TranslateProtobufDeviceMode(
+ response.register_response().enrollment_type());
+ }
+
+ if (device_dm_token_callback_) {
+ std::vector<std::string> user_affiliation_ids(
+ response.register_response().user_affiliation_ids().begin(),
+ response.register_response().user_affiliation_ids().end());
+ device_dm_token_ = device_dm_token_callback_.Run(user_affiliation_ids);
+ }
+ NotifyRegistrationStateChanged();
+ } else {
+ NotifyClientError();
+ }
+}
+
+void CloudPolicyClient::OnFetchRobotAuthCodesCompleted(
+ RobotAuthCodeCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ // Remove the job before executing the callback because |this| might be
+ // deleted during the callback.
+ RemoveJob(job);
+
+ if (status == DM_STATUS_SUCCESS &&
+ (!response.has_service_api_access_response())) {
+ LOG(WARNING) << "Invalid service api access response.";
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ }
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS) {
+ DVLOG(1) << "Device robot account auth code fetch complete - code = "
+ << response.service_api_access_response().auth_code();
+ std::move(callback).Run(status,
+ response.service_api_access_response().auth_code());
+ } else {
+ std::move(callback).Run(status, std::string());
+ }
+ // |this| might be deleted at this point.
+}
+
+void CloudPolicyClient::OnPolicyFetchCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ if (status == DM_STATUS_SUCCESS) {
+ if (!response.has_policy_response() ||
+ response.policy_response().responses_size() == 0) {
+ LOG(WARNING) << "Empty policy response.";
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ }
+ }
+
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS) {
+ const em::DevicePolicyResponse& policy_response =
+ response.policy_response();
+ // Log histogram on first device policy fetch response to check the state
+ // keys.
+ if (responses_.empty()) {
+ base::UmaHistogramBoolean("Ash.StateKeysPresent",
+ !state_keys_to_upload_.empty());
+ }
+ responses_.clear();
+ for (int i = 0; i < policy_response.responses_size(); ++i) {
+ const em::PolicyFetchResponse& fetch_response =
+ policy_response.responses(i);
+ em::PolicyData policy_data;
+ if (!policy_data.ParseFromString(fetch_response.policy_data()) ||
+ !policy_data.IsInitialized() || !policy_data.has_policy_type()) {
+ LOG(WARNING) << "Invalid PolicyData received, ignoring";
+ continue;
+ }
+ const std::string& type = policy_data.policy_type();
+ std::string entity_id;
+ if (policy_data.has_settings_entity_id())
+ entity_id = policy_data.settings_entity_id();
+ std::pair<std::string, std::string> key(type, entity_id);
+ if (base::Contains(responses_, key)) {
+ LOG(WARNING) << "Duplicate PolicyFetchResponse for type: " << type
+ << ", entity: " << entity_id << ", ignoring";
+ continue;
+ }
+ responses_[key] = fetch_response;
+ }
+ state_keys_to_upload_.clear();
+ NotifyPolicyFetched();
+
+ VLOG(2) << "Policy fetch success";
+ } else {
+ NotifyClientError();
+
+ VLOG(2) << "Policy fetch error: " << status;
+
+ if (status == DM_STATUS_SERVICE_DEVICE_NOT_FOUND) {
+ // Mark as unregistered and initialize re-registration flow.
+ reregistration_dm_token_ = dm_token_;
+ dm_token_.clear();
+ NotifyRegistrationStateChanged();
+ }
+ }
+}
+
+void CloudPolicyClient::OnUnregisterCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ if (status == DM_STATUS_SUCCESS && !response.has_unregister_response()) {
+ // Assume unregistration has succeeded either way.
+ LOG(WARNING) << "Empty unregistration response.";
+ }
+
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS) {
+ dm_token_.clear();
+ // Cancel all outstanding jobs.
+ request_jobs_.clear();
+ app_install_report_request_job_ = nullptr;
+ extension_install_report_request_job_ = nullptr;
+ device_dm_token_.clear();
+ NotifyRegistrationStateChanged();
+ } else {
+ NotifyClientError();
+ }
+}
+
+void CloudPolicyClient::OnCertificateUploadCompleted(
+ CloudPolicyClient::StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ bool success = true;
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS) {
+ success = false;
+ NotifyClientError();
+ } else if (!response.has_cert_upload_response()) {
+ LOG(WARNING) << "Empty upload certificate response.";
+ success = false;
+ }
+ std::move(callback).Run(success);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::OnDeviceAttributeUpdatePermissionCompleted(
+ CloudPolicyClient::StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ bool success = false;
+
+ if (status == DM_STATUS_SUCCESS &&
+ !response.has_device_attribute_update_permission_response()) {
+ LOG(WARNING) << "Invalid device attribute update permission response.";
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ }
+
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS &&
+ response.device_attribute_update_permission_response().has_result() &&
+ response.device_attribute_update_permission_response().result() ==
+ em::DeviceAttributeUpdatePermissionResponse::
+ ATTRIBUTE_UPDATE_ALLOWED) {
+ success = true;
+ }
+
+ std::move(callback).Run(success);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::OnDeviceAttributeUpdated(
+ CloudPolicyClient::StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ bool success = false;
+
+ if (status == DM_STATUS_SUCCESS &&
+ !response.has_device_attribute_update_response()) {
+ LOG(WARNING) << "Invalid device attribute update response.";
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ }
+
+ status_ = status;
+ if (status == DM_STATUS_SUCCESS &&
+ response.device_attribute_update_response().has_result() &&
+ response.device_attribute_update_response().result() ==
+ em::DeviceAttributeUpdateResponse::ATTRIBUTE_UPDATE_SUCCESS) {
+ success = true;
+ }
+
+ std::move(callback).Run(success);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::RemoveJob(DeviceManagementService::Job* job) {
+ if (app_install_report_request_job_ == job) {
+ app_install_report_request_job_ = nullptr;
+ } else if (extension_install_report_request_job_ == job) {
+ extension_install_report_request_job_ = nullptr;
+ }
+ for (auto it = request_jobs_.begin(); it != request_jobs_.end(); ++it) {
+ if (it->get() == job) {
+ request_jobs_.erase(it);
+ return;
+ }
+ }
+ // This job was already deleted from our list, somehow. This shouldn't
+ // happen since deleting the job should cancel the callback.
+ NOTREACHED();
+}
+
+void CloudPolicyClient::OnReportUploadCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS)
+ NotifyClientError();
+
+ std::move(callback).Run(status == DM_STATUS_SUCCESS);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::OnRealtimeReportUploadCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ absl::optional<base::Value::Dict> response) {
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS)
+ NotifyClientError();
+
+ std::move(callback).Run(status == DM_STATUS_SUCCESS);
+ RemoveJob(job);
+}
+
+// |job| can be null if the owning EncryptedReportingJobConfiguration is
+// destroyed prior to calling OnUploadComplete. In that case, callback will be
+// called with nullopt value.
+void CloudPolicyClient::OnEncryptedReportUploadCompleted(
+ ResponseCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ absl::optional<base::Value::Dict> response) {
+ if (job == nullptr) {
+ std::move(callback).Run(absl::nullopt);
+ return;
+ }
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS) {
+ NotifyClientError();
+ }
+ std::move(callback).Run(std::move(response));
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::OnRemoteCommandsFetched(
+ RemoteCommandCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ auto [decoded_status, commands] = DecodeRemoteCommands(status, response);
+
+ std::move(callback).Run(decoded_status, commands);
+ RemoveJob(job);
+}
+
+void CloudPolicyClient::OnGcmIdUpdated(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ status_ = status;
+ if (status != DM_STATUS_SUCCESS)
+ NotifyClientError();
+
+ std::move(callback).Run(status == DM_STATUS_SUCCESS);
+ RemoveJob(job);
+}
+
+namespace {
+// Checks all error-like fields of a client cert provisioning response. Uses
+// |status| as an input and output parameter. Extracts error and try_again_later
+// fields from the |response| into |response_error| and |try_later|. Returns
+// true if all error-like fields are empty or "ok" and the parsing of the
+// |response| can be continued.
+bool CheckCommonClientCertProvisioningResponse(
+ const em::DeviceManagementResponse& response,
+ policy::DeviceManagementStatus* status,
+ absl::optional<CertProvisioningResponseErrorType>* response_error,
+ absl::optional<int64_t>* try_later) {
+ if (*status != DM_STATUS_SUCCESS) {
+ return false;
+ }
+
+ if (!response.has_client_certificate_provisioning_response()) {
+ *status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ return false;
+ }
+
+ const em::ClientCertificateProvisioningResponse& cert_provisioning_response =
+ response.client_certificate_provisioning_response();
+
+ if (cert_provisioning_response.has_error()) {
+ *response_error = cert_provisioning_response.error();
+ return false;
+ }
+
+ if (cert_provisioning_response.has_try_again_later()) {
+ *try_later = cert_provisioning_response.try_again_later();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+void CloudPolicyClient::OnClientCertProvisioningStartCsrResponse(
+ ClientCertProvisioningStartCsrCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ base::ScopedClosureRunner job_cleaner(base::BindOnce(
+ &CloudPolicyClient::RemoveJob, base::Unretained(this), job));
+
+ status_ = status;
+ absl::optional<CertProvisioningResponseErrorType> response_error;
+ absl::optional<int64_t> try_later;
+
+ // Single step loop for convenience.
+ do {
+ if (!CheckCommonClientCertProvisioningResponse(
+ response, &status, &response_error, &try_later)) {
+ break;
+ }
+
+ const em::ClientCertificateProvisioningResponse&
+ cert_provisioning_response =
+ response.client_certificate_provisioning_response();
+
+ if (!cert_provisioning_response.has_start_csr_response()) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+
+ const em::StartCsrResponse& start_csr_response =
+ cert_provisioning_response.start_csr_response();
+
+ if (!start_csr_response.has_hashing_algorithm() ||
+ !start_csr_response.has_signing_algorithm() ||
+ !start_csr_response.has_data_to_sign()) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+
+ if (start_csr_response.signing_algorithm() !=
+ em::SigningAlgorithm::RSA_PKCS1_V1_5) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+
+ const std::string empty_str;
+
+ const std::string& invalidation_topic =
+ start_csr_response.has_invalidation_topic()
+ ? start_csr_response.invalidation_topic()
+ : empty_str;
+
+ const std::string& va_challenge = start_csr_response.has_va_challenge()
+ ? start_csr_response.va_challenge()
+ : empty_str;
+
+ // Everything is ok, run |callback| with data.
+ std::move(callback).Run(status, response_error, try_later,
+ invalidation_topic, va_challenge,
+ start_csr_response.hashing_algorithm(),
+ start_csr_response.data_to_sign());
+ return;
+ } while (false);
+
+ // Something went wrong. Return error via |status|, |response_error|,
+ // |try_later|.
+ const std::string empty_str;
+ em::HashingAlgorithm hash_algo = {};
+ std::move(callback).Run(status, response_error, try_later, empty_str,
+ empty_str, hash_algo, empty_str);
+}
+
+void CloudPolicyClient::OnClientCertProvisioningFinishCsrResponse(
+ ClientCertProvisioningFinishCsrCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ base::ScopedClosureRunner job_cleaner(base::BindOnce(
+ &CloudPolicyClient::RemoveJob, base::Unretained(this), job));
+
+ status_ = status;
+ absl::optional<CertProvisioningResponseErrorType> response_error;
+ absl::optional<int64_t> try_later;
+
+ // Single step loop for convenience.
+ do {
+ if (!CheckCommonClientCertProvisioningResponse(
+ response, &status, &response_error, &try_later)) {
+ break;
+ }
+
+ const em::ClientCertificateProvisioningResponse&
+ cert_provisioning_response =
+ response.client_certificate_provisioning_response();
+
+ if (!cert_provisioning_response.has_finish_csr_response()) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+ } while (false);
+
+ std::move(callback).Run(status, response_error, try_later);
+}
+
+void CloudPolicyClient::OnClientCertProvisioningDownloadCertResponse(
+ ClientCertProvisioningDownloadCertCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const em::DeviceManagementResponse& response) {
+ base::ScopedClosureRunner job_cleaner(base::BindOnce(
+ &CloudPolicyClient::RemoveJob, base::Unretained(this), job));
+
+ status_ = status;
+ absl::optional<CertProvisioningResponseErrorType> response_error;
+ absl::optional<int64_t> try_later;
+
+ // Single step loop for convenience.
+ do {
+ if (!CheckCommonClientCertProvisioningResponse(
+ response, &status, &response_error, &try_later)) {
+ break;
+ }
+
+ const em::ClientCertificateProvisioningResponse&
+ cert_provisioning_response =
+ response.client_certificate_provisioning_response();
+
+ if (!cert_provisioning_response.has_download_cert_response()) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+
+ const em::DownloadCertResponse& download_cert_response =
+ cert_provisioning_response.download_cert_response();
+
+ if (!download_cert_response.has_pem_encoded_certificate()) {
+ status = DM_STATUS_RESPONSE_DECODING_ERROR;
+ break;
+ }
+
+ // Everything is ok, run |callback| with data.
+ std::move(callback).Run(status, response_error, try_later,
+ download_cert_response.pem_encoded_certificate());
+ return;
+ } while (false);
+
+ // Something went wrong. Return error via |status|, |response_error|,
+ // |try_later|.
+ std::move(callback).Run(status, response_error, try_later, std::string());
+}
+
+void CloudPolicyClient::NotifyPolicyFetched() {
+ for (auto& observer : observers_)
+ observer.OnPolicyFetched(this);
+}
+
+void CloudPolicyClient::NotifyRegistrationStateChanged() {
+ for (auto& observer : observers_)
+ observer.OnRegistrationStateChanged(this);
+}
+
+void CloudPolicyClient::NotifyClientError() {
+ for (auto& observer : observers_)
+ observer.OnClientError(this);
+}
+
+void CloudPolicyClient::NotifyServiceAccountSet(
+ const std::string& account_email) {
+ for (auto& observer : observers_)
+ observer.OnServiceAccountSet(this, account_email);
+}
+
+void CloudPolicyClient::CreateDeviceRegisterRequest(
+ const RegistrationParameters& params,
+ const std::string& client_id,
+ em::DeviceRegisterRequest* request) {
+ if (!client_id.empty())
+ request->set_reregister(true);
+ request->set_type(params.registration_type);
+ request->set_flavor(params.flavor);
+ request->set_lifetime(params.lifetime);
+ if (!machine_id_.empty())
+ request->set_machine_id(machine_id_);
+ if (!machine_model_.empty())
+ request->set_machine_model(machine_model_);
+ if (!brand_code_.empty())
+ request->set_brand_code(brand_code_);
+ if (!attested_device_id_.empty())
+ request->mutable_device_register_identification()->set_attested_device_id(
+ attested_device_id_);
+ if (!ethernet_mac_address_.empty())
+ request->set_ethernet_mac_address(ethernet_mac_address_);
+ if (!dock_mac_address_.empty())
+ request->set_dock_mac_address(dock_mac_address_);
+ if (!manufacture_date_.empty())
+ request->set_manufacture_date(manufacture_date_);
+ if (!params.requisition.empty())
+ request->set_requisition(params.requisition);
+ if (!params.current_state_key.empty())
+ request->set_server_backed_state_key(params.current_state_key);
+ if (params.psm_execution_result.has_value())
+ request->set_psm_execution_result(params.psm_execution_result.value());
+ if (params.psm_determination_timestamp.has_value()) {
+ request->set_psm_determination_timestamp_ms(
+ params.psm_determination_timestamp.value());
+ }
+ if (params.license_type_.has_value()) {
+ request->mutable_license_type()->set_license_type(
+ params.license_type_.value());
+ }
+}
+
+void CloudPolicyClient::CreateUniqueRequestJob(
+ std::unique_ptr<RegistrationJobConfiguration> config) {
+ unique_request_job_ = service_->CreateJob(std::move(config));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_client.h b/chromium/components/policy/core/common/cloud/cloud_policy_client.h
new file mode 100644
index 00000000000..e29d81120a2
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_client.h
@@ -0,0 +1,912 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class ClientDataDelegate;
+class DMServerJobConfiguration;
+class RegistrationJobConfiguration;
+class SigningService;
+
+// Implements the core logic required to talk to the device management service.
+// Also keeps track of the current state of the association with the service,
+// such as whether there is a valid registration (DMToken is present in that
+// case) and whether and what errors occurred in the latest request.
+//
+// Note that CloudPolicyClient doesn't do any validation of policy responses
+// such as signature and time stamp checks. These happen once the policy gets
+// installed in the cloud policy cache.
+class POLICY_EXPORT CloudPolicyClient {
+ public:
+ // Maps a (policy type, settings entity ID) pair to its corresponding
+ // PolicyFetchResponse.
+ using ResponseMap = std::map<std::pair<std::string, std::string>,
+ enterprise_management::PolicyFetchResponse>;
+
+ // A callback which receives boolean status of an operation. If the operation
+ // succeeded, |status| is true.
+ using StatusCallback = base::OnceCallback<void(bool status)>;
+
+ // A callback which receives fetched remote commands.
+ using RemoteCommandCallback = base::OnceCallback<void(
+ DeviceManagementStatus,
+ const std::vector<enterprise_management::SignedData>&)>;
+
+ // A callback for fetching device robot OAuth2 authorization tokens.
+ // Only occurs during enrollment, after the device is registered.
+ using RobotAuthCodeCallback =
+ base::OnceCallback<void(DeviceManagementStatus, const std::string&)>;
+
+ // A callback which fetches device dm_token based on user affiliation.
+ // Should be called once per registration.
+ using DeviceDMTokenCallback = base::RepeatingCallback<std::string(
+ const std::vector<std::string>& user_affiliation_ids)>;
+
+ // Callback that processes response value received from the server,
+ // or nullopt, if there was a failure.
+ using ResponseCallback =
+ base::OnceCallback<void(absl::optional<base::Value::Dict>)>;
+
+ using ClientCertProvisioningStartCsrCallback = base::OnceCallback<void(
+ DeviceManagementStatus,
+ absl::optional<
+ enterprise_management::ClientCertificateProvisioningResponse::Error>,
+ absl::optional<int64_t> try_later,
+ const std::string& invalidation_topic,
+ const std::string& va_challenge,
+ enterprise_management::HashingAlgorithm hash_algorithm,
+ const std::string& data_to_sign)>;
+
+ using ClientCertProvisioningFinishCsrCallback = base::OnceCallback<void(
+ DeviceManagementStatus,
+ absl::optional<
+ enterprise_management::ClientCertificateProvisioningResponse::Error>,
+ absl::optional<int64_t> try_later)>;
+
+ using ClientCertProvisioningDownloadCertCallback = base::OnceCallback<void(
+ DeviceManagementStatus,
+ absl::optional<
+ enterprise_management::ClientCertificateProvisioningResponse::Error>,
+ absl::optional<int64_t> try_later,
+ const std::string& pem_encoded_certificate)>;
+
+ // Observer interface for state and policy changes.
+ class POLICY_EXPORT Observer {
+ public:
+ virtual ~Observer();
+
+ // Called when a policy fetch completes successfully. If a policy fetch
+ // triggers an error, OnClientError() will fire.
+ virtual void OnPolicyFetched(CloudPolicyClient* client) = 0;
+
+ // Called upon registration state changes. This callback is invoked for
+ // successful completion of registration and unregistration requests.
+ virtual void OnRegistrationStateChanged(CloudPolicyClient* client) = 0;
+
+ // Indicates there's been an error in a previously-issued request.
+ virtual void OnClientError(CloudPolicyClient* client) = 0;
+
+ // Called when the Service Account Identity is set on a policy data object
+ // after a policy fetch. |service_account_email()| will return the new
+ // account's email.
+ virtual void OnServiceAccountSet(CloudPolicyClient* client,
+ const std::string& account_email) {}
+ };
+
+ struct POLICY_EXPORT RegistrationParameters {
+ public:
+ RegistrationParameters(
+ enterprise_management::DeviceRegisterRequest::Type registration_type,
+ enterprise_management::DeviceRegisterRequest::Flavor flavor);
+ ~RegistrationParameters();
+
+ // A setter for |psm_execution_result| field.
+ void SetPsmExecutionResult(
+ absl::optional<
+ enterprise_management::DeviceRegisterRequest::PsmExecutionResult>
+ new_psm_result);
+
+ // A setter for |psm_determination_timestamp| field.
+ void SetPsmDeterminationTimestamp(
+ absl::optional<int64_t> new_psm_timestamp);
+
+ void SetLicenseType(
+ enterprise_management::LicenseType_LicenseTypeEnum license_type);
+
+ enterprise_management::DeviceRegisterRequest::Type registration_type;
+ enterprise_management::DeviceRegisterRequest::Flavor flavor;
+
+ absl::optional<enterprise_management::LicenseType_LicenseTypeEnum>
+ license_type_;
+
+ // Lifetime of registration. Used for easier clean up of ephemeral session
+ // registrations.
+ enterprise_management::DeviceRegisterRequest::Lifetime lifetime =
+ enterprise_management::DeviceRegisterRequest::LIFETIME_INDEFINITE;
+
+ // Device requisition.
+ std::string requisition;
+
+ // Server-backed state keys (used for forced enrollment check).
+ std::string current_state_key;
+
+ // The following field is relevant only to Chrome OS.
+ // PSM protocol execution result. Its value will exist if the device
+ // undergoes enrollment and a PSM server-backed state determination was
+ // performed before (on Chrome OS, as encoded in the
+ // `prefs::kEnrollmentPsmResult` pref).
+ absl::optional<
+ enterprise_management::DeviceRegisterRequest::PsmExecutionResult>
+ psm_execution_result;
+
+ // The following field is relevant only to Chrome OS.
+ // PSM protocol determination timestamp. Its value will exist if the device
+ // undergoes enrollment and PSM got executed successfully (on ChromeOS, as
+ // encoded in `prefs::kEnrollmentPsmDeterminationTime` pref).
+ absl::optional<int64_t> psm_determination_timestamp;
+ };
+
+ // If non-empty, |machine_id|, |machine_model|, |brand_code|,
+ // |attested_device_id|, |ethernet_mac_address|, |dock_mac_address| and
+ // |manufacture_date| are passed to the server verbatim. As these reveal
+ // machine identity, they must only be used where this is appropriate (i.e.
+ // device policy, but not user policy). |service| is weak pointer and it's
+ // the caller's responsibility to keep it valid for the lifetime of
+ // CloudPolicyClient. |device_dm_token_callback| is used to retrieve device
+ // DMToken for affiliated users. Could be null if it's not possible to use
+ // device DMToken for user policy fetches.
+ CloudPolicyClient(
+ const std::string& machine_id,
+ const std::string& machine_model,
+ const std::string& brand_code,
+ const std::string& attested_device_id,
+ const std::string& ethernet_mac_address,
+ const std::string& dock_mac_address,
+ const std::string& manufacture_date,
+ DeviceManagementService* service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceDMTokenCallback device_dm_token_callback);
+ // A simpler constructor for those that do not need any of the identification
+ // strings of the full constructor.
+ CloudPolicyClient(
+ DeviceManagementService* service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceDMTokenCallback device_dm_token_callback);
+ CloudPolicyClient(const CloudPolicyClient&) = delete;
+ CloudPolicyClient& operator=(const CloudPolicyClient&) = delete;
+
+ virtual ~CloudPolicyClient();
+
+ // Sets the DMToken, thereby establishing a registration with the server. A
+ // policy fetch is not automatically issued but can be requested by calling
+ // FetchPolicy().
+ // |user_affiliation_ids| are used to get device DMToken if relevant.
+ virtual void SetupRegistration(
+ const std::string& dm_token,
+ const std::string& client_id,
+ const std::vector<std::string>& user_affiliation_ids);
+
+ // Attempts to register with the device management service. Results in a
+ // registration change or error notification.
+ virtual void Register(const RegistrationParameters& parameters,
+ const std::string& client_id,
+ const std::string& oauth_token);
+
+ // Attempts to register with the device management service using a
+ // registration certificate. Results in a registration change or
+ // error notification. The |signing_service| is used to sign the request and
+ // is expected to be available until caller receives
+ // |OnRegistrationStateChanged| or |OnClientError|.
+ // TODO(crbug.com/1236148): Remove SigningService from CloudPolicyClient and
+ // make callees sign their data themselves.
+ virtual void RegisterWithCertificate(const RegistrationParameters& parameters,
+ const std::string& client_id,
+ const std::string& pem_certificate_chain,
+ const std::string& sub_organization,
+ SigningService* signing_service);
+
+ // Attempts to enroll with the device management service using an enrollment
+ // token. Results in a registration change or error notification.
+ // This method is used to register browser (e.g. for machine-level policies).
+ // Device registration with enrollment token should be performed using
+ // RegisterWithCertificate method.
+ virtual void RegisterWithToken(
+ const std::string& token,
+ const std::string& client_id,
+ const ClientDataDelegate& client_data_delegate);
+
+ // Sets information about a policy invalidation. Subsequent fetch operations
+ // will use the given info, and callers can use fetched_invalidation_version
+ // to determine which version of policy was fetched.
+ void SetInvalidationInfo(int64_t version, const std::string& payload);
+
+ // Sets OAuth token to be used as an additional authentication in requests to
+ // DMServer. It is used for child user. This class does not track validity of
+ // the |oauth_token|. It should be provided with a fresh token when the
+ // previous token expires. If OAuth token is set for the client, it will be
+ // automatically included in the folllowing requests:
+ // * policy fetch
+ // * status report upload
+ virtual void SetOAuthTokenAsAdditionalAuth(const std::string& oauth_token);
+
+ // Requests a policy fetch. The client being registered is a prerequisite to
+ // this operation and this call will CHECK if the client is not in registered
+ // state. FetchPolicy() triggers a policy fetch from the cloud. A policy
+ // change notification is reported to the observers and the new policy blob
+ // can be retrieved once the policy fetch operation completes. In case of
+ // multiple requests to fetch policy, new requests will cancel any pending
+ // requests and the latest request will eventually trigger notifications.
+ virtual void FetchPolicy();
+
+ // Upload a policy validation report to the server. Like FetchPolicy, this
+ // method requires that the client is in a registered state. This method
+ // should only be called if the policy was rejected (e.g. validation or
+ // serialization error).
+ virtual void UploadPolicyValidationReport(
+ CloudPolicyValidatorBase::Status status,
+ const std::vector<ValueValidationIssue>& value_validation_issues,
+ const std::string& policy_type,
+ const std::string& policy_token);
+
+ // Requests OAuth2 auth codes for the device robot account. The client being
+ // registered is a prerequisite to this operation and this call will CHECK if
+ // the client is not in registered state. |oauth_scopes| is the scopes for
+ // which the robot auth codes will be valid, and |device_type| should match
+ // the type of the robot account associated with this request.
+ // The |callback| will be called when the operation completes.
+ virtual void FetchRobotAuthCodes(
+ DMAuth auth,
+ enterprise_management::DeviceServiceApiAccessRequest::DeviceType
+ device_type,
+ const std::set<std::string>& oauth_scopes,
+ RobotAuthCodeCallback callback);
+
+ // Sends an unregistration request to the server.
+ virtual void Unregister();
+
+ // Upload a machine certificate to the server. Like FetchPolicy, this method
+ // requires that the client is in a registered state. |certificate_data| must
+ // hold the X.509 certificate data to be sent to the server. The |callback|
+ // will be called when the operation completes.
+ virtual void UploadEnterpriseMachineCertificate(
+ const std::string& certificate_data,
+ StatusCallback callback);
+
+ // Upload an enrollment certificate to the server. Like FetchPolicy, this
+ // method requires that the client is in a registered state.
+ // |certificate_data| must hold the X.509 certificate data to be sent to the
+ // server. The |callback| will be called when the operation completes.
+ virtual void UploadEnterpriseEnrollmentCertificate(
+ const std::string& certificate_data,
+ StatusCallback callback);
+
+ // Upload an enrollment identifier to the server. Like FetchPolicy, this
+ // method requires that the client is in a registered state.
+ // |enrollment_id| must hold an enrollment identifier. The |callback| will be
+ // called when the operation completes.
+ virtual void UploadEnterpriseEnrollmentId(const std::string& enrollment_id,
+ StatusCallback callback);
+
+ // Uploads status to the server. The client must be in a registered state.
+ // Only non-null statuses will be included in the upload status request. The
+ // |callback| will be called when the operation completes.
+ virtual void UploadDeviceStatus(
+ const enterprise_management::DeviceStatusReportRequest* device_status,
+ const enterprise_management::SessionStatusReportRequest* session_status,
+ const enterprise_management::ChildStatusReportRequest* child_status,
+ StatusCallback callback);
+
+ // Uploads Chrome Desktop report to the server. As above, the client must be
+ // in a registered state. |chrome_desktop_report| will be included in the
+ // upload request. The |callback| will be called when the operation completes.
+ virtual void UploadChromeDesktopReport(
+ std::unique_ptr<enterprise_management::ChromeDesktopReportRequest>
+ chrome_desktop_report,
+ StatusCallback callback);
+
+ // Uploads Chrome OS User report to the server. The user's DM token must be
+ // set. |chrome_os_user_report| will be included in the upload request. The
+ // |callback| will be called when the operation completes.
+ virtual void UploadChromeOsUserReport(
+ std::unique_ptr<enterprise_management::ChromeOsUserReportRequest>
+ chrome_os_user_report,
+ StatusCallback callback);
+
+ // Uploads Chrome profile report to the server. The user's DM token must be
+ // set. |chrome_profile_report| will be included in the upload request. The
+ // |callback| will be called when the operation completes.
+ virtual void UploadChromeProfileReport(
+ std::unique_ptr<enterprise_management::ChromeProfileReportRequest>
+ chrome_profile_report,
+ StatusCallback callback);
+
+ // Uploads a report containing enterprise connectors real-time security
+ // events for |context|. As above, the client must be in a registered state.
+ // If |include_device_info| is true, information specific to the device such
+ // as the device name, user, id and OS will be included in the report. The
+ // |callback| will be called when the operation completes.
+ virtual void UploadSecurityEventReport(content::BrowserContext* context,
+ bool include_device_info,
+ base::Value::Dict report,
+ StatusCallback callback);
+
+ // Uploads a report containing |merging_payload| (merged into the default
+ // payload of the job). The client must be in a registered state. The
+ // |callback| will be called when the operation completes.
+ virtual void UploadEncryptedReport(base::Value::Dict merging_payload,
+ absl::optional<base::Value::Dict> context,
+ ResponseCallback callback);
+
+ // Uploads a report on the status of app push-installs. The client must be in
+ // a registered state. The |callback| will be called when the operation
+ // completes.
+ // Only one outstanding app push-install report upload is allowed.
+ // In case the new push-installs report upload is started, the previous one
+ // will be canceled.
+ virtual void UploadAppInstallReport(base::Value::Dict report,
+ StatusCallback callback);
+
+ // Cancels the pending app push-install status report upload, if exists.
+ virtual void CancelAppInstallReportUpload();
+
+ // Uploads a report on the status of extension installs. The client must be in
+ // a registered state. The |callback| will be called when the operation
+ // completes.
+ // Only one outstanding extension install report upload is allowed.
+ // In case the new installs report upload is started, the previous one
+ // will be canceled.
+ virtual void UploadExtensionInstallReport(base::Value::Dict report,
+ StatusCallback callback);
+
+ // Cancels the pending extension install status report upload, if exists.
+ virtual void CancelExtensionInstallReportUpload();
+
+ // Attempts to fetch remote commands, with |last_command_id| being the ID of
+ // the last command that finished execution and |command_results| being
+ // results for previous commands which have not been reported yet. The
+ // |callback| will be called when the operation completes.
+ // Note that sending |last_command_id| will acknowledge this command and any
+ // previous commands. A nullptr indicates that no commands have finished
+ // execution.
+ virtual void FetchRemoteCommands(
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
+ const std::vector<enterprise_management::RemoteCommandResult>&
+ command_results,
+ RemoteCommandCallback callback);
+
+ // Sends a device attribute update permission request to the server, uses
+ // |auth| to identify user who requests a permission to name a device, calls
+ // a |callback| from the enrollment screen to indicate whether the device
+ // naming prompt should be shown.
+ void GetDeviceAttributeUpdatePermission(DMAuth auth, StatusCallback callback);
+
+ // Sends a device naming information (Asset Id and Location) to the
+ // device management server, uses |auth| to identify user who names a device,
+ // the |callback| will be called when the operation completes.
+ void UpdateDeviceAttributes(DMAuth auth,
+ const std::string& asset_id,
+ const std::string& location,
+ StatusCallback callback);
+
+ // Sends a GCM id update request to the DM server. The server will
+ // associate the DM token in authorization header with |gcm_id|, and
+ // |callback| will be called when the operation completes.
+ virtual void UpdateGcmId(const std::string& gcm_id, StatusCallback callback);
+
+ // Sends a request with EUICCs on device to the DM server.
+ virtual void UploadEuiccInfo(
+ std::unique_ptr<enterprise_management::UploadEuiccInfoRequest> request,
+ StatusCallback callback);
+
+ // Sends certificate provisioning start csr request. It is Step 1 in the
+ // certificate provisioning flow. |cert_scope| defines if it is a user- or
+ // device-level request, |cert_profile_id| defines for which profile from
+ // policies the request applies, |public_key| is used to build the CSR.
+ // |callback| will be called when the operation completes. It is expected to
+ // receive the CSR and VA challenge.
+ virtual void ClientCertProvisioningStartCsr(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningStartCsrCallback callback);
+
+ // Sends certificate provisioning finish csr request. It is Step 2 in the
+ // certificate provisioning flow. |cert_scope| defines if it is a user- or
+ // device-level request, |cert_profile_id| and |public_key| define the
+ // provisioning flow that should be continued. |va_challenge_response| is a
+ // challenge response to the challenge from the previous step. |signature| is
+ // cryptographic signature of the CSR from the previous step, the algorithm
+ // for it is defined in a corresponding certificate profile. |callback| will
+ // be called when the operation completes. It is expected to receive a
+ // confirmation that the request is accepted.
+ virtual void ClientCertProvisioningFinishCsr(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ const std::string& va_challenge_response,
+ const std::string& signature,
+ ClientCertProvisioningFinishCsrCallback callback);
+
+ // Sends certificate provisioning download certificate request. It is Step 3
+ // (final) in the certificate provisioning flow. |cert_scope|,
+ // |cert_profile_id|, |public_key| are the same as for finish csr request.
+ // |callback| will be called when the operation completes. It is expected to
+ // receive a certificate that was issued according to the CSR that was
+ // generated during previous steps.
+ virtual void ClientCertProvisioningDownloadCert(
+ const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningDownloadCertCallback callback);
+
+ // Used the update the current service account email associated with this
+ // policy client and notify observers.
+ void UpdateServiceAccount(const std::string& account_email);
+
+ // Adds an observer to be called back upon policy and state changes.
+ void AddObserver(Observer* observer);
+
+ // Removes the specified observer.
+ void RemoveObserver(Observer* observer);
+
+ const std::string& machine_id() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return machine_id_;
+ }
+ const std::string& machine_model() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return machine_model_;
+ }
+ const std::string& brand_code() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return brand_code_;
+ }
+ const std::string& attested_device_id() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return attested_device_id_;
+ }
+ const std::string& ethernet_mac_address() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return ethernet_mac_address_;
+ }
+ const std::string& dock_mac_address() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return dock_mac_address_;
+ }
+ const std::string& manufacture_date() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return manufacture_date_;
+ }
+
+ void set_last_policy_timestamp(const base::Time& timestamp) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ last_policy_timestamp_ = timestamp;
+ }
+
+ const base::Time& last_policy_timestamp() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return last_policy_timestamp_;
+ }
+
+ void set_public_key_version(int public_key_version) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ public_key_version_ = public_key_version;
+ public_key_version_valid_ = true;
+ }
+
+ void clear_public_key_version() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ public_key_version_valid_ = false;
+ }
+
+ // FetchPolicy() calls will request this policy type.
+ // If |settings_entity_id| is empty then it won't be set in the
+ // PolicyFetchRequest.
+ void AddPolicyTypeToFetch(const std::string& policy_type,
+ const std::string& settings_entity_id);
+
+ // FetchPolicy() calls won't request the given policy type and optional
+ // |settings_entity_id| anymore.
+ void RemovePolicyTypeToFetch(const std::string& policy_type,
+ const std::string& settings_entity_id);
+
+ // Configures a set of device state keys to transfer to the server in the next
+ // policy fetch. If the fetch is successful, the keys will be cleared so they
+ // are only uploaded once.
+ void SetStateKeysToUpload(const std::vector<std::string>& keys);
+
+ // Whether the client is registered with the device management service.
+ bool is_registered() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return !dm_token_.empty();
+ }
+
+ // Whether the client requires reregistration with the device management
+ // service.
+ bool requires_reregistration() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return !reregistration_dm_token_.empty();
+ }
+
+ DeviceManagementService* service() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return service_;
+ }
+ const std::string& dm_token() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return dm_token_;
+ }
+ const std::string& client_id() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return client_id_;
+ }
+ const base::DictionaryValue* configuration_seed() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return configuration_seed_.get();
+ }
+
+ // The device mode as received in the registration request.
+ DeviceMode device_mode() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return device_mode_;
+ }
+
+ // The policy responses as obtained by the last request to the cloud. These
+ // policies haven't gone through verification, so their contents cannot be
+ // trusted. Use CloudPolicyStore::policy() and CloudPolicyStore::policy_map()
+ // instead for making policy decisions.
+ const ResponseMap& responses() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return responses_;
+ }
+
+ // Returns the policy response for the (|policy_type|, |settings_entity_id|)
+ // pair if found in |responses()|. Otherwise returns nullptr.
+ const enterprise_management::PolicyFetchResponse* GetPolicyFor(
+ const std::string& policy_type,
+ const std::string& settings_entity_id) const;
+
+ DeviceManagementStatus status() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return status_;
+ }
+
+ // Returns the invalidation version that was used for the last FetchPolicy.
+ // Observers can call this method from their OnPolicyFetched method to
+ // determine which at which invalidation version the policy was fetched.
+ int64_t fetched_invalidation_version() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return fetched_invalidation_version_;
+ }
+
+ scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory();
+
+ void add_connector_url_params(bool value) {
+ add_connector_url_params_ = value;
+ }
+
+ // Returns the number of active requests.
+ int GetActiveRequestCountForTest() const;
+
+ void SetURLLoaderFactoryForTesting(
+ scoped_refptr<network::SharedURLLoaderFactory> factory);
+
+ protected:
+ // A set of (policy type, settings entity ID) pairs to fetch.
+ typedef std::set<std::pair<std::string, std::string>> PolicyTypeSet;
+
+ // Upload a certificate to the server. Like FetchPolicy, this method
+ // requires that the client is in a registered state. |certificate_data| must
+ // hold the X.509 certificate data to be sent to the server. The |callback|
+ // will be called when the operation completes.
+ void UploadCertificate(
+ const std::string& certificate_data,
+ enterprise_management::DeviceCertUploadRequest::CertificateType
+ certificate_type,
+ StatusCallback callback);
+
+ // This is called when a RegisterWithCertiifcate request has been signed.
+ void OnRegisterWithCertificateRequestSigned(
+ bool success,
+ enterprise_management::SignedData signed_data);
+
+ // Callback for registration requests.
+ void OnRegisterCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for policy fetch requests.
+ void OnPolicyFetchCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for robot account api authorization requests.
+ void OnFetchRobotAuthCodesCompleted(
+ RobotAuthCodeCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for unregistration requests.
+ void OnUnregisterCompleted(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for certificate upload requests.
+ void OnCertificateUploadCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for several types of status/report upload requests.
+ void OnReportUploadCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for realtime report upload requests.
+ void OnRealtimeReportUploadCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ absl::optional<base::Value::Dict> response);
+
+ // Callback for encrypted report upload requests.
+ void OnEncryptedReportUploadCompleted(
+ ResponseCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ absl::optional<base::Value::Dict> response);
+
+ // Callback for remote command fetch requests.
+ void OnRemoteCommandsFetched(
+ RemoteCommandCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for device attribute update permission requests.
+ void OnDeviceAttributeUpdatePermissionCompleted(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for device attribute update requests.
+ void OnDeviceAttributeUpdated(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for gcm id update requests.
+ void OnGcmIdUpdated(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for EUICC info upload requests.
+ void OnEuiccInfoUploaded(
+ StatusCallback callback,
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for certificate provisioning start csr requests.
+ void OnClientCertProvisioningStartCsrResponse(
+ ClientCertProvisioningStartCsrCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for certificate provisioning finish csr requests.
+ void OnClientCertProvisioningFinishCsrResponse(
+ ClientCertProvisioningFinishCsrCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Callback for certificate provisioning download cert requests.
+ void OnClientCertProvisioningDownloadCertResponse(
+ ClientCertProvisioningDownloadCertCallback callback,
+ policy::DeviceManagementService::Job* job,
+ policy::DeviceManagementStatus status,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Helper to remove a job from request_jobs_.
+ void RemoveJob(DeviceManagementService::Job* job);
+
+ // Observer notification helpers.
+ void NotifyPolicyFetched();
+ void NotifyRegistrationStateChanged();
+ void NotifyClientError();
+ void NotifyServiceAccountSet(const std::string& account_email);
+
+ // Assert non-concurrent usage in debug builds.
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Data necessary for constructing policy requests.
+ const std::string machine_id_;
+ const std::string machine_model_;
+ const std::string brand_code_;
+ const std::string attested_device_id_;
+ const std::string ethernet_mac_address_;
+ const std::string dock_mac_address_;
+ const std::string manufacture_date_;
+ PolicyTypeSet types_to_fetch_;
+ std::vector<std::string> state_keys_to_upload_;
+
+ // OAuth token that if set is used as an additional form of authentication
+ // (next to |dm_token_|) in policy fetch requests.
+ std::string oauth_token_;
+
+ std::string dm_token_;
+ std::unique_ptr<base::DictionaryValue> configuration_seed_;
+ DeviceMode device_mode_ = DEVICE_MODE_NOT_SET;
+ std::string client_id_;
+ base::Time last_policy_timestamp_;
+ int public_key_version_ = -1;
+ bool public_key_version_valid_ = false;
+ // Device DMToken for affiliated user policy requests.
+ // Retrieved from |device_dm_token_callback_| on registration.
+ std::string device_dm_token_;
+
+ // Information for the latest policy invalidation received.
+ int64_t invalidation_version_ = 0;
+ std::string invalidation_payload_;
+
+ // The invalidation version used for the most recent fetch operation.
+ int64_t fetched_invalidation_version_ = 0;
+
+ // Used for issuing requests to the cloud.
+ raw_ptr<DeviceManagementService> service_ = nullptr;
+
+ // Only one outstanding policy fetch or device/user registration request is
+ // allowed. Using a separate job to track those requests. If multiple
+ // requests have been started, only the last one will be kept.
+ std::unique_ptr<DeviceManagementService::Job> unique_request_job_;
+
+ // All of the outstanding non-policy-fetch request jobs. These jobs are
+ // silently cancelled if Unregister() is called.
+ std::vector<std::unique_ptr<DeviceManagementService::Job>> request_jobs_;
+
+ // Only one outstanding app push-install report upload is allowed, and it must
+ // be accessible so that it can be canceled.
+ raw_ptr<DeviceManagementService::Job> app_install_report_request_job_ =
+ nullptr;
+
+ // Only one outstanding extension install report upload is allowed, and it
+ // must be accessible so that it can be canceled.
+ raw_ptr<DeviceManagementService::Job> extension_install_report_request_job_ =
+ nullptr;
+
+ // The policy responses returned by the last policy fetch operation.
+ ResponseMap responses_;
+ DeviceManagementStatus status_ = DM_STATUS_SUCCESS;
+
+ DeviceDMTokenCallback device_dm_token_callback_;
+
+ base::ObserverList<Observer, true>::Unchecked observers_;
+
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+ private:
+ // Creates a new real-time reporting job and appends it to |request_jobs_|.
+ // The job will send its report to the |server_url| endpoint. If
+ // |include_device_info| is true, information specific to the device such as
+ // the device name, user, id and OS will be included in the report. If
+ // |add_connector_url_params| is true then URL paramaters specific to
+ // enterprise connectors are added to the request uploading the report.
+ // |callback| is invoked once the report is uploaded.
+ DeviceManagementService::Job* CreateNewRealtimeReportingJob(
+ base::Value::Dict report,
+ const std::string& server_url,
+ bool include_device_info,
+ bool add_connector_url_params,
+ StatusCallback callback);
+
+ void SetClientId(const std::string& client_id);
+ // Fills in the common fields of a DeviceRegisterRequest for |Register| and
+ // |RegisterWithCertificate|.
+ void CreateDeviceRegisterRequest(
+ const RegistrationParameters& params,
+ const std::string& client_id,
+ enterprise_management::DeviceRegisterRequest* request);
+
+ // Prepare the certificate upload request field for uploading a certificate.
+ void PrepareCertUploadRequest(
+ DMServerJobConfiguration* config,
+ const std::string& certificate_data,
+ enterprise_management::DeviceCertUploadRequest::CertificateType
+ certificate_type);
+
+ // Creates a job config to upload a certificate.
+ std::unique_ptr<DMServerJobConfiguration> CreateCertUploadJobConfiguration(
+ CloudPolicyClient::StatusCallback callback);
+
+ // Executes a job to upload a certificate. Onwership of the job is
+ // retained by this method.
+ void ExecuteCertUploadJob(std::unique_ptr<DMServerJobConfiguration> config);
+
+ // Sets `unique_request_job_` with a new job created with `config`.
+ void CreateUniqueRequestJob(
+ std::unique_ptr<RegistrationJobConfiguration> config);
+
+ // Used to store a copy of the previously used |dm_token_|. This is used
+ // during re-registration, which gets triggered by a failed policy fetch with
+ // error |DM_STATUS_SERVICE_DEVICE_NOT_FOUND|.
+ std::string reregistration_dm_token_;
+
+ // Whether extra enterprise connectors URL parameters should be included
+ // in real-time reports. Only reports uploaded using UploadRealtimeReport()
+ // are affected.
+ bool add_connector_url_params_ = false;
+
+ // Used to create tasks which run delayed on the UI thread.
+ base::WeakPtrFactory<CloudPolicyClient> weak_ptr_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.cc b/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.cc
new file mode 100644
index 00000000000..0c9a8237fc5
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.cc
@@ -0,0 +1,212 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/client_data_delegate.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/signin/public/identity_manager/access_token_fetcher.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/scope_set.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace policy {
+
+// The key under which the hosted-domain value is stored in the UserInfo
+// response.
+const char kGetHostedDomainKey[] = "hd";
+
+typedef base::OnceCallback<void(const std::string&)> StringCallback;
+
+// This class fetches an OAuth2 token scoped for the userinfo and DM services.
+class CloudPolicyClientRegistrationHelper::IdentityManagerHelper {
+ public:
+ IdentityManagerHelper() = default;
+
+ void FetchAccessToken(signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ StringCallback callback);
+
+ private:
+ void OnAccessTokenFetchComplete(GoogleServiceAuthError error,
+ signin::AccessTokenInfo token_info);
+
+ StringCallback callback_;
+ std::unique_ptr<signin::AccessTokenFetcher> access_token_fetcher_;
+};
+
+void CloudPolicyClientRegistrationHelper::IdentityManagerHelper::
+ FetchAccessToken(signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ StringCallback callback) {
+ DCHECK(!access_token_fetcher_);
+ // The caller must supply a username.
+ DCHECK(!account_id.empty());
+ DCHECK(identity_manager->HasAccountWithRefreshToken(account_id));
+
+ callback_ = std::move(callback);
+
+ signin::ScopeSet scopes;
+ scopes.insert(GaiaConstants::kDeviceManagementServiceOAuth);
+ scopes.insert(GaiaConstants::kGoogleUserInfoEmail);
+
+ access_token_fetcher_ = identity_manager->CreateAccessTokenFetcherForAccount(
+ account_id, "cloud_policy", scopes,
+ base::BindOnce(&CloudPolicyClientRegistrationHelper::
+ IdentityManagerHelper::OnAccessTokenFetchComplete,
+ base::Unretained(this)),
+ signin::AccessTokenFetcher::Mode::kImmediate);
+}
+
+void CloudPolicyClientRegistrationHelper::IdentityManagerHelper::
+ OnAccessTokenFetchComplete(GoogleServiceAuthError error,
+ signin::AccessTokenInfo token_info) {
+ DCHECK(access_token_fetcher_);
+ access_token_fetcher_.reset();
+
+ if (error.state() == GoogleServiceAuthError::NONE)
+ std::move(callback_).Run(token_info.token);
+ else
+ std::move(callback_).Run("");
+}
+
+CloudPolicyClientRegistrationHelper::CloudPolicyClientRegistrationHelper(
+ CloudPolicyClient* client,
+ enterprise_management::DeviceRegisterRequest::Type registration_type)
+ : client_(client), registration_type_(registration_type) {
+ DCHECK(client_);
+}
+
+CloudPolicyClientRegistrationHelper::~CloudPolicyClientRegistrationHelper() {
+ // Clean up any pending observers in case the browser is shutdown while
+ // trying to register for policy.
+ if (client_)
+ client_->RemoveObserver(this);
+}
+
+void CloudPolicyClientRegistrationHelper::StartRegistration(
+ signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceClosure callback) {
+ DVLOG(1) << "Starting registration process with account_id";
+ DCHECK(!client_->is_registered());
+ callback_ = std::move(callback);
+ client_->AddObserver(this);
+
+ identity_manager_helper_ = std::make_unique<IdentityManagerHelper>();
+ identity_manager_helper_->FetchAccessToken(
+ identity_manager, account_id,
+ base::BindOnce(&CloudPolicyClientRegistrationHelper::OnTokenFetched,
+ base::Unretained(this)));
+}
+
+void CloudPolicyClientRegistrationHelper::StartRegistrationWithEnrollmentToken(
+ const std::string& token,
+ const std::string& client_id,
+ const ClientDataDelegate& client_data_delegate,
+ base::OnceClosure callback) {
+ DVLOG(1) << "Starting registration process with enrollment token";
+ DCHECK(!client_->is_registered());
+ callback_ = std::move(callback);
+ client_->AddObserver(this);
+ client_->RegisterWithToken(token, client_id, client_data_delegate);
+}
+
+void CloudPolicyClientRegistrationHelper::OnTokenFetched(
+ const std::string& access_token) {
+ identity_manager_helper_.reset();
+
+ if (access_token.empty()) {
+ DLOG(WARNING) << "Could not fetch access token for "
+ << GaiaConstants::kDeviceManagementServiceOAuth;
+ RequestCompleted();
+ return;
+ }
+
+ // Cache the access token to be used after the GetUserInfo call.
+ oauth_access_token_ = access_token;
+ DVLOG(1) << "Fetched new scoped OAuth token:" << oauth_access_token_;
+ // Now we've gotten our access token - contact GAIA to see if this is a
+ // hosted domain.
+ user_info_fetcher_ =
+ std::make_unique<UserInfoFetcher>(this, client_->GetURLLoaderFactory());
+ user_info_fetcher_->Start(oauth_access_token_);
+}
+
+void CloudPolicyClientRegistrationHelper::OnGetUserInfoFailure(
+ const GoogleServiceAuthError& error) {
+ DVLOG(1) << "Failed to fetch user info from GAIA: " << error.state();
+ user_info_fetcher_.reset();
+ RequestCompleted();
+}
+
+void CloudPolicyClientRegistrationHelper::OnGetUserInfoSuccess(
+ const base::DictionaryValue* data) {
+ user_info_fetcher_.reset();
+ if (!data->FindKey(kGetHostedDomainKey)) {
+ DVLOG(1) << "User not from a hosted domain - skipping registration";
+ RequestCompleted();
+ return;
+ }
+ DVLOG(1) << "Registering CloudPolicyClient for user from hosted domain";
+ // The user is from a hosted domain, so it's OK to register the
+ // CloudPolicyClient and make requests to DMServer.
+ if (client_->is_registered()) {
+ // Client should not be registered yet.
+ NOTREACHED();
+ RequestCompleted();
+ return;
+ }
+
+ // Kick off registration of the CloudPolicyClient with our newly minted
+ // oauth_access_token_.
+ client_->Register(
+ CloudPolicyClient::RegistrationParameters(
+ registration_type_, enterprise_management::DeviceRegisterRequest::
+ FLAVOR_USER_REGISTRATION),
+ std::string() /* client_id */, oauth_access_token_);
+}
+
+void CloudPolicyClientRegistrationHelper::OnPolicyFetched(
+ CloudPolicyClient* client) {
+ // Ignored.
+}
+
+void CloudPolicyClientRegistrationHelper::OnRegistrationStateChanged(
+ CloudPolicyClient* client) {
+ DVLOG(1) << "Client registration succeeded";
+ DCHECK_EQ(client, client_);
+ DCHECK(client->is_registered());
+ RequestCompleted();
+}
+
+void CloudPolicyClientRegistrationHelper::OnClientError(
+ CloudPolicyClient* client) {
+ DVLOG(1) << "Client registration failed";
+ DCHECK_EQ(client, client_);
+ RequestCompleted();
+}
+
+void CloudPolicyClientRegistrationHelper::RequestCompleted() {
+ if (client_) {
+ client_->RemoveObserver(this);
+ // |client_| may be freed by the callback so clear it now.
+ client_ = nullptr;
+ std::move(callback_).Run();
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.h b/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.h
new file mode 100644
index 00000000000..fc42763ca68
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_client_registration_helper.h
@@ -0,0 +1,104 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_REGISTRATION_HELPER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_REGISTRATION_HELPER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/user_info_fetcher.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "google_apis/gaia/core_account_id.h"
+
+namespace signin {
+class IdentityManager;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class ClientDataDelegate;
+
+// Helper class that registers a CloudPolicyClient. It fetches an OAuth2 token
+// for the DM service if needed, and checks with Gaia if the account has policy
+// management enabled.
+class POLICY_EXPORT CloudPolicyClientRegistrationHelper
+ : public UserInfoFetcher::Delegate,
+ public CloudPolicyClient::Observer {
+ public:
+ // |context| and |client| are not owned and must outlive this object.
+ CloudPolicyClientRegistrationHelper(
+ CloudPolicyClient* client,
+ enterprise_management::DeviceRegisterRequest::Type registration_type);
+ CloudPolicyClientRegistrationHelper(
+ const CloudPolicyClientRegistrationHelper&) = delete;
+ CloudPolicyClientRegistrationHelper& operator=(
+ const CloudPolicyClientRegistrationHelper&) = delete;
+ ~CloudPolicyClientRegistrationHelper() override;
+
+ // Starts the client registration process. This version uses the
+ // supplied IdentityManager to mint the new token for the userinfo
+ // and DM services, using the |account_id|.
+ // |callback| is invoked when the registration is complete.
+ void StartRegistration(signin::IdentityManager* identity_manager,
+ const CoreAccountId& account_id,
+ base::OnceClosure callback);
+
+ // Starts the device registration with an token enrollment process.
+ // |callback| is invoked when the registration is complete.
+ void StartRegistrationWithEnrollmentToken(
+ const std::string& token,
+ const std::string& client_id,
+ const ClientDataDelegate& client_data_delegate,
+ base::OnceClosure callback);
+
+ private:
+ class IdentityManagerHelper;
+
+ void OnTokenFetched(const std::string& oauth_access_token);
+
+ // UserInfoFetcher::Delegate implementation:
+ void OnGetUserInfoSuccess(const base::DictionaryValue* response) override;
+ void OnGetUserInfoFailure(const GoogleServiceAuthError& error) override;
+
+ // CloudPolicyClient::Observer implementation:
+ void OnPolicyFetched(CloudPolicyClient* client) override;
+ void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+ void OnClientError(CloudPolicyClient* client) override;
+
+ // Invoked when the registration request has been completed.
+ void RequestCompleted();
+
+ // Internal helper class that uses IdentityManager to fetch an OAuth
+ // access token.
+ std::unique_ptr<IdentityManagerHelper> identity_manager_helper_;
+
+ // Helper class for fetching information from GAIA about the currently
+ // signed-in user.
+ std::unique_ptr<UserInfoFetcher> user_info_fetcher_;
+
+ // Access token used to register the CloudPolicyClient and also access
+ // GAIA to get information about the signed in user.
+ std::string oauth_access_token_;
+
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ raw_ptr<CloudPolicyClient> client_;
+ enterprise_management::DeviceRegisterRequest::Type registration_type_;
+ base::OnceClosure callback_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CLIENT_REGISTRATION_HELPER_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
new file mode 100644
index 00000000000..ce5e29ed00e
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
@@ -0,0 +1,3012 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/client_data_delegate.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_device_management_service.h"
+#include "components/policy/core/common/cloud/mock_signing_service.h"
+#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/version_info/version_info.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/system/fake_statistics_provider.h"
+#endif
+
+using testing::_;
+using testing::Contains;
+using testing::DoAll;
+using testing::ElementsAre;
+using testing::Invoke;
+using testing::Mock;
+using testing::Not;
+using testing::Pair;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::WithArg;
+
+// Matcher for absl::optional. Can be combined with Not().
+MATCHER(HasValue, "Has value") {
+ return arg.has_value();
+}
+
+// The type for variables containing an error from DM Server response.
+using CertProvisioningResponseErrorType =
+ enterprise_management::ClientCertificateProvisioningResponse::Error;
+// The namespace that contains convenient aliases for error values, e.g.
+// UNDEFINED, TIMED_OUT, IDENTITY_VERIFICATION_ERROR, CA_ERROR.
+using CertProvisioningResponseError =
+ enterprise_management::ClientCertificateProvisioningResponse;
+
+namespace em = enterprise_management;
+
+// An enum for PSM execution result values.
+using PsmExecutionResult = em::DeviceRegisterRequest::PsmExecutionResult;
+
+namespace policy {
+
+namespace {
+
+constexpr char kClientID[] = "fake-client-id";
+constexpr char kMachineID[] = "fake-machine-id";
+constexpr char kMachineModel[] = "fake-machine-model";
+constexpr char kBrandCode[] = "fake-brand-code";
+constexpr char kAttestedDeviceId[] = "fake-attested-device-id";
+constexpr char kEthernetMacAddress[] = "fake-ethernet-mac-address";
+constexpr char kDockMacAddress[] = "fake-dock-mac-address";
+constexpr char kManufactureDate[] = "fake-manufacture-date";
+constexpr char kOAuthToken[] = "fake-oauth-token";
+constexpr char kDMToken[] = "fake-dm-token";
+constexpr char kDeviceDMToken[] = "fake-device-dm-token";
+constexpr char kMachineCertificate[] = "fake-machine-certificate";
+constexpr char kEnrollmentCertificate[] = "fake-enrollment-certificate";
+constexpr char kEnrollmentId[] = "fake-enrollment-id";
+constexpr char kOsName[] = "fake-os-name";
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || \
+ (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+constexpr char kEnrollmentToken[] = "enrollment_token";
+#endif
+
+constexpr char kRequisition[] = "fake-requisition";
+constexpr char kStateKey[] = "fake-state-key";
+constexpr char kPayload[] = "input_payload";
+constexpr char kResultPayload[] = "output_payload";
+constexpr char kAssetId[] = "fake-asset-id";
+constexpr char kLocation[] = "fake-location";
+constexpr char kGcmID[] = "fake-gcm-id";
+constexpr char kPolicyToken[] = "fake-policy-token";
+constexpr char kPolicyName[] = "fake-policy-name";
+constexpr char kValueValidationMessage[] = "fake-value-validation-message";
+constexpr char kRobotAuthCode[] = "fake-robot-auth-code";
+constexpr char kApiAuthScope[] = "fake-api-auth-scope";
+
+constexpr int64_t kAgeOfCommand = 123123123;
+constexpr int64_t kLastCommandId = 123456789;
+constexpr int64_t kTimestamp = 987654321;
+
+MATCHER_P(MatchProto, expected, "matches protobuf") {
+ return arg.SerializePartialAsString() == expected.SerializePartialAsString();
+}
+
+base::Value::Dict ConvertEncryptedRecordToValue(
+ const ::reporting::EncryptedRecord& record) {
+ base::Value::Dict record_request;
+ if (record.has_encrypted_wrapped_record()) {
+ base::Value::Dict encrypted_wrapped_record;
+ std::string base64_encode;
+ base::Base64Encode(record.encrypted_wrapped_record(), &base64_encode);
+ record_request.Set("encryptedWrappedRecord", base64_encode);
+ }
+ if (record.has_encryption_info()) {
+ base::Value::Dict encryption_info;
+ if (record.encryption_info().has_encryption_key()) {
+ std::string base64_encode;
+ base::Base64Encode(record.encryption_info().encryption_key(),
+ &base64_encode);
+ encryption_info.Set("encryptionKey", base64_encode);
+ }
+ if (record.encryption_info().has_public_key_id()) {
+ encryption_info.Set(
+ "publicKeyId",
+ base::NumberToString(record.encryption_info().public_key_id()));
+ }
+ record_request.Set("encryptionInfo", std::move(encryption_info));
+ }
+ if (record.has_sequence_information()) {
+ base::Value::Dict sequence_information;
+ if (record.sequence_information().has_sequencing_id()) {
+ sequence_information.Set(
+ "sequencingId",
+ base::NumberToString(record.sequence_information().sequencing_id()));
+ }
+ if (record.sequence_information().has_generation_id()) {
+ sequence_information.Set(
+ "generationId",
+ base::NumberToString(record.sequence_information().generation_id()));
+ }
+ if (record.sequence_information().has_priority()) {
+ sequence_information.Set("priority",
+ record.sequence_information().priority());
+ }
+ record_request.Set("sequencingInformation",
+ std::move(sequence_information));
+ }
+ return record_request;
+}
+
+// A mock class to allow us to set expectations on upload callbacks.
+struct MockStatusCallbackObserver {
+ MOCK_METHOD(void, OnCallbackComplete, (bool));
+};
+
+// A mock class to allow us to set expectations on remote command fetch
+// callbacks.
+struct MockRemoteCommandsObserver {
+ MOCK_METHOD(void,
+ OnRemoteCommandsFetched,
+ (DeviceManagementStatus, const std::vector<em::SignedData>&));
+};
+
+struct MockDeviceDMTokenCallbackObserver {
+ MOCK_METHOD(std::string,
+ OnDeviceDMTokenRequested,
+ (const std::vector<std::string>&));
+};
+
+struct MockRobotAuthCodeCallbackObserver {
+ MOCK_METHOD(void,
+ OnRobotAuthCodeFetched,
+ (DeviceManagementStatus, const std::string&));
+};
+
+struct MockResponseCallbackObserver {
+ MOCK_METHOD(void, OnResponseReceived, (absl::optional<base::Value::Dict>));
+};
+
+class FakeClientDataDelegate : public ClientDataDelegate {
+ public:
+ void FillRegisterBrowserRequest(
+ enterprise_management::RegisterBrowserRequest* request,
+ base::OnceClosure callback) const override {
+ request->set_os_platform(GetOSPlatform());
+ request->set_os_version(GetOSVersion());
+
+ std::move(callback).Run();
+ }
+};
+
+std::string CreatePolicyData(const std::string& policy_value) {
+ em::PolicyData policy_data;
+ policy_data.set_policy_type(dm_protocol::kChromeUserPolicyType);
+ policy_data.set_policy_value(policy_value);
+ return policy_data.SerializeAsString();
+}
+
+em::DeviceManagementRequest GetPolicyRequest() {
+ em::DeviceManagementRequest policy_request;
+
+ em::PolicyFetchRequest* policy_fetch_request =
+ policy_request.mutable_policy_request()->add_requests();
+ policy_fetch_request->set_policy_type(dm_protocol::kChromeUserPolicyType);
+ policy_fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA);
+ policy_fetch_request->set_verification_key_hash(kPolicyVerificationKeyHash);
+ policy_fetch_request->set_device_dm_token(kDeviceDMToken);
+
+ return policy_request;
+}
+
+em::DeviceManagementResponse GetPolicyResponse() {
+ em::DeviceManagementResponse policy_response;
+ policy_response.mutable_policy_response()->add_responses()->set_policy_data(
+ CreatePolicyData("fake-policy-data"));
+ return policy_response;
+}
+
+em::DeviceManagementRequest GetRegistrationRequest() {
+ em::DeviceManagementRequest request;
+
+ em::DeviceRegisterRequest* register_request =
+ request.mutable_register_request();
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_machine_id(kMachineID);
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_brand_code(kBrandCode);
+ register_request->mutable_device_register_identification()
+ ->set_attested_device_id(kAttestedDeviceId);
+ register_request->set_ethernet_mac_address(kEthernetMacAddress);
+ register_request->set_dock_mac_address(kDockMacAddress);
+ register_request->set_manufacture_date(kManufactureDate);
+ register_request->set_lifetime(
+ em::DeviceRegisterRequest::LIFETIME_INDEFINITE);
+ register_request->set_flavor(
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+
+ return request;
+}
+
+em::DeviceManagementResponse GetRegistrationResponse() {
+ em::DeviceManagementResponse registration_response;
+ registration_response.mutable_register_response()
+ ->set_device_management_token(kDMToken);
+ return registration_response;
+}
+
+em::DeviceManagementRequest GetReregistrationRequest() {
+ em::DeviceManagementRequest request;
+
+ em::DeviceRegisterRequest* reregister_request =
+ request.mutable_register_request();
+ reregister_request->set_type(em::DeviceRegisterRequest::USER);
+ reregister_request->set_machine_id(kMachineID);
+ reregister_request->set_machine_model(kMachineModel);
+ reregister_request->set_brand_code(kBrandCode);
+ reregister_request->mutable_device_register_identification()
+ ->set_attested_device_id(kAttestedDeviceId);
+ reregister_request->set_ethernet_mac_address(kEthernetMacAddress);
+ reregister_request->set_dock_mac_address(kDockMacAddress);
+ reregister_request->set_manufacture_date(kManufactureDate);
+ reregister_request->set_lifetime(
+ em::DeviceRegisterRequest::LIFETIME_INDEFINITE);
+ reregister_request->set_flavor(
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_RECOVERY);
+ reregister_request->set_reregister(true);
+ reregister_request->set_reregistration_dm_token(kDMToken);
+
+ return request;
+}
+
+// Constructs the DeviceManagementRequest with
+// CertificateBasedDeviceRegistrationData.
+// Also, if |psm_execution_result| or |psm_determination_timestamp| has a value,
+// then populate its corresponding PSM field in DeviceRegisterRequest.
+em::DeviceManagementRequest GetCertBasedRegistrationRequest(
+ FakeSigningService* fake_signing_service,
+ absl::optional<PsmExecutionResult> psm_execution_result,
+ absl::optional<int64_t> psm_determination_timestamp) {
+ em::CertificateBasedDeviceRegistrationData data;
+ data.set_certificate_type(em::CertificateBasedDeviceRegistrationData::
+ ENTERPRISE_ENROLLMENT_CERTIFICATE);
+ data.set_device_certificate(kEnrollmentCertificate);
+
+ em::DeviceRegisterRequest* register_request =
+ data.mutable_device_register_request();
+ register_request->set_type(em::DeviceRegisterRequest::DEVICE);
+ register_request->set_machine_id(kMachineID);
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_brand_code(kBrandCode);
+ register_request->mutable_device_register_identification()
+ ->set_attested_device_id(kAttestedDeviceId);
+ register_request->set_ethernet_mac_address(kEthernetMacAddress);
+ register_request->set_dock_mac_address(kDockMacAddress);
+ register_request->set_manufacture_date(kManufactureDate);
+ register_request->set_lifetime(
+ em::DeviceRegisterRequest::LIFETIME_INDEFINITE);
+ register_request->set_flavor(
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_ATTESTATION);
+ if (psm_determination_timestamp.has_value()) {
+ register_request->set_psm_determination_timestamp_ms(
+ psm_determination_timestamp.value());
+ }
+ if (psm_execution_result.has_value())
+ register_request->set_psm_execution_result(psm_execution_result.value());
+
+ em::DeviceManagementRequest request;
+
+ em::CertificateBasedDeviceRegisterRequest* cert_based_register_request =
+ request.mutable_certificate_based_register_request();
+ fake_signing_service->SignDataSynchronously(
+ data.SerializeAsString(),
+ cert_based_register_request->mutable_signed_request());
+
+ return request;
+}
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || \
+ (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+em::DeviceManagementRequest GetEnrollmentRequest() {
+ em::DeviceManagementRequest request;
+
+ em::RegisterBrowserRequest* enrollment_request =
+ request.mutable_register_browser_request();
+ enrollment_request->set_os_platform(GetOSPlatform());
+ enrollment_request->set_os_version(GetOSVersion());
+ return request;
+}
+#endif
+
+em::DeviceManagementRequest GetUnregistrationRequest() {
+ em::DeviceManagementRequest unregistration_request;
+ // Accessing the field sets the type of the request.
+ unregistration_request.mutable_unregister_request();
+ return unregistration_request;
+}
+
+em::DeviceManagementResponse GetUnregistrationResponse() {
+ em::DeviceManagementResponse unregistration_response;
+ // Accessing the field sets the type of the response.
+ unregistration_response.mutable_unregister_response();
+ return unregistration_response;
+}
+
+em::DeviceManagementRequest GetUploadMachineCertificateRequest() {
+ em::DeviceManagementRequest upload_machine_certificate_request;
+ upload_machine_certificate_request.mutable_cert_upload_request()
+ ->set_device_certificate(kMachineCertificate);
+ upload_machine_certificate_request.mutable_cert_upload_request()
+ ->set_certificate_type(
+ em::DeviceCertUploadRequest::ENTERPRISE_MACHINE_CERTIFICATE);
+ return upload_machine_certificate_request;
+}
+
+em::DeviceManagementRequest GetUploadEnrollmentCertificateRequest() {
+ em::DeviceManagementRequest upload_enrollment_certificate_request;
+ upload_enrollment_certificate_request.mutable_cert_upload_request()
+ ->set_device_certificate(kEnrollmentCertificate);
+ upload_enrollment_certificate_request.mutable_cert_upload_request()
+ ->set_certificate_type(
+ em::DeviceCertUploadRequest::ENTERPRISE_ENROLLMENT_CERTIFICATE);
+ return upload_enrollment_certificate_request;
+}
+
+em::DeviceManagementResponse GetUploadCertificateResponse() {
+ em::DeviceManagementResponse upload_certificate_response;
+ upload_certificate_response.mutable_cert_upload_response();
+ return upload_certificate_response;
+}
+
+em::DeviceManagementRequest GetUploadStatusRequest() {
+ em::DeviceManagementRequest upload_status_request;
+ upload_status_request.mutable_device_status_report_request();
+ upload_status_request.mutable_session_status_report_request();
+ upload_status_request.mutable_child_status_report_request();
+ return upload_status_request;
+}
+
+em::DeviceManagementRequest GetRemoteCommandRequest() {
+ em::DeviceManagementRequest remote_command_request;
+
+ remote_command_request.mutable_remote_command_request()
+ ->set_last_command_unique_id(kLastCommandId);
+ em::RemoteCommandResult* command_result =
+ remote_command_request.mutable_remote_command_request()
+ ->add_command_results();
+ command_result->set_command_id(kLastCommandId);
+ command_result->set_result(em::RemoteCommandResult_ResultType_RESULT_SUCCESS);
+ command_result->set_payload(kResultPayload);
+ command_result->set_timestamp(kTimestamp);
+ remote_command_request.mutable_remote_command_request()
+ ->set_send_secure_commands(true);
+
+ return remote_command_request;
+}
+
+em::DeviceManagementRequest GetRobotAuthCodeFetchRequest() {
+ em::DeviceManagementRequest robot_auth_code_fetch_request;
+
+ em::DeviceServiceApiAccessRequest* api_request =
+ robot_auth_code_fetch_request.mutable_service_api_access_request();
+ api_request->set_oauth2_client_id(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id());
+ api_request->add_auth_scopes(kApiAuthScope);
+ api_request->set_device_type(em::DeviceServiceApiAccessRequest::CHROME_OS);
+
+ return robot_auth_code_fetch_request;
+}
+
+em::DeviceManagementResponse GetRobotAuthCodeFetchResponse() {
+ em::DeviceManagementResponse robot_auth_code_fetch_response;
+
+ em::DeviceServiceApiAccessResponse* api_response =
+ robot_auth_code_fetch_response.mutable_service_api_access_response();
+ api_response->set_auth_code(kRobotAuthCode);
+
+ return robot_auth_code_fetch_response;
+}
+
+em::DeviceManagementResponse GetEmptyResponse() {
+ return em::DeviceManagementResponse();
+}
+
+} // namespace
+
+class CloudPolicyClientTest : public testing::Test {
+ protected:
+ CloudPolicyClientTest()
+ : job_type_(DeviceManagementService::JobConfiguration::TYPE_INVALID),
+ client_id_(kClientID),
+ policy_type_(dm_protocol::kChromeUserPolicyType) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ fake_statistics_provider_.SetMachineStatistic(
+ chromeos::system::kSerialNumberKeyForTest, "fake_serial_number");
+#endif
+ }
+
+ void SetUp() override { CreateClient(); }
+
+ void TearDown() override { client_->RemoveObserver(&observer_); }
+
+ void RegisterClient(const std::string& device_dm_token) {
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(device_dm_token));
+ client_->SetupRegistration(kDMToken, client_id_,
+ std::vector<std::string>());
+ }
+
+ void RegisterClient() { RegisterClient(kDeviceDMToken); }
+
+ void CreateClient() {
+ service_.ScheduleInitialization(0);
+ base::RunLoop().RunUntilIdle();
+
+ if (client_)
+ client_->RemoveObserver(&observer_);
+
+ shared_url_loader_factory_ =
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &url_loader_factory_);
+ client_ = std::make_unique<CloudPolicyClient>(
+ kMachineID, kMachineModel, kBrandCode, kAttestedDeviceId,
+ kEthernetMacAddress, kDockMacAddress, kManufactureDate, &service_,
+ shared_url_loader_factory_,
+ base::BindRepeating(
+ &MockDeviceDMTokenCallbackObserver::OnDeviceDMTokenRequested,
+ base::Unretained(&device_dmtoken_callback_observer_)));
+ client_->AddPolicyTypeToFetch(policy_type_, std::string());
+ client_->AddObserver(&observer_);
+ }
+
+ base::Value::Dict MakeDefaultRealtimeReport() {
+ base::Value::Dict context;
+ context.SetByDottedPath("profile.gaiaEmail", "name@gmail.com");
+ context.SetByDottedPath("browser.userAgent", "User-Agent");
+ context.SetByDottedPath("profile.profileName", "Profile 1");
+ context.SetByDottedPath("profile.profilePath", "C:\\User Data\\Profile 1");
+
+ base::Value::Dict event;
+ event.Set("time", "2019-05-22T13:01:45Z");
+ event.SetByDottedPath("foo.prop1", "value1");
+ event.SetByDottedPath("foo.prop2", "value2");
+ event.SetByDottedPath("foo.prop3", "value3");
+
+ base::Value::List event_list;
+ event_list.Append(std::move(event));
+ return policy::RealtimeReportingJobConfiguration::BuildReport(
+ std::move(event_list), std::move(context));
+ }
+
+ void ExpectAndCaptureJob(const em::DeviceManagementResponse& response) {
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureAuthData(&auth_data_),
+ service_.CaptureQueryParams(&query_params_),
+ service_.CaptureRequest(&job_request_),
+ service_.SendJobOKAsync(response)));
+ }
+
+ void ExpectAndCaptureJSONJob(const std::string& response) {
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureAuthData(&auth_data_),
+ service_.CaptureQueryParams(&query_params_),
+ service_.CapturePayload(&job_payload_),
+ service_.SendJobOKAsync(response)));
+ }
+
+ void ExpectAndCaptureJobReplyFailure(int net_error, int response_code) {
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(
+ DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureAuthData(&auth_data_),
+ service_.CaptureQueryParams(&query_params_),
+ service_.CaptureRequest(&job_request_),
+ service_.SendJobResponseAsync(net_error, response_code)));
+ }
+
+ void CheckPolicyResponse(
+ const em::DeviceManagementResponse& policy_response) {
+ ASSERT_TRUE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_THAT(*client_->GetPolicyFor(policy_type_, std::string()),
+ MatchProto(policy_response.policy_response().responses(0)));
+ }
+
+ void AttemptUploadEncryptedWaitUntilIdle(
+ const ::reporting::EncryptedRecord& record,
+ absl::optional<base::Value::Dict> context = absl::nullopt) {
+ CloudPolicyClient::ResponseCallback response_callback =
+ base::BindOnce(&MockResponseCallbackObserver::OnResponseReceived,
+ base::Unretained(&response_callback_observer_));
+ client_->UploadEncryptedReport(ConvertEncryptedRecordToValue(record),
+ std::move(context),
+ std::move(response_callback));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void VerifyQueryParameter() {
+#if !BUILDFLAG(IS_IOS)
+ EXPECT_THAT(query_params_,
+ Contains(Pair(dm_protocol::kParamOAuthToken, kOAuthToken)));
+#endif
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ DeviceManagementService::JobConfiguration::JobType job_type_;
+ DeviceManagementService::JobConfiguration::ParameterMap query_params_;
+ DMAuth auth_data_;
+ em::DeviceManagementRequest job_request_;
+ std::string job_payload_;
+ std::string client_id_;
+ std::string policy_type_;
+ StrictMock<MockJobCreationHandler> job_creation_handler_;
+ FakeDeviceManagementService service_{&job_creation_handler_};
+ StrictMock<MockCloudPolicyClientObserver> observer_;
+ StrictMock<MockStatusCallbackObserver> callback_observer_;
+ StrictMock<MockDeviceDMTokenCallbackObserver>
+ device_dmtoken_callback_observer_;
+ StrictMock<MockRobotAuthCodeCallbackObserver>
+ robot_auth_code_callback_observer_;
+ StrictMock<MockResponseCallbackObserver> response_callback_observer_;
+ FakeSigningService fake_signing_service_;
+ std::unique_ptr<CloudPolicyClient> client_;
+ network::TestURLLoaderFactory url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+#endif
+};
+
+TEST_F(CloudPolicyClientTest, Init) {
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(0, client_->fetched_invalidation_version());
+}
+
+TEST_F(CloudPolicyClientTest, SetupRegistrationAndPolicyFetch) {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ client_->SetupRegistration(kDMToken, client_id_, std::vector<std::string>());
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, SetupRegistrationAndPolicyFetchWithOAuthToken) {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ client_->SetupRegistration(kDMToken, client_id_, std::vector<std::string>());
+ client_->SetOAuthTokenAsAdditionalAuth(kOAuthToken);
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || \
+ (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+TEST_F(CloudPolicyClientTest, RegistrationWithTokenAndPolicyFetch) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ features::kUploadBrowserDeviceIdentifier);
+
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ FakeClientDataDelegate client_data_delegate;
+ client_->RegisterWithToken(kEnrollmentToken, "device_id",
+ client_data_delegate);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_TOKEN_ENROLLMENT,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetEnrollmentRequest().SerializePartialAsString());
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+#endif
+
+TEST_F(CloudPolicyClientTest, RegistrationAndPolicyFetch) {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ CloudPolicyClient::RegistrationParameters register_user(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+ client_->Register(register_user, std::string() /* no client_id*/,
+ kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetRegistrationRequest().SerializePartialAsString());
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationAndPolicyFetchWithOAuthToken) {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ CloudPolicyClient::RegistrationParameters register_user(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+ client_->Register(register_user, std::string() /* no client_id*/,
+ kOAuthToken);
+ client_->SetOAuthTokenAsAdditionalAuth(kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetRegistrationRequest().SerializePartialAsString());
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationWithCertificateAndPolicyFetch) {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ fake_signing_service_.set_success(true);
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ CloudPolicyClient::RegistrationParameters device_attestation(
+ em::DeviceRegisterRequest::DEVICE,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_ATTESTATION);
+ client_->RegisterWithCertificate(
+ device_attestation, std::string() /* client_id */, kEnrollmentCertificate,
+ std::string() /* sub_organization */, &fake_signing_service_);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_BASED_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetCertBasedRegistrationRequest(
+ &fake_signing_service_,
+ /*psm_execution_result=*/absl::nullopt,
+ /*psm_determination_timestamp=*/absl::nullopt)
+ .SerializePartialAsString());
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationWithCertificateFailToSignRequest) {
+ fake_signing_service_.set_success(false);
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::RegistrationParameters device_attestation(
+ em::DeviceRegisterRequest::DEVICE,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_ATTESTATION);
+ client_->RegisterWithCertificate(
+ device_attestation, std::string() /* client_id */, kEnrollmentCertificate,
+ std::string() /* sub_organization */, &fake_signing_service_);
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_EQ(DM_STATUS_CANNOT_SIGN_REQUEST, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationParametersPassedThrough) {
+ em::DeviceManagementRequest registration_request = GetRegistrationRequest();
+ registration_request.mutable_register_request()->set_reregister(true);
+ registration_request.mutable_register_request()->set_requisition(
+ kRequisition);
+ registration_request.mutable_register_request()->set_server_backed_state_key(
+ kStateKey);
+ registration_request.mutable_register_request()->set_flavor(
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_MANUAL);
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+
+ CloudPolicyClient::RegistrationParameters register_parameters(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_MANUAL);
+ register_parameters.requisition = kRequisition;
+ register_parameters.current_state_key = kStateKey;
+
+ client_->Register(register_parameters, kClientID, kOAuthToken);
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ registration_request.SerializePartialAsString());
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_EQ(kClientID, client_id_);
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationNoToken) {
+ em::DeviceManagementResponse registration_response =
+ GetRegistrationResponse();
+ registration_response.mutable_register_response()
+ ->clear_device_management_token();
+ ExpectAndCaptureJob(registration_response);
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::RegistrationParameters register_user(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+ client_->Register(register_user, std::string() /* no client_id*/,
+ kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetRegistrationRequest().SerializePartialAsString());
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_RESPONSE_DECODING_ERROR, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RegistrationFailure) {
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(
+ service_.CaptureJobType(&job_type),
+ service_.SendJobResponseAsync(
+ net::ERR_FAILED, DeviceManagementService::kInvalidArgument)));
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::RegistrationParameters register_user(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+ client_->Register(register_user, std::string() /* no client_id*/,
+ kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type);
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RetryRegistration) {
+ // Force the register to fail with an error that causes a retry.
+ enterprise_management::DeviceManagementRequest request;
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ DeviceManagementService::JobForTesting job;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type),
+ service_.CaptureRequest(&request), SaveArg<0>(&job)));
+ CloudPolicyClient::RegistrationParameters register_user(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_USER_REGISTRATION);
+ client_->Register(register_user, std::string() /* no client_id*/,
+ kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type);
+ EXPECT_EQ(GetRegistrationRequest().SerializePartialAsString(),
+ request.SerializePartialAsString());
+ EXPECT_FALSE(request.register_request().reregister());
+ EXPECT_FALSE(client_->is_registered());
+ Mock::VerifyAndClearExpectations(&service_);
+
+ // Retry with network errors |DeviceManagementService::kMaxRetries| times.
+ for (int i = 0; i < DeviceManagementService::kMaxRetries; ++i) {
+ service_.SendJobResponseNow(&job, net::ERR_NETWORK_CHANGED, 0);
+ ASSERT_TRUE(job.IsActive());
+ request.ParseFromString(job.GetConfigurationForTesting()->GetPayload());
+ EXPECT_TRUE(request.register_request().reregister());
+ }
+
+ // Expect failure with yet another retry.
+ EXPECT_CALL(observer_, OnClientError);
+ service_.SendJobResponseNow(&job, net::ERR_NETWORK_CHANGED, 0);
+ EXPECT_FALSE(job.IsActive());
+ EXPECT_FALSE(client_->is_registered());
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(CloudPolicyClientTest, PolicyUpdate) {
+ RegisterClient();
+
+ const em::DeviceManagementRequest policy_request = GetPolicyRequest();
+
+ {
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+ }
+
+ {
+ em::DeviceManagementResponse policy_response;
+ policy_response.mutable_policy_response()->add_responses()->set_policy_data(
+ CreatePolicyData("updated-fake-policy-data"));
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+ }
+}
+
+TEST_F(CloudPolicyClientTest, PolicyFetchWithMetaData) {
+ RegisterClient();
+
+ const int kPublicKeyVersion = 42;
+ const base::Time kOldTimestamp(base::Time::UnixEpoch() + base::Days(20));
+
+ em::DeviceManagementRequest policy_request = GetPolicyRequest();
+ em::PolicyFetchRequest* policy_fetch_request =
+ policy_request.mutable_policy_request()->mutable_requests(0);
+ policy_fetch_request->set_timestamp(kOldTimestamp.ToJavaTime());
+ policy_fetch_request->set_public_key_version(kPublicKeyVersion);
+
+ em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ client_->set_last_policy_timestamp(kOldTimestamp);
+ client_->set_public_key_version(kPublicKeyVersion);
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, PolicyFetchWithInvalidation) {
+ RegisterClient();
+
+ const int64_t kInvalidationVersion = 12345;
+ const std::string kInvalidationPayload("12345");
+
+ em::DeviceManagementRequest policy_request = GetPolicyRequest();
+ em::PolicyFetchRequest* policy_fetch_request =
+ policy_request.mutable_policy_request()->mutable_requests(0);
+ policy_fetch_request->set_invalidation_version(kInvalidationVersion);
+ policy_fetch_request->set_invalidation_payload(kInvalidationPayload);
+
+ em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ int64_t previous_version = client_->fetched_invalidation_version();
+ client_->SetInvalidationInfo(kInvalidationVersion, kInvalidationPayload);
+ EXPECT_EQ(previous_version, client_->fetched_invalidation_version());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+ EXPECT_EQ(12345, client_->fetched_invalidation_version());
+}
+
+TEST_F(CloudPolicyClientTest, PolicyFetchWithInvalidationNoPayload) {
+ RegisterClient();
+
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ int64_t previous_version = client_->fetched_invalidation_version();
+ client_->SetInvalidationInfo(-12345, std::string());
+ EXPECT_EQ(previous_version, client_->fetched_invalidation_version());
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+ EXPECT_EQ(-12345, client_->fetched_invalidation_version());
+}
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
+TEST_F(CloudPolicyClientTest, PolicyFetchWithBrowserDeviceIdentifier) {
+ RegisterClient();
+
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ features::kUploadBrowserDeviceIdentifier);
+
+ // Add the policy type that contains browser device identifier.
+ client_->AddPolicyTypeToFetch(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType, std::string());
+
+ // Make a policy fetch.
+ ExpectAndCaptureJob(GetPolicyResponse());
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+
+ // Collect the policy requests.
+ ASSERT_TRUE(job_request_.has_policy_request());
+ const em::DevicePolicyRequest& policy_request = job_request_.policy_request();
+ std::map<std::pair<std::string, std::string>, em::PolicyFetchRequest>
+ request_map;
+ for (int i = 0; i < policy_request.requests_size(); ++i) {
+ const em::PolicyFetchRequest& fetch_request = policy_request.requests(i);
+ ASSERT_TRUE(fetch_request.has_policy_type());
+ std::pair<std::string, std::string> key(fetch_request.policy_type(),
+ fetch_request.settings_entity_id());
+ EXPECT_EQ(0UL, request_map.count(key));
+ request_map[key].CopyFrom(fetch_request);
+ }
+
+ std::map<std::pair<std::string, std::string>, em::PolicyFetchRequest>
+ expected_requests;
+ // Expected user policy fetch request.
+ std::pair<std::string, std::string> user_policy_key(
+ dm_protocol::kChromeUserPolicyType, std::string());
+ expected_requests[user_policy_key] =
+ GetPolicyRequest().policy_request().requests(0);
+ // Expected user cloud policy fetch request.
+ std::pair<std::string, std::string> user_cloud_policy_key(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType, std::string());
+ em::PolicyFetchRequest policy_fetch_request =
+ GetPolicyRequest().policy_request().requests(0);
+ policy_fetch_request.set_policy_type(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ policy_fetch_request.set_allocated_browser_device_identifier(
+ GetBrowserDeviceIdentifier().release());
+ expected_requests[user_cloud_policy_key] = policy_fetch_request;
+
+ EXPECT_EQ(request_map.size(), expected_requests.size());
+ for (auto it = expected_requests.begin(); it != expected_requests.end();
+ ++it) {
+ EXPECT_EQ(1UL, request_map.count(it->first));
+ EXPECT_EQ(request_map[it->first].SerializePartialAsString(),
+ it->second.SerializePartialAsString());
+ }
+}
+#endif
+
+// Tests that previous OAuth token is no longer sent in policy fetch after its
+// value was cleared.
+TEST_F(CloudPolicyClientTest, PolicyFetchClearOAuthToken) {
+ RegisterClient();
+
+ em::DeviceManagementRequest policy_request = GetPolicyRequest();
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->SetOAuthTokenAsAdditionalAuth(kOAuthToken);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->SetOAuthTokenAsAdditionalAuth("");
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, BadPolicyResponse) {
+ RegisterClient();
+
+ const em::DeviceManagementRequest policy_request = GetPolicyRequest();
+ em::DeviceManagementResponse policy_response;
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnClientError);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_RESPONSE_DECODING_ERROR, client_->status());
+
+ policy_response.mutable_policy_response()->add_responses()->set_policy_data(
+ CreatePolicyData("fake-policy-data"));
+ policy_response.mutable_policy_response()->add_responses()->set_policy_data(
+ CreatePolicyData("excess-fake-policy-data"));
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ policy_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ CheckPolicyResponse(policy_response);
+}
+
+TEST_F(CloudPolicyClientTest, PolicyRequestFailure) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(
+ service_.CaptureJobType(&job_type),
+ service_.SendJobResponseAsync(
+ net::ERR_FAILED, DeviceManagementService::kInvalidArgument)));
+ EXPECT_CALL(observer_, OnClientError);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type);
+ EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+}
+
+TEST_F(CloudPolicyClientTest, Unregister) {
+ RegisterClient();
+
+ ExpectAndCaptureJob(GetUnregistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ client_->Unregister();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUnregistrationRequest().SerializePartialAsString());
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UnregisterEmpty) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ em::DeviceManagementResponse unregistration_response =
+ GetUnregistrationResponse();
+ unregistration_response.clear_unregister_response();
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type),
+ service_.SendJobOKAsync(unregistration_response)));
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ client_->Unregister();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION,
+ job_type);
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UnregisterFailure) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(
+ service_.CaptureJobType(&job_type),
+ service_.SendJobResponseAsync(
+ net::ERR_FAILED, DeviceManagementService::kInvalidArgument)));
+ EXPECT_CALL(observer_, OnClientError);
+ client_->Unregister();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION,
+ job_type);
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, PolicyFetchWithExtensionPolicy) {
+ RegisterClient();
+
+ em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ // Set up the |expected_responses| and |policy_response|.
+ static const char* kExtensions[] = {
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "cccccccccccccccccccccccccccccccc",
+ };
+ typedef std::map<std::pair<std::string, std::string>, em::PolicyFetchResponse>
+ ResponseMap;
+ ResponseMap expected_responses;
+ std::set<std::pair<std::string, std::string>> expected_namespaces;
+ std::pair<std::string, std::string> key(dm_protocol::kChromeUserPolicyType,
+ std::string());
+ // Copy the user policy fetch request.
+ expected_responses[key].CopyFrom(
+ policy_response.policy_response().responses(0));
+ expected_namespaces.insert(key);
+ key.first = dm_protocol::kChromeExtensionPolicyType;
+ expected_namespaces.insert(key);
+ for (size_t i = 0; i < std::size(kExtensions); ++i) {
+ key.second = kExtensions[i];
+ em::PolicyData policy_data;
+ policy_data.set_policy_type(key.first);
+ policy_data.set_settings_entity_id(key.second);
+ expected_responses[key].set_policy_data(policy_data.SerializeAsString());
+ policy_response.mutable_policy_response()->add_responses()->CopyFrom(
+ expected_responses[key]);
+ }
+
+ // Make a policy fetch.
+ em::DeviceManagementRequest request;
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type),
+ service_.CaptureRequest(&request),
+ service_.SendJobOKAsync(policy_response)));
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->AddPolicyTypeToFetch(dm_protocol::kChromeExtensionPolicyType,
+ std::string());
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type);
+
+ // Verify that the request includes the expected namespaces.
+ ASSERT_TRUE(request.has_policy_request());
+ const em::DevicePolicyRequest& policy_request = request.policy_request();
+ ASSERT_EQ(2, policy_request.requests_size());
+ for (int i = 0; i < policy_request.requests_size(); ++i) {
+ const em::PolicyFetchRequest& fetch_request = policy_request.requests(i);
+ ASSERT_TRUE(fetch_request.has_policy_type());
+ EXPECT_FALSE(fetch_request.has_settings_entity_id());
+ key = {fetch_request.policy_type(), std::string()};
+ EXPECT_EQ(1u, expected_namespaces.erase(key));
+ }
+ EXPECT_TRUE(expected_namespaces.empty());
+
+ // Verify that the client got all the responses mapped to their namespaces.
+ for (auto it = expected_responses.begin(); it != expected_responses.end();
+ ++it) {
+ const em::PolicyFetchResponse* response =
+ client_->GetPolicyFor(it->first.first, it->first.second);
+ ASSERT_TRUE(response);
+ EXPECT_EQ(it->second.SerializeAsString(), response->SerializeAsString());
+ }
+}
+
+TEST_F(CloudPolicyClientTest, UploadEnterpriseMachineCertificate) {
+ RegisterClient();
+
+ ExpectAndCaptureJob(GetUploadCertificateResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseMachineCertificate(kMachineCertificate,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadMachineCertificateRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadEnterpriseEnrollmentCertificate) {
+ RegisterClient();
+
+ ExpectAndCaptureJob(GetUploadCertificateResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseEnrollmentCertificate(kEnrollmentCertificate,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadEnrollmentCertificateRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadEnterpriseMachineCertificateEmpty) {
+ RegisterClient();
+
+ em::DeviceManagementResponse upload_certificate_response =
+ GetUploadCertificateResponse();
+ upload_certificate_response.clear_cert_upload_response();
+ ExpectAndCaptureJob(upload_certificate_response);
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(false)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseMachineCertificate(kMachineCertificate,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadMachineCertificateRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadEnterpriseEnrollmentCertificateEmpty) {
+ RegisterClient();
+
+ em::DeviceManagementResponse upload_certificate_response =
+ GetUploadCertificateResponse();
+ upload_certificate_response.clear_cert_upload_response();
+ ExpectAndCaptureJob(upload_certificate_response);
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(false)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseEnrollmentCertificate(kEnrollmentCertificate,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadEnrollmentCertificateRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadCertificateFailure) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(false)).Times(1);
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(
+ service_.CaptureJobType(&job_type),
+ service_.SendJobResponseAsync(
+ net::ERR_FAILED, DeviceManagementService::kInvalidArgument)));
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseMachineCertificate(kMachineCertificate,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type);
+ EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadEnterpriseEnrollmentId) {
+ RegisterClient();
+
+ em::DeviceManagementRequest upload_enrollment_id_request;
+ upload_enrollment_id_request.mutable_cert_upload_request()->set_enrollment_id(
+ kEnrollmentId);
+
+ ExpectAndCaptureJob(GetUploadCertificateResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseEnrollmentId(kEnrollmentId, std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ upload_enrollment_id_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadStatus) {
+ RegisterClient();
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadStatusRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadStatusWithOAuthToken) {
+ RegisterClient();
+
+ // Test that OAuth token is sent in status upload.
+ client_->SetOAuthTokenAsAdditionalAuth(kOAuthToken);
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadStatusRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ // Tests that previous OAuth token is no longer sent in status upload after
+ // its value was cleared.
+ client_->SetOAuthTokenAsAdditionalAuth("");
+
+ callback = base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+#if !BUILDFLAG(IS_IOS)
+ EXPECT_THAT(query_params_,
+ Not(Contains(Pair(dm_protocol::kParamOAuthToken, kOAuthToken))));
+#endif
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUploadStatusRequest().SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadStatusWhilePolicyFetchActive) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type),
+ service_.SendJobOKAsync(GetEmptyResponse())));
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ job_type);
+
+ // Now initiate a policy fetch - this should not cancel the upload job.
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+
+ ExpectAndCaptureJob(policy_response);
+ EXPECT_CALL(observer_, OnPolicyFetched);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetPolicyRequest().SerializePartialAsString());
+ CheckPolicyResponse(policy_response);
+
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadPolicyValidationReport) {
+ RegisterClient();
+
+ em::DeviceManagementRequest upload_policy_validation_report_request;
+ {
+ em::PolicyValidationReportRequest* policy_validation_report_request =
+ upload_policy_validation_report_request
+ .mutable_policy_validation_report_request();
+ policy_validation_report_request->set_policy_type(policy_type_);
+ policy_validation_report_request->set_policy_token(kPolicyToken);
+ policy_validation_report_request->set_validation_result_type(
+ em::PolicyValidationReportRequest::
+ VALIDATION_RESULT_TYPE_VALUE_WARNING);
+ em::PolicyValueValidationIssue* policy_value_validation_issue =
+ policy_validation_report_request->add_policy_value_validation_issues();
+ policy_value_validation_issue->set_policy_name(kPolicyName);
+ policy_value_validation_issue->set_severity(
+ em::PolicyValueValidationIssue::
+ VALUE_VALIDATION_ISSUE_SEVERITY_WARNING);
+ policy_value_validation_issue->set_debug_message(kValueValidationMessage);
+ }
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ std::vector<ValueValidationIssue> issues;
+ issues.push_back(
+ {kPolicyName, ValueValidationIssue::kWarning, kValueValidationMessage});
+ client_->UploadPolicyValidationReport(
+ CloudPolicyValidatorBase::VALIDATION_VALUE_WARNING, issues, policy_type_,
+ kPolicyToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_POLICY_VALIDATION_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ upload_policy_validation_report_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadChromeDesktopReport) {
+ RegisterClient();
+
+ em::DeviceManagementRequest chrome_desktop_report_request;
+ chrome_desktop_report_request.mutable_chrome_desktop_report_request();
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ std::unique_ptr<em::ChromeDesktopReportRequest> chrome_desktop_report =
+ std::make_unique<em::ChromeDesktopReportRequest>();
+ client_->UploadChromeDesktopReport(std::move(chrome_desktop_report),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_DESKTOP_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ chrome_desktop_report_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadChromeOsUserReport) {
+ RegisterClient();
+
+ em::DeviceManagementRequest chrome_os_user_report_request;
+ chrome_os_user_report_request.mutable_chrome_os_user_report_request();
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ std::unique_ptr<em::ChromeOsUserReportRequest> chrome_os_user_report =
+ std::make_unique<em::ChromeOsUserReportRequest>();
+ client_->UploadChromeOsUserReport(std::move(chrome_os_user_report),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_OS_USER_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ chrome_os_user_report_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, UploadChromeProfileReport) {
+ RegisterClient();
+
+ em::DeviceManagementRequest device_managment_request;
+ device_managment_request.mutable_chrome_profile_report_request()
+ ->mutable_os_report()
+ ->set_name(kOsName);
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ auto chrome_profile_report =
+ std::make_unique<em::ChromeProfileReportRequest>();
+ chrome_profile_report->mutable_os_report()->set_name(kOsName);
+ client_->UploadChromeProfileReport(std::move(chrome_profile_report),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CHROME_PROFILE_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ device_managment_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+// A helper class to test all em::DeviceRegisterRequest::PsmExecutionResult enum
+// values.
+class CloudPolicyClientRegisterWithPsmParamsTest
+ : public CloudPolicyClientTest,
+ public testing::WithParamInterface<PsmExecutionResult> {
+ public:
+ PsmExecutionResult GetPsmExecutionResult() const { return GetParam(); }
+};
+
+TEST_P(CloudPolicyClientRegisterWithPsmParamsTest,
+ RegistrationWithCertificateAndPsmResult) {
+ const int64_t kExpectedPsmDeterminationTimestamp = 2;
+
+ const em::DeviceManagementResponse policy_response = GetPolicyResponse();
+ const PsmExecutionResult psm_execution_result = GetPsmExecutionResult();
+
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ fake_signing_service_.set_success(true);
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ CloudPolicyClient::RegistrationParameters device_attestation(
+ em::DeviceRegisterRequest::DEVICE,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_ATTESTATION);
+ device_attestation.SetPsmDeterminationTimestamp(
+ kExpectedPsmDeterminationTimestamp);
+ device_attestation.SetPsmExecutionResult(psm_execution_result);
+ client_->RegisterWithCertificate(
+ device_attestation, std::string() /* client_id */, kEnrollmentCertificate,
+ std::string() /* sub_organization */, &fake_signing_service_);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_BASED_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetCertBasedRegistrationRequest(&fake_signing_service_,
+ psm_execution_result,
+ kExpectedPsmDeterminationTimestamp)
+ .SerializePartialAsString());
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ CloudPolicyClientRegisterWithPsmParams,
+ CloudPolicyClientRegisterWithPsmParamsTest,
+ ::testing::Values(
+ em::DeviceRegisterRequest::PSM_RESULT_UNKNOWN,
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE,
+ em::DeviceRegisterRequest::PSM_RESULT_ERROR));
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
+ BUILDFLAG(IS_CHROMEOS)
+
+class CloudPolicyClientUploadSecurityEventTest
+ : public CloudPolicyClientTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ bool include_device_info() const { return GetParam(); }
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+ CloudPolicyClientUploadSecurityEventTest,
+ testing::Bool());
+
+TEST_P(CloudPolicyClientUploadSecurityEventTest, Test) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ client_->UploadSecurityEventReport(nullptr, include_device_info(),
+ MakeDefaultRealtimeReport(),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ absl::optional<base::Value> payload = base::JSONReader::Read(job_payload_);
+ ASSERT_TRUE(payload);
+
+ ASSERT_FALSE(policy::GetDeviceName().empty());
+ EXPECT_EQ(version_info::GetVersionNumber(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetChromeVersionPath()));
+
+ if (include_device_info()) {
+ EXPECT_EQ(kDMToken, *payload->FindStringPath(
+ ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetDMTokenPath()));
+ EXPECT_EQ(client_id_, *payload->FindStringPath(
+ ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetClientIdPath()));
+ EXPECT_EQ(policy::GetOSUsername(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetMachineUserPath()));
+ EXPECT_EQ(GetOSPlatform(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSPlatformPath()));
+ EXPECT_EQ(GetOSVersion(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSVersionPath()));
+ EXPECT_EQ(
+ policy::GetDeviceName(),
+ *payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetNamePath()));
+ } else {
+ EXPECT_FALSE(
+ payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetDMTokenPath()));
+ EXPECT_FALSE(payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetClientIdPath()));
+ EXPECT_FALSE(payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetMachineUserPath()));
+ EXPECT_FALSE(payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSPlatformPath()));
+ EXPECT_FALSE(payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSVersionPath()));
+ EXPECT_FALSE(payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetNamePath()));
+ }
+
+ base::Value* events =
+ payload->FindPath(RealtimeReportingJobConfiguration::kEventListKey);
+ EXPECT_EQ(base::Value::Type::LIST, events->type());
+ EXPECT_EQ(1u, events->GetListDeprecated().size());
+}
+
+TEST_F(CloudPolicyClientTest, RealtimeReportMerge) {
+ auto config = std::make_unique<RealtimeReportingJobConfiguration>(
+ client_.get(), service_.configuration()->GetRealtimeReportingServerUrl(),
+ /*include_device_info*/ true, /*add_connector_url_params=*/false,
+ RealtimeReportingJobConfiguration::UploadCompleteCallback());
+
+ // Add one report to the config.
+ {
+ base::Value::Dict context;
+ context.SetByDottedPath("profile.gaiaEmail", "name@gmail.com");
+ context.SetByDottedPath("browser.userAgent", "User-Agent");
+ context.SetByDottedPath("profile.profileName", "Profile 1");
+ context.SetByDottedPath("profile.profilePath", "C:\\User Data\\Profile 1");
+
+ base::Value::Dict event;
+ event.Set("time", "2019-09-10T20:01:45Z");
+ event.SetByDottedPath("foo.prop1", "value1");
+ event.SetByDottedPath("foo.prop2", "value2");
+ event.SetByDottedPath("foo.prop3", "value3");
+
+ base::Value::List events;
+ events.Append(std::move(event));
+
+ base::Value::Dict report;
+ report.Set(RealtimeReportingJobConfiguration::kEventListKey,
+ std::move(events));
+ report.Set(RealtimeReportingJobConfiguration::kContextKey,
+ std::move(context));
+
+ ASSERT_TRUE(config->AddReport(std::move(report)));
+ }
+
+ // Add a second report to the config with a different context.
+ {
+ base::Value::Dict context;
+ context.SetByDottedPath("profile.gaiaEmail", "name2@gmail.com");
+ context.SetByDottedPath("browser.userAgent", "User-Agent2");
+ context.SetByDottedPath("browser.version", "1.0.0.0");
+
+ base::Value::Dict event;
+ event.Set("time", "2019-09-10T20:02:45Z");
+ event.SetByDottedPath("foo.prop1", "value1");
+ event.SetByDottedPath("foo.prop2", "value2");
+ event.SetByDottedPath("foo.prop3", "value3");
+
+ base::Value::List events;
+ events.Append(std::move(event));
+
+ base::Value::Dict report;
+ report.Set(RealtimeReportingJobConfiguration::kEventListKey,
+ std::move(events));
+ report.Set(RealtimeReportingJobConfiguration::kContextKey,
+ std::move(context));
+
+ ASSERT_TRUE(config->AddReport(std::move(report)));
+ }
+
+ // The second config should trump the first.
+ DeviceManagementService::JobConfiguration* job_config = config.get();
+ absl::optional<base::Value> payload =
+ base::JSONReader::Read(job_config->GetPayload());
+ ASSERT_TRUE(payload);
+
+ ASSERT_EQ("name2@gmail.com", *payload->FindStringPath("profile.gaiaEmail"));
+ ASSERT_EQ("User-Agent2", *payload->FindStringPath("browser.userAgent"));
+ ASSERT_EQ("Profile 1", *payload->FindStringPath("profile.profileName"));
+ ASSERT_EQ("C:\\User Data\\Profile 1",
+ *payload->FindStringPath("profile.profilePath"));
+ ASSERT_EQ("1.0.0.0", *payload->FindStringPath("browser.version"));
+ ASSERT_EQ(
+ 2u,
+ payload->FindListPath(RealtimeReportingJobConfiguration::kEventListKey)
+ ->GetListDeprecated()
+ .size());
+}
+
+TEST_F(CloudPolicyClientTest, UploadEncryptedReport) {
+ // Create record
+ ::reporting::EncryptedRecord record;
+ record.set_encrypted_wrapped_record("Enterprise");
+ auto* sequence_information = record.mutable_sequence_information();
+ sequence_information->set_sequencing_id(1701);
+ sequence_information->set_generation_id(12345678);
+ sequence_information->set_priority(::reporting::IMMEDIATE);
+
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(response_callback_observer_, OnResponseReceived(HasValue()))
+ .Times(1);
+ AttemptUploadEncryptedWaitUntilIdle(record);
+
+ EXPECT_EQ(
+ job_type_,
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_ENCRYPTED_REPORT);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(client_->status(), DM_STATUS_SUCCESS);
+}
+
+TEST_F(CloudPolicyClientTest, UploadAppInstallReport) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ client_->UploadAppInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, CancelUploadAppInstallReport) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(0);
+
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ em::AppInstallReportRequest app_install_report;
+ client_->UploadAppInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+
+ // The job expected by the call to ExpectRealTimeReport() completes
+ // when base::RunLoop().RunUntilIdle() is called. To simulate a cancel
+ // before the response for the request is processed, make sure to cancel it
+ // before running a loop.
+ client_->CancelAppInstallReportUpload();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+}
+
+TEST_F(CloudPolicyClientTest, UploadAppInstallReportSupersedesPending) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(0);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ client_->UploadAppInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+ Mock::VerifyAndClearExpectations(&service_);
+ Mock::VerifyAndClearExpectations(&callback_observer_);
+
+ // Starting another app push-install report upload should cancel the pending
+ // one.
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ callback = base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadAppInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+}
+
+TEST_F(CloudPolicyClientTest, UploadExtensionInstallReport) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ client_->UploadExtensionInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, CancelUploadExtensionInstallReport) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(0);
+
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ em::ExtensionInstallReportRequest app_install_report;
+ client_->UploadExtensionInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+
+ // The job expected by the call to ExpectRealTimeReport() completes
+ // when base::RunLoop().RunUntilIdle() is called. To simulate a cancel
+ // before the response for the request is processed, make sure to cancel it
+ // before running a loop.
+ client_->CancelExtensionInstallReportUpload();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+}
+
+TEST_F(CloudPolicyClientTest, UploadExtensionInstallReportSupersedesPending) {
+ RegisterClient();
+
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(0);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ client_->UploadExtensionInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+ Mock::VerifyAndClearExpectations(&service_);
+ Mock::VerifyAndClearExpectations(&callback_observer_);
+
+ // Starting another extension install report upload should cancel the pending
+ // one.
+ ExpectAndCaptureJSONJob(/*response=*/"{}");
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+ callback = base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadExtensionInstallReport(MakeDefaultRealtimeReport(),
+ std::move(callback));
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_UPLOAD_REAL_TIME_REPORT,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+}
+
+#endif
+
+TEST_F(CloudPolicyClientTest, MultipleActiveRequests) {
+ RegisterClient();
+
+ // Set up pending upload status job.
+ DeviceManagementService::JobConfiguration::JobType upload_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&upload_type),
+ service_.SendJobOKAsync(GetEmptyResponse())));
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+
+ // Set up pending upload certificate job.
+ DeviceManagementService::JobConfiguration::JobType cert_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&cert_type),
+ service_.SendJobOKAsync(GetUploadCertificateResponse())));
+
+ // Expect two calls on our upload observer, one for the status upload and
+ // one for the certificate upload.
+ CloudPolicyClient::StatusCallback callback2 =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UploadEnterpriseMachineCertificate(kMachineCertificate,
+ std::move(callback2));
+ EXPECT_EQ(2, client_->GetActiveRequestCountForTest());
+
+ // Now satisfy both active jobs.
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(2);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ upload_type);
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE,
+ cert_type);
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+}
+
+TEST_F(CloudPolicyClientTest, UploadStatusFailure) {
+ RegisterClient();
+
+ DeviceManagementService::JobConfiguration::JobType job_type;
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(false)).Times(1);
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(
+ service_.CaptureJobType(&job_type),
+ service_.SendJobResponseAsync(
+ net::ERR_FAILED, DeviceManagementService::kInvalidArgument)));
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ job_type);
+ EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RequestCancelOnUnregister) {
+ RegisterClient();
+
+ // Set up pending upload status job.
+ DeviceManagementService::JobConfiguration::JobType upload_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&upload_type)));
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ em::DeviceStatusReportRequest device_status;
+ em::SessionStatusReportRequest session_status;
+ em::ChildStatusReportRequest child_status;
+ client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ ExpectAndCaptureJob(GetUnregistrationResponse());
+ client_->Unregister();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS,
+ upload_type);
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetUnregistrationRequest().SerializePartialAsString());
+ EXPECT_EQ(0, client_->GetActiveRequestCountForTest());
+}
+
+TEST_F(CloudPolicyClientTest, ShouldRejectUnsignedCommands) {
+ const DeviceManagementStatus expected_error =
+ DM_STATUS_RESPONSE_DECODING_ERROR;
+
+ RegisterClient();
+
+ em::DeviceManagementResponse remote_command_response;
+ em::RemoteCommand* command =
+ remote_command_response.mutable_remote_command_response()->add_commands();
+ command->set_age_of_command(kAgeOfCommand);
+ command->set_payload(kPayload);
+ command->set_command_id(kLastCommandId + 1);
+ command->set_type(em::RemoteCommand_Type_COMMAND_ECHO_TEST);
+
+ ExpectAndCaptureJob(remote_command_response);
+
+ StrictMock<MockRemoteCommandsObserver> remote_commands_observer;
+ EXPECT_CALL(remote_commands_observer,
+ OnRemoteCommandsFetched(expected_error, _))
+ .Times(1);
+ CloudPolicyClient::RemoteCommandCallback callback =
+ base::BindOnce(&MockRemoteCommandsObserver::OnRemoteCommandsFetched,
+ base::Unretained(&remote_commands_observer));
+
+ client_->FetchRemoteCommands(
+ std::make_unique<RemoteCommandJob::UniqueIDType>(kLastCommandId), {},
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(CloudPolicyClientTest,
+ ShouldIgnoreSignedCommandsIfUnsignedCommandsArePresent) {
+ const DeviceManagementStatus expected_error =
+ DM_STATUS_RESPONSE_DECODING_ERROR;
+
+ RegisterClient();
+
+ em::DeviceManagementResponse remote_command_response;
+ auto* response = remote_command_response.mutable_remote_command_response();
+ response->add_commands();
+ response->add_secure_commands();
+
+ ExpectAndCaptureJob(remote_command_response);
+
+ std::vector<em::SignedData> received_commands;
+ StrictMock<MockRemoteCommandsObserver> remote_commands_observer;
+ EXPECT_CALL(remote_commands_observer,
+ OnRemoteCommandsFetched(expected_error, _))
+ .WillOnce(SaveArg<1>(&received_commands));
+ CloudPolicyClient::RemoteCommandCallback callback =
+ base::BindOnce(&MockRemoteCommandsObserver::OnRemoteCommandsFetched,
+ base::Unretained(&remote_commands_observer));
+
+ client_->FetchRemoteCommands(
+ std::make_unique<RemoteCommandJob::UniqueIDType>(kLastCommandId), {},
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(received_commands, ElementsAre());
+}
+
+TEST_F(CloudPolicyClientTest, ShouldNotFailIfRemoteCommandResponseIsEmpty) {
+ const DeviceManagementStatus expected_result = DM_STATUS_SUCCESS;
+
+ RegisterClient();
+
+ em::DeviceManagementResponse empty_server_response;
+
+ ExpectAndCaptureJob(empty_server_response);
+
+ std::vector<em::SignedData> received_commands;
+ StrictMock<MockRemoteCommandsObserver> remote_commands_observer;
+ EXPECT_CALL(remote_commands_observer,
+ OnRemoteCommandsFetched(expected_result, _))
+ .Times(1);
+ CloudPolicyClient::RemoteCommandCallback callback =
+ base::BindOnce(&MockRemoteCommandsObserver::OnRemoteCommandsFetched,
+ base::Unretained(&remote_commands_observer));
+
+ client_->FetchRemoteCommands(
+ std::make_unique<RemoteCommandJob::UniqueIDType>(kLastCommandId), {},
+ std::move(callback));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(received_commands, ElementsAre());
+}
+
+TEST_F(CloudPolicyClientTest, FetchSecureRemoteCommands) {
+ RegisterClient();
+
+ em::DeviceManagementRequest remote_command_request =
+ GetRemoteCommandRequest();
+
+ em::DeviceManagementResponse remote_command_response;
+ em::SignedData* signed_command =
+ remote_command_response.mutable_remote_command_response()
+ ->add_secure_commands();
+ signed_command->set_data("signed-data");
+ signed_command->set_signature("signed-signature");
+
+ ExpectAndCaptureJob(remote_command_response);
+
+ StrictMock<MockRemoteCommandsObserver> remote_commands_observer;
+ EXPECT_CALL(
+ remote_commands_observer,
+ OnRemoteCommandsFetched(
+ DM_STATUS_SUCCESS,
+ ElementsAre(MatchProto(
+ remote_command_response.remote_command_response().secure_commands(
+ 0)))))
+ .Times(1);
+
+ base::RunLoop run_loop;
+ CloudPolicyClient::RemoteCommandCallback callback =
+ base::BindLambdaForTesting(
+ [&](DeviceManagementStatus status,
+ const std::vector<enterprise_management::SignedData>&
+ signed_commands) {
+ remote_commands_observer.OnRemoteCommandsFetched(status,
+ signed_commands);
+ run_loop.Quit();
+ });
+ const std::vector<em::RemoteCommandResult> command_results(
+ 1, remote_command_request.remote_command_request().command_results(0));
+ client_->FetchRemoteCommands(
+ std::make_unique<RemoteCommandJob::UniqueIDType>(kLastCommandId),
+ command_results, std::move(callback));
+ run_loop.Run();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REMOTE_COMMANDS,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ remote_command_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RequestDeviceAttributeUpdatePermission) {
+ RegisterClient();
+
+ em::DeviceManagementRequest attribute_update_permission_request;
+ attribute_update_permission_request
+ .mutable_device_attribute_update_permission_request();
+
+ em::DeviceManagementResponse attribute_update_permission_response;
+ attribute_update_permission_response
+ .mutable_device_attribute_update_permission_response()
+ ->set_result(
+ em::DeviceAttributeUpdatePermissionResponse_ResultType_ATTRIBUTE_UPDATE_ALLOWED);
+
+ ExpectAndCaptureJob(attribute_update_permission_response);
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->GetDeviceAttributeUpdatePermission(
+ DMAuth::FromOAuthToken(kOAuthToken), std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::
+ TYPE_ATTRIBUTE_UPDATE_PERMISSION,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ attribute_update_permission_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RequestDeviceAttributeUpdate) {
+ RegisterClient();
+
+ em::DeviceManagementRequest attribute_update_request;
+ attribute_update_request.mutable_device_attribute_update_request()
+ ->set_asset_id(kAssetId);
+ attribute_update_request.mutable_device_attribute_update_request()
+ ->set_location(kLocation);
+
+ em::DeviceManagementResponse attribute_update_response;
+ attribute_update_response.mutable_device_attribute_update_response()
+ ->set_result(
+ em::DeviceAttributeUpdateResponse_ResultType_ATTRIBUTE_UPDATE_SUCCESS);
+
+ ExpectAndCaptureJob(attribute_update_response);
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UpdateDeviceAttributes(DMAuth::FromOAuthToken(kOAuthToken), kAssetId,
+ kLocation, std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_ATTRIBUTE_UPDATE,
+ job_type_);
+ VerifyQueryParameter();
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ attribute_update_request.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RequestGcmIdUpdate) {
+ RegisterClient();
+
+ em::DeviceManagementRequest gcm_id_update_request;
+ gcm_id_update_request.mutable_gcm_id_update_request()->set_gcm_id(kGcmID);
+
+ ExpectAndCaptureJob(GetEmptyResponse());
+ EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
+
+ CloudPolicyClient::StatusCallback callback =
+ base::BindOnce(&MockStatusCallbackObserver::OnCallbackComplete,
+ base::Unretained(&callback_observer_));
+ client_->UpdateGcmId(kGcmID, std::move(callback));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_GCM_ID_UPDATE,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ gcm_id_update_request.SerializePartialAsString());
+}
+
+TEST_F(CloudPolicyClientTest, PolicyReregistration) {
+ RegisterClient();
+
+ // Handle 410 (unknown deviceID) on policy fetch.
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->requires_reregistration());
+ DeviceManagementService::JobConfiguration::JobType upload_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&upload_type),
+ service_.SendJobResponseAsync(
+ net::OK, DeviceManagementService::kDeviceNotFound)));
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(observer_, OnClientError);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DM_STATUS_SERVICE_DEVICE_NOT_FOUND, client_->status());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_TRUE(client_->requires_reregistration());
+
+ // Re-register.
+ ExpectAndCaptureJob(GetRegistrationResponse());
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(device_dmtoken_callback_observer_,
+ OnDeviceDMTokenRequested(
+ /*user_affiliation_ids=*/std::vector<std::string>()))
+ .WillOnce(Return(kDeviceDMToken));
+ CloudPolicyClient::RegistrationParameters user_recovery(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_RECOVERY);
+ client_->Register(user_recovery, client_id_, kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ upload_type);
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetReregistrationRequest().SerializePartialAsString());
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->requires_reregistration());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, PolicyReregistrationFailsWithNonMatchingDMToken) {
+ RegisterClient();
+
+ // Handle 410 (unknown deviceID) on policy fetch.
+ EXPECT_TRUE(client_->is_registered());
+ EXPECT_FALSE(client_->requires_reregistration());
+ DeviceManagementService::JobConfiguration::JobType upload_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&upload_type),
+ service_.SendJobResponseAsync(
+ net::OK, DeviceManagementService::kDeviceNotFound)));
+ EXPECT_CALL(observer_, OnRegistrationStateChanged);
+ EXPECT_CALL(observer_, OnClientError);
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DM_STATUS_SERVICE_DEVICE_NOT_FOUND, client_->status());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_TRUE(client_->requires_reregistration());
+
+ // Re-register (server sends wrong DMToken).
+ ExpectAndCaptureJobReplyFailure(
+ net::OK, DeviceManagementService::kInvalidAuthCookieOrDMToken);
+ EXPECT_CALL(observer_, OnClientError);
+ CloudPolicyClient::RegistrationParameters user_recovery(
+ em::DeviceRegisterRequest::USER,
+ em::DeviceRegisterRequest::FLAVOR_ENROLLMENT_RECOVERY);
+ client_->Register(user_recovery, client_id_, kOAuthToken);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ upload_type);
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::NoAuth());
+ VerifyQueryParameter();
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ GetReregistrationRequest().SerializePartialAsString());
+ EXPECT_FALSE(client_->is_registered());
+ EXPECT_TRUE(client_->requires_reregistration());
+ EXPECT_FALSE(client_->GetPolicyFor(policy_type_, std::string()));
+ EXPECT_EQ(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest, RequestFetchRobotAuthCodes) {
+ RegisterClient();
+
+ em::DeviceManagementRequest robot_auth_code_fetch_request =
+ GetRobotAuthCodeFetchRequest();
+ em::DeviceManagementResponse robot_auth_code_fetch_response =
+ GetRobotAuthCodeFetchResponse();
+
+ ExpectAndCaptureJob(robot_auth_code_fetch_response);
+ EXPECT_CALL(robot_auth_code_callback_observer_,
+ OnRobotAuthCodeFetched(_, kRobotAuthCode));
+
+ em::DeviceServiceApiAccessRequest::DeviceType device_type =
+ em::DeviceServiceApiAccessRequest::CHROME_OS;
+ std::set<std::string> oauth_scopes = {kApiAuthScope};
+ client_->FetchRobotAuthCodes(
+ DMAuth::FromDMToken(kDMToken), device_type, oauth_scopes,
+ base::BindOnce(&MockRobotAuthCodeCallbackObserver::OnRobotAuthCodeFetched,
+ base::Unretained(&robot_auth_code_callback_observer_)));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+ EXPECT_EQ(robot_auth_code_fetch_request.SerializePartialAsString(),
+ job_request_.SerializePartialAsString());
+ EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
+}
+
+TEST_F(CloudPolicyClientTest,
+ RequestFetchRobotAuthCodesNotInterruptedByPolicyFetch) {
+ RegisterClient();
+
+ em::DeviceManagementResponse robot_auth_code_fetch_response =
+ GetRobotAuthCodeFetchResponse();
+
+ // Expect a robot auth code fetch request that never runs its callback to
+ // simulate something happening while we wait for the request to return.
+ DeviceManagementService::JobForTesting robot_job;
+ DeviceManagementService::JobConfiguration::JobType robot_job_type;
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&robot_job_type),
+ SaveArg<0>(&robot_job)));
+
+ EXPECT_CALL(robot_auth_code_callback_observer_,
+ OnRobotAuthCodeFetched(_, kRobotAuthCode));
+
+ em::DeviceServiceApiAccessRequest::DeviceType device_type =
+ em::DeviceServiceApiAccessRequest::CHROME_OS;
+ std::set<std::string> oauth_scopes = {kApiAuthScope};
+ client_->FetchRobotAuthCodes(
+ DMAuth::FromDMToken(kDMToken), device_type, oauth_scopes,
+ base::BindOnce(&MockRobotAuthCodeCallbackObserver::OnRobotAuthCodeFetched,
+ base::Unretained(&robot_auth_code_callback_observer_)));
+ base::RunLoop().RunUntilIdle();
+
+ ExpectAndCaptureJob(GetPolicyResponse());
+ EXPECT_CALL(observer_, OnPolicyFetched);
+
+ client_->FetchPolicy();
+ base::RunLoop().RunUntilIdle();
+
+ // Try to manually finish the robot auth code fetch job.
+ service_.SendJobResponseNow(&robot_job, net::OK,
+ DeviceManagementService::kSuccess,
+ robot_auth_code_fetch_response);
+
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+ robot_job_type);
+ EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ job_type_);
+ EXPECT_EQ(auth_data_, DMAuth::FromDMToken(kDMToken));
+}
+
+struct MockClientCertProvisioningStartCsrCallbackObserver {
+ MOCK_METHOD(void,
+ Callback,
+ (DeviceManagementStatus,
+ absl::optional<CertProvisioningResponseErrorType>,
+ absl::optional<int64_t> try_later,
+ const std::string& invalidation_topic,
+ const std::string& va_challenge,
+ em::HashingAlgorithm hash_algorithm,
+ const std::string& data_to_sign),
+ (const));
+};
+
+class CloudPolicyClientCertProvisioningStartCsrTest
+ : public CloudPolicyClientTest,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ void RunTest(const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningStartCsrCallbackObserver&
+ callback_observer);
+
+ // Wraps the test parameter - returns true if in this test run
+ // CloudPolicyClient has knowledge of the device DMToken.
+ bool HasDeviceDMToken() { return GetParam(); }
+};
+
+void CloudPolicyClientCertProvisioningStartCsrTest::RunTest(
+ const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningStartCsrCallbackObserver&
+ callback_observer) {
+ const std::string cert_scope = "fake_cert_scope_1";
+ const std::string cert_profile_id = "fake_cert_profile_id_1";
+ const std::string cert_profile_version = "fake_cert_profile_version_1";
+ const std::string public_key = "fake_public_key_1";
+
+ em::DeviceManagementRequest expected_request;
+ {
+ em::ClientCertificateProvisioningRequest* inner_request =
+ expected_request.mutable_client_certificate_provisioning_request();
+ inner_request->set_certificate_scope(cert_scope);
+ inner_request->set_cert_profile_id(cert_profile_id);
+ inner_request->set_policy_version(cert_profile_version);
+ inner_request->set_public_key(public_key);
+ if (HasDeviceDMToken()) {
+ inner_request->set_device_dm_token(kDeviceDMToken);
+ }
+ // Sets the request type, no actual data is required.
+ inner_request->mutable_start_csr_request();
+ }
+
+ if (HasDeviceDMToken()) {
+ RegisterClient(kDeviceDMToken);
+ } else {
+ RegisterClient(/*device_dm_token=*/std::string());
+ }
+
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureRequest(&job_request_),
+ service_.SendJobOKAsync(fake_response)));
+
+ client_->ClientCertProvisioningStartCsr(
+ cert_scope, cert_profile_id, cert_profile_version, public_key,
+ base::BindOnce(
+ &MockClientCertProvisioningStartCsrCallbackObserver::Callback,
+ base::Unretained(&callback_observer)));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ expected_request.SerializePartialAsString());
+}
+
+// 1. Checks that |ClientCertProvisioningStartCsr| generates a correct request.
+// 2. Checks that |OnClientCertProvisioningStartCsrResponse| correctly extracts
+// data from a response that contains data.
+TEST_P(CloudPolicyClientCertProvisioningStartCsrTest,
+ RequestClientCertProvisioningStartCsrSuccess) {
+ const std::string invalidation_topic = "fake_invalidation_topic_1";
+ const std::string va_challenge = "fake_va_challenge_1";
+ const std::string data_to_sign = "fake_data_to_sign_1";
+ em::HashingAlgorithm hash_algorithm = em::HashingAlgorithm::SHA256;
+ em::SigningAlgorithm sign_algorithm = em::SigningAlgorithm::RSA_PKCS1_V1_5;
+
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ em::StartCsrResponse* start_csr_response =
+ inner_response->mutable_start_csr_response();
+ start_csr_response->set_invalidation_topic(invalidation_topic);
+ start_csr_response->set_va_challenge(va_challenge);
+ start_csr_response->set_hashing_algorithm(hash_algorithm);
+ start_csr_response->set_signing_algorithm(sign_algorithm);
+ start_csr_response->set_data_to_sign(data_to_sign);
+ }
+
+ MockClientCertProvisioningStartCsrCallbackObserver callback_observer;
+ EXPECT_CALL(
+ callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS,
+ testing::Eq(absl::nullopt), testing::Eq(absl::nullopt),
+ invalidation_topic, va_challenge, hash_algorithm, data_to_sign))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+// 1. Checks that |ClientCertProvisioningStartCsr| generates a correct request.
+// 2. Checks that |OnClientCertProvisioningStartCsrResponse| correctly extracts
+// data from a response that contains the try_later field.
+TEST_P(CloudPolicyClientCertProvisioningStartCsrTest,
+ RequestClientCertProvisioningStartCsrTryLater) {
+ const int64_t try_later = 60000;
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ inner_response->set_try_again_later(try_later);
+ }
+
+ MockClientCertProvisioningStartCsrCallbackObserver callback_observer;
+ EXPECT_CALL(callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS,
+ testing::Eq(absl::nullopt), testing::Eq(try_later),
+ std::string(), std::string(),
+ em::HashingAlgorithm::HASHING_ALGORITHM_UNSPECIFIED,
+ std::string()))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+// 1. Checks that |ClientCertProvisioningStartCsr| generates a correct request.
+// 2. Checks that |OnClientCertProvisioningStartCsrResponse| correctly extracts
+// data from a response that contains the error field.
+TEST_P(CloudPolicyClientCertProvisioningStartCsrTest,
+ RequestClientCertProvisioningStartCsrError) {
+ const CertProvisioningResponseErrorType error =
+ CertProvisioningResponseError::CA_ERROR;
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ inner_response->set_error(error);
+ }
+
+ MockClientCertProvisioningStartCsrCallbackObserver callback_observer;
+ EXPECT_CALL(
+ callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS, testing::Eq(error),
+ testing::Eq(absl::nullopt), std::string(), std::string(),
+ em::HashingAlgorithm::HASHING_ALGORITHM_UNSPECIFIED,
+ std::string()))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ CloudPolicyClientCertProvisioningStartCsrTest,
+ ::testing::Values(false, true));
+
+class MockClientCertProvisioningFinishCsrCallbackObserver {
+ public:
+ MockClientCertProvisioningFinishCsrCallbackObserver() = default;
+
+ MOCK_METHOD(void,
+ Callback,
+ (DeviceManagementStatus,
+ absl::optional<CertProvisioningResponseErrorType>,
+ absl::optional<int64_t> try_later),
+ (const));
+};
+
+class CloudPolicyClientCertProvisioningFinishCsrTest
+ : public CloudPolicyClientTest,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ void RunTest(const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningFinishCsrCallbackObserver&
+ callback_observer);
+
+ // Wraps the test parameter - returns true if in this test run
+ // CloudPolicyClient has knowledge of the device DMToken.
+ bool HasDeviceDMToken() { return GetParam(); }
+};
+
+void CloudPolicyClientCertProvisioningFinishCsrTest::RunTest(
+ const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningFinishCsrCallbackObserver&
+ callback_observer) {
+ const std::string cert_scope = "fake_cert_scope_1";
+ const std::string cert_profile_id = "fake_cert_profile_id_1";
+ const std::string cert_profile_version = "fake_cert_profile_version_1";
+ const std::string public_key = "fake_public_key_1";
+ const std::string va_challenge_response = "fake_va_challenge_response_1";
+ const std::string signature = "fake_signature_1";
+
+ em::DeviceManagementRequest expected_request;
+ {
+ em::ClientCertificateProvisioningRequest* inner_request =
+ expected_request.mutable_client_certificate_provisioning_request();
+ inner_request->set_certificate_scope(cert_scope);
+ inner_request->set_cert_profile_id(cert_profile_id);
+ inner_request->set_policy_version(cert_profile_version);
+ inner_request->set_public_key(public_key);
+ if (HasDeviceDMToken()) {
+ inner_request->set_device_dm_token(kDeviceDMToken);
+ }
+
+ em::FinishCsrRequest* finish_csr_request =
+ inner_request->mutable_finish_csr_request();
+ finish_csr_request->set_va_challenge_response(va_challenge_response);
+ finish_csr_request->set_signature(signature);
+ }
+
+ if (HasDeviceDMToken()) {
+ RegisterClient(kDeviceDMToken);
+ } else {
+ RegisterClient(/*device_dm_token=*/std::string());
+ }
+
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureRequest(&job_request_),
+ service_.SendJobOKAsync(fake_response)));
+
+ client_->ClientCertProvisioningFinishCsr(
+ cert_scope, cert_profile_id, cert_profile_version, public_key,
+ va_challenge_response, signature,
+ base::BindOnce(
+ &MockClientCertProvisioningFinishCsrCallbackObserver::Callback,
+ base::Unretained(&callback_observer)));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ expected_request.SerializePartialAsString());
+}
+
+// 1. Checks that |ClientCertProvisioningFinishCsr| generates a correct request.
+// 2. Checks that |OnClientCertProvisioningFinishCsrResponse| correctly extracts
+// data from a response that contains success status code.
+TEST_P(CloudPolicyClientCertProvisioningFinishCsrTest,
+ RequestClientCertProvisioningFinishCsrSuccess) {
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ // Sets the response id, no actual data is required.
+ inner_response->mutable_finish_csr_response();
+ }
+
+ MockClientCertProvisioningFinishCsrCallbackObserver callback_observer;
+ EXPECT_CALL(callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS,
+ testing::Eq(absl::nullopt), testing::Eq(absl::nullopt)))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+// 1. Checks that |ClientCertProvisioningFinishCsr| generates a correct request.
+// 2. Checks that |OnClientCertProvisioningFinishCsrResponse| correctly extracts
+// data from a response that contains the error field.
+TEST_P(CloudPolicyClientCertProvisioningFinishCsrTest,
+ RequestClientCertProvisioningFinishCsrError) {
+ const CertProvisioningResponseErrorType error =
+ CertProvisioningResponseError::CA_ERROR;
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ inner_response->set_error(error);
+ }
+
+ MockClientCertProvisioningFinishCsrCallbackObserver callback_observer;
+ EXPECT_CALL(callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS,
+ testing::Eq(error), testing::Eq(absl::nullopt)))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ CloudPolicyClientCertProvisioningFinishCsrTest,
+ ::testing::Values(false, true));
+
+class MockClientCertProvisioningDownloadCertCallbackObserver {
+ public:
+ MockClientCertProvisioningDownloadCertCallbackObserver() = default;
+
+ MOCK_METHOD(void,
+ Callback,
+ (DeviceManagementStatus,
+ absl::optional<CertProvisioningResponseErrorType>,
+ absl::optional<int64_t> try_later,
+ const std::string& pem_encoded_certificate),
+ (const));
+};
+
+class CloudPolicyClientCertProvisioningDownloadCertTest
+ : public CloudPolicyClientTest,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ void RunTest(const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningDownloadCertCallbackObserver&
+ callback_observer);
+
+ // Wraps the test parameter - returns true if in this test run
+ // CloudPolicyClient has knowledge of the device DMToken.
+ bool HasDeviceDMToken() { return GetParam(); }
+};
+
+void CloudPolicyClientCertProvisioningDownloadCertTest::RunTest(
+ const em::DeviceManagementResponse& fake_response,
+ const MockClientCertProvisioningDownloadCertCallbackObserver&
+ callback_observer) {
+ const std::string cert_scope = "fake_cert_scope_1";
+ const std::string cert_profile_id = "fake_cert_profile_id_1";
+ const std::string cert_profile_version = "fake_cert_profile_version_1";
+ const std::string public_key = "fake_public_key_1";
+
+ em::DeviceManagementRequest expected_request;
+ {
+ em::ClientCertificateProvisioningRequest* inner_request =
+ expected_request.mutable_client_certificate_provisioning_request();
+ inner_request->set_certificate_scope(cert_scope);
+ inner_request->set_cert_profile_id(cert_profile_id);
+ inner_request->set_policy_version(cert_profile_version);
+ inner_request->set_public_key(public_key);
+ if (HasDeviceDMToken()) {
+ inner_request->set_device_dm_token(kDeviceDMToken);
+ }
+ // Sets the request type, no actual data is required.
+ inner_request->mutable_download_cert_request();
+ }
+
+ if (HasDeviceDMToken()) {
+ RegisterClient(kDeviceDMToken);
+ } else {
+ RegisterClient(/*device_dm_token=*/std::string());
+ }
+
+ EXPECT_CALL(job_creation_handler_, OnJobCreation)
+ .WillOnce(DoAll(service_.CaptureJobType(&job_type_),
+ service_.CaptureRequest(&job_request_),
+ service_.SendJobOKAsync(fake_response)));
+
+ client_->ClientCertProvisioningDownloadCert(
+ cert_scope, cert_profile_id, cert_profile_version, public_key,
+ base::BindOnce(
+ &MockClientCertProvisioningDownloadCertCallbackObserver::Callback,
+ base::Unretained(&callback_observer)));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_PROVISIONING_REQUEST,
+ job_type_);
+ EXPECT_EQ(job_request_.SerializePartialAsString(),
+ expected_request.SerializePartialAsString());
+}
+
+// 1. Checks that |ClientCertProvisioningDownloadCert| generates a correct
+// request.
+// 2. Checks that |OnClientCertProvisioningDownloadCertResponse| correctly
+// extracts data from a response that contains success status code.
+TEST_P(CloudPolicyClientCertProvisioningDownloadCertTest,
+ RequestClientCertProvisioningDownloadCertSuccess) {
+ const std::string pem_encoded_cert = "fake_pem_encoded_cert_1";
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+
+ em::DownloadCertResponse* download_cert_response =
+ inner_response->mutable_download_cert_response();
+ download_cert_response->set_pem_encoded_certificate(pem_encoded_cert);
+ }
+
+ MockClientCertProvisioningDownloadCertCallbackObserver callback_observer;
+ EXPECT_CALL(callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS,
+ testing::Eq(absl::nullopt), testing::Eq(absl::nullopt),
+ pem_encoded_cert))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+// 1. Checks that |ClientCertProvisioningDownloadCert| generates a correct
+// request.
+// 2. Checks that |OnClientCertProvisioningDownloadCertResponse| correctly
+// extracts data from a response that contains the error field.
+TEST_P(CloudPolicyClientCertProvisioningDownloadCertTest,
+ RequestClientCertProvisioningDownloadCertError) {
+ const CertProvisioningResponseErrorType error =
+ CertProvisioningResponseError::CA_ERROR;
+ em::DeviceManagementResponse fake_response;
+ {
+ em::ClientCertificateProvisioningResponse* inner_response =
+ fake_response.mutable_client_certificate_provisioning_response();
+ inner_response->set_error(error);
+ }
+
+ MockClientCertProvisioningDownloadCertCallbackObserver callback_observer;
+ EXPECT_CALL(
+ callback_observer,
+ Callback(DeviceManagementStatus::DM_STATUS_SUCCESS, testing::Eq(error),
+ testing::Eq(absl::nullopt), std::string()))
+ .Times(1);
+
+ RunTest(fake_response, callback_observer);
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+ CloudPolicyClientCertProvisioningDownloadCertTest,
+ ::testing::Values(false, true));
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_constants.cc b/chromium/components/policy/core/common/cloud/cloud_policy_constants.cc
new file mode 100644
index 00000000000..3afc5d9f064
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_constants.cc
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+
+#include <stdint.h>
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/policy_switches.h"
+
+namespace policy {
+
+// Constants related to the device management protocol.
+namespace dm_protocol {
+
+// Name constants for URL query parameters.
+const char kParamAgent[] = "agent";
+const char kParamAppType[] = "apptype";
+const char kParamCritical[] = "critical";
+const char kParamDeviceID[] = "deviceid";
+const char kParamDeviceType[] = "devicetype";
+const char kParamLastError[] = "lasterror";
+const char kParamOAuthToken[] = "oauth_token";
+const char kParamPlatform[] = "platform";
+const char kParamRequest[] = "request";
+const char kParamRetry[] = "retry";
+
+// Policy constants used in authorization header.
+const char kAuthHeader[] = "Authorization";
+const char kServiceTokenAuthHeaderPrefix[] = "GoogleLogin auth=";
+const char kDMTokenAuthHeaderPrefix[] = "GoogleDMToken token=";
+const char kEnrollmentTokenAuthHeaderPrefix[] = "GoogleEnrollmentToken token=";
+
+// String constants for the device and app type we report to the server.
+const char kValueAppType[] = "Chrome";
+const char kValueBrowserUploadPublicKey[] = "browser_public_key_upload";
+const char kValueDeviceType[] = "2";
+const char kValueRequestAutoEnrollment[] = "enterprise_check";
+const char kValueRequestPsmHasDeviceState[] = "enterprise_psm_check";
+const char kValueCheckUserAccount[] = "check_user_account";
+const char kValueRequestPolicy[] = "policy";
+const char kValueRequestRegister[] = "register";
+const char kValueRequestApiAuthorization[] = "api_authorization";
+const char kValueRequestUnregister[] = "unregister";
+const char kValueRequestUploadCertificate[] = "cert_upload";
+const char kValueRequestUploadEuiccInfo[] = "upload_euicc_info";
+const char kValueRequestDeviceStateRetrieval[] = "device_state_retrieval";
+const char kValueRequestUploadStatus[] = "status_upload";
+const char kValueRequestRemoteCommands[] = "remote_commands";
+const char kValueRequestDeviceAttributeUpdatePermission[] =
+ "device_attribute_update_permission";
+const char kValueRequestDeviceAttributeUpdate[] = "device_attribute_update";
+const char kValueRequestGcmIdUpdate[] = "gcm_id_update";
+const char kValueRequestCheckAndroidManagement[] = "check_android_management";
+const char kValueRequestCertBasedRegister[] = "certificate_based_register";
+const char kValueRequestActiveDirectoryEnrollPlayUser[] =
+ "active_directory_enroll_play_user";
+const char kValueRequestActiveDirectoryPlayActivity[] =
+ "active_directory_play_activity";
+const char kValueRequestAppInstallReport[] = "app_install_report";
+const char kValueRequestTokenEnrollment[] = "register_browser";
+const char kValueRequestChromeDesktopReport[] = "chrome_desktop_report";
+const char kValueRequestChromeOsUserReport[] = "chrome_os_user_report";
+const char kValueRequestInitialEnrollmentStateRetrieval[] =
+ "device_initial_enrollment_state";
+const char kValueRequestUploadPolicyValidationReport[] =
+ "policy_validation_report";
+const char kValueRequestPublicSamlUser[] = "public_saml_user_request";
+const char kValueRequestCertProvisioningRequest[] = "client_cert_provisioning";
+const char kValueRequestChromeProfileReport[] = "chrome_profile_report";
+
+const char kChromeDevicePolicyType[] = "google/chromeos/device";
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+const char kChromeUserPolicyType[] = "google/chromeos/user";
+#elif BUILDFLAG(IS_ANDROID)
+const char kChromeUserPolicyType[] = "google/android/user";
+#elif BUILDFLAG(IS_IOS)
+// TODO(crbug.com/1312263): Change this for "google/ios/user" once supported
+// by the dmserver. The type for Desktop is temporarily used on iOS to allow
+// early testing of the feature before the DMServer can support iOS User
+// Policy.
+const char kChromeUserPolicyType[] = "google/chrome/user";
+#else
+const char kChromeUserPolicyType[] = "google/chrome/user";
+#endif
+const char kChromePublicAccountPolicyType[] = "google/chromeos/publicaccount";
+const char kChromeExtensionPolicyType[] = "google/chrome/extension";
+const char kChromeSigninExtensionPolicyType[] =
+ "google/chromeos/signinextension";
+const char kChromeMachineLevelUserCloudPolicyType[] =
+ "google/chrome/machine-level-user";
+const char kChromeMachineLevelUserCloudPolicyAndroidType[] =
+ "google/chrome/machine-level-user-android";
+const char kChromeMachineLevelUserCloudPolicyIOSType[] =
+ "google/chrome/machine-level-user-ios";
+const char kChromeMachineLevelExtensionCloudPolicyType[] =
+ "google/chrome/machine-level-extension";
+const char kChromeRemoteCommandPolicyType[] = "google/chromeos/remotecommand";
+
+const char kChromeMachineLevelUserCloudPolicyTypeBase64[] =
+ "Z29vZ2xlL2Nocm9tZS9tYWNoaW5lLWxldmVsLXVzZXI=";
+
+} // namespace dm_protocol
+
+const uint8_t kPolicyVerificationKey[] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
+ 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00,
+ 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xA7, 0xB3, 0xF9,
+ 0x0D, 0xC7, 0xC7, 0x8D, 0x84, 0x3D, 0x4B, 0x80, 0xDD, 0x9A, 0x2F, 0xF8,
+ 0x69, 0xD4, 0xD1, 0x14, 0x5A, 0xCA, 0x04, 0x4B, 0x1C, 0xBC, 0x28, 0xEB,
+ 0x5E, 0x10, 0x01, 0x36, 0xFD, 0x81, 0xEB, 0xE4, 0x3C, 0x16, 0x40, 0xA5,
+ 0x8A, 0xE6, 0x08, 0xEE, 0xEF, 0x39, 0x1F, 0x6B, 0x10, 0x29, 0x50, 0x84,
+ 0xCE, 0xEE, 0x33, 0x5C, 0x48, 0x4A, 0x33, 0xB0, 0xC8, 0x8A, 0x66, 0x0D,
+ 0x10, 0x11, 0x9D, 0x6B, 0x55, 0x4C, 0x9A, 0x62, 0x40, 0x9A, 0xE2, 0xCA,
+ 0x21, 0x01, 0x1F, 0x10, 0x1E, 0x7B, 0xC6, 0x89, 0x94, 0xDA, 0x39, 0x69,
+ 0xBE, 0x27, 0x28, 0x50, 0x5E, 0xA2, 0x55, 0xB9, 0x12, 0x3C, 0x79, 0x6E,
+ 0xDF, 0x24, 0xBF, 0x34, 0x88, 0xF2, 0x5E, 0xD0, 0xC4, 0x06, 0xEE, 0x95,
+ 0x6D, 0xC2, 0x14, 0xBF, 0x51, 0x7E, 0x3F, 0x55, 0x10, 0x85, 0xCE, 0x33,
+ 0x8F, 0x02, 0x87, 0xFC, 0xD2, 0xDD, 0x42, 0xAF, 0x59, 0xBB, 0x69, 0x3D,
+ 0xBC, 0x77, 0x4B, 0x3F, 0xC7, 0x22, 0x0D, 0x5F, 0x72, 0xC7, 0x36, 0xB6,
+ 0x98, 0x3D, 0x03, 0xCD, 0x2F, 0x68, 0x61, 0xEE, 0xF4, 0x5A, 0xF5, 0x07,
+ 0xAE, 0xAE, 0x79, 0xD1, 0x1A, 0xB2, 0x38, 0xE0, 0xAB, 0x60, 0x5C, 0x0C,
+ 0x14, 0xFE, 0x44, 0x67, 0x2C, 0x8A, 0x08, 0x51, 0x9C, 0xCD, 0x3D, 0xDB,
+ 0x13, 0x04, 0x57, 0xC5, 0x85, 0xB6, 0x2A, 0x0F, 0x02, 0x46, 0x0D, 0x2D,
+ 0xCA, 0xE3, 0x3F, 0x84, 0x9E, 0x8B, 0x8A, 0x5F, 0xFC, 0x4D, 0xAA, 0xBE,
+ 0xBD, 0xE6, 0x64, 0x9F, 0x26, 0x9A, 0x2B, 0x97, 0x69, 0xA9, 0xBA, 0x0B,
+ 0xBD, 0x48, 0xE4, 0x81, 0x6B, 0xD4, 0x4B, 0x78, 0xE6, 0xAF, 0x95, 0x66,
+ 0xC1, 0x23, 0xDA, 0x23, 0x45, 0x36, 0x6E, 0x25, 0xF3, 0xC7, 0xC0, 0x61,
+ 0xFC, 0xEC, 0x66, 0x9D, 0x31, 0xD4, 0xD6, 0xB6, 0x36, 0xE3, 0x7F, 0x81,
+ 0x87, 0x02, 0x03, 0x01, 0x00, 0x01};
+
+const char kPolicyVerificationKeyHash[] = "1:356l7w";
+
+const char kDemoModeDomain[] = "cros-demo-mode.com";
+
+std::string GetPolicyVerificationKey() {
+ return std::string(reinterpret_cast<const char*>(kPolicyVerificationKey),
+ sizeof(kPolicyVerificationKey));
+}
+// Notes from the past: When the key is rotated in the future, the old one may
+// still worth being kept to verified any existing policy cache so that browser
+// can load it one last time. However, it really depends on the reason of the
+// rotation. From a different angle, if a key is no longer trusted, so should
+// anything bound to it.
+
+const char kPolicyFCMInvalidationSenderID[] = "1013309121859";
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_constants.h b/chromium/components/policy/core/common/cloud/cloud_policy_constants.h
new file mode 100644
index 00000000000..8ca4a521f14
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_constants.h
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CONSTANTS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CONSTANTS_H_
+
+#include <string>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Constants related to the device management protocol.
+namespace dm_protocol {
+
+// Name extern constants for URL query parameters.
+POLICY_EXPORT extern const char kParamAgent[];
+POLICY_EXPORT extern const char kParamAppType[];
+POLICY_EXPORT extern const char kParamCritical[];
+POLICY_EXPORT extern const char kParamDeviceID[];
+POLICY_EXPORT extern const char kParamDeviceType[];
+POLICY_EXPORT extern const char kParamLastError[];
+POLICY_EXPORT extern const char kParamOAuthToken[];
+POLICY_EXPORT extern const char kParamPlatform[];
+POLICY_EXPORT extern const char kParamRequest[];
+POLICY_EXPORT extern const char kParamRetry[];
+
+// Policy constants used in authorization header.
+POLICY_EXPORT extern const char kAuthHeader[];
+POLICY_EXPORT extern const char kServiceTokenAuthHeaderPrefix[];
+POLICY_EXPORT extern const char kDMTokenAuthHeaderPrefix[];
+POLICY_EXPORT extern const char kEnrollmentTokenAuthHeaderPrefix[];
+
+// String extern constants for the device and app type we report to the server.
+POLICY_EXPORT extern const char kValueAppType[];
+POLICY_EXPORT extern const char kValueBrowserUploadPublicKey[];
+POLICY_EXPORT extern const char kValueDeviceType[];
+POLICY_EXPORT extern const char kValueRequestAutoEnrollment[];
+POLICY_EXPORT extern const char kValueRequestPsmHasDeviceState[];
+POLICY_EXPORT extern const char kValueCheckUserAccount[];
+POLICY_EXPORT extern const char kValueRequestPolicy[];
+POLICY_EXPORT extern const char kValueRequestRegister[];
+POLICY_EXPORT extern const char kValueRequestApiAuthorization[];
+POLICY_EXPORT extern const char kValueRequestUnregister[];
+POLICY_EXPORT extern const char kValueRequestUploadCertificate[];
+POLICY_EXPORT extern const char kValueRequestUploadEuiccInfo[];
+POLICY_EXPORT extern const char kValueRequestDeviceStateRetrieval[];
+POLICY_EXPORT extern const char kValueRequestUploadStatus[];
+POLICY_EXPORT extern const char kValueRequestRemoteCommands[];
+POLICY_EXPORT extern const char kValueRequestDeviceAttributeUpdatePermission[];
+POLICY_EXPORT extern const char kValueRequestDeviceAttributeUpdate[];
+POLICY_EXPORT extern const char kValueRequestGcmIdUpdate[];
+POLICY_EXPORT extern const char kValueRequestCheckAndroidManagement[];
+POLICY_EXPORT extern const char kValueRequestCertBasedRegister[];
+POLICY_EXPORT extern const char kValueRequestActiveDirectoryEnrollPlayUser[];
+POLICY_EXPORT extern const char kValueRequestActiveDirectoryPlayActivity[];
+POLICY_EXPORT extern const char kValueRequestAppInstallReport[];
+POLICY_EXPORT extern const char kValueRequestTokenEnrollment[];
+POLICY_EXPORT extern const char kValueRequestChromeDesktopReport[];
+POLICY_EXPORT extern const char kValueRequestInitialEnrollmentStateRetrieval[];
+POLICY_EXPORT extern const char kValueRequestUploadPolicyValidationReport[];
+POLICY_EXPORT extern const char kValueRequestPublicSamlUser[];
+POLICY_EXPORT extern const char kValueRequestChromeOsUserReport[];
+POLICY_EXPORT extern const char kValueRequestCertProvisioningRequest[];
+POLICY_EXPORT extern const char kValueRequestChromeProfileReport[];
+
+// Policy type strings for the policy_type field in PolicyFetchRequest.
+POLICY_EXPORT extern const char kChromeDevicePolicyType[];
+POLICY_EXPORT extern const char kChromeUserPolicyType[];
+POLICY_EXPORT extern const char kChromePublicAccountPolicyType[];
+POLICY_EXPORT extern const char kChromeExtensionPolicyType[];
+POLICY_EXPORT extern const char kChromeSigninExtensionPolicyType[];
+POLICY_EXPORT extern const char kChromeMachineLevelUserCloudPolicyType[];
+POLICY_EXPORT extern const char kChromeMachineLevelUserCloudPolicyAndroidType[];
+POLICY_EXPORT extern const char kChromeMachineLevelUserCloudPolicyIOSType[];
+POLICY_EXPORT extern const char kChromeMachineLevelExtensionCloudPolicyType[];
+POLICY_EXPORT extern const char kChromeRemoteCommandPolicyType[];
+
+POLICY_EXPORT extern const char kChromeMachineLevelUserCloudPolicyTypeBase64[];
+
+// These codes are sent in the |error_code| field of PolicyFetchResponse.
+enum PolicyFetchStatus {
+ POLICY_FETCH_SUCCESS = 200,
+ POLICY_FETCH_ERROR_NOT_FOUND = 902,
+};
+
+} // namespace dm_protocol
+
+// Public half of the verification key that is used to verify that policy
+// signing keys are originating from DM server.
+POLICY_EXPORT std::string GetPolicyVerificationKey();
+
+// Corresponding hash.
+POLICY_EXPORT extern const char kPolicyVerificationKeyHash[];
+
+// Status codes for communication errors with the device management service.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration.
+enum DeviceManagementStatus {
+ // All is good.
+ DM_STATUS_SUCCESS = 0,
+ // Request payload invalid.
+ DM_STATUS_REQUEST_INVALID = 1,
+ // The HTTP request failed.
+ DM_STATUS_REQUEST_FAILED = 2,
+ // The server returned an error code that points to a temporary problem.
+ DM_STATUS_TEMPORARY_UNAVAILABLE = 3,
+ // The HTTP request returned a non-success code.
+ DM_STATUS_HTTP_STATUS_ERROR = 4,
+ // Response could not be decoded.
+ DM_STATUS_RESPONSE_DECODING_ERROR = 5,
+ // Service error: Management not supported.
+ DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED = 6,
+ // Service error: Device not found.
+ DM_STATUS_SERVICE_DEVICE_NOT_FOUND = 7,
+ // Service error: Device token invalid.
+ DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID = 8,
+ // Service error: Activation pending.
+ DM_STATUS_SERVICE_ACTIVATION_PENDING = 9,
+ // Service error: The serial number is not valid or not known to the server.
+ DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER = 10,
+ // Service error: The device id used for registration is already taken.
+ DM_STATUS_SERVICE_DEVICE_ID_CONFLICT = 11,
+ // Service error: The licenses have expired or have been exhausted.
+ DM_STATUS_SERVICE_MISSING_LICENSES = 12,
+ // Service error: The administrator has deprovisioned this client.
+ DM_STATUS_SERVICE_DEPROVISIONED = 13,
+ // Service error: Device registration for the wrong domain.
+ DM_STATUS_SERVICE_DOMAIN_MISMATCH = 14,
+ // Client error: Request could not be signed.
+ DM_STATUS_CANNOT_SIGN_REQUEST = 15,
+ // Client error: Request body is too large.
+ DM_STATUS_REQUEST_TOO_LARGE = 16,
+ // Client error: Too many request.
+ DM_STATUS_SERVICE_TOO_MANY_REQUESTS = 17,
+ // Service error: Policy not found. Error code defined by the DM folks.
+ DM_STATUS_SERVICE_POLICY_NOT_FOUND = 902,
+ // Service error: ARC is not enabled on this domain.
+ DM_STATUS_SERVICE_ARC_DISABLED = 904,
+ // Service error: Non-dasher account with packaged license can't enroll.
+ DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE = 905,
+ // Service error: Not eligible enterprise account can't enroll.
+ DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL = 906,
+ // Service error: Enterprise TOS has not been accepted.
+ DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED = 907,
+ // Service error: Illegal account for packaged EDU license.
+ DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE = 908,
+};
+
+// List of modes that the device can be locked into.
+enum DeviceMode {
+ DEVICE_MODE_PENDING, // The device mode is not yet available.
+ DEVICE_MODE_NOT_SET, // The device is not yet enrolled or owned.
+ DEVICE_MODE_CONSUMER, // The device is locally owned as consumer
+ // device.
+ DEVICE_MODE_ENTERPRISE, // The device is enrolled as an enterprise
+ // device.
+ DEVICE_MODE_ENTERPRISE_AD, // The device has joined AD.
+ DEPRECATED_DEVICE_MODE_LEGACY_RETAIL_MODE, // The device is enrolled as a
+ // retail kiosk device. This is
+ // deprecated.
+ DEVICE_MODE_CONSUMER_KIOSK_AUTOLAUNCH, // The device is locally owned as
+ // consumer kiosk with ability to auto
+ // launch a kiosk webapp.
+ DEVICE_MODE_DEMO, // The device is in demo mode. It was
+ // either enrolled online or setup
+ // offline into demo mode domain -
+ // see kDemoModeDomain.
+};
+
+// Domain that demo mode devices are enrolled into: cros-demo-mode.com
+POLICY_EXPORT extern const char kDemoModeDomain[];
+
+// Indicate this device's market segment. go/cros-rlz-segments.
+// This enum should be kept in sync with MarketSegment enum in
+// device_management_backend.proto (http://shortn/_p0P58C4BRV). If any additions
+// are made to this proto, the UserDeviceMatrix in
+// src/tools/metrics/histograms/enums.xml should also be updated, as well as the
+// browser test suite in usertype_by_devicetype_metrics_provider_browsertest.cc
+// (http://shortn/_gD5uIM9Z78) to account for the new user / device type combo.
+enum class MarketSegment {
+ UNKNOWN, // If device is not enrolled or market segment is not specified.
+ EDUCATION,
+ ENTERPRISE,
+};
+
+// Sender ID of FCM (Firebase Cloud Messaging)
+// Policy Invalidation sender coming from the Firebase console.
+extern const char kPolicyFCMInvalidationSenderID[];
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CONSTANTS_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_core.cc b/chromium/components/policy/core/common/cloud/cloud_policy_core.cc
new file mode 100644
index 00000000000..244794f9692
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_core.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/policy_invalidation_scope.h"
+#include "components/policy/core/common/remote_commands/remote_commands_factory.h"
+#include "components/policy/core/common/remote_commands/remote_commands_service.h"
+#include "components/prefs/pref_service.h"
+
+namespace policy {
+
+CloudPolicyCore::Observer::~Observer() = default;
+
+void CloudPolicyCore::Observer::OnRemoteCommandsServiceStarted(
+ CloudPolicyCore* core) {
+}
+
+CloudPolicyCore::CloudPolicyCore(
+ const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyStore* store,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter)
+ : policy_type_(policy_type),
+ settings_entity_id_(settings_entity_id),
+ store_(store),
+ task_runner_(task_runner),
+ network_connection_tracker_getter_(
+ std::move(network_connection_tracker_getter)) {}
+
+CloudPolicyCore::~CloudPolicyCore() = default;
+
+void CloudPolicyCore::Connect(std::unique_ptr<CloudPolicyClient> client) {
+ CHECK(!client_);
+ CHECK(client);
+ client_ = std::move(client);
+ service_ = std::make_unique<CloudPolicyService>(
+ policy_type_, settings_entity_id_, client_.get(), store_);
+ for (auto& observer : observers_)
+ observer.OnCoreConnected(this);
+}
+
+void CloudPolicyCore::Disconnect() {
+ if (client_)
+ for (auto& observer : observers_)
+ observer.OnCoreDisconnecting(this);
+ refresh_delay_.reset();
+ refresh_scheduler_.reset();
+ remote_commands_service_.reset();
+ service_.reset();
+ client_.reset();
+}
+
+void CloudPolicyCore::StartRemoteCommandsService(
+ std::unique_ptr<RemoteCommandsFactory> factory,
+ PolicyInvalidationScope scope) {
+ DCHECK(client_);
+ DCHECK(factory);
+
+ remote_commands_service_ = std::make_unique<RemoteCommandsService>(
+ std::move(factory), client_.get(), store_, scope);
+
+ // Do an initial remote commands fetch immediately.
+ remote_commands_service_->FetchRemoteCommands();
+
+ for (auto& observer : observers_)
+ observer.OnRemoteCommandsServiceStarted(this);
+}
+
+void CloudPolicyCore::RefreshSoon() {
+ if (refresh_scheduler_)
+ refresh_scheduler_->RefreshSoon();
+}
+
+void CloudPolicyCore::StartRefreshScheduler() {
+ if (!refresh_scheduler_) {
+ refresh_scheduler_ = std::make_unique<CloudPolicyRefreshScheduler>(
+ client_.get(), store_, service_.get(), task_runner_,
+ network_connection_tracker_getter_);
+ UpdateRefreshDelayFromPref();
+ for (auto& observer : observers_)
+ observer.OnRefreshSchedulerStarted(this);
+ }
+}
+
+void CloudPolicyCore::TrackRefreshDelayPref(
+ PrefService* pref_service,
+ const std::string& refresh_pref_name) {
+ refresh_delay_ = std::make_unique<IntegerPrefMember>();
+ refresh_delay_->Init(
+ refresh_pref_name, pref_service,
+ base::BindRepeating(&CloudPolicyCore::UpdateRefreshDelayFromPref,
+ base::Unretained(this)));
+ UpdateRefreshDelayFromPref();
+}
+
+void CloudPolicyCore::AddObserver(CloudPolicyCore::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void CloudPolicyCore::RemoveObserver(CloudPolicyCore::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void CloudPolicyCore::ConnectForTesting(
+ std::unique_ptr<CloudPolicyService> service,
+ std::unique_ptr<CloudPolicyClient> client) {
+ service_ = std::move(service);
+ client_ = std::move(client);
+ for (auto& observer : observers_)
+ observer.OnCoreConnected(this);
+}
+
+void CloudPolicyCore::UpdateRefreshDelayFromPref() {
+ if (refresh_scheduler_ && refresh_delay_)
+ refresh_scheduler_->SetDesiredRefreshDelay(refresh_delay_->GetValue());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_core.h b/chromium/components/policy/core/common/cloud/cloud_policy_core.h
new file mode 100644
index 00000000000..8c6495b93b4
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_core.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/cloud/policy_invalidation_scope.h"
+#include "components/policy/policy_export.h"
+#include "components/prefs/pref_member.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+class PrefService;
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class CloudPolicyClient;
+class CloudPolicyRefreshScheduler;
+class CloudPolicyService;
+class CloudPolicyStore;
+class RemoteCommandsFactory;
+class RemoteCommandsService;
+
+// CloudPolicyCore glues together the ingredients that are essential for
+// obtaining a fully-functional cloud policy system: CloudPolicyClient and
+// CloudPolicyStore, which are responsible for fetching policy from the cloud
+// and storing it locally, respectively, as well as a CloudPolicyService
+// instance that moves data between the two former components, and
+// CloudPolicyRefreshScheduler which triggers periodic refreshes.
+class POLICY_EXPORT CloudPolicyCore {
+ public:
+ // Callbacks for policy core events.
+ class POLICY_EXPORT Observer {
+ public:
+ virtual ~Observer();
+
+ // Called after the core is connected.
+ virtual void OnCoreConnected(CloudPolicyCore* core) = 0;
+
+ // Called after the refresh scheduler is started.
+ virtual void OnRefreshSchedulerStarted(CloudPolicyCore* core) = 0;
+
+ // Called before the core is disconnected.
+ virtual void OnCoreDisconnecting(CloudPolicyCore* core) = 0;
+
+ // Called after the remote commands service is started. Defaults to be
+ // empty.
+ virtual void OnRemoteCommandsServiceStarted(CloudPolicyCore* core);
+ };
+
+ // |task_runner| is the runner for policy refresh tasks.
+ CloudPolicyCore(const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyStore* store,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+ CloudPolicyCore(const CloudPolicyCore&) = delete;
+ CloudPolicyCore& operator=(const CloudPolicyCore&) = delete;
+ ~CloudPolicyCore();
+
+ CloudPolicyClient* client() { return client_.get(); }
+ const CloudPolicyClient* client() const { return client_.get(); }
+
+ CloudPolicyStore* store() { return store_; }
+ const CloudPolicyStore* store() const { return store_; }
+
+ CloudPolicyService* service() { return service_.get(); }
+ const CloudPolicyService* service() const { return service_.get(); }
+
+ CloudPolicyRefreshScheduler* refresh_scheduler() {
+ return refresh_scheduler_.get();
+ }
+ const CloudPolicyRefreshScheduler* refresh_scheduler() const {
+ return refresh_scheduler_.get();
+ }
+
+ RemoteCommandsService* remote_commands_service() {
+ return remote_commands_service_.get();
+ }
+ const RemoteCommandsService* remote_commands_service() const {
+ return remote_commands_service_.get();
+ }
+
+ // Initializes the cloud connection.
+ void Connect(std::unique_ptr<CloudPolicyClient> client);
+
+ // Shuts down the cloud connection.
+ void Disconnect();
+
+ // Starts a remote commands service, with the provided factory. Will attempt
+ // to fetch commands immediately, thus requiring the cloud policy client to
+ // be registered.
+ void StartRemoteCommandsService(
+ std::unique_ptr<RemoteCommandsFactory> factory,
+ PolicyInvalidationScope scope);
+
+ // Requests a policy refresh to be performed soon. This may apply throttling,
+ // and the request may not be immediately sent.
+ void RefreshSoon();
+
+ // Starts a refresh scheduler in case none is running yet.
+ void StartRefreshScheduler();
+
+ // Watches the pref named |refresh_pref_name| in |pref_service| and adjusts
+ // |refresh_scheduler_|'s refresh delay accordingly.
+ void TrackRefreshDelayPref(PrefService* pref_service,
+ const std::string& refresh_pref_name);
+
+ // Registers an observer to be notified of policy core events.
+ void AddObserver(Observer* observer);
+
+ // Removes the specified observer.
+ void RemoveObserver(Observer* observer);
+
+ // Initializes the cloud connection using injected |service| and |client|.
+ void ConnectForTesting(std::unique_ptr<CloudPolicyService> service,
+ std::unique_ptr<CloudPolicyClient> client);
+
+ private:
+ // Updates the refresh scheduler on refresh delay changes.
+ void UpdateRefreshDelayFromPref();
+
+ std::string policy_type_;
+ std::string settings_entity_id_;
+ raw_ptr<CloudPolicyStore> store_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter_;
+ std::unique_ptr<CloudPolicyClient> client_;
+ std::unique_ptr<CloudPolicyService> service_;
+ std::unique_ptr<CloudPolicyRefreshScheduler> refresh_scheduler_;
+ std::unique_ptr<RemoteCommandsService> remote_commands_service_;
+ std::unique_ptr<IntegerPrefMember> refresh_delay_;
+ base::ObserverList<Observer, true>::Unchecked observers_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CORE_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_core_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_core_unittest.cc
new file mode 100644
index 00000000000..4b0ca2bb1e5
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_core_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+
+#include "base/base64.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class CloudPolicyCoreTest : public testing::Test,
+ public CloudPolicyCore::Observer {
+ public:
+ CloudPolicyCoreTest(const CloudPolicyCoreTest&) = delete;
+ CloudPolicyCoreTest& operator=(const CloudPolicyCoreTest&) = delete;
+
+ protected:
+ CloudPolicyCoreTest()
+ : core_(dm_protocol::kChromeUserPolicyType,
+ std::string(),
+ &store_,
+ base::ThreadTaskRunnerHandle::Get(),
+ network::TestNetworkConnectionTracker::CreateGetter()),
+ core_connected_callback_count_(0),
+ refresh_scheduler_started_callback_count_(0),
+ core_disconnecting_callback_count_(0),
+ bad_callback_count_(0) {
+ prefs_.registry()->RegisterIntegerPref(
+ policy_prefs::kUserPolicyRefreshRate,
+ CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs);
+ core_.AddObserver(this);
+ }
+
+ ~CloudPolicyCoreTest() override { core_.RemoveObserver(this); }
+
+ void OnCoreConnected(CloudPolicyCore* core) override {
+ // Make sure core is connected at callback time.
+ if (core_.client())
+ core_connected_callback_count_++;
+ else
+ bad_callback_count_++;
+ }
+
+ void OnRefreshSchedulerStarted(CloudPolicyCore* core) override {
+ // Make sure refresh scheduler is started at callback time.
+ if (core_.refresh_scheduler())
+ refresh_scheduler_started_callback_count_++;
+ else
+ bad_callback_count_++;
+ }
+
+ void OnCoreDisconnecting(CloudPolicyCore* core) override {
+ // Make sure core is still connected at callback time.
+ if (core_.client())
+ core_disconnecting_callback_count_++;
+ else
+ bad_callback_count_++;
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+
+ TestingPrefServiceSimple prefs_;
+ MockCloudPolicyStore store_;
+ CloudPolicyCore core_;
+
+ int core_connected_callback_count_;
+ int refresh_scheduler_started_callback_count_;
+ int core_disconnecting_callback_count_;
+ int bad_callback_count_;
+};
+
+TEST_F(CloudPolicyCoreTest, ConnectAndDisconnect) {
+ EXPECT_TRUE(core_.store());
+ EXPECT_FALSE(core_.client());
+ EXPECT_FALSE(core_.service());
+ EXPECT_FALSE(core_.refresh_scheduler());
+
+ // Connect() brings up client and service.
+ core_.Connect(
+ std::unique_ptr<CloudPolicyClient>(new MockCloudPolicyClient()));
+ EXPECT_TRUE(core_.client());
+ EXPECT_TRUE(core_.service());
+ EXPECT_FALSE(core_.refresh_scheduler());
+ EXPECT_EQ(1, core_connected_callback_count_);
+ EXPECT_EQ(0, refresh_scheduler_started_callback_count_);
+ EXPECT_EQ(0, core_disconnecting_callback_count_);
+
+ // Disconnect() goes back to no client and service.
+ core_.Disconnect();
+ EXPECT_FALSE(core_.client());
+ EXPECT_FALSE(core_.service());
+ EXPECT_FALSE(core_.refresh_scheduler());
+ EXPECT_EQ(1, core_connected_callback_count_);
+ EXPECT_EQ(0, refresh_scheduler_started_callback_count_);
+ EXPECT_EQ(1, core_disconnecting_callback_count_);
+
+ // Calling Disconnect() twice doesn't do bad things.
+ core_.Disconnect();
+ EXPECT_FALSE(core_.client());
+ EXPECT_FALSE(core_.service());
+ EXPECT_FALSE(core_.refresh_scheduler());
+ EXPECT_EQ(1, core_connected_callback_count_);
+ EXPECT_EQ(0, refresh_scheduler_started_callback_count_);
+ EXPECT_EQ(1, core_disconnecting_callback_count_);
+ EXPECT_EQ(0, bad_callback_count_);
+}
+
+TEST_F(CloudPolicyCoreTest, RefreshScheduler) {
+ EXPECT_FALSE(core_.refresh_scheduler());
+ core_.Connect(
+ std::unique_ptr<CloudPolicyClient>(new MockCloudPolicyClient()));
+ core_.StartRefreshScheduler();
+ ASSERT_TRUE(core_.refresh_scheduler());
+
+ int default_refresh_delay =
+ core_.refresh_scheduler()->GetActualRefreshDelay();
+
+ const int kRefreshRate = 1000 * 60 * 60;
+ prefs_.SetInteger(policy_prefs::kUserPolicyRefreshRate, kRefreshRate);
+ core_.TrackRefreshDelayPref(&prefs_, policy_prefs::kUserPolicyRefreshRate);
+ EXPECT_EQ(kRefreshRate, core_.refresh_scheduler()->GetActualRefreshDelay());
+
+ prefs_.ClearPref(policy_prefs::kUserPolicyRefreshRate);
+ EXPECT_EQ(default_refresh_delay,
+ core_.refresh_scheduler()->GetActualRefreshDelay());
+
+ EXPECT_EQ(1, core_connected_callback_count_);
+ EXPECT_EQ(1, refresh_scheduler_started_callback_count_);
+ EXPECT_EQ(0, core_disconnecting_callback_count_);
+ EXPECT_EQ(0, bad_callback_count_);
+}
+
+TEST_F(CloudPolicyCoreTest, DmProtocolBase64Constants) {
+ std::string encoded;
+ base::Base64Encode(dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ &encoded);
+ EXPECT_EQ(encoded, dm_protocol::kChromeMachineLevelUserCloudPolicyTypeBase64);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_manager.cc b/chromium/components/policy/core/common/cloud/cloud_policy_manager.cc
new file mode 100644
index 00000000000..219d72f5f0a
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_manager.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check_op.h"
+#include "base/files/file_path.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/prefs/pref_service.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#include "components/policy/core/common/cloud/resource_cache.h"
+#endif
+
+namespace policy {
+
+CloudPolicyManager::CloudPolicyManager(
+ const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyStore* cloud_policy_store,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter)
+ : core_(policy_type,
+ settings_entity_id,
+ cloud_policy_store,
+ task_runner,
+ std::move(network_connection_tracker_getter)),
+ waiting_for_policy_refresh_(false) {}
+
+CloudPolicyManager::~CloudPolicyManager() {}
+
+bool CloudPolicyManager::IsClientRegistered() const {
+ return client() && client()->is_registered();
+}
+
+void CloudPolicyManager::Init(SchemaRegistry* registry) {
+ ConfigurationPolicyProvider::Init(registry);
+
+ store()->AddObserver(this);
+
+ // If the underlying store is already initialized, pretend it was loaded now.
+ // Note: It is not enough to just copy OnStoreLoaded's contents here because
+ // subclasses can override it.
+ if (store()->is_initialized())
+ OnStoreLoaded(store());
+ else
+ store()->Load();
+}
+
+void CloudPolicyManager::Shutdown() {
+ component_policy_service_.reset();
+ core_.Disconnect();
+ store()->RemoveObserver(this);
+ ConfigurationPolicyProvider::Shutdown();
+}
+
+bool CloudPolicyManager::IsInitializationComplete(PolicyDomain domain) const {
+ if (domain == POLICY_DOMAIN_CHROME)
+ return store()->is_initialized();
+ if (ComponentCloudPolicyService::SupportsDomain(domain) &&
+ component_policy_service_) {
+ return component_policy_service_->is_initialized();
+ }
+ return true;
+}
+
+bool CloudPolicyManager::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
+ return store()->first_policies_loaded();
+}
+
+void CloudPolicyManager::RefreshPolicies() {
+ if (service()) {
+ waiting_for_policy_refresh_ = true;
+ service()->RefreshPolicy(base::BindOnce(
+ &CloudPolicyManager::OnRefreshComplete, base::Unretained(this)));
+ } else {
+ OnRefreshComplete(false);
+ }
+}
+
+void CloudPolicyManager::OnStoreLoaded(CloudPolicyStore* cloud_policy_store) {
+ DCHECK_EQ(store(), cloud_policy_store);
+ CheckAndPublishPolicy();
+}
+
+void CloudPolicyManager::OnStoreError(CloudPolicyStore* cloud_policy_store) {
+ DCHECK_EQ(store(), cloud_policy_store);
+ // Publish policy (even though it hasn't changed) in order to signal load
+ // complete on the ConfigurationPolicyProvider interface. Technically, this
+ // is only required on the first load, but doesn't hurt in any case.
+ CheckAndPublishPolicy();
+}
+
+void CloudPolicyManager::OnComponentCloudPolicyUpdated() {
+ CheckAndPublishPolicy();
+}
+
+void CloudPolicyManager::CheckAndPublishPolicy() {
+ if (IsInitializationComplete(POLICY_DOMAIN_CHROME) &&
+ !waiting_for_policy_refresh_) {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle);
+ GetChromePolicy(
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ if (component_policy_service_)
+ bundle->MergeFrom(component_policy_service_->policy());
+ UpdatePolicy(std::move(bundle));
+ }
+}
+
+void CloudPolicyManager::GetChromePolicy(PolicyMap* policy_map) {
+ *policy_map = store()->policy_map().Clone();
+}
+
+void CloudPolicyManager::CreateComponentCloudPolicyService(
+ const std::string& policy_type,
+ const base::FilePath& policy_cache_path,
+ CloudPolicyClient* client,
+ SchemaRegistry* schema_registry) {
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+ // Init() must have been called.
+ CHECK(schema_registry);
+ // Called at most once.
+ CHECK(!component_policy_service_);
+ // The core can't be connected yet.
+ // See the comments on ComponentCloudPolicyService for the details.
+ CHECK(!core()->client());
+
+ if (policy_cache_path.empty())
+ return;
+
+ // TODO(emaxx, 729082): Make ComponentCloudPolicyStore (and other
+ // implementation details of it) not use the blocking task runner whenever
+ // possible because the real file operations are only done by ResourceCache,
+ // and most of the rest doesn't need the blocking behaviour. Also
+ // ComponentCloudPolicyService's |backend_task_runner| and |cache| must live
+ // on the same task runner.
+ const auto task_runner =
+ base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
+ std::unique_ptr<ResourceCache> resource_cache(new ResourceCache(
+ policy_cache_path, task_runner, /* max_cache_size */ absl::nullopt));
+ component_policy_service_ = std::make_unique<ComponentCloudPolicyService>(
+ policy_type, this, schema_registry, core(), client,
+ std::move(resource_cache), task_runner);
+#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+}
+
+void CloudPolicyManager::ClearAndDestroyComponentCloudPolicyService() {
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+ if (component_policy_service_) {
+ component_policy_service_->ClearCache();
+ component_policy_service_.reset();
+ }
+#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+}
+
+void CloudPolicyManager::OnRefreshComplete(bool success) {
+ waiting_for_policy_refresh_ = false;
+ CheckAndPublishPolicy();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_manager.h b/chromium/components/policy/core/common/cloud/cloud_policy_manager.h
new file mode 100644
index 00000000000..ec33995a9f1
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_manager.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+#include "components/prefs/pref_member.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class PolicyMap;
+
+// CloudPolicyManager is the main switching central between cloud policy and the
+// upper layers of the policy stack. It wires up a CloudPolicyCore to the
+// ConfigurationPolicyProvider interface.
+//
+// This class contains the base functionality, there are subclasses that add
+// functionality specific to user-level and device-level cloud policy, such as
+// blocking on initial user policy fetch or device enrollment.
+class POLICY_EXPORT CloudPolicyManager
+ : public ConfigurationPolicyProvider,
+ public CloudPolicyStore::Observer,
+ public ComponentCloudPolicyService::Delegate {
+ public:
+ // |task_runner| is the runner for policy refresh tasks.
+ CloudPolicyManager(
+ const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyStore* cloud_policy_store,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+ CloudPolicyManager(const CloudPolicyManager&) = delete;
+ CloudPolicyManager& operator=(const CloudPolicyManager&) = delete;
+ ~CloudPolicyManager() override;
+
+ CloudPolicyCore* core() { return &core_; }
+ const CloudPolicyCore* core() const { return &core_; }
+ ComponentCloudPolicyService* component_policy_service() const {
+ return component_policy_service_.get();
+ }
+
+ // Returns true if the underlying CloudPolicyClient is already registered.
+ // Virtual for mocking.
+ virtual bool IsClientRegistered() const;
+
+ // ConfigurationPolicyProvider:
+ void Init(SchemaRegistry* registry) override;
+ void Shutdown() override;
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+ void RefreshPolicies() override;
+
+ // CloudPolicyStore::Observer:
+ void OnStoreLoaded(CloudPolicyStore* cloud_policy_store) override;
+ void OnStoreError(CloudPolicyStore* cloud_policy_store) override;
+
+ // ComponentCloudPolicyService::Delegate:
+ void OnComponentCloudPolicyUpdated() override;
+
+ protected:
+ // Check whether fully initialized and if so, publish policy by calling
+ // ConfigurationPolicyStore::UpdatePolicy().
+ void CheckAndPublishPolicy();
+
+ // Writes Chrome policy into |policy_map|. This is intended to be overridden
+ // by subclasses that want to post-process policy before publishing it. The
+ // default implementation just copies over |store()->policy_map()|.
+ virtual void GetChromePolicy(PolicyMap* policy_map);
+
+ void CreateComponentCloudPolicyService(
+ const std::string& policy_type,
+ const base::FilePath& policy_cache_path,
+ CloudPolicyClient* client,
+ SchemaRegistry* schema_registry);
+
+ void ClearAndDestroyComponentCloudPolicyService();
+
+ // Convenience accessors to core() components.
+ CloudPolicyClient* client() { return core_.client(); }
+ const CloudPolicyClient* client() const { return core_.client(); }
+ CloudPolicyStore* store() { return core_.store(); }
+ const CloudPolicyStore* store() const { return core_.store(); }
+ CloudPolicyService* service() { return core_.service(); }
+ const CloudPolicyService* service() const { return core_.service(); }
+
+ private:
+ // Completion handler for policy refresh operations.
+ void OnRefreshComplete(bool success);
+
+ CloudPolicyCore core_;
+ std::unique_ptr<ComponentCloudPolicyService> component_policy_service_;
+
+ // Whether there's a policy refresh operation pending, in which case all
+ // policy update notifications are deferred until after it completes.
+ bool waiting_for_policy_refresh_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_MANAGER_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_manager_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_manager_unittest.cc
new file mode 100644
index 00000000000..b60e8cef609
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_manager_unittest.cc
@@ -0,0 +1,356 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::_;
+
+namespace em = enterprise_management;
+
+namespace policy {
+namespace {
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ explicit TestHarness(PolicyLevel level);
+ TestHarness(const TestHarness&) = delete;
+ TestHarness& operator=(const TestHarness&) = delete;
+ ~TestHarness() override;
+
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) override;
+
+ // Creates harnesses for mandatory and recommended levels, respectively.
+ static PolicyProviderTestHarness* CreateMandatory();
+ static PolicyProviderTestHarness* CreateRecommended();
+
+ private:
+ MockCloudPolicyStore store_;
+};
+
+TestHarness::TestHarness(PolicyLevel level)
+ : PolicyProviderTestHarness(level, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD) {
+}
+
+TestHarness::~TestHarness() {}
+
+void TestHarness::SetUp() {}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ // Create and initialize the store.
+ store_.NotifyStoreLoaded();
+ ConfigurationPolicyProvider* provider = new CloudPolicyManager(
+ dm_protocol::kChromeUserPolicyType, std::string(), &store_, task_runner,
+ network::TestNetworkConnectionTracker::CreateGetter());
+ Mock::VerifyAndClearExpectations(&store_);
+ return provider;
+}
+
+void TestHarness::InstallEmptyPolicy() {}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ store_.policy_map_.Set(policy_name, policy_level(), policy_scope(),
+ POLICY_SOURCE_CLOUD, base::Value(policy_value),
+ nullptr);
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ store_.policy_map_.Set(policy_name, policy_level(), policy_scope(),
+ POLICY_SOURCE_CLOUD, base::Value(policy_value),
+ nullptr);
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ store_.policy_map_.Set(policy_name, policy_level(), policy_scope(),
+ POLICY_SOURCE_CLOUD, base::Value(policy_value),
+ nullptr);
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ store_.policy_map_.Set(policy_name, policy_level(), policy_scope(),
+ POLICY_SOURCE_CLOUD, policy_value->Clone(), nullptr);
+}
+
+void TestHarness::InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) {
+ store_.policy_map_.Set(policy_name, policy_level(), policy_scope(),
+ POLICY_SOURCE_CLOUD, policy_value->Clone(), nullptr);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::CreateMandatory() {
+ return new TestHarness(POLICY_LEVEL_MANDATORY);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::CreateRecommended() {
+ return new TestHarness(POLICY_LEVEL_RECOMMENDED);
+}
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_SUITE_P(UserCloudPolicyManagerProviderTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::CreateMandatory,
+ TestHarness::CreateRecommended));
+
+class TestCloudPolicyManager : public CloudPolicyManager {
+ public:
+ TestCloudPolicyManager(
+ CloudPolicyStore* store,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
+ : CloudPolicyManager(
+ dm_protocol::kChromeUserPolicyType,
+ std::string(),
+ store,
+ task_runner,
+ network::TestNetworkConnectionTracker::CreateGetter()) {}
+ TestCloudPolicyManager(const TestCloudPolicyManager&) = delete;
+ TestCloudPolicyManager& operator=(const TestCloudPolicyManager&) = delete;
+ ~TestCloudPolicyManager() override = default;
+
+ // Publish the protected members for testing.
+ using CloudPolicyManager::client;
+ using CloudPolicyManager::store;
+ using CloudPolicyManager::service;
+ using CloudPolicyManager::CheckAndPublishPolicy;
+};
+
+MATCHER_P(ProtoMatches, proto, std::string()) {
+ return arg.SerializePartialAsString() == proto.SerializePartialAsString();
+}
+
+class CloudPolicyManagerTest : public testing::Test {
+ public:
+ CloudPolicyManagerTest(const CloudPolicyManagerTest&) = delete;
+ CloudPolicyManagerTest& operator=(const CloudPolicyManagerTest&) = delete;
+
+ protected:
+ CloudPolicyManagerTest()
+ : policy_type_(dm_protocol::kChromeUserPolicyType) {}
+
+ void SetUp() override {
+ // Set up a policy map for testing.
+ policy_map_.Set("key", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+ expected_bundle_.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy_map_.Clone();
+
+ policy_.payload().mutable_searchsuggestenabled()->set_value(false);
+ policy_.Build();
+
+ EXPECT_CALL(store_, Load());
+ manager_ = std::make_unique<TestCloudPolicyManager>(
+ &store_, task_environment_.GetMainThreadTaskRunner());
+ manager_->Init(&schema_registry_);
+ Mock::VerifyAndClearExpectations(&store_);
+ manager_->AddObserver(&observer_);
+ }
+
+ void TearDown() override {
+ manager_->RemoveObserver(&observer_);
+ manager_->Shutdown();
+ }
+
+ // Needs to be the first member.
+ base::test::TaskEnvironment task_environment_;
+
+ // Testing policy.
+ const std::string policy_type_;
+ UserPolicyBuilder policy_;
+ PolicyMap policy_map_;
+ PolicyBundle expected_bundle_;
+
+ // Policy infrastructure.
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ MockCloudPolicyStore store_;
+ std::unique_ptr<TestCloudPolicyManager> manager_;
+};
+
+TEST_F(CloudPolicyManagerTest, InitAndShutdown) {
+ PolicyBundle empty_bundle;
+ EXPECT_TRUE(empty_bundle.Equals(manager_->policies()));
+ EXPECT_FALSE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(_)).Times(0);
+ manager_->CheckAndPublishPolicy();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ store_.policy_map_ = policy_map_.Clone();
+ store_.set_policy_data_for_testing(
+ std::make_unique<em::PolicyData>(policy_.policy_data()));
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+
+ MockCloudPolicyClient* client = new MockCloudPolicyClient();
+ EXPECT_CALL(*client, SetupRegistration(_, _, _));
+ manager_->core()->Connect(std::unique_ptr<CloudPolicyClient>(client));
+ Mock::VerifyAndClearExpectations(client);
+ EXPECT_TRUE(manager_->client());
+ EXPECT_TRUE(manager_->service());
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ manager_->CheckAndPublishPolicy();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ manager_->core()->Disconnect();
+ EXPECT_FALSE(manager_->client());
+ EXPECT_FALSE(manager_->service());
+}
+
+TEST_F(CloudPolicyManagerTest, RegistrationAndFetch) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+
+ MockCloudPolicyClient* client = new MockCloudPolicyClient();
+ manager_->core()->Connect(std::unique_ptr<CloudPolicyClient>(client));
+
+ client->SetDMToken(policy_.policy_data().request_token());
+ client->NotifyRegistrationStateChanged();
+
+ client->SetPolicy(policy_type_, std::string(), policy_.policy());
+ EXPECT_CALL(store_, Store(ProtoMatches(policy_.policy())));
+ client->NotifyPolicyFetched();
+ Mock::VerifyAndClearExpectations(&store_);
+
+ store_.policy_map_ = policy_map_.Clone();
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
+}
+
+TEST_F(CloudPolicyManagerTest, Update) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ PolicyBundle empty_bundle;
+ EXPECT_TRUE(empty_bundle.Equals(manager_->policies()));
+
+ store_.policy_map_ = policy_map_.Clone();
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+}
+
+TEST_F(CloudPolicyManagerTest, RefreshNotRegistered) {
+ MockCloudPolicyClient* client = new MockCloudPolicyClient();
+ manager_->core()->Connect(std::unique_ptr<CloudPolicyClient>(client));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // A refresh on a non-registered store should not block.
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ manager_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(CloudPolicyManagerTest, RefreshSuccessful) {
+ MockCloudPolicyClient* client = new MockCloudPolicyClient();
+ manager_->core()->Connect(std::unique_ptr<CloudPolicyClient>(client));
+
+ // Simulate a store load.
+ store_.set_policy_data_for_testing(
+ std::make_unique<em::PolicyData>(policy_.policy_data()));
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ EXPECT_CALL(*client, SetupRegistration(_, _, _));
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(client);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Acknowledge registration.
+ client->SetDMToken(policy_.policy_data().request_token());
+
+ // Start a refresh.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_)).Times(0);
+ EXPECT_CALL(*client, FetchPolicy());
+ manager_->RefreshPolicies();
+ Mock::VerifyAndClearExpectations(client);
+ Mock::VerifyAndClearExpectations(&observer_);
+ store_.policy_map_ = policy_map_.Clone();
+
+ // A stray reload should be suppressed until the refresh completes.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_)).Times(0);
+ store_.NotifyStoreLoaded();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Respond to the policy fetch, which should trigger a write to |store_|.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_)).Times(0);
+ EXPECT_CALL(store_, Store(_));
+ client->SetPolicy(policy_type_, std::string(), policy_.policy());
+ client->NotifyPolicyFetched();
+ Mock::VerifyAndClearExpectations(&observer_);
+ Mock::VerifyAndClearExpectations(&store_);
+
+ // The load notification from |store_| should trigger the policy update.
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreLoaded();
+ EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(CloudPolicyManagerTest, SignalOnError) {
+ // Simulate a failed load and verify that it triggers OnUpdatePolicy().
+ store_.set_policy_data_for_testing(
+ std::make_unique<em::PolicyData>(policy_.policy_data()));
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get()));
+ store_.NotifyStoreError();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+}
+
+} // namespace
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.cc b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.cc
new file mode 100644
index 00000000000..68d01ce9c1c
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/rand_util.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+
+namespace policy {
+
+namespace {
+
+base::Clock* clock_for_testing_ = nullptr;
+
+const base::Clock* GetClock() {
+ if (clock_for_testing_)
+ return clock_for_testing_;
+ return base::DefaultClock::GetInstance();
+}
+
+base::TickClock* tick_clock_for_testing_ = nullptr;
+
+const base::TickClock* GetTickClock() {
+ if (tick_clock_for_testing_)
+ return tick_clock_for_testing_;
+ return base::DefaultTickClock::GetInstance();
+}
+
+} // namespace
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+
+const int64_t CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+const int64_t CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+// Delay for periodic refreshes when the invalidations service is available,
+// in milliseconds.
+// TODO(joaodasilva): increase this value once we're confident that the
+// invalidations channel works as expected.
+const int64_t CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+const int64_t CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs =
+ 5 * 60 * 1000; // 5 minutes.
+const int64_t CloudPolicyRefreshScheduler::kRefreshDelayMinMs =
+ 30 * 60 * 1000; // 30 minutes.
+const int64_t CloudPolicyRefreshScheduler::kRefreshDelayMaxMs =
+ 7 * 24 * 60 * 60 * 1000; // 1 week.
+const int64_t CloudPolicyRefreshScheduler::kRandomSaltDelayMaxValueMs =
+ 5 * 60 * 1000; // 5 minutes.
+
+#else
+
+const int64_t CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs =
+ 3 * 60 * 60 * 1000; // 3 hours.
+const int64_t CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+// Delay for periodic refreshes when the invalidations service is available,
+// in milliseconds.
+const int64_t CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+const int64_t CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs =
+ 5 * 60 * 1000; // 5 minutes.
+const int64_t CloudPolicyRefreshScheduler::kRefreshDelayMinMs =
+ 30 * 60 * 1000; // 30 minutes.
+const int64_t CloudPolicyRefreshScheduler::kRefreshDelayMaxMs =
+ 24 * 60 * 60 * 1000; // 1 day.
+const int64_t CloudPolicyRefreshScheduler::kRandomSaltDelayMaxValueMs =
+ 5 * 60 * 1000; // 5 minutes.
+
+#endif
+
+CloudPolicyRefreshScheduler::CloudPolicyRefreshScheduler(
+ CloudPolicyClient* client,
+ CloudPolicyStore* store,
+ CloudPolicyService* service,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter)
+ : client_(client),
+ store_(store),
+ service_(service),
+ task_runner_(task_runner),
+ network_connection_tracker_(network_connection_tracker_getter.Run()),
+ error_retry_delay_ms_(kInitialErrorRetryDelayMs),
+ refresh_delay_ms_(kDefaultRefreshDelayMs),
+ refresh_delay_salt_ms_(static_cast<int64_t>(
+ base::RandGenerator(kRandomSaltDelayMaxValueMs))),
+ invalidations_available_(false),
+ creation_time_(GetClock()->Now()) {
+ client_->AddObserver(this);
+ store_->AddObserver(this);
+ network_connection_tracker_->AddNetworkConnectionObserver(this);
+
+ UpdateLastRefreshFromPolicy();
+ ScheduleRefresh();
+}
+
+CloudPolicyRefreshScheduler::~CloudPolicyRefreshScheduler() {
+ store_->RemoveObserver(this);
+ client_->RemoveObserver(this);
+ if (network_connection_tracker_)
+ network_connection_tracker_->RemoveNetworkConnectionObserver(this);
+}
+
+void CloudPolicyRefreshScheduler::SetDesiredRefreshDelay(
+ int64_t refresh_delay) {
+ refresh_delay_ms_ =
+ base::clamp(refresh_delay, kRefreshDelayMinMs, kRefreshDelayMaxMs);
+ ScheduleRefresh();
+}
+
+int64_t CloudPolicyRefreshScheduler::GetActualRefreshDelay() const {
+ // Returns the refresh delay, possibly modified/lengthened due to the presence
+ // of invalidations (we don't have to poll as often if we have policy
+ // invalidations because policy invalidations provide for timely refreshes.
+ if (invalidations_available_) {
+ // If policy invalidations are available then periodic updates are done at
+ // a much lower rate; otherwise use the |refresh_delay_ms_| value.
+ return std::max(refresh_delay_ms_, kWithInvalidationsRefreshDelayMs);
+ } else {
+ return refresh_delay_ms_;
+ }
+}
+
+void CloudPolicyRefreshScheduler::RefreshSoon() {
+ // If the client isn't registered, there is nothing to do.
+ if (!client_->is_registered())
+ return;
+
+ is_scheduled_for_soon_ = true;
+ RefreshAfter(0);
+}
+
+void CloudPolicyRefreshScheduler::SetInvalidationServiceAvailability(
+ bool is_available) {
+ if (!creation_time_.is_null()) {
+ base::TimeDelta elapsed = GetClock()->Now() - creation_time_;
+ UMA_HISTOGRAM_MEDIUM_TIMES("Enterprise.PolicyInvalidationsStartupTime",
+ elapsed);
+ creation_time_ = base::Time();
+ }
+
+ if (is_available == invalidations_available_) {
+ // No change in state.
+ return;
+ }
+
+ invalidations_available_ = is_available;
+
+ // Schedule a refresh since the refresh delay has been updated.
+ ScheduleRefresh();
+}
+
+void CloudPolicyRefreshScheduler::OnPolicyFetched(CloudPolicyClient* client) {
+ error_retry_delay_ms_ = kInitialErrorRetryDelayMs;
+
+ // Schedule the next refresh.
+ UpdateLastRefresh();
+ ScheduleRefresh();
+}
+
+void CloudPolicyRefreshScheduler::OnRegistrationStateChanged(
+ CloudPolicyClient* client) {
+ if (!client->is_registered()) {
+ CancelRefresh();
+ return;
+ }
+
+ // The client has registered, so trigger an immediate refresh.
+ error_retry_delay_ms_ = kInitialErrorRetryDelayMs;
+ RefreshSoon();
+}
+
+void CloudPolicyRefreshScheduler::OnClientError(CloudPolicyClient* client) {
+ // Save the status for below.
+ DeviceManagementStatus status = client_->status();
+
+ // Schedule an error retry if applicable.
+ UpdateLastRefresh();
+ ScheduleRefresh();
+
+ // Update the retry delay.
+ if (client->is_registered() && (status == DM_STATUS_REQUEST_FAILED ||
+ status == DM_STATUS_TEMPORARY_UNAVAILABLE)) {
+ error_retry_delay_ms_ =
+ std::min(error_retry_delay_ms_ * 2, refresh_delay_ms_);
+ } else {
+ error_retry_delay_ms_ = kInitialErrorRetryDelayMs;
+ }
+}
+
+void CloudPolicyRefreshScheduler::OnStoreLoaded(CloudPolicyStore* store) {
+ UpdateLastRefreshFromPolicy();
+
+ // Re-schedule the next refresh in case the is_managed bit changed.
+ ScheduleRefresh();
+}
+
+void CloudPolicyRefreshScheduler::OnStoreError(CloudPolicyStore* store) {
+ // If |store_| fails, the is_managed bit that it provides may become stale.
+ // The best guess in that situation is to assume is_managed didn't change and
+ // continue using the stale information. Thus, no specific response to a store
+ // error is required. NB: Changes to is_managed fire OnStoreLoaded().
+}
+
+void CloudPolicyRefreshScheduler::OnConnectionChanged(
+ network::mojom::ConnectionType type) {
+ if (type == network::mojom::ConnectionType::CONNECTION_NONE)
+ return;
+
+ if (client_->status() == DM_STATUS_REQUEST_FAILED) {
+ RefreshSoon();
+ return;
+ }
+
+ // If this is triggered by the device wake-up event, the scheduled refresh
+ // that is in the queue is off because it's based on TimeTicks. Check when the
+ // the next refresh should happen based on system time, and if this provides
+ // shorter delay then re-schedule the next refresh. It has to happen sooner,
+ // according to delay based on system time. If we have no information about
+ // the last refresh based on system time, there's nothing we can do in
+ // applying the above logic.
+ if (last_refresh_.is_null() || !client_->is_registered())
+ return;
+
+ const base::TimeDelta refresh_delay =
+ base::Milliseconds(GetActualRefreshDelay());
+ const base::TimeDelta system_delta = std::max(
+ last_refresh_ + refresh_delay - GetClock()->Now(), base::TimeDelta());
+ const base::TimeDelta ticks_delta =
+ last_refresh_ticks_ + refresh_delay - GetTickClock()->NowTicks();
+ if (ticks_delta > system_delta)
+ RefreshAfter(system_delta.InMilliseconds());
+}
+
+void CloudPolicyRefreshScheduler::UpdateLastRefreshFromPolicy() {
+ if (!last_refresh_.is_null())
+ return;
+
+ // If the client has already fetched policy, assume that happened recently. If
+ // that assumption ever breaks, the proper thing to do probably is to move the
+ // |last_refresh_| bookkeeping to CloudPolicyClient.
+ if (!client_->responses().empty()) {
+ UpdateLastRefresh();
+ return;
+ }
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+ // Refreshing on mobile platforms:
+ // - if no user is signed-in then the |client_| is never registered and
+ // nothing happens here.
+ // - if the user is signed-in but isn't enterprise then the |client_| is
+ // never registered and nothing happens here.
+ // - if the user is signed-in but isn't registered for policy yet then the
+ // |client_| isn't registered either; the UserPolicySigninService will try
+ // to register, and OnRegistrationStateChanged() will be invoked later.
+ // - if the client is signed-in and has policy then its timestamp is used to
+ // determine when to perform the next fetch, which will be once the cached
+ // version is considered "old enough".
+ //
+ // If there is an old policy cache then a fetch will be performed "soon"; if
+ // that fetch fails then a retry is attempted after a delay, with exponential
+ // backoff. If those fetches keep failing then the cached timestamp is *not*
+ // updated, and another fetch (and subsequent retries) will be attempted
+ // again on the next startup.
+ //
+ // But if the cached policy is considered fresh enough then we try to avoid
+ // fetching again on startup; the Android logic differs from the desktop in
+ // this aspect.
+ if (store_->has_policy() && store_->policy()->has_timestamp()) {
+ last_refresh_ = base::Time::FromJavaTime(store_->policy()->timestamp());
+ last_refresh_ticks_ =
+ GetTickClock()->NowTicks() + (last_refresh_ - GetClock()->Now());
+ }
+#else
+ // If there is a cached non-managed response, make sure to only re-query the
+ // server after kUnmanagedRefreshDelayMs. NB: For existing policy, an
+ // immediate refresh is intentional.
+ if (store_->has_policy() && store_->policy()->has_timestamp() &&
+ !store_->is_managed()) {
+ last_refresh_ = base::Time::FromJavaTime(store_->policy()->timestamp());
+ last_refresh_ticks_ =
+ GetTickClock()->NowTicks() + (last_refresh_ - GetClock()->Now());
+ }
+#endif
+}
+
+void CloudPolicyRefreshScheduler::ScheduleRefresh() {
+ // If the client isn't registered, there is nothing to do.
+ if (!client_->is_registered()) {
+ CancelRefresh();
+ return;
+ }
+
+ // Ignore the refresh request if there's a request scheduled for soon.
+ if (is_scheduled_for_soon_) {
+ DCHECK(!refresh_callback_.IsCancelled());
+ return;
+ }
+
+ // If there is a registration, go by the client's status. That will tell us
+ // what the appropriate refresh delay should be.
+ switch (client_->status()) {
+ case DM_STATUS_SUCCESS:
+ if (store_->is_managed())
+ RefreshAfter(GetActualRefreshDelay());
+ else
+ RefreshAfter(kUnmanagedRefreshDelayMs);
+ return;
+ case DM_STATUS_SERVICE_ACTIVATION_PENDING:
+ case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
+ case DM_STATUS_SERVICE_TOO_MANY_REQUESTS:
+ RefreshAfter(GetActualRefreshDelay());
+ return;
+ case DM_STATUS_REQUEST_FAILED:
+ case DM_STATUS_TEMPORARY_UNAVAILABLE:
+ case DM_STATUS_CANNOT_SIGN_REQUEST:
+ RefreshAfter(error_retry_delay_ms_);
+ return;
+ case DM_STATUS_REQUEST_INVALID:
+ case DM_STATUS_HTTP_STATUS_ERROR:
+ case DM_STATUS_RESPONSE_DECODING_ERROR:
+ case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
+ case DM_STATUS_REQUEST_TOO_LARGE:
+ RefreshAfter(kUnmanagedRefreshDelayMs);
+ return;
+ case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
+ case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
+ case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
+ case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
+ case DM_STATUS_SERVICE_MISSING_LICENSES:
+ case DM_STATUS_SERVICE_DEPROVISIONED:
+ case DM_STATUS_SERVICE_DOMAIN_MISMATCH:
+ case DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE:
+ case DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL:
+ case DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED:
+ case DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE:
+ // Need a re-registration, no use in retrying.
+ CancelRefresh();
+ return;
+ case DM_STATUS_SERVICE_ARC_DISABLED:
+ // This doesn't occur during policy refresh, don't change the schedule.
+ return;
+ }
+
+ NOTREACHED() << "Invalid client status " << client_->status();
+ RefreshAfter(kUnmanagedRefreshDelayMs);
+}
+
+void CloudPolicyRefreshScheduler::PerformRefresh() {
+ CancelRefresh();
+
+ if (client_->is_registered()) {
+ // Update |last_refresh_| so another fetch isn't triggered inadvertently.
+ UpdateLastRefresh();
+
+ // The result of this operation will be reported through OnPolicyFetched()
+ // and OnPolicyRefreshed() callbacks. Next refresh will be scheduled in
+ // OnPolicyFetched().
+ service_->RefreshPolicy(
+ base::BindOnce(&CloudPolicyRefreshScheduler::OnPolicyRefreshed,
+ base::Unretained(this)));
+ return;
+ }
+
+ // This should never happen, as the registration change should have been
+ // handled via OnRegistrationStateChanged().
+ NOTREACHED();
+}
+
+void CloudPolicyRefreshScheduler::RefreshAfter(int delta_ms) {
+ const base::TimeDelta delta(base::Milliseconds(delta_ms));
+
+ // Schedule the callback, calculating the delay based on both, system time
+ // and TimeTicks, whatever comes up to become earlier update. This is done to
+ // make sure the refresh is not delayed too much when the system time moved
+ // backward after the last refresh.
+ const base::TimeDelta system_delay =
+ std::max((last_refresh_ + delta) - GetClock()->Now(), base::TimeDelta());
+ const base::TimeDelta time_ticks_delay =
+ std::max((last_refresh_ticks_ + delta) - GetTickClock()->NowTicks(),
+ base::TimeDelta());
+ base::TimeDelta delay = std::min(system_delay, time_ticks_delay);
+
+ // Unless requesting an immediate refresh, add a delay to the scheduled policy
+ // refresh in order to spread out server load.
+ if (!delay.is_zero())
+ delay += base::Milliseconds(refresh_delay_salt_ms_);
+
+ refresh_callback_.Reset(base::BindOnce(
+ &CloudPolicyRefreshScheduler::PerformRefresh, base::Unretained(this)));
+ task_runner_->PostDelayedTask(FROM_HERE, refresh_callback_.callback(), delay);
+}
+
+void CloudPolicyRefreshScheduler::CancelRefresh() {
+ refresh_callback_.Cancel();
+ is_scheduled_for_soon_ = false;
+}
+
+void CloudPolicyRefreshScheduler::UpdateLastRefresh() {
+ last_refresh_ = GetClock()->Now();
+ last_refresh_ticks_ = GetTickClock()->NowTicks();
+}
+
+void CloudPolicyRefreshScheduler::OnPolicyRefreshed(bool success) {
+ // Next policy fetch is scheduled in OnPolicyFetched() callback.
+ VLOG(1) << "Scheduled policy refresh "
+ << (success ? "successful" : "unsuccessful");
+}
+
+// static
+base::ScopedClosureRunner CloudPolicyRefreshScheduler::OverrideClockForTesting(
+ base::Clock* clock_for_testing) {
+ CHECK(!clock_for_testing_);
+ clock_for_testing_ = clock_for_testing;
+ return base::ScopedClosureRunner(
+ base::BindOnce([]() { clock_for_testing_ = nullptr; }));
+}
+
+// static
+base::ScopedClosureRunner
+CloudPolicyRefreshScheduler::OverrideTickClockForTesting(
+ base::TickClock* tick_clock_for_testing) {
+ CHECK(!tick_clock_for_testing_);
+ tick_clock_for_testing_ = tick_clock_for_testing;
+ return base::ScopedClosureRunner(
+ base::BindOnce([]() { tick_clock_for_testing_ = nullptr; }));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h
new file mode 100644
index 00000000000..090ba2acca7
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_REFRESH_SCHEDULER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_REFRESH_SCHEDULER_H_
+
+#include <stdint.h>
+
+#include "base/callback_helpers.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/policy_export.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class CloudPolicyService;
+
+// Observes CloudPolicyClient and CloudPolicyStore to trigger periodic policy
+// fetches and issue retries on error conditions.
+class POLICY_EXPORT CloudPolicyRefreshScheduler
+ : public CloudPolicyClient::Observer,
+ public CloudPolicyStore::Observer,
+ public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ public:
+ // Refresh constants.
+ static const int64_t kDefaultRefreshDelayMs;
+ static const int64_t kUnmanagedRefreshDelayMs;
+ static const int64_t kWithInvalidationsRefreshDelayMs;
+ static const int64_t kInitialErrorRetryDelayMs;
+ static const int64_t kRandomSaltDelayMaxValueMs;
+
+ // Refresh delay bounds.
+ static const int64_t kRefreshDelayMinMs;
+ static const int64_t kRefreshDelayMaxMs;
+
+ // |client|, |store| and |service| pointers must stay valid throughout the
+ // lifetime of CloudPolicyRefreshScheduler.
+ CloudPolicyRefreshScheduler(
+ CloudPolicyClient* client,
+ CloudPolicyStore* store,
+ CloudPolicyService* service,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+ CloudPolicyRefreshScheduler(const CloudPolicyRefreshScheduler&) = delete;
+ CloudPolicyRefreshScheduler& operator=(const CloudPolicyRefreshScheduler&) =
+ delete;
+ ~CloudPolicyRefreshScheduler() override;
+
+ base::Time last_refresh() const { return last_refresh_; }
+
+ // Sets the refresh delay to |refresh_delay| (actual refresh delay may vary
+ // due to min/max clamping, changes to delay due to invalidations, etc).
+ void SetDesiredRefreshDelay(int64_t refresh_delay);
+
+ // Returns the current fixed refresh delay (can vary depending on whether
+ // invalidations are available or not).
+ int64_t GetActualRefreshDelay() const;
+
+ // For testing: get the value randomly assigned to refresh_delay_salt_ms_.
+ int64_t GetSaltDelayForTesting() const { return refresh_delay_salt_ms_; }
+
+ // Schedules a refresh to be performed immediately.
+ void RefreshSoon();
+
+ // The refresh scheduler starts by assuming that invalidations are not
+ // available. This call can be used to signal whether the invalidations
+ // service is available or not, and can be called multiple times.
+ // When the invalidations service is available then the refresh rate is much
+ // lower.
+ void SetInvalidationServiceAvailability(bool is_available);
+
+ // Whether the invalidations service is available and receiving notifications
+ // of policy updates.
+ bool invalidations_available() const { return invalidations_available_; }
+
+ // CloudPolicyClient::Observer:
+ void OnPolicyFetched(CloudPolicyClient* client) override;
+ void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+ void OnClientError(CloudPolicyClient* client) override;
+
+ // CloudPolicyStore::Observer:
+ void OnStoreLoaded(CloudPolicyStore* store) override;
+ void OnStoreError(CloudPolicyStore* store) override;
+
+ // network::NetworkConnectionTracker::NetworkConnectionObserver:
+ // Triggered also when the device wakes up.
+ void OnConnectionChanged(network::mojom::ConnectionType type) override;
+
+ // Overrides clock or tick clock in tests. Returned closure removes the
+ // override when destroyed.
+ static base::ScopedClosureRunner OverrideClockForTesting(
+ base::Clock* clock_for_testing);
+ static base::ScopedClosureRunner OverrideTickClockForTesting(
+ base::TickClock* tick_clock_for_testing);
+
+ private:
+ // Initializes |last_refresh_| to the policy timestamp from |store_| in case
+ // there is policy present that indicates this client is not managed. This
+ // results in policy fetches only to occur after the entire unmanaged refresh
+ // delay expires, even over restarts. For managed clients, we want to trigger
+ // a refresh on every restart.
+ void UpdateLastRefreshFromPolicy();
+
+ // Evaluates when the next refresh is pending and updates the callback to
+ // execute that refresh at the appropriate time.
+ void ScheduleRefresh();
+
+ // Triggers a policy refresh.
+ void PerformRefresh();
+
+ // Schedules a policy refresh to happen no later than |delta_ms| +
+ // |refresh_delay_salt_ms_| msecs after |last_refresh_| or
+ // |last_refresh_ticks_| whichever is sooner.
+ void RefreshAfter(int delta_ms);
+
+ // Cancels the scheduled policy refresh.
+ void CancelRefresh();
+
+ // Sets the |last_refresh_| and |last_refresh_ticks_| to current time.
+ void UpdateLastRefresh();
+
+ // Called when policy was refreshed after refresh request.
+ // It is different than OnPolicyFetched(), which will be called every time
+ // policy was fetched by the |client_|, does not matter where it was
+ // requested.
+ void OnPolicyRefreshed(bool success);
+
+ raw_ptr<CloudPolicyClient> client_;
+ raw_ptr<CloudPolicyStore> store_;
+ raw_ptr<CloudPolicyService> service_;
+
+ // For scheduling delayed tasks.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // For listening for network connection changes.
+ raw_ptr<network::NetworkConnectionTracker> network_connection_tracker_;
+
+ // The delayed refresh callback.
+ base::CancelableOnceClosure refresh_callback_;
+
+ // Whether the refresh is scheduled for soon (using |RefreshSoon| or
+ // |RefreshNow|).
+ bool is_scheduled_for_soon_ = false;
+
+ // The last time a policy fetch was attempted or completed.
+ base::Time last_refresh_;
+
+ // The same |last_refresh_|, but based on TimeTicks. This allows to schedule
+ // the refresh times based on both, system time and TimeTicks, providing a
+ // protection against changes in system time.
+ base::TimeTicks last_refresh_ticks_;
+
+ // Error retry delay in milliseconds.
+ int64_t error_retry_delay_ms_;
+
+ // The refresh delay.
+ int64_t refresh_delay_ms_;
+
+ // A randomly-generated (between 0 and |kRandomSaltDelayMaxValueMs|) delay
+ // added to all non-immediately scheduled refresh requests.
+ const int64_t refresh_delay_salt_ms_;
+
+ // Whether the invalidations service is available and receiving notifications
+ // of policy updates.
+ bool invalidations_available_;
+
+ // Used to measure how long it took for the invalidations service to report
+ // its initial status.
+ base::Time creation_time_;
+
+ base::WeakPtrFactory<CloudPolicyRefreshScheduler> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_REFRESH_SCHEDULER_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler_unittest.cc
new file mode 100644
index 00000000000..a901918dfd9
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_refresh_scheduler_unittest.cc
@@ -0,0 +1,599 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/clock.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_service.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+using testing::Mock;
+using testing::_;
+
+namespace policy {
+
+namespace {
+
+const int64_t kPolicyRefreshRate = 4 * 60 * 60 * 1000;
+
+const int64_t kInitialCacheAgeMinutes = 1;
+
+} // namespace
+
+class CloudPolicyRefreshSchedulerTest : public testing::Test {
+ protected:
+ CloudPolicyRefreshSchedulerTest()
+ : service_(std::make_unique<MockCloudPolicyService>(&client_, &store_)),
+ task_runner_(new base::TestSimpleTaskRunner()),
+ mock_clock_(std::make_unique<base::SimpleTestClock>()) {}
+
+ void SetUp() override {
+ client_.SetDMToken("token");
+
+ // Set up the protobuf timestamp to be one minute in the past. Since the
+ // protobuf field only has millisecond precision, we convert the actual
+ // value back to get a millisecond-clamped time stamp for the checks below.
+ base::Time now = base::Time::NowFromSystemTime();
+ base::TimeDelta initial_age = base::Minutes(kInitialCacheAgeMinutes);
+ policy_data_.set_timestamp((now - initial_age).ToJavaTime());
+ store_.set_policy_data_for_testing(
+ std::make_unique<em::PolicyData>(policy_data_));
+ last_update_ = base::Time::FromJavaTime(store_.policy()->timestamp());
+ last_update_ticks_ = base::TimeTicks::Now() +
+ (last_update_ - base::Time::NowFromSystemTime());
+ }
+
+ CloudPolicyRefreshScheduler* CreateRefreshScheduler() {
+ EXPECT_EQ(0u, task_runner_->NumPendingTasks());
+ CloudPolicyRefreshScheduler* scheduler = new CloudPolicyRefreshScheduler(
+ &client_, &store_, service_.get(), task_runner_,
+ network::TestNetworkConnectionTracker::CreateGetter());
+ // Make sure the NetworkConnectionTracker has been set up.
+ base::RunLoop().RunUntilIdle();
+ scheduler->SetDesiredRefreshDelay(kPolicyRefreshRate);
+ return scheduler;
+ }
+
+ void NotifyConnectionChanged() {
+ network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+ network::mojom::ConnectionType::CONNECTION_WIFI);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ base::ScopedClosureRunner EmulateSleepThroughLastRefreshTime() {
+ // Mock wall clock time, but make sure that it starts from current time.
+ mock_clock_->Advance(base::Time::Now() - base::Time());
+
+ // Simulate a sleep of the device by advancing the wall clock time, but
+ // keeping tick count clock unchanged. Since tick clock is not mocked, some
+ // time will pass and difference between clocks will be smaller than 1 day,
+ // but it should still be larger than the refresh rate (4 hours).
+ mock_clock_->Advance(base::Days(1));
+
+ return CloudPolicyRefreshScheduler::OverrideClockForTesting(
+ mock_clock_.get());
+ }
+
+ base::TimeDelta GetLastDelay() const {
+ if (!task_runner_->HasPendingTask())
+ return base::TimeDelta();
+ return task_runner_->FinalPendingTaskDelay();
+ }
+
+ void CheckTiming(CloudPolicyRefreshScheduler* const scheduler,
+ int64_t expected_delay_ms) const {
+ CheckTimingWithAge(scheduler, base::Milliseconds(expected_delay_ms),
+ base::TimeDelta());
+ }
+
+ // Checks that the latest refresh scheduled used an offset of
+ // |offset_from_last_refresh| from the time of the previous refresh.
+ // |cache_age| is how old the cache was when the refresh was issued.
+ void CheckTimingWithAge(CloudPolicyRefreshScheduler* const scheduler,
+ const base::TimeDelta& offset_from_last_refresh,
+ const base::TimeDelta& cache_age) const {
+ EXPECT_TRUE(task_runner_->HasPendingTask());
+ base::Time now(base::Time::NowFromSystemTime());
+ base::TimeTicks now_ticks(base::TimeTicks::Now());
+ base::TimeDelta offset_since_refresh_plus_salt = offset_from_last_refresh;
+ // The salt is only applied for non-immediate scheduled refreshes.
+ if (!offset_from_last_refresh.is_zero()) {
+ offset_since_refresh_plus_salt +=
+ base::Milliseconds(scheduler->GetSaltDelayForTesting());
+ }
+ // |last_update_| was updated and then a refresh was scheduled at time S,
+ // so |last_update_| is a bit before that.
+ // Now is a bit later, N.
+ // GetLastDelay() + S is the time when the refresh will run, T.
+ // |cache_age| is the age of the cache at time S. It was thus created at
+ // S - cache_age.
+ //
+ // Schematically:
+ //
+ // . S . N . . . . . . . T . . . .
+ // | | |
+ // set "last_refresh_" and then scheduled the next refresh; the cache
+ // was "cache_age" old at this point.
+ // | |
+ // some time elapsed on the test execution since then;
+ // this is the current time, "now"
+ // |
+ // the refresh will execute at this time
+ //
+ // So the exact delay is T - S - |cache_age|, but we don't have S here.
+ //
+ // |last_update_| was a bit before S, so if
+ // elapsed = now - |last_update_| then the delay is more than
+ // |offset_since_refresh_plus_salt| - elapsed.
+ //
+ // The delay is also less than offset_since_refresh_plus_salt, because some
+ // time already elapsed. Additionally, if the cache was already considered
+ // old when the schedule was performed then its age at that time has been
+ // discounted from the delay. So the delay is a bit less than
+ // |offset_since_refresh_plus_salt - cache_age|.
+ // The logic of time based on TimeTicks is added to be on the safe side,
+ // since CloudPolicyRefreshScheduler implementation is based on both, the
+ // system time and the time in TimeTicks.
+ base::TimeDelta system_delta = (now - last_update_);
+ base::TimeDelta ticks_delta = (now_ticks - last_update_ticks_);
+ EXPECT_GE(GetLastDelay(), offset_since_refresh_plus_salt -
+ std::max(system_delta, ticks_delta));
+ EXPECT_LE(GetLastDelay(), offset_since_refresh_plus_salt - cache_age);
+ }
+
+ void CheckInitialRefresh(CloudPolicyRefreshScheduler* const scheduler,
+ bool with_invalidations) const {
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+ // The mobile platforms take the cache age into account for the initial
+ // fetch. Usually the cache age is ignored for the initial refresh, but on
+ // mobile it's used to restrain from refreshing on every startup.
+ base::TimeDelta rate = base::Milliseconds(
+ with_invalidations
+ ? CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs
+ : kPolicyRefreshRate);
+ CheckTimingWithAge(scheduler, rate, base::Minutes(kInitialCacheAgeMinutes));
+#else
+ // Other platforms refresh immediately.
+ EXPECT_EQ(base::TimeDelta(), GetLastDelay());
+#endif
+ }
+
+ void SetLastUpdateToNow() {
+ last_update_ = base::Time::NowFromSystemTime();
+ last_update_ticks_ = base::TimeTicks::Now();
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ MockCloudPolicyClient client_;
+ MockCloudPolicyStore store_;
+ em::PolicyData policy_data_;
+ std::unique_ptr<MockCloudPolicyService> service_;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+
+ // Base time for the refresh that the scheduler should be using.
+ base::Time last_update_;
+ base::TimeTicks last_update_ticks_;
+
+ std::unique_ptr<base::SimpleTestClock> mock_clock_;
+};
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshNoPolicy) {
+ store_.set_policy_data_for_testing(std::make_unique<em::PolicyData>());
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ EXPECT_TRUE(task_runner_->HasPendingTask());
+ EXPECT_EQ(GetLastDelay(), base::TimeDelta());
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshUnmanaged) {
+ policy_data_.set_state(em::PolicyData::UNMANAGED);
+ store_.set_policy_data_for_testing(
+ std::make_unique<em::PolicyData>(policy_data_));
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ CheckTiming(scheduler.get(),
+ CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs);
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshManagedNotYetFetched) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ EXPECT_TRUE(task_runner_->HasPendingTask());
+ CheckInitialRefresh(scheduler.get(), false);
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshManagedAlreadyFetched) {
+ SetLastUpdateToNow();
+ client_.SetPolicy(dm_protocol::kChromeUserPolicyType, std::string(),
+ em::PolicyFetchResponse());
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ CheckTiming(scheduler.get(), kPolicyRefreshRate);
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, Unregistered) {
+ client_.SetDMToken(std::string());
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ client_.NotifyPolicyFetched();
+ client_.NotifyRegistrationStateChanged();
+ client_.NotifyClientError();
+ scheduler->SetDesiredRefreshDelay(12 * 60 * 60 * 1000);
+ store_.NotifyStoreLoaded();
+ store_.NotifyStoreError();
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, RefreshSoon) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ scheduler->RefreshSoon();
+ task_runner_->RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&client_);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, RefreshSoonOverriding) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+
+ // The refresh scheduled for soon overrides the previously scheduled refresh.
+ scheduler->RefreshSoon();
+ CheckTiming(scheduler.get(), 0);
+
+ // The refresh scheduled for soon is not overridden by the change of the
+ // desired refresh delay.
+ const int64_t kNewPolicyRefreshRate = 12 * 60 * 60 * 1000;
+ scheduler->SetDesiredRefreshDelay(kNewPolicyRefreshRate);
+ CheckTiming(scheduler.get(), 0);
+
+ // The refresh scheduled for soon is not overridden by the notification on the
+ // already fetched policy.
+ client_.SetPolicy(dm_protocol::kChromeUserPolicyType, std::string(),
+ em::PolicyFetchResponse());
+ store_.NotifyStoreLoaded();
+ CheckTiming(scheduler.get(), 0);
+
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&client_);
+
+ // The next refresh is scheduled according to the normal rate.
+ client_.NotifyPolicyFetched();
+ CheckTiming(scheduler.get(), kNewPolicyRefreshRate);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsAvailable) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ new CloudPolicyRefreshScheduler(
+ &client_, &store_, service_.get(), task_runner_,
+ network::TestNetworkConnectionTracker::CreateGetter()));
+ scheduler->SetDesiredRefreshDelay(kPolicyRefreshRate);
+
+ // The scheduler has scheduled refreshes at the initial refresh rate.
+ EXPECT_EQ(2u, task_runner_->NumPendingTasks());
+
+ // Signal that invalidations are available.
+ scheduler->SetInvalidationServiceAvailability(true);
+ EXPECT_EQ(CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs,
+ scheduler->GetActualRefreshDelay());
+ EXPECT_EQ(3u, task_runner_->NumPendingTasks());
+
+ CheckInitialRefresh(scheduler.get(), true);
+
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&client_);
+
+ // Complete that fetch.
+ SetLastUpdateToNow();
+ client_.NotifyPolicyFetched();
+
+ // The next refresh has been scheduled using a lower refresh rate.
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+ CheckTiming(scheduler.get(),
+ CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsNotAvailable) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ new CloudPolicyRefreshScheduler(
+ &client_, &store_, service_.get(), task_runner_,
+ network::TestNetworkConnectionTracker::CreateGetter()));
+ scheduler->SetDesiredRefreshDelay(kPolicyRefreshRate);
+
+ // Signal that invalidations are not available. The scheduler will not
+ // schedule refreshes since the available state is not changed.
+ for (int i = 0; i < 10; ++i) {
+ scheduler->SetInvalidationServiceAvailability(false);
+ EXPECT_EQ(2u, task_runner_->NumPendingTasks());
+ }
+
+ // This scheduled the initial refresh.
+ CheckInitialRefresh(scheduler.get(), false);
+ EXPECT_EQ(kPolicyRefreshRate, scheduler->GetActualRefreshDelay());
+
+ // Perform that fetch now.
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&client_);
+
+ // Complete that fetch.
+ SetLastUpdateToNow();
+ client_.NotifyPolicyFetched();
+
+ // The next refresh has been scheduled at the normal rate.
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+ CheckTiming(scheduler.get(), kPolicyRefreshRate);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsOffAndOn) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ new CloudPolicyRefreshScheduler(
+ &client_, &store_, service_.get(), task_runner_,
+ network::TestNetworkConnectionTracker::CreateGetter()));
+ scheduler->SetDesiredRefreshDelay(kPolicyRefreshRate);
+ scheduler->SetInvalidationServiceAvailability(true);
+ // Initial fetch.
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&client_);
+ SetLastUpdateToNow();
+ client_.NotifyPolicyFetched();
+
+ // The next refresh has been scheduled using a lower refresh rate.
+ CheckTiming(scheduler.get(),
+ CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs);
+
+ // If the service goes down and comes back up before the timeout then a
+ // refresh is rescheduled at the lower rate again; after executing all
+ // pending tasks only 1 fetch is performed.
+ scheduler->SetInvalidationServiceAvailability(false);
+ scheduler->SetInvalidationServiceAvailability(true);
+ // The next refresh has been scheduled using a lower refresh rate.
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ CheckTiming(scheduler.get(),
+ CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs);
+ task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&client_);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsDisconnected) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ new CloudPolicyRefreshScheduler(
+ &client_, &store_, service_.get(), task_runner_,
+ network::TestNetworkConnectionTracker::CreateGetter()));
+ scheduler->SetDesiredRefreshDelay(kPolicyRefreshRate);
+ scheduler->SetInvalidationServiceAvailability(true);
+ // Initial fetch.
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&client_);
+ SetLastUpdateToNow();
+ client_.NotifyPolicyFetched();
+
+ // The next refresh has been scheduled using a lower refresh rate.
+ // Flush that task.
+ CheckTiming(scheduler.get(),
+ CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs);
+ EXPECT_CALL(*service_.get(), RefreshPolicy(_)).Times(1);
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&client_);
+
+ // If the service goes down then the refresh scheduler falls back on the
+ // default polling rate.
+ scheduler->SetInvalidationServiceAvailability(false);
+ CheckTiming(scheduler.get(), kPolicyRefreshRate);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerTest, OnConnectionChangedUnregistered) {
+ client_.SetDMToken(std::string());
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+
+ client_.NotifyClientError();
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+
+ auto closure = EmulateSleepThroughLastRefreshTime();
+ scheduler->OnConnectionChanged(
+ network::mojom::ConnectionType::CONNECTION_WIFI);
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+// TODO(igorcov): Before sleep in normal flow there's a task pending. When the
+// device wakes up, OnConnectionChanged is called which should cancel the
+// pending task and queue a new task to run earlier. It is desirable to
+// simulate that flow here.
+TEST_F(CloudPolicyRefreshSchedulerTest, OnConnectionChangedAfterSleep) {
+ std::unique_ptr<CloudPolicyRefreshScheduler> scheduler(
+ CreateRefreshScheduler());
+
+ client_.SetPolicy(dm_protocol::kChromeUserPolicyType, std::string(),
+ em::PolicyFetchResponse());
+ task_runner_->RunPendingTasks();
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+
+ auto closure = EmulateSleepThroughLastRefreshTime();
+ scheduler->OnConnectionChanged(
+ network::mojom::ConnectionType::CONNECTION_WIFI);
+ EXPECT_TRUE(task_runner_->HasPendingTask());
+ task_runner_->ClearPendingTasks();
+}
+
+class CloudPolicyRefreshSchedulerSteadyStateTest
+ : public CloudPolicyRefreshSchedulerTest {
+ protected:
+ CloudPolicyRefreshSchedulerSteadyStateTest() = default;
+
+ void SetUp() override {
+ refresh_scheduler_.reset(CreateRefreshScheduler());
+ refresh_scheduler_->SetDesiredRefreshDelay(kPolicyRefreshRate);
+ CloudPolicyRefreshSchedulerTest::SetUp();
+ SetLastUpdateToNow();
+ client_.NotifyPolicyFetched();
+ CheckTiming(refresh_scheduler_.get(), kPolicyRefreshRate);
+ }
+
+ std::unique_ptr<CloudPolicyRefreshScheduler> refresh_scheduler_;
+};
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnPolicyFetched) {
+ client_.NotifyPolicyFetched();
+ CheckTiming(refresh_scheduler_.get(), kPolicyRefreshRate);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnRegistrationStateChanged) {
+ client_.SetDMToken("new_token");
+ client_.NotifyRegistrationStateChanged();
+ EXPECT_EQ(GetLastDelay(), base::TimeDelta());
+
+ task_runner_->ClearPendingTasks();
+ client_.SetDMToken(std::string());
+ client_.NotifyRegistrationStateChanged();
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnStoreLoaded) {
+ store_.NotifyStoreLoaded();
+ CheckTiming(refresh_scheduler_.get(), kPolicyRefreshRate);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnStoreError) {
+ task_runner_->ClearPendingTasks();
+ store_.NotifyStoreError();
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, RefreshDelayChange) {
+ const int delay_short_ms = 5 * 60 * 1000;
+ refresh_scheduler_->SetDesiredRefreshDelay(delay_short_ms);
+ CheckTiming(refresh_scheduler_.get(),
+ CloudPolicyRefreshScheduler::kRefreshDelayMinMs);
+
+ const int delay_ms = 12 * 60 * 60 * 1000;
+ refresh_scheduler_->SetDesiredRefreshDelay(delay_ms);
+ CheckTiming(refresh_scheduler_.get(), delay_ms);
+
+ const int delay_long_ms = 20 * 24 * 60 * 60 * 1000;
+ refresh_scheduler_->SetDesiredRefreshDelay(delay_long_ms);
+ CheckTiming(refresh_scheduler_.get(),
+ CloudPolicyRefreshScheduler::kRefreshDelayMaxMs);
+}
+
+TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnConnectionChanged) {
+ client_.SetStatus(DM_STATUS_REQUEST_FAILED);
+ NotifyConnectionChanged();
+ EXPECT_EQ(GetLastDelay(), base::TimeDelta());
+}
+
+struct ClientErrorTestParam {
+ DeviceManagementStatus client_error;
+ int64_t expected_delay_ms;
+ int backoff_factor;
+};
+
+static const ClientErrorTestParam kClientErrorTestCases[] = {
+ {DM_STATUS_REQUEST_INVALID,
+ CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1},
+ {DM_STATUS_REQUEST_FAILED,
+ CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs, 2},
+ {DM_STATUS_TEMPORARY_UNAVAILABLE,
+ CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs, 2},
+ {DM_STATUS_HTTP_STATUS_ERROR,
+ CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1},
+ {DM_STATUS_RESPONSE_DECODING_ERROR,
+ CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1},
+ {DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED,
+ CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1},
+ {DM_STATUS_SERVICE_DEVICE_NOT_FOUND, -1, 1},
+ {DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID, -1, 1},
+ {DM_STATUS_SERVICE_ACTIVATION_PENDING, kPolicyRefreshRate, 1},
+ {DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER, -1, 1},
+ {DM_STATUS_SERVICE_MISSING_LICENSES, -1, 1},
+ {DM_STATUS_SERVICE_DEVICE_ID_CONFLICT, -1, 1},
+ {DM_STATUS_SERVICE_POLICY_NOT_FOUND, kPolicyRefreshRate, 1},
+ {DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE, -1, 1},
+ {DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL, -1, 1},
+ {DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED, -1, 1},
+ {DM_STATUS_SERVICE_TOO_MANY_REQUESTS, kPolicyRefreshRate, 1},
+ {DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE, -1, 1},
+};
+
+class CloudPolicyRefreshSchedulerClientErrorTest
+ : public CloudPolicyRefreshSchedulerSteadyStateTest,
+ public testing::WithParamInterface<ClientErrorTestParam> {};
+
+TEST_P(CloudPolicyRefreshSchedulerClientErrorTest, OnClientError) {
+ client_.SetStatus(GetParam().client_error);
+ task_runner_->ClearPendingTasks();
+
+ // See whether the error triggers the right refresh delay.
+ int64_t expected_delay_ms = GetParam().expected_delay_ms;
+ client_.NotifyClientError();
+ if (expected_delay_ms >= 0) {
+ CheckTiming(refresh_scheduler_.get(), expected_delay_ms);
+
+ // Check whether exponential backoff is working as expected and capped at
+ // the regular refresh rate (if applicable).
+ do {
+ expected_delay_ms *= GetParam().backoff_factor;
+ SetLastUpdateToNow();
+ client_.NotifyClientError();
+ CheckTiming(refresh_scheduler_.get(),
+ std::max(std::min(expected_delay_ms, kPolicyRefreshRate),
+ GetParam().expected_delay_ms));
+ } while (GetParam().backoff_factor > 1 &&
+ expected_delay_ms <= kPolicyRefreshRate);
+ } else {
+ EXPECT_EQ(base::TimeDelta(), GetLastDelay());
+ EXPECT_FALSE(task_runner_->HasPendingTask());
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(CloudPolicyRefreshSchedulerClientErrorTest,
+ CloudPolicyRefreshSchedulerClientErrorTest,
+ testing::ValuesIn(kClientErrorTestCases));
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_service.cc b/chromium/components/policy/core/common/cloud/cloud_policy_service.cc
new file mode 100644
index 00000000000..f8bd0d295fb
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_service.cc
@@ -0,0 +1,281 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+
+#include <stddef.h>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+CloudPolicyService::CloudPolicyService(const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyClient* client,
+ CloudPolicyStore* store)
+ : policy_type_(policy_type),
+ settings_entity_id_(settings_entity_id),
+ client_(client),
+ store_(store),
+ refresh_state_(REFRESH_NONE),
+ unregister_state_(UNREGISTER_NONE),
+ initialization_complete_(false) {
+ client_->AddPolicyTypeToFetch(policy_type_, settings_entity_id_);
+ client_->AddObserver(this);
+ store_->AddObserver(this);
+
+ // Make sure we initialize |client_| from the policy data that might be
+ // already present in |store_|.
+ OnStoreLoaded(store_);
+}
+
+CloudPolicyService::~CloudPolicyService() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ client_->RemovePolicyTypeToFetch(policy_type_, settings_entity_id_);
+ client_->RemoveObserver(this);
+ store_->RemoveObserver(this);
+}
+
+void CloudPolicyService::RefreshPolicy(RefreshPolicyCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If the client is not registered or is unregistering, bail out.
+ if (!client_->is_registered() || unregister_state_ != UNREGISTER_NONE) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ // Else, trigger a refresh.
+ refresh_callbacks_.push_back(std::move(callback));
+ refresh_state_ = REFRESH_POLICY_FETCH;
+ client_->FetchPolicy();
+}
+
+void CloudPolicyService::Unregister(UnregisterCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Abort all pending refresh requests.
+ if (refresh_state_ != REFRESH_NONE)
+ RefreshCompleted(false);
+
+ // Abort previous unregister request if any.
+ if (unregister_state_ != UNREGISTER_NONE)
+ UnregisterCompleted(false);
+
+ unregister_callback_ = std::move(callback);
+ unregister_state_ = UNREGISTER_PENDING;
+ client_->Unregister();
+}
+
+void CloudPolicyService::OnPolicyFetched(CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (client_->status() != DM_STATUS_SUCCESS) {
+ RefreshCompleted(false);
+ return;
+ }
+
+ const em::PolicyFetchResponse* policy =
+ client_->GetPolicyFor(policy_type_, settings_entity_id_);
+ if (policy) {
+ if (refresh_state_ != REFRESH_NONE)
+ refresh_state_ = REFRESH_POLICY_STORE;
+ policy_pending_validation_signature_ = policy->policy_data_signature();
+ store_->Store(*policy, client->fetched_invalidation_version());
+ } else {
+ RefreshCompleted(false);
+ }
+}
+
+void CloudPolicyService::OnRegistrationStateChanged(CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (unregister_state_ == UNREGISTER_PENDING) {
+ DCHECK(!client->is_registered());
+ UnregisterCompleted(true);
+ }
+}
+
+void CloudPolicyService::OnClientError(CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (refresh_state_ == REFRESH_POLICY_FETCH)
+ RefreshCompleted(false);
+ if (unregister_state_ == UNREGISTER_PENDING)
+ UnregisterCompleted(false);
+}
+
+void CloudPolicyService::OnStoreLoaded(CloudPolicyStore* store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Update the client with state from the store.
+ const em::PolicyData* policy(store_->policy());
+
+ // Timestamp.
+ base::Time policy_timestamp;
+ if (policy && policy->has_timestamp())
+ policy_timestamp = base::Time::FromJavaTime(policy->timestamp());
+
+ const base::Time& old_timestamp = client_->last_policy_timestamp();
+ if (!policy_timestamp.is_null() && !old_timestamp.is_null() &&
+ policy_timestamp != old_timestamp) {
+ const base::TimeDelta age = policy_timestamp - old_timestamp;
+ if (policy_type_ == dm_protocol::kChromeUserPolicyType) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Enterprise.PolicyUpdatePeriod.User",
+ age.InDays(), 1, 1000, 100);
+ } else if (policy_type_ == dm_protocol::kChromeDevicePolicyType) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Enterprise.PolicyUpdatePeriod.Device",
+ age.InDays(), 1, 1000, 100);
+ } else if (IsMachineLevelUserCloudPolicyType(policy_type_)) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Enterprise.PolicyUpdatePeriod.MachineLevelUser", age.InDays(), 1,
+ 1000, 100);
+ }
+ }
+ client_->set_last_policy_timestamp(policy_timestamp);
+
+ // Public key version.
+ if (policy && policy->has_public_key_version())
+ client_->set_public_key_version(policy->public_key_version());
+ else
+ client_->clear_public_key_version();
+
+ // Finally, set up registration if necessary.
+ if (policy && policy->has_request_token() && policy->has_device_id() &&
+ !client_->is_registered()) {
+ DVLOG(1) << "Setting up registration with request token: "
+ << policy->request_token();
+ std::vector<std::string> user_affiliation_ids(
+ policy->user_affiliation_ids().begin(),
+ policy->user_affiliation_ids().end());
+ client_->SetupRegistration(policy->request_token(), policy->device_id(),
+ user_affiliation_ids);
+ }
+
+ if (refresh_state_ == REFRESH_POLICY_STORE)
+ RefreshCompleted(true);
+
+ CheckInitializationCompleted();
+
+ ReportValidationResult(store);
+}
+
+void CloudPolicyService::OnStoreError(CloudPolicyStore* store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (refresh_state_ == REFRESH_POLICY_STORE)
+ RefreshCompleted(false);
+ CheckInitializationCompleted();
+ ReportValidationResult(store);
+}
+
+void CloudPolicyService::ReportValidationResult(CloudPolicyStore* store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ const CloudPolicyValidatorBase::ValidationResult* validation_result =
+ store->validation_result();
+ if (!validation_result)
+ return;
+
+ if (policy_pending_validation_signature_.empty() ||
+ policy_pending_validation_signature_ !=
+ validation_result->policy_data_signature) {
+ return;
+ }
+ policy_pending_validation_signature_.clear();
+
+ if (validation_result->policy_token.empty())
+ return;
+
+ if (validation_result->status ==
+ CloudPolicyValidatorBase::Status::VALIDATION_OK &&
+ validation_result->value_validation_issues.empty()) {
+ return;
+ }
+
+ // TODO(hendrich,pmarko): https://crbug.com/794848
+ // Update the status to reflect value validation errors/warnings. For now we
+ // don't want to reject policies on value validation errors, therefore the
+ // validation result will be |VALIDATION_OK| even though we might have value
+ // validation errors/warnings.
+ // Also update UploadPolicyValidationReport to only receive |policy_type_| and
+ // |validation_result|.
+ CloudPolicyValidatorBase::Status status = validation_result->status;
+ if (status == CloudPolicyValidatorBase::Status::VALIDATION_OK) {
+ status = CloudPolicyValidatorBase::Status::VALIDATION_VALUE_WARNING;
+ for (const ValueValidationIssue& issue :
+ validation_result->value_validation_issues) {
+ if (issue.severity == ValueValidationIssue::Severity::kError) {
+ status = CloudPolicyValidatorBase::Status::VALIDATION_VALUE_ERROR;
+ break;
+ }
+ }
+ }
+
+ client_->UploadPolicyValidationReport(
+ status, validation_result->value_validation_issues, policy_type_,
+ validation_result->policy_token);
+}
+
+void CloudPolicyService::CheckInitializationCompleted() {
+ if (!IsInitializationComplete() && store_->is_initialized()) {
+ initialization_complete_ = true;
+ for (auto& observer : observers_)
+ observer.OnCloudPolicyServiceInitializationCompleted();
+ }
+}
+
+void CloudPolicyService::RefreshCompleted(bool success) {
+ if (!initial_policy_refresh_result_.has_value())
+ initial_policy_refresh_result_ = success;
+
+ // If there was an error while fetching the policies the first time, assume
+ // that there are no policies until the next retry.
+ if (!success)
+ store_->SetFirstPoliciesLoaded(true);
+
+ // Clear state and |refresh_callbacks_| before actually invoking them, s.t.
+ // triggering new policy fetches behaves as expected.
+ std::vector<RefreshPolicyCallback> callbacks;
+ callbacks.swap(refresh_callbacks_);
+ refresh_state_ = REFRESH_NONE;
+
+ for (auto& callback : callbacks)
+ std::move(callback).Run(success);
+
+ for (auto& observer : observers_)
+ observer.OnPolicyRefreshed(success);
+}
+
+void CloudPolicyService::UnregisterCompleted(bool success) {
+ if (!success)
+ LOG(ERROR) << "Unregister request failed.";
+
+ unregister_state_ = UNREGISTER_NONE;
+ std::move(unregister_callback_).Run(success);
+}
+
+void CloudPolicyService::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.AddObserver(observer);
+}
+
+void CloudPolicyService::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_service.h b/chromium/components/policy/core/common/cloud/cloud_policy_service.h
new file mode 100644
index 00000000000..42d1c3e850e
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_service.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+// Coordinates cloud policy handling, moving downloaded policy from the client
+// to the store, and setting up client registrations from cached data in the
+// store. Also coordinates actions on policy refresh triggers.
+class POLICY_EXPORT CloudPolicyService : public CloudPolicyClient::Observer,
+ public CloudPolicyStore::Observer {
+ public:
+ // Callback invoked once the policy refresh attempt has completed. Passed
+ // bool parameter is true if the refresh was successful (no error).
+ using RefreshPolicyCallback = base::OnceCallback<void(bool)>;
+
+ // Callback invoked once the unregister attempt has completed. Passed bool
+ // parameter is true if unregistering was successful (no error).
+ using UnregisterCallback = base::OnceCallback<void(bool)>;
+
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked when CloudPolicyService has finished initializing (any initial
+ // policy load activity has completed and the CloudPolicyClient has
+ // been registered, if possible).
+ virtual void OnCloudPolicyServiceInitializationCompleted() = 0;
+
+ // Called when policy refresh finshed. |success| indicates whether refresh
+ // was successful.
+ virtual void OnPolicyRefreshed(bool success) {}
+
+ virtual ~Observer() {}
+ };
+
+ // |client| and |store| must remain valid for the object life time.
+ CloudPolicyService(const std::string& policy_type,
+ const std::string& settings_entity_id,
+ CloudPolicyClient* client,
+ CloudPolicyStore* store);
+ CloudPolicyService(const CloudPolicyService&) = delete;
+ CloudPolicyService& operator=(const CloudPolicyService&) = delete;
+ ~CloudPolicyService() override;
+
+ // Refreshes policy. |callback| will be invoked after the operation completes
+ // or aborts because of errors.
+ virtual void RefreshPolicy(RefreshPolicyCallback callback);
+
+ // Unregisters the device. |callback| will be invoked after the operation
+ // completes or aborts because of errors. All pending refresh policy requests
+ // will be aborted, and no further refresh policy requests will be allowed.
+ void Unregister(UnregisterCallback callback);
+
+ // Adds/Removes an Observer for this object.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // CloudPolicyClient::Observer:
+ void OnPolicyFetched(CloudPolicyClient* client) override;
+ void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+ void OnClientError(CloudPolicyClient* client) override;
+
+ // CloudPolicyStore::Observer:
+ void OnStoreLoaded(CloudPolicyStore* store) override;
+ void OnStoreError(CloudPolicyStore* store) override;
+
+ void ReportValidationResult(CloudPolicyStore* store);
+
+ bool IsInitializationComplete() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return initialization_complete_;
+ }
+
+ // If initial policy refresh was completed returns its result.
+ // This allows ChildPolicyObserver to know whether policy was fetched before
+ // profile creation.
+ absl::optional<bool> initial_policy_refresh_result() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return initial_policy_refresh_result_;
+ }
+
+ private:
+ // Helper function that is called when initialization may be complete, and
+ // which is responsible for notifying observers.
+ void CheckInitializationCompleted();
+
+ // Invokes the refresh callbacks and clears refresh state. The |success| flag
+ // is passed through to the refresh callbacks.
+ void RefreshCompleted(bool success);
+
+ // Invokes the unregister callback and clears unregister state. The |success|
+ // flag is passed through to the unregister callback.
+ void UnregisterCompleted(bool success);
+
+ // Assert non-concurrent usage in debug builds.
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // The policy type that will be fetched by the |client_|, with the optional
+ // |settings_entity_id_|.
+ std::string policy_type_;
+ std::string settings_entity_id_;
+
+ // The client used to talk to the cloud.
+ raw_ptr<CloudPolicyClient> client_;
+
+ // Takes care of persisting and decoding cloud policy.
+ raw_ptr<CloudPolicyStore> store_;
+
+ // Tracks the state of a pending refresh operation, if any.
+ enum {
+ // No refresh pending.
+ REFRESH_NONE,
+ // Policy fetch is pending.
+ REFRESH_POLICY_FETCH,
+ // Policy store is pending.
+ REFRESH_POLICY_STORE,
+ } refresh_state_;
+
+ // Tracks the state of a pending unregister operation, if any.
+ enum {
+ UNREGISTER_NONE,
+ UNREGISTER_PENDING,
+ } unregister_state_;
+
+ // Callbacks to invoke upon policy refresh.
+ std::vector<RefreshPolicyCallback> refresh_callbacks_;
+
+ UnregisterCallback unregister_callback_;
+
+ // Set to true once the service is initialized (initial policy load/refresh
+ // is complete).
+ bool initialization_complete_;
+
+ // Set to true if initial policy refresh was successful. Set to false
+ // otherwise.
+ absl::optional<bool> initial_policy_refresh_result_;
+
+ // Observers who will receive notifications when the service has finished
+ // initializing.
+ base::ObserverList<Observer, true>::Unchecked observers_;
+
+ // Identifier from the stored policy. Policy validations results are only
+ // reported once if the validated policy's data signature matches with this
+ // one. Will be cleared once we send the validation report.
+ std::string policy_pending_validation_signature_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_service_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_service_unittest.cc
new file mode 100644
index 00000000000..6b38620c665
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_service_unittest.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_service.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+using testing::_;
+
+namespace policy {
+
+class CloudPolicyServiceTest : public testing::Test {
+ public:
+ CloudPolicyServiceTest()
+ : policy_type_(dm_protocol::kChromeUserPolicyType),
+ service_(policy_type_, std::string(), &client_, &store_) {}
+
+ MOCK_METHOD1(OnPolicyRefresh, void(bool));
+ MOCK_METHOD1(OnUnregister, void(bool));
+
+ protected:
+ std::string policy_type_;
+ MockCloudPolicyClient client_;
+ MockCloudPolicyStore store_;
+ CloudPolicyService service_;
+};
+
+MATCHER_P(ProtoMatches, proto, std::string()) {
+ return arg.SerializePartialAsString() == proto.SerializePartialAsString();
+}
+
+TEST_F(CloudPolicyServiceTest, PolicyUpdateSuccess) {
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data("fake policy");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ client_.NotifyPolicyFetched();
+
+ // After |store_| initializes, credentials and other meta data should be
+ // transferred to |client_|.
+ auto policy_data = std::make_unique<em::PolicyData>();
+ policy_data->set_request_token("fake token");
+ policy_data->set_device_id("fake client id");
+ policy_data->set_timestamp(32);
+ policy_data->set_public_key_version(17);
+ policy_data->add_user_affiliation_ids("id1");
+ store_.set_policy_data_for_testing(std::move(policy_data));
+ std::vector<std::string> user_affiliation_ids = {
+ store_.policy()->user_affiliation_ids(0)};
+ EXPECT_CALL(client_, SetupRegistration(store_.policy()->request_token(),
+ store_.policy()->device_id(),
+ user_affiliation_ids))
+ .Times(1);
+ EXPECT_CALL(client_, UploadPolicyValidationReport(_, _, _, _)).Times(0);
+ store_.NotifyStoreLoaded();
+ EXPECT_EQ(base::Time::FromJavaTime(32), client_.last_policy_timestamp_);
+ EXPECT_TRUE(client_.public_key_version_valid_);
+ EXPECT_EQ(17, client_.public_key_version_);
+}
+
+TEST_F(CloudPolicyServiceTest, PolicyUpdateClientFailure) {
+ client_.SetStatus(DM_STATUS_REQUEST_FAILED);
+ EXPECT_CALL(store_, Store(_)).Times(0);
+ client_.NotifyPolicyFetched();
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicySuccess) {
+ testing::InSequence seq;
+
+ EXPECT_CALL(*this, OnPolicyRefresh(_)).Times(0);
+ client_.SetDMToken("fake token");
+
+ // Trigger a fetch on the client.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // Client responds, push policy to store.
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data("fake policy");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ client_.fetched_invalidation_version_ = 12345;
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ EXPECT_EQ(0, store_.invalidation_version());
+ client_.NotifyPolicyFetched();
+ EXPECT_EQ(12345, store_.invalidation_version());
+
+ // Store reloads policy, callback gets triggered.
+ auto policy_data = std::make_unique<em::PolicyData>();
+ policy_data->set_request_token("token");
+ policy_data->set_device_id("device-id");
+ store_.set_policy_data_for_testing(std::move(policy_data));
+ EXPECT_CALL(*this, OnPolicyRefresh(true)).Times(1);
+ store_.NotifyStoreLoaded();
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicyNotRegistered) {
+ // Clear the token so the client is not registered.
+ client_.SetDMToken(std::string());
+
+ EXPECT_CALL(client_, FetchPolicy()).Times(0);
+ EXPECT_CALL(*this, OnPolicyRefresh(false)).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicyClientError) {
+ testing::InSequence seq;
+
+ EXPECT_CALL(*this, OnPolicyRefresh(_)).Times(0);
+ client_.SetDMToken("fake token");
+
+ // Trigger a fetch on the client.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // Client responds with an error, which should trigger the callback.
+ client_.SetStatus(DM_STATUS_REQUEST_FAILED);
+ EXPECT_CALL(*this, OnPolicyRefresh(false)).Times(1);
+ client_.NotifyClientError();
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicyStoreError) {
+ testing::InSequence seq;
+
+ EXPECT_CALL(*this, OnPolicyRefresh(_)).Times(0);
+ client_.SetDMToken("fake token");
+
+ // Trigger a fetch on the client.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // Client responds, push policy to store.
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data("fake policy");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ client_.NotifyPolicyFetched();
+
+ // Store fails, which should trigger the callback.
+ EXPECT_CALL(*this, OnPolicyRefresh(false)).Times(1);
+ store_.NotifyStoreError();
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicyConcurrent) {
+ testing::InSequence seq;
+
+ EXPECT_CALL(*this, OnPolicyRefresh(_)).Times(0);
+ client_.SetDMToken("fake token");
+
+ // Trigger a fetch on the client.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // Triggering another policy refresh should generate a new fetch request.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // Client responds, push policy to store.
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data("fake policy");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ client_.NotifyPolicyFetched();
+
+ // Trigger another policy fetch.
+ EXPECT_CALL(client_, FetchPolicy()).Times(1);
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+
+ // The store finishing the first load should not generate callbacks.
+ EXPECT_CALL(*this, OnPolicyRefresh(_)).Times(0);
+ store_.NotifyStoreLoaded();
+
+ // Second policy fetch finishes.
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ client_.NotifyPolicyFetched();
+
+ // Corresponding store operation finishes, all _three_ callbacks fire.
+ EXPECT_CALL(*this, OnPolicyRefresh(true)).Times(3);
+ store_.NotifyStoreLoaded();
+}
+
+TEST_F(CloudPolicyServiceTest, UnregisterSucceeds) {
+ EXPECT_CALL(client_, Unregister());
+ EXPECT_CALL(*this, OnUnregister(true));
+
+ service_.Unregister(base::BindOnce(&CloudPolicyServiceTest::OnUnregister,
+ base::Unretained(this)));
+ client_.NotifyRegistrationStateChanged();
+}
+
+TEST_F(CloudPolicyServiceTest, UnregisterFailsOnClientError) {
+ EXPECT_CALL(client_, Unregister());
+ EXPECT_CALL(*this, OnUnregister(false));
+
+ service_.Unregister(base::BindOnce(&CloudPolicyServiceTest::OnUnregister,
+ base::Unretained(this)));
+ client_.NotifyClientError();
+}
+
+TEST_F(CloudPolicyServiceTest, UnregisterRevokesAllOnGoingPolicyRefreshes) {
+ EXPECT_CALL(client_, Unregister());
+ EXPECT_CALL(*this, OnPolicyRefresh(false)).Times(2);
+
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+ service_.Unregister(base::BindOnce(&CloudPolicyServiceTest::OnUnregister,
+ base::Unretained(this)));
+}
+
+TEST_F(CloudPolicyServiceTest, RefreshPolicyFailsWhenUnregistering) {
+ EXPECT_CALL(client_, Unregister());
+ EXPECT_CALL(*this, OnPolicyRefresh(false));
+
+ service_.Unregister(base::BindOnce(&CloudPolicyServiceTest::OnUnregister,
+ base::Unretained(this)));
+ service_.RefreshPolicy(base::BindOnce(
+ &CloudPolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+}
+
+TEST_F(CloudPolicyServiceTest, StoreAlreadyInitialized) {
+ // Service should start off initialized if the store has already loaded
+ // policy.
+ store_.NotifyStoreLoaded();
+ CloudPolicyService service(policy_type_, std::string(), &client_, &store_);
+ EXPECT_TRUE(service.IsInitializationComplete());
+}
+
+TEST_F(CloudPolicyServiceTest, StoreLoadAfterCreation) {
+ // Service should start off un-initialized if the store has not yet loaded
+ // policy.
+ EXPECT_FALSE(service_.IsInitializationComplete());
+ MockCloudPolicyServiceObserver observer;
+ service_.AddObserver(&observer);
+ // Service should be marked as initialized and observer should be called back.
+ EXPECT_CALL(observer, OnCloudPolicyServiceInitializationCompleted()).Times(1);
+ store_.NotifyStoreLoaded();
+ EXPECT_TRUE(service_.IsInitializationComplete());
+ testing::Mock::VerifyAndClearExpectations(&observer);
+
+ // Now, the next time the store is loaded, the observer should not be called
+ // again.
+ EXPECT_CALL(observer, OnCloudPolicyServiceInitializationCompleted()).Times(0);
+ store_.NotifyStoreLoaded();
+ service_.RemoveObserver(&observer);
+}
+
+TEST_F(CloudPolicyServiceTest, ReportValidationResult) {
+ // Sync |policy_data_signature| between store and service by fetching a
+ // policy.
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data_signature("fake-policy-data-signature");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ EXPECT_CALL(client_, UploadPolicyValidationReport(_, _, _, _)).Times(0);
+ client_.NotifyPolicyFetched();
+
+ // Simulate a value validation error from the store and expect a validation
+ // report to be uploaded.
+ store_.validation_result_ =
+ std::make_unique<CloudPolicyValidatorBase::ValidationResult>();
+ store_.validation_result_->status =
+ CloudPolicyValidatorBase::VALIDATION_VALUE_ERROR;
+ store_.validation_result_->value_validation_issues.push_back(
+ {"fake-policy-name", ValueValidationIssue::kError, "message"});
+ store_.validation_result_->policy_token = "fake-policy-token";
+ store_.validation_result_->policy_data_signature =
+ "fake-policy-data-signature";
+ EXPECT_CALL(client_,
+ UploadPolicyValidationReport(
+ store_.validation_result_->status,
+ store_.validation_result_->value_validation_issues,
+ policy_type_, store_.validation_result_->policy_token))
+ .Times(1);
+ store_.NotifyStoreError();
+
+ // A second validation of the same policy should not trigger another upload.
+ EXPECT_CALL(client_, UploadPolicyValidationReport(_, _, _, _)).Times(0);
+ store_.NotifyStoreError();
+
+ testing::Mock::VerifyAndClearExpectations(&client_);
+}
+
+TEST_F(CloudPolicyServiceTest, ReportValidationResultWrongSignature) {
+ // Sync |policy_data_signature| between store and service by fetching a
+ // policy.
+ em::PolicyFetchResponse policy;
+ policy.set_policy_data_signature("fake-policy-data-signature-1");
+ client_.SetPolicy(policy_type_, std::string(), policy);
+ EXPECT_CALL(store_, Store(ProtoMatches(policy))).Times(1);
+ EXPECT_CALL(client_, UploadPolicyValidationReport(_, _, _, _)).Times(0);
+ client_.NotifyPolicyFetched();
+
+ // Simulate a value validation error from the store with a different policy
+ // data signature. No Validation report should be uploaded in that case.
+ store_.validation_result_ =
+ std::make_unique<CloudPolicyValidatorBase::ValidationResult>();
+ store_.validation_result_->status =
+ CloudPolicyValidatorBase::VALIDATION_VALUE_ERROR;
+ store_.validation_result_->value_validation_issues.push_back(
+ {"fake-policy-name", ValueValidationIssue::kError, "message"});
+ store_.validation_result_->policy_token = "fake-policy-token";
+ store_.validation_result_->policy_data_signature =
+ "fake-policy-data-signature-2";
+ EXPECT_CALL(client_, UploadPolicyValidationReport(_, _, _, _)).Times(0);
+ store_.NotifyStoreError();
+
+ testing::Mock::VerifyAndClearExpectations(&client_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/cloud_policy_store.cc
new file mode 100644
index 00000000000..bf3bbf9e015
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_store.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+
+#include "base/check.h"
+#include "base/observer_list.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+CloudPolicyStore::Observer::~Observer() = default;
+void CloudPolicyStore::Observer::OnStoreDestruction(CloudPolicyStore* store) {}
+
+CloudPolicyStore::CloudPolicyStore() = default;
+
+CloudPolicyStore::~CloudPolicyStore() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!external_data_manager_);
+ NotifyStoreDestruction();
+}
+
+bool CloudPolicyStore::is_managed() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ return policy_.get() &&
+ policy_->state() == enterprise_management::PolicyData::ACTIVE;
+}
+
+void CloudPolicyStore::Store(
+ const enterprise_management::PolicyFetchResponse& policy,
+ int64_t invalidation_version) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ invalidation_version_ = invalidation_version;
+ Store(policy);
+}
+
+void CloudPolicyStore::AddObserver(CloudPolicyStore::Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.AddObserver(observer);
+}
+
+void CloudPolicyStore::RemoveObserver(CloudPolicyStore::Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ observers_.RemoveObserver(observer);
+}
+
+void CloudPolicyStore::NotifyStoreLoaded() {
+ is_initialized_ = true;
+ UpdateFirstPoliciesLoaded();
+
+ // The |external_data_manager_| must be notified first so that when other
+ // observers are informed about the changed policies and try to fetch external
+ // data referenced by these, the |external_data_manager_| has the required
+ // metadata already.
+ if (external_data_manager_)
+ external_data_manager_->OnPolicyStoreLoaded();
+ for (auto& observer : observers_)
+ observer.OnStoreLoaded(this);
+}
+
+void CloudPolicyStore::UpdateFirstPoliciesLoaded() {
+ first_policies_loaded_ |= has_policy();
+}
+
+void CloudPolicyStore::SetPolicy(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ std::unique_ptr<enterprise_management::PolicyData> policy_data) {
+ DCHECK(policy_fetch_response);
+ DCHECK(policy_data);
+ policy_fetch_response_ = std::move(policy_fetch_response);
+ policy_ = std::move(policy_data);
+}
+
+void CloudPolicyStore::ResetPolicy() {
+ policy_.reset();
+ policy_fetch_response_.reset();
+}
+
+void CloudPolicyStore::NotifyStoreError() {
+ is_initialized_ = true;
+ UpdateFirstPoliciesLoaded();
+
+ for (auto& observer : observers_)
+ observer.OnStoreError(this);
+}
+
+void CloudPolicyStore::NotifyStoreDestruction() {
+ for (auto& observer : observers_)
+ observer.OnStoreDestruction(this);
+}
+
+void CloudPolicyStore::SetExternalDataManager(
+ base::WeakPtr<CloudExternalDataManager> external_data_manager) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!external_data_manager_);
+
+ external_data_manager_ = external_data_manager;
+ if (is_initialized_)
+ external_data_manager_->OnPolicyStoreLoaded();
+}
+
+void CloudPolicyStore::SetFirstPoliciesLoaded(bool loaded) {
+ first_policies_loaded_ = loaded;
+}
+
+void CloudPolicyStore::set_policy_data_for_testing(
+ std::unique_ptr<enterprise_management::PolicyData> policy) {
+ policy_ = std::move(policy);
+ if (policy_) {
+ policy_fetch_response_ =
+ std::make_unique<enterprise_management::PolicyFetchResponse>();
+ policy_fetch_response_->set_policy_data(policy_->SerializeAsString());
+ } else {
+ policy_fetch_response_.reset();
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_store.h b/chromium/components/policy/core/common/cloud/cloud_policy_store.h
new file mode 100644
index 00000000000..8ad96606771
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_store.h
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/check_op.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_export.h"
+
+namespace enterprise_management {
+class PolicyData;
+class PolicyFetchResponse;
+}
+
+namespace policy {
+
+class CloudExternalDataManager;
+
+// Defines the low-level interface used by the cloud policy code to:
+// 1. Validate policy blobs that should be applied locally
+// 2. Persist policy blobs
+// 3. Decode policy blobs to PolicyMap representation
+class POLICY_EXPORT CloudPolicyStore {
+ public:
+ // Status codes.
+ enum Status {
+ // Everything is in good order.
+ STATUS_OK,
+ // Loading policy from the underlying data store failed.
+ STATUS_LOAD_ERROR,
+ // Failed to store policy to the data store.
+ STATUS_STORE_ERROR,
+ // Failed to parse the policy read from the data store.
+ STATUS_PARSE_ERROR,
+ // Failed to serialize policy for storage.
+ STATUS_SERIALIZE_ERROR,
+ // Validation error.
+ STATUS_VALIDATION_ERROR,
+ // Store cannot accept policy (e.g. non-enterprise device).
+ STATUS_BAD_STATE,
+ };
+
+ // Callbacks for policy store events. Most importantly, policy updates.
+ class POLICY_EXPORT Observer {
+ public:
+ virtual ~Observer();
+
+ // Called on changes to store->policy() and/or store->policy_map().
+ virtual void OnStoreLoaded(CloudPolicyStore* store) = 0;
+
+ // Called upon encountering errors.
+ virtual void OnStoreError(CloudPolicyStore* store) = 0;
+
+ // Called upon store destruction.
+ virtual void OnStoreDestruction(CloudPolicyStore* store);
+ };
+
+ CloudPolicyStore();
+ CloudPolicyStore(const CloudPolicyStore&) = delete;
+ CloudPolicyStore& operator=(const CloudPolicyStore&) = delete;
+ virtual ~CloudPolicyStore();
+
+ // Indicates whether the store has been fully initialized. This is
+ // accomplished by calling Load() after startup.
+ bool is_initialized() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return is_initialized_;
+ }
+
+ base::WeakPtr<CloudExternalDataManager> external_data_manager() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return external_data_manager_;
+ }
+
+ const PolicyMap& policy_map() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return policy_map_;
+ }
+ bool has_policy() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(policy_.get() != nullptr,
+ policy_fetch_response_.get() != nullptr);
+ return policy_.get() != nullptr;
+ }
+ const enterprise_management::PolicyData* policy() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return policy_.get();
+ }
+ const enterprise_management::PolicyFetchResponse* policy_fetch_response()
+ const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return policy_fetch_response_.get();
+ }
+ bool is_managed() const;
+ Status status() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return status_;
+ }
+ bool first_policies_loaded() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return first_policies_loaded_;
+ }
+ CloudPolicyValidatorBase::Status validation_status() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return validation_result_.get() ? validation_result_->status
+ : CloudPolicyValidatorBase::VALIDATION_OK;
+ }
+ const CloudPolicyValidatorBase::ValidationResult* validation_result() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return validation_result_.get();
+ }
+ const std::string& policy_signature_public_key() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return policy_signature_public_key_;
+ }
+
+ // Store a new policy blob. Pending load/store operations will be canceled.
+ // The store operation may proceed asynchronously and observers are notified
+ // once the operation finishes. If successful, OnStoreLoaded() will be invoked
+ // on the observers and the updated policy can be read through policy().
+ // Errors generate OnStoreError() notifications.
+ // |invalidation_version| is the invalidation version of the policy to be
+ // stored.
+ void Store(const enterprise_management::PolicyFetchResponse& policy,
+ int64_t invalidation_version);
+
+ virtual void Store(
+ const enterprise_management::PolicyFetchResponse& policy) = 0;
+
+ // Load the current policy blob from persistent storage. Pending load/store
+ // operations will be canceled. This may trigger asynchronous operations.
+ // Upon success, OnStoreLoaded() will be called on the registered observers.
+ // Otherwise, OnStoreError() reports the reason for failure.
+ virtual void Load() = 0;
+
+ // Registers an observer to be notified when policy changes.
+ void AddObserver(Observer* observer);
+
+ // Removes the specified observer.
+ void RemoveObserver(Observer* observer);
+
+ // The invalidation version of the last policy stored. This value can be read
+ // by observers to determine which version of the policy is now available.
+ int64_t invalidation_version() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return invalidation_version_;
+ }
+
+ // Indicate that external data referenced by policies in this store is managed
+ // by |external_data_manager|. The |external_data_manager| will be notified
+ // about policy changes before any other observers.
+ void SetExternalDataManager(
+ base::WeakPtr<CloudExternalDataManager> external_data_manager);
+
+ // Sets whether or not the first policies for this policy store were loaded.
+ void SetFirstPoliciesLoaded(bool loaded);
+
+ // Test helper to set |policy_|.
+ void set_policy_data_for_testing(
+ std::unique_ptr<enterprise_management::PolicyData> policy);
+
+ protected:
+ // Invokes the corresponding callback on all registered observers.
+ void NotifyStoreLoaded();
+ void NotifyStoreError();
+ void NotifyStoreDestruction();
+
+ // Updates whether or not the first policies were loaded.
+ virtual void UpdateFirstPoliciesLoaded();
+
+ void SetPolicy(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ std::unique_ptr<enterprise_management::PolicyData> policy_data);
+ void ResetPolicy();
+
+ // Assert non-concurrent usage in debug builds.
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Manages external data referenced by policies.
+ base::WeakPtr<CloudExternalDataManager> external_data_manager_;
+
+ // Decoded version of the currently effective policy.
+ PolicyMap policy_map_;
+
+ // Latest status code.
+ Status status_ = STATUS_OK;
+
+ bool first_policies_loaded_ = false;
+
+ // Latest validation result.
+ std::unique_ptr<CloudPolicyValidatorBase::ValidationResult>
+ validation_result_;
+
+ // The invalidation version of the last policy stored.
+ int64_t invalidation_version_ = 0;
+
+ // The public part of signing key that is used by the currently effective
+ // policy. The subclasses should keep its value up to date to correspond to
+ // the currently effective policy. The member should be empty if no policy is
+ // currently effective, or if signature verification was not possible for the
+ // policy.
+ std::string policy_signature_public_key_;
+
+ private:
+ // Whether the store has completed asynchronous initialization, which is
+ // triggered by calling Load().
+ bool is_initialized_ = false;
+
+ // Currently effective policy. Should be always in sync and kept private.
+ // Use `SetPolicy()` and `ResetPolicy()` to alter the fields.
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response_;
+ std::unique_ptr<enterprise_management::PolicyData> policy_;
+
+ base::ObserverList<Observer, true>::Unchecked observers_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_util.cc b/chromium/components/policy/core/common/cloud/cloud_policy_util.cc
new file mode 100644
index 00000000000..699ca03c7c6
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_util.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <Windows.h> // For GetComputerNameW()
+// SECURITY_WIN32 must be defined in order to get
+// EXTENDED_NAME_FORMAT enumeration.
+#define SECURITY_WIN32 1
+#include <security.h>
+#undef SECURITY_WIN32
+#include <wincred.h>
+#endif
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) || \
+ BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_FUCHSIA)
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#if BUILDFLAG(IS_APPLE)
+#include <stddef.h>
+#include <sys/sysctl.h>
+#endif
+
+#if BUILDFLAG(IS_MAC)
+#import <SystemConfiguration/SCDynamicStoreCopySpecific.h>
+#endif
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
+#include <limits.h> // For HOST_NAME_MAX
+#endif
+
+#include <utility>
+
+#include "base/check.h"
+#include "base/cxx17_backports.h"
+#include "base/notreached.h"
+#include "base/system/sys_info.h"
+#if BUILDFLAG(IS_WIN)
+#include "base/win/wmi.h"
+#endif
+#include "components/version_info/version_info.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/system/statistics_provider.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
+#endif
+
+#if BUILDFLAG(IS_WIN)
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#endif
+
+#if BUILDFLAG(IS_APPLE)
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#endif
+
+#if BUILDFLAG(IS_IOS)
+#include "base/ios/device_util.h"
+#endif
+
+namespace policy {
+
+namespace em = enterprise_management;
+
+std::string GetMachineName() {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) || \
+ BUILDFLAG(IS_FUCHSIA)
+ char hostname[HOST_NAME_MAX];
+ if (gethostname(hostname, HOST_NAME_MAX) == 0) // Success.
+ return hostname;
+ return std::string();
+#elif BUILDFLAG(IS_IOS)
+ // Use the Vendor ID as the machine name.
+ return ios::device_util::GetVendorId();
+#elif BUILDFLAG(IS_MAC)
+ // Do not use NSHost currentHost, as it's very slow. http://crbug.com/138570
+ SCDynamicStoreContext context = {0, NULL, NULL, NULL};
+ base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
+ kCFAllocatorDefault, CFSTR("chrome_sync"), NULL, &context));
+ base::ScopedCFTypeRef<CFStringRef> machine_name(
+ SCDynamicStoreCopyLocalHostName(store.get()));
+ if (machine_name.get())
+ return base::SysCFStringRefToUTF8(machine_name.get());
+
+ // Fall back to get computer name.
+ base::ScopedCFTypeRef<CFStringRef> computer_name(
+ SCDynamicStoreCopyComputerName(store.get(), NULL));
+ if (computer_name.get())
+ return base::SysCFStringRefToUTF8(computer_name.get());
+
+ // If all else fails, return to using a slightly nicer version of the
+ // hardware model.
+ char modelBuffer[256];
+ size_t length = sizeof(modelBuffer);
+ if (!sysctlbyname("hw.model", modelBuffer, &length, NULL, 0)) {
+ for (size_t i = 0; i < length; i++) {
+ if (base::IsAsciiDigit(modelBuffer[i]))
+ return std::string(modelBuffer, 0, i);
+ }
+ return std::string(modelBuffer, 0, length);
+ }
+ return std::string();
+#elif BUILDFLAG(IS_WIN)
+ wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0};
+ DWORD size = std::size(computer_name);
+ if (::GetComputerNameW(computer_name, &size)) {
+ std::string result;
+ bool conversion_successful = base::WideToUTF8(computer_name, size, &result);
+ DCHECK(conversion_successful);
+ return result;
+ }
+ return std::string();
+#elif BUILDFLAG(IS_ANDROID)
+ return std::string();
+#elif BUILDFLAG(IS_CHROMEOS)
+ NOTREACHED();
+ return std::string();
+#else
+#error Unsupported platform
+#endif
+}
+
+std::string GetOSVersion() {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_APPLE) || \
+ BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
+ return base::SysInfo::OperatingSystemVersion();
+#elif BUILDFLAG(IS_WIN)
+ base::win::OSInfo::VersionNumber version_number =
+ base::win::OSInfo::GetInstance()->version_number();
+ return base::StringPrintf("%u.%u.%u.%u", version_number.major,
+ version_number.minor, version_number.build,
+ version_number.patch);
+#else
+ NOTREACHED();
+ return std::string();
+#endif
+}
+
+std::string GetOSPlatform() {
+ return version_info::GetOSType();
+}
+
+std::string GetOSArchitecture() {
+ return base::SysInfo::OperatingSystemArchitecture();
+}
+
+std::string GetOSUsername() {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_APPLE)
+ struct passwd* creds = getpwuid(getuid());
+ if (!creds || !creds->pw_name)
+ return std::string();
+
+ return creds->pw_name;
+#elif BUILDFLAG(IS_WIN)
+ WCHAR username[CREDUI_MAX_USERNAME_LENGTH + 1] = {};
+ DWORD username_length = sizeof(username);
+
+ // The SAM compatible username works on both standalone workstations and
+ // domain joined machines. The form is "DOMAIN\username", where DOMAIN is the
+ // the name of the machine for standalone workstations.
+ if (!::GetUserNameEx(::NameSamCompatible, username, &username_length) ||
+ username_length <= 0) {
+ return std::string();
+ }
+
+ return base::WideToUTF8(username);
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
+ if (!user_manager::UserManager::IsInitialized())
+ return std::string();
+ auto* user = user_manager::UserManager::Get()->GetPrimaryUser();
+ if (!user)
+ return std::string();
+ return user->GetAccountId().GetUserEmail();
+#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
+ // TODO(crbug.com/1257674): This should be fully implemented when there is
+ // support in fuchsia.
+ return std::string();
+#else
+ NOTREACHED();
+ return std::string();
+#endif
+}
+
+em::Channel ConvertToProtoChannel(version_info::Channel channel) {
+ switch (channel) {
+ case version_info::Channel::UNKNOWN:
+ return em::CHANNEL_UNKNOWN;
+ case version_info::Channel::CANARY:
+ return em::CHANNEL_CANARY;
+ case version_info::Channel::DEV:
+ return em::CHANNEL_DEV;
+ case version_info::Channel::BETA:
+ return em::CHANNEL_BETA;
+ case version_info::Channel::STABLE:
+ return em::CHANNEL_STABLE;
+ }
+}
+
+std::string GetDeviceName() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ return chromeos::system::StatisticsProvider::GetInstance()
+ ->GetEnterpriseMachineID();
+#else
+ return GetMachineName();
+#endif
+}
+
+std::unique_ptr<em::BrowserDeviceIdentifier> GetBrowserDeviceIdentifier() {
+ std::unique_ptr<em::BrowserDeviceIdentifier> device_identifier =
+ std::make_unique<em::BrowserDeviceIdentifier>();
+ device_identifier->set_computer_name(GetMachineName());
+#if BUILDFLAG(IS_WIN)
+ device_identifier->set_serial_number(base::WideToUTF8(
+ base::win::WmiComputerSystemInfo::Get().serial_number()));
+#else
+ device_identifier->set_serial_number("");
+#endif
+ return device_identifier;
+}
+
+bool IsMachineLevelUserCloudPolicyType(const std::string& type) {
+ return type == GetMachineLevelUserCloudPolicyTypeForCurrentOS();
+}
+
+std::string GetMachineLevelUserCloudPolicyTypeForCurrentOS() {
+#if BUILDFLAG(IS_IOS)
+ return dm_protocol::kChromeMachineLevelUserCloudPolicyIOSType;
+#elif BUILDFLAG(IS_ANDROID)
+ return dm_protocol::kChromeMachineLevelUserCloudPolicyAndroidType;
+#else
+ return dm_protocol::kChromeMachineLevelUserCloudPolicyType;
+#endif
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_util.h b/chromium/components/policy/core/common/cloud/cloud_policy_util.h
new file mode 100644
index 00000000000..8f7e9fdc53f
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_util.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_UTIL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_UTIL_H_
+
+#include <memory>
+#include <string>
+
+#include "components/policy/policy_export.h"
+#include "components/version_info/channel.h"
+
+namespace enterprise_management {
+class BrowserDeviceIdentifier;
+enum Channel : int;
+} // namespace enterprise_management
+
+namespace policy {
+
+// Returns the name of the machine. This function is platform specific.
+POLICY_EXPORT std::string GetMachineName();
+
+// Returns the OS version of the machine. This function is platform specific.
+POLICY_EXPORT std::string GetOSVersion();
+
+// Returns the OS platform of the machine. This function is platform specific.
+POLICY_EXPORT std::string GetOSPlatform();
+
+// Returns the bitness of the OS. This function is platform specific.
+POLICY_EXPORT std::string GetOSArchitecture();
+
+// Returns the username of the logged in user in the OS. This function is
+// platform specific. Note that on Windows, this returns the username including
+// the domain, whereas on POSIX, this just returns the username.
+POLICY_EXPORT std::string GetOSUsername();
+
+// Converts |version_info::Channel| to |enterprise_management::Channel|.
+POLICY_EXPORT enterprise_management::Channel ConvertToProtoChannel(
+ version_info::Channel channel);
+
+// Returns the name of the device. This is equivalent to GetMachineName on
+// non-CrOS platforms and returns the serial number of the device on CrOS.
+POLICY_EXPORT std::string GetDeviceName();
+
+// Returns the browser device identifier for non-CrOS platforms. It includes
+// several identifiers we collect from the device.
+POLICY_EXPORT std::unique_ptr<enterprise_management::BrowserDeviceIdentifier>
+GetBrowserDeviceIdentifier();
+
+// Returns true if the given policy type corresponds to the machine-level user
+// cloud policy type of the current platform.
+POLICY_EXPORT bool IsMachineLevelUserCloudPolicyType(const std::string& type);
+
+// Returns the machine-level user cloud policy type for the current platform.
+POLICY_EXPORT std::string GetMachineLevelUserCloudPolicyTypeForCurrentOS();
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_UTIL_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_validator.cc b/chromium/components/policy/core/common/cloud/cloud_policy_validator.cc
new file mode 100644
index 00000000000..874f0a82d78
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_validator.cc
@@ -0,0 +1,659 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/signature_verifier.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "base/command_line.h"
+#include "base/system/sys_info.h"
+#include "components/policy/core/common/policy_switches.h"
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+const char kMetricPolicyUserVerification[] =
+ "Enterprise.PolicyUserVerification";
+
+enum class MetricPolicyUserVerification {
+ // Gaia id check used, but failed.
+ kGaiaIdFailed = 0,
+ // Gaia id check used and succeeded.
+ kGaiaIdSucceeded = 1,
+ // Gaia id is not present and username check failed.
+ kUsernameFailed = 2,
+ // Gaia id is not present for user and username check succeeded.
+ kUsernameSucceeded = 3,
+ // Gaia id is not present in policy and username check succeeded.
+ kGaiaIdMissingUsernameSucceeded = 4,
+ kMaxValue = kGaiaIdMissingUsernameSucceeded,
+};
+
+} // namespace
+
+// static
+const char* CloudPolicyValidatorBase::StatusToString(Status status) {
+ switch (status) {
+ case VALIDATION_OK:
+ return "OK";
+ case VALIDATION_BAD_INITIAL_SIGNATURE:
+ return "BAD_INITIAL_SIGNATURE";
+ case VALIDATION_BAD_SIGNATURE:
+ return "BAD_SIGNATURE";
+ case VALIDATION_ERROR_CODE_PRESENT:
+ return "ERROR_CODE_PRESENT";
+ case VALIDATION_PAYLOAD_PARSE_ERROR:
+ return "PAYLOAD_PARSE_ERROR";
+ case VALIDATION_WRONG_POLICY_TYPE:
+ return "WRONG_POLICY_TYPE";
+ case VALIDATION_WRONG_SETTINGS_ENTITY_ID:
+ return "WRONG_SETTINGS_ENTITY_ID";
+ case VALIDATION_BAD_TIMESTAMP:
+ return "BAD_TIMESTAMP";
+ case VALIDATION_BAD_DM_TOKEN:
+ return "BAD_DM_TOKEN";
+ case VALIDATION_BAD_DEVICE_ID:
+ return "BAD_DEVICE_ID";
+ case VALIDATION_BAD_USER:
+ return "BAD_USER";
+ case VALIDATION_POLICY_PARSE_ERROR:
+ return "POLICY_PARSE_ERROR";
+ case VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE:
+ return "BAD_KEY_VERIFICATION_SIGNATURE";
+ case VALIDATION_VALUE_WARNING:
+ return "VALUE_WARNING";
+ case VALIDATION_VALUE_ERROR:
+ return "VALUE_ERROR";
+ case VALIDATION_STATUS_SIZE:
+ return "UNKNOWN";
+ }
+ return "UNKNOWN";
+}
+
+CloudPolicyValidatorBase::ValidationResult::ValidationResult() = default;
+CloudPolicyValidatorBase::ValidationResult::~ValidationResult() = default;
+
+CloudPolicyValidatorBase::~CloudPolicyValidatorBase() {}
+
+std::unique_ptr<CloudPolicyValidatorBase::ValidationResult>
+CloudPolicyValidatorBase::GetValidationResult() const {
+ std::unique_ptr<ValidationResult> result =
+ std::make_unique<ValidationResult>();
+ result->status = status_;
+ result->value_validation_issues = value_validation_issues_;
+ result->policy_token = policy_data_->policy_token();
+ result->policy_data_signature = policy_->policy_data_signature();
+ return result;
+}
+
+void CloudPolicyValidatorBase::ValidateTimestamp(
+ base::Time not_before,
+ ValidateTimestampOption timestamp_option) {
+ validation_flags_ |= VALIDATE_TIMESTAMP;
+ timestamp_not_before_ = not_before.ToJavaTime();
+ timestamp_option_ = timestamp_option;
+}
+
+void CloudPolicyValidatorBase::ValidateUser(const AccountId& account_id) {
+ validation_flags_ |= VALIDATE_USER;
+ username_ = account_id.GetUserEmail();
+ gaia_id_ = account_id.GetGaiaId();
+ // Always canonicalize when falls back to username check,
+ // because it checks only for regular users.
+ canonicalize_user_ = true;
+}
+
+void CloudPolicyValidatorBase::ValidateUsernameAndGaiaId(
+ const std::string& expected_user,
+ const std::string& gaia_id) {
+ validation_flags_ |= VALIDATE_USER;
+ username_ = expected_user;
+ gaia_id_ = gaia_id;
+ canonicalize_user_ = false;
+}
+
+void CloudPolicyValidatorBase::ValidateUsername(
+ const std::string& expected_user) {
+ validation_flags_ |= VALIDATE_USER;
+ username_ = expected_user;
+ gaia_id_.clear();
+ canonicalize_user_ = false;
+}
+
+void CloudPolicyValidatorBase::ValidateDomain(
+ const std::string& expected_domain) {
+ validation_flags_ |= VALIDATE_DOMAIN;
+ domain_ = gaia::CanonicalizeDomain(expected_domain);
+}
+
+void CloudPolicyValidatorBase::ValidateDMToken(
+ const std::string& expected_dm_token,
+ ValidateDMTokenOption dm_token_option) {
+ validation_flags_ |= VALIDATE_DM_TOKEN;
+ dm_token_ = expected_dm_token;
+ dm_token_option_ = dm_token_option;
+}
+
+void CloudPolicyValidatorBase::ValidateDeviceId(
+ const std::string& expected_device_id,
+ ValidateDeviceIdOption device_id_option) {
+ validation_flags_ |= VALIDATE_DEVICE_ID;
+ device_id_ = expected_device_id;
+ device_id_option_ = device_id_option;
+}
+
+void CloudPolicyValidatorBase::ValidatePolicyType(
+ const std::string& policy_type) {
+ validation_flags_ |= VALIDATE_POLICY_TYPE;
+ policy_type_ = policy_type;
+}
+
+void CloudPolicyValidatorBase::ValidateSettingsEntityId(
+ const std::string& settings_entity_id) {
+ validation_flags_ |= VALIDATE_ENTITY_ID;
+ settings_entity_id_ = settings_entity_id;
+}
+
+void CloudPolicyValidatorBase::ValidatePayload() {
+ validation_flags_ |= VALIDATE_PAYLOAD;
+}
+
+void CloudPolicyValidatorBase::ValidateCachedKey(
+ const std::string& cached_key,
+ const std::string& cached_key_signature,
+ const std::string& owning_domain) {
+ validation_flags_ |= VALIDATE_CACHED_KEY;
+ set_owning_domain(owning_domain);
+ cached_key_ = cached_key;
+ cached_key_signature_ = cached_key_signature;
+}
+
+void CloudPolicyValidatorBase::ValidateSignature(const std::string& key) {
+ validation_flags_ |= VALIDATE_SIGNATURE;
+ DCHECK(key_.empty() || key_ == key);
+ key_ = key;
+}
+
+void CloudPolicyValidatorBase::ValidateSignatureAllowingRotation(
+ const std::string& key,
+ const std::string& owning_domain) {
+ validation_flags_ |= VALIDATE_SIGNATURE;
+ DCHECK(key_.empty() || key_ == key);
+ key_ = key;
+ set_owning_domain(owning_domain);
+ allow_key_rotation_ = true;
+}
+
+void CloudPolicyValidatorBase::ValidateInitialKey(
+ const std::string& owning_domain) {
+ validation_flags_ |= VALIDATE_INITIAL_KEY;
+ set_owning_domain(owning_domain);
+}
+
+void CloudPolicyValidatorBase::ValidateAgainstCurrentPolicy(
+ const em::PolicyData* policy_data,
+ ValidateTimestampOption timestamp_option,
+ ValidateDMTokenOption dm_token_option,
+ ValidateDeviceIdOption device_id_option) {
+ base::Time last_policy_timestamp;
+ std::string expected_dm_token;
+ std::string expected_device_id;
+ if (policy_data) {
+ last_policy_timestamp = base::Time::FromJavaTime(policy_data->timestamp());
+ expected_dm_token = policy_data->request_token();
+ expected_device_id = policy_data->device_id();
+ }
+ ValidateTimestamp(last_policy_timestamp, timestamp_option);
+ ValidateDMToken(expected_dm_token, dm_token_option);
+ ValidateDeviceId(expected_device_id, device_id_option);
+}
+
+// static
+bool CloudPolicyValidatorBase::VerifySignature(const std::string& data,
+ const std::string& key,
+ const std::string& signature,
+ SignatureType signature_type) {
+ crypto::SignatureVerifier verifier;
+ crypto::SignatureVerifier::SignatureAlgorithm algorithm;
+ switch (signature_type) {
+ case SHA1:
+ algorithm = crypto::SignatureVerifier::RSA_PKCS1_SHA1;
+ break;
+ case SHA256:
+ algorithm = crypto::SignatureVerifier::RSA_PKCS1_SHA256;
+ break;
+ default:
+ NOTREACHED() << "Invalid signature type: " << signature_type;
+ return false;
+ }
+
+ if (!verifier.VerifyInit(algorithm,
+ base::as_bytes(base::make_span(signature)),
+ base::as_bytes(base::make_span(key)))) {
+ DLOG(ERROR) << "Invalid verification signature/key format";
+ return false;
+ }
+ verifier.VerifyUpdate(base::as_bytes(base::make_span(data)));
+ return verifier.VerifyFinal();
+}
+
+CloudPolicyValidatorBase::CloudPolicyValidatorBase(
+ std::unique_ptr<em::PolicyFetchResponse> policy_response,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+ : validation_flags_(0),
+ status_(VALIDATION_OK),
+ policy_(std::move(policy_response)),
+ timestamp_not_before_(0),
+ timestamp_option_(TIMESTAMP_VALIDATED),
+ dm_token_option_(DM_TOKEN_REQUIRED),
+ device_id_option_(DEVICE_ID_REQUIRED),
+ canonicalize_user_(false),
+ verification_key_(GetPolicyVerificationKey()),
+ allow_key_rotation_(false),
+ background_task_runner_(background_task_runner) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ // Empty `verification_key_` is only allowed on Chrome OS test image when
+ // policy key verification is disabled via command line flag.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kDisablePolicyKeyVerification)) {
+ base::SysInfo::CrashIfChromeOSNonTestImage();
+ // GetPolicyVerificationKey() returns a non-empty string.
+ verification_key_ = absl::nullopt;
+ } else {
+ DCHECK(verification_key_);
+ }
+#else
+ DCHECK(verification_key_);
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+}
+
+// static
+void CloudPolicyValidatorBase::PostValidationTask(
+ std::unique_ptr<CloudPolicyValidatorBase> validator,
+ base::OnceClosure completion_callback) {
+ const auto task_runner = validator->background_task_runner_;
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CloudPolicyValidatorBase::PerformValidation,
+ std::move(validator), base::ThreadTaskRunnerHandle::Get(),
+ std::move(completion_callback)));
+}
+
+// static
+void CloudPolicyValidatorBase::PerformValidation(
+ std::unique_ptr<CloudPolicyValidatorBase> self,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure completion_callback) {
+ // Run the validation activities on this thread.
+ self->RunValidation();
+
+ // Report completion on |task_runner|.
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&CloudPolicyValidatorBase::ReportCompletion,
+ std::move(self), std::move(completion_callback)));
+}
+
+// static
+void CloudPolicyValidatorBase::ReportCompletion(
+ std::unique_ptr<CloudPolicyValidatorBase> self,
+ base::OnceClosure completion_callback) {
+ std::move(completion_callback).Run();
+}
+
+void CloudPolicyValidatorBase::RunValidation() {
+ policy_data_ = std::make_unique<em::PolicyData>();
+ RunChecks();
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckProtoPayload(
+ google::protobuf::MessageLite* payload) {
+ DCHECK(payload);
+ if (!policy_data_ || !policy_data_->has_policy_value() ||
+ !payload->ParseFromString(policy_data_->policy_value()) ||
+ !payload->IsInitialized()) {
+ LOG(ERROR) << "Failed to decode policy payload protobuf";
+ return VALIDATION_POLICY_PARSE_ERROR;
+ }
+ return VALIDATION_OK;
+}
+
+void CloudPolicyValidatorBase::RunChecks() {
+ status_ = VALIDATION_OK;
+ if ((policy_->has_error_code() && policy_->error_code() != 200) ||
+ (policy_->has_error_message() && !policy_->error_message().empty())) {
+ LOG(ERROR) << "Error in policy blob."
+ << " code: " << policy_->error_code()
+ << " message: " << policy_->error_message();
+ status_ = VALIDATION_ERROR_CODE_PRESENT;
+ return;
+ }
+
+ // Parse policy data.
+ if (!policy_data_->ParseFromString(policy_->policy_data()) ||
+ !policy_data_->IsInitialized()) {
+ LOG(ERROR) << "Failed to parse policy response";
+ status_ = VALIDATION_PAYLOAD_PARSE_ERROR;
+ return;
+ }
+
+ // Table of checks we run. These are sorted by descending severity of the
+ // error, s.t. the most severe check will determine the validation status.
+ static const struct {
+ int flag;
+ Status (CloudPolicyValidatorBase::*checkFunction)();
+ } kCheckFunctions[] = {
+ {VALIDATE_SIGNATURE, &CloudPolicyValidatorBase::CheckSignature},
+ {VALIDATE_INITIAL_KEY, &CloudPolicyValidatorBase::CheckInitialKey},
+ {VALIDATE_CACHED_KEY, &CloudPolicyValidatorBase::CheckCachedKey},
+ {VALIDATE_POLICY_TYPE, &CloudPolicyValidatorBase::CheckPolicyType},
+ {VALIDATE_ENTITY_ID, &CloudPolicyValidatorBase::CheckEntityId},
+ {VALIDATE_DM_TOKEN, &CloudPolicyValidatorBase::CheckDMToken},
+ {VALIDATE_DEVICE_ID, &CloudPolicyValidatorBase::CheckDeviceId},
+ {VALIDATE_USER, &CloudPolicyValidatorBase::CheckUser},
+ {VALIDATE_DOMAIN, &CloudPolicyValidatorBase::CheckDomain},
+ {VALIDATE_TIMESTAMP, &CloudPolicyValidatorBase::CheckTimestamp},
+ {VALIDATE_PAYLOAD, &CloudPolicyValidatorBase::CheckPayload},
+ {VALIDATE_VALUES, &CloudPolicyValidatorBase::CheckValues},
+ };
+
+ for (size_t i = 0; i < std::size(kCheckFunctions); ++i) {
+ if (validation_flags_ & kCheckFunctions[i].flag) {
+ status_ = (this->*(kCheckFunctions[i].checkFunction))();
+ if (status_ != VALIDATION_OK)
+ break;
+ }
+ }
+}
+
+// Verifies the |new_public_key_verification_signature_deprecated| for the
+// |new_public_key| in the policy blob.
+bool CloudPolicyValidatorBase::CheckNewPublicKeyVerificationSignature() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ // Skip verification if the key is empty (disabled via command line).
+ if (!verification_key_) {
+ return true;
+ }
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+ if (!policy_->has_new_public_key_verification_signature_deprecated()) {
+ // Policy does not contain a verification signature, so log an error.
+ LOG(ERROR) << "Policy is missing public_key_verification_signature";
+ return false;
+ }
+
+ if (!CheckVerificationKeySignature(
+ policy_->new_public_key(), verification_key_.value(),
+ policy_->new_public_key_verification_signature_deprecated())) {
+ LOG(ERROR) << "Signature verification failed";
+ return false;
+ }
+ // Signature verification succeeded - return success to the caller.
+ DVLOG(1) << "Signature verification succeeded";
+ return true;
+}
+
+bool CloudPolicyValidatorBase::CheckVerificationKeySignature(
+ const std::string& key,
+ const std::string& verification_key,
+ const std::string& signature) {
+ DCHECK(!verification_key.empty());
+ em::DEPRECATEDPolicyPublicKeyAndDomain signed_data;
+ signed_data.set_new_public_key(key);
+
+ // If no owning_domain_ supplied, try extracting the domain from the policy
+ // itself (this happens on certain platforms during startup, when we validate
+ // cached policy before prefs are loaded).
+ std::string domain =
+ owning_domain_.empty() ? ExtractDomainFromPolicy() : owning_domain_;
+ if (domain.empty()) {
+ LOG(ERROR) << "Policy does not contain a domain";
+ return false;
+ }
+ signed_data.set_domain(domain);
+ std::string signed_data_as_string;
+ if (!signed_data.SerializeToString(&signed_data_as_string)) {
+ DLOG(ERROR) << "Could not serialize verification key to string";
+ return false;
+ }
+ return VerifySignature(signed_data_as_string, verification_key, signature,
+ SHA256);
+}
+
+std::string CloudPolicyValidatorBase::ExtractDomainFromPolicy() {
+ std::string domain;
+ if (policy_data_->has_username()) {
+ domain = gaia::ExtractDomainName(
+ gaia::CanonicalizeEmail(gaia::SanitizeEmail(policy_data_->username())));
+ }
+ return domain;
+}
+
+void CloudPolicyValidatorBase::set_owning_domain(
+ const std::string& owning_domain) {
+ // Make sure we aren't overwriting the owning domain with a different one.
+ DCHECK(owning_domain_.empty() || owning_domain_ == owning_domain);
+ owning_domain_ = owning_domain;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() {
+ const std::string* signature_key = &key_;
+ if (policy_->has_new_public_key() && allow_key_rotation_) {
+ signature_key = &policy_->new_public_key();
+ if (!policy_->has_new_public_key_signature() ||
+ !VerifySignature(policy_->new_public_key(), key_,
+ policy_->new_public_key_signature(), SHA1)) {
+ LOG(ERROR) << "New public key rotation signature verification failed";
+ return VALIDATION_BAD_SIGNATURE;
+ }
+
+ if (!CheckNewPublicKeyVerificationSignature()) {
+ LOG(ERROR) << "New public key root verification failed";
+ return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
+ }
+ }
+
+ if (!policy_->has_policy_data_signature() ||
+ !VerifySignature(policy_->policy_data(), *signature_key,
+ policy_->policy_data_signature(), SHA1)) {
+ LOG(ERROR) << "Policy signature validation failed";
+ return VALIDATION_BAD_SIGNATURE;
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckInitialKey() {
+ if (!policy_->has_new_public_key() || !policy_->has_policy_data_signature() ||
+ !VerifySignature(policy_->policy_data(), policy_->new_public_key(),
+ policy_->policy_data_signature(), SHA1)) {
+ LOG(ERROR) << "Initial policy signature validation failed";
+ return VALIDATION_BAD_INITIAL_SIGNATURE;
+ }
+
+ if (!CheckNewPublicKeyVerificationSignature()) {
+ LOG(ERROR) << "Initial policy root signature validation failed";
+ return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
+ }
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckCachedKey() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ // Skip verification if the key is empty (disabled via command line).
+ if (!verification_key_) {
+ return VALIDATION_OK;
+ }
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+ if (!CheckVerificationKeySignature(cached_key_, verification_key_.value(),
+ cached_key_signature_)) {
+ LOG(ERROR) << "Cached key signature verification failed";
+ return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE;
+ } else {
+ DVLOG(1) << "Cached key signature verification succeeded";
+ }
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckPolicyType() {
+ if (!policy_data_->has_policy_type() ||
+ policy_data_->policy_type() != policy_type_) {
+ LOG(ERROR) << "Wrong policy type " << policy_data_->policy_type();
+ return VALIDATION_WRONG_POLICY_TYPE;
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckEntityId() {
+ if (!policy_data_->has_settings_entity_id() ||
+ policy_data_->settings_entity_id() != settings_entity_id_) {
+ LOG(ERROR) << "Wrong settings_entity_id "
+ << policy_data_->settings_entity_id() << ", expected "
+ << settings_entity_id_;
+ return VALIDATION_WRONG_SETTINGS_ENTITY_ID;
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckTimestamp() {
+ if (timestamp_option_ == TIMESTAMP_NOT_VALIDATED)
+ return VALIDATION_OK;
+
+ if (!policy_data_->has_timestamp()) {
+ LOG(ERROR) << "Policy timestamp missing";
+ return VALIDATION_BAD_TIMESTAMP;
+ }
+
+ if (policy_data_->timestamp() < timestamp_not_before_) {
+ LOG(ERROR) << "Policy too old: " << policy_data_->timestamp();
+ return VALIDATION_BAD_TIMESTAMP;
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckDMToken() {
+ if (dm_token_option_ == DM_TOKEN_REQUIRED &&
+ (!policy_data_->has_request_token() ||
+ policy_data_->request_token().empty())) {
+ LOG(ERROR) << "Empty DM token encountered - expected: " << dm_token_;
+ return VALIDATION_BAD_DM_TOKEN;
+ }
+ if (!dm_token_.empty() && policy_data_->request_token() != dm_token_) {
+ LOG(ERROR) << "Invalid DM token: " << policy_data_->request_token()
+ << " - expected: " << dm_token_;
+ return VALIDATION_BAD_DM_TOKEN;
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckDeviceId() {
+ if (device_id_option_ == DEVICE_ID_REQUIRED &&
+ (!policy_data_->has_device_id() || policy_data_->device_id().empty())) {
+ LOG(ERROR) << "Empty device id encountered - expected: " << device_id_;
+ return VALIDATION_BAD_DEVICE_ID;
+ }
+ if (!device_id_.empty() && policy_data_->device_id() != device_id_) {
+ LOG(ERROR) << "Invalid device id: " << policy_data_->device_id()
+ << " - expected: " << device_id_;
+ return VALIDATION_BAD_DEVICE_ID;
+ }
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckUser() {
+ if (!policy_data_->has_username() && !policy_data_->has_gaia_id()) {
+ LOG(ERROR) << "Policy is missing user name and gaia id";
+ return VALIDATION_BAD_USER;
+ }
+
+ if (policy_data_->has_gaia_id() && !policy_data_->gaia_id().empty() &&
+ !gaia_id_.empty()) {
+ std::string expected = gaia_id_;
+ std::string actual = policy_data_->gaia_id();
+
+ if (expected != actual) {
+ LOG(ERROR) << "Invalid gaia id: " << actual;
+ UMA_HISTOGRAM_ENUMERATION(kMetricPolicyUserVerification,
+ MetricPolicyUserVerification::kGaiaIdFailed);
+ return VALIDATION_BAD_USER;
+ }
+ UMA_HISTOGRAM_ENUMERATION(kMetricPolicyUserVerification,
+ MetricPolicyUserVerification::kGaiaIdSucceeded);
+ } else {
+ std::string expected = username_;
+ std::string actual = policy_data_->username();
+ if (canonicalize_user_) {
+ expected = gaia::CanonicalizeEmail(gaia::SanitizeEmail(expected));
+ actual = gaia::CanonicalizeEmail(gaia::SanitizeEmail(actual));
+ }
+
+ if (expected != actual) {
+ LOG(ERROR) << "Invalid user name " << actual << ", expected " << expected;
+ UMA_HISTOGRAM_ENUMERATION(kMetricPolicyUserVerification,
+ MetricPolicyUserVerification::kUsernameFailed);
+ return VALIDATION_BAD_USER;
+ }
+ if (gaia_id_.empty()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ kMetricPolicyUserVerification,
+ MetricPolicyUserVerification::kUsernameSucceeded);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ kMetricPolicyUserVerification,
+ MetricPolicyUserVerification::kGaiaIdMissingUsernameSucceeded);
+ }
+ }
+
+ return VALIDATION_OK;
+}
+
+CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckDomain() {
+ std::string policy_domain = ExtractDomainFromPolicy();
+ if (policy_domain.empty()) {
+ LOG(ERROR) << "Policy is missing user name";
+ return VALIDATION_BAD_USER;
+ }
+
+ if (domain_ != policy_domain) {
+ LOG(ERROR) << "Invalid domain name " << policy_domain << " - " << domain_;
+ return VALIDATION_BAD_USER;
+ }
+
+ return VALIDATION_OK;
+}
+
+template class CloudPolicyValidator<em::CloudPolicySettings>;
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+template class CloudPolicyValidator<em::ExternalPolicyData>;
+#endif
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_validator.h b/chromium/components/policy/core/common/cloud/cloud_policy_validator.h
new file mode 100644
index 00000000000..244c4663757
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_validator.h
@@ -0,0 +1,452 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_VALIDATOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_VALIDATOR_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/policy_value_validator.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#endif
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace google {
+namespace protobuf {
+class MessageLite;
+}
+} // namespace google
+
+namespace enterprise_management {
+class PolicyData;
+class PolicyFetchResponse;
+} // namespace enterprise_management
+
+namespace policy {
+
+// Helper class that implements the gory details of validating a policy blob.
+// Since signature checks are expensive, validation can happen on a background
+// thread. The pattern is to create a validator, configure its behavior through
+// the ValidateXYZ() functions, and then call StartValidation(). Alternatively,
+// RunValidation() can be used to perform validation on the current thread.
+class POLICY_EXPORT CloudPolicyValidatorBase {
+ public:
+ // Validation result codes. These values are also used for UMA histograms by
+ // UserCloudPolicyStoreAsh and must stay stable - new elements should
+ // be added at the end before VALIDATION_STATUS_SIZE. Also update the
+ // associated enum definition in histograms.xml.
+ enum Status {
+ // Indicates successful validation.
+ VALIDATION_OK,
+ // Bad signature on the initial key.
+ VALIDATION_BAD_INITIAL_SIGNATURE,
+ // Bad signature.
+ VALIDATION_BAD_SIGNATURE,
+ // Policy blob contains error code.
+ VALIDATION_ERROR_CODE_PRESENT,
+ // Policy payload failed to decode.
+ VALIDATION_PAYLOAD_PARSE_ERROR,
+ // Unexpected policy type.
+ VALIDATION_WRONG_POLICY_TYPE,
+ // Unexpected settings entity id.
+ VALIDATION_WRONG_SETTINGS_ENTITY_ID,
+ // Timestamp is missing or is older than expected.
+ VALIDATION_BAD_TIMESTAMP,
+ // DM token is empty or doesn't match.
+ VALIDATION_BAD_DM_TOKEN,
+ // Device id is empty or doesn't match.
+ VALIDATION_BAD_DEVICE_ID,
+ // User id doesn't match.
+ VALIDATION_BAD_USER,
+ // Policy payload protobuf parse error.
+ VALIDATION_POLICY_PARSE_ERROR,
+ // Policy key signature could not be verified using the hard-coded
+ // verification key.
+ VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE,
+ // Policy value validation raised warning(s).
+ VALIDATION_VALUE_WARNING,
+ // Policy value validation failed with error(s).
+ VALIDATION_VALUE_ERROR,
+ VALIDATION_STATUS_SIZE // MUST BE LAST
+ };
+
+ enum ValidateDMTokenOption {
+ // The DM token from policy must match the expected DM token unless the
+ // expected DM token is empty. In addition, the DM token from policy must
+ // not be empty.
+ DM_TOKEN_REQUIRED,
+
+ // The DM token from policy must match the expected DM token unless the
+ // expected DM token is empty.
+ DM_TOKEN_NOT_REQUIRED,
+ };
+
+ enum ValidateDeviceIdOption {
+ // The device id from policy must match the expected device id unless the
+ // expected device id is empty. In addition, the device id from policy must
+ // not be empty.
+ DEVICE_ID_REQUIRED,
+
+ // The device id from policy must match the expected device id unless the
+ // expected device id is empty.
+ DEVICE_ID_NOT_REQUIRED,
+ };
+
+ enum ValidateTimestampOption {
+ // The policy must have a timestamp field and the timestamp is checked
+ // against the |not_before| value.
+ TIMESTAMP_VALIDATED,
+
+ // The timestamp is not validated.
+ TIMESTAMP_NOT_VALIDATED,
+ };
+
+ enum SignatureType { SHA1, SHA256 };
+
+ struct POLICY_EXPORT ValidationResult {
+ // Validation status.
+ Status status = VALIDATION_OK;
+
+ // Value validation issues.
+ std::vector<ValueValidationIssue> value_validation_issues;
+
+ // Policy identifiers.
+ std::string policy_token;
+ std::string policy_data_signature;
+
+ ValidationResult();
+ ~ValidationResult();
+ };
+
+ // Returns a human-readable representation of |status|.
+ static const char* StatusToString(Status status);
+
+ CloudPolicyValidatorBase(const CloudPolicyValidatorBase&) = delete;
+ CloudPolicyValidatorBase& operator=(const CloudPolicyValidatorBase&) = delete;
+ virtual ~CloudPolicyValidatorBase();
+
+ // Validation status which can be read after completion has been signaled.
+ Status status() const { return status_; }
+ bool success() const { return status_ == VALIDATION_OK; }
+
+ // The policy objects owned by the validator. These are unique_ptr
+ // references, so ownership can be passed on once validation is complete.
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>& policy() {
+ return policy_;
+ }
+ std::unique_ptr<enterprise_management::PolicyData>& policy_data() {
+ return policy_data_;
+ }
+
+ // Retrieve the policy value validation result.
+ std::unique_ptr<ValidationResult> GetValidationResult() const;
+
+ // Instruct the validator to check that the policy timestamp is present and is
+ // not before |not_before| if |timestamp_option| is TIMESTAMP_VALIDATED, or to
+ // not check the policy timestamp if |timestamp_option| is
+ // TIMESTAMP_NOT_VALIDATED.
+ void ValidateTimestamp(base::Time not_before,
+ ValidateTimestampOption timestamp_option);
+
+ // Instruct the validator to check that the user in the policy blob
+ // matches |account_id|. It checks GAIA ID if both policy blob and
+ // |account_id| have it, otherwise falls back to username check.
+ void ValidateUser(const AccountId& account_id);
+
+ // Instruct the validator to check that the username in the policy blob
+ // matches |expected_user|.
+ // This is used for DeviceLocalAccounts that doesn't have AccountId.
+ void ValidateUsername(const std::string& expected_user);
+
+ // Instruct the validator to check that the username in the policy blob
+ // matches the user credentials. It checks GAIA ID if policy blob has it,
+ // otherwise falls back to username check.
+ void ValidateUsernameAndGaiaId(const std::string& expected_user,
+ const std::string& gaia_id);
+
+ // Instruct the validator to check that the policy blob is addressed to
+ // |expected_domain|. This uses the domain part of the username field in the
+ // policy for the check.
+ void ValidateDomain(const std::string& expected_domain);
+
+ // Instruct the validator to check that the DM token from policy matches
+ // |expected_dm_token| unless |expected_dm_token| is empty. In addition, the
+ // DM token from policy must not be empty if |dm_token_option| is
+ // DM_TOKEN_REQUIRED.
+ void ValidateDMToken(const std::string& expected_dm_token,
+ ValidateDMTokenOption dm_token_option);
+
+ // Instruct the validator to check that the device id from policy matches
+ // |expected_device_id| unless |expected_device_id| is empty. In addition, the
+ // device id from policy must not be empty if |device_id_option| is
+ // DEVICE_ID_REQUIRED.
+ void ValidateDeviceId(const std::string& expected_device_id,
+ ValidateDeviceIdOption device_id_option);
+
+ // Instruct the validator to check the policy type.
+ void ValidatePolicyType(const std::string& policy_type);
+
+ // Instruct the validator to check the settings_entity_id value.
+ void ValidateSettingsEntityId(const std::string& settings_entity_id);
+
+ // Instruct the validator to check that the payload can be decoded
+ // successfully.
+ void ValidatePayload();
+
+ // Instruct the validator to check that |cached_key| is valid by verifying the
+ // |cached_key_signature| using the passed |owning_domain| and the baked-in
+ // policy verification key.
+ void ValidateCachedKey(const std::string& cached_key,
+ const std::string& cached_key_signature,
+ const std::string& owning_domain);
+
+ // Instruct the validator to check that the signature on the policy blob
+ // verifies against |key|.
+ void ValidateSignature(const std::string& key);
+
+ // Instruct the validator to check that the signature on the policy blob
+ // verifies against |key|. If there is a key rotation present in the policy
+ // blob, this checks the signature on the new key against |key| and the policy
+ // blob against the new key. New key is also validated using the passed
+ // |owning_domain| and the baked-in policy verification key against the
+ // proto's new_public_key_verification_signature_deprecated field.
+ void ValidateSignatureAllowingRotation(const std::string& key,
+ const std::string& owning_domain);
+
+ // Similar to ValidateSignature(), this instructs the validator to check the
+ // signature on the policy blob. However, this variant expects a new policy
+ // key set in the policy blob and makes sure the policy is signed using that
+ // key. This should be called at setup time when there is no existing policy
+ // key present to check against. New key is validated using the baked-in
+ // policy verification key against the proto's
+ // new_public_key_verification_signature_deprecated field.
+ void ValidateInitialKey(const std::string& owning_domain);
+
+ // Convenience helper that instructs the validator to check timestamp, DM
+ // token and device id based on the current policy blob. |policy_data| may be
+ // nullptr, in which case the timestamp lower bound check is waived and the DM
+ // token as well as the device id are checked against empty strings.
+ // |timestamp_option|, |dm_token_option| and |device_id_option| have the same
+ // effect as the corresponding parameters for ValidateTimestamp(),
+ // ValidateDMToken() and ValidateDeviceId().
+ void ValidateAgainstCurrentPolicy(
+ const enterprise_management::PolicyData* policy_data,
+ ValidateTimestampOption timestamp_option,
+ ValidateDMTokenOption dm_token_option,
+ ValidateDeviceIdOption device_id_option);
+
+ // Immediately performs validation on the current thread.
+ void RunValidation();
+
+ // Verifies the SHA1/ or SHA256/RSA |signature| on |data| against |key|.
+ // |signature_type| specifies the type of signature (SHA1 or SHA256 ).
+ static bool VerifySignature(const std::string& data,
+ const std::string& key,
+ const std::string& signature,
+ SignatureType signature_type);
+
+ protected:
+ // Internal flags indicating what to check.
+ enum ValidationFlags {
+ VALIDATE_TIMESTAMP = 1 << 0,
+ VALIDATE_USER = 1 << 1,
+ VALIDATE_DOMAIN = 1 << 2,
+ VALIDATE_DM_TOKEN = 1 << 3,
+ VALIDATE_POLICY_TYPE = 1 << 4,
+ VALIDATE_ENTITY_ID = 1 << 5,
+ VALIDATE_PAYLOAD = 1 << 6,
+ VALIDATE_SIGNATURE = 1 << 7,
+ VALIDATE_INITIAL_KEY = 1 << 8,
+ VALIDATE_CACHED_KEY = 1 << 9,
+ VALIDATE_DEVICE_ID = 1 << 10,
+ VALIDATE_VALUES = 1 << 11,
+ VALIDATE_USERNAME = 1 << 12,
+ };
+
+ // Create a new validator that checks |policy_response|.
+ CloudPolicyValidatorBase(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_response,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+
+ // Posts an asynchronous call to PerformValidation of the passed |validator|,
+ // which will eventually report its result via |completion_callback|.
+ static void PostValidationTask(
+ std::unique_ptr<CloudPolicyValidatorBase> validator,
+ base::OnceClosure completion_callback);
+
+ // Helper to check MessageLite-type payloads. It exists so the implementation
+ // can be moved to the .cc (PolicyValidators with protobuf payloads are
+ // templated).
+ Status CheckProtoPayload(google::protobuf::MessageLite* payload);
+
+ std::vector<ValueValidationIssue> value_validation_issues_;
+
+ int validation_flags_;
+
+ private:
+ // Performs validation, called on a background thread.
+ static void PerformValidation(
+ std::unique_ptr<CloudPolicyValidatorBase> self,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure completion_callback);
+
+ // Reports completion to the |completion_callback_|.
+ static void ReportCompletion(std::unique_ptr<CloudPolicyValidatorBase> self,
+ base::OnceClosure completion_callback);
+
+ // Invokes all the checks and reports the result.
+ void RunChecks();
+
+ // Helper routine that verifies that the new public key in the policy blob
+ // is properly signed by the baked-in policy verification key.
+ bool CheckNewPublicKeyVerificationSignature();
+
+ // Helper routine that performs a verification-key-based signature check,
+ // which includes the domain name associated with this policy. Returns true
+ // if the verification succeeds, or if |signature| is empty.
+ bool CheckVerificationKeySignature(const std::string& key_to_verify,
+ const std::string& server_key,
+ const std::string& signature);
+
+ // Returns the domain name from the policy being validated. Returns an
+ // empty string if the policy does not contain a username field.
+ std::string ExtractDomainFromPolicy();
+
+ // Sets the owning domain used to verify new public keys, and ensures that
+ // callers don't try to set conflicting values.
+ void set_owning_domain(const std::string& owning_domain);
+
+ // Helper functions implementing individual checks.
+ Status CheckTimestamp();
+ Status CheckUser();
+ Status CheckDomain();
+ Status CheckDMToken();
+ Status CheckDeviceId();
+ Status CheckPolicyType();
+ Status CheckEntityId();
+ Status CheckSignature();
+ Status CheckInitialKey();
+ Status CheckCachedKey();
+
+ // Payload type and value validation depends on the validator, checking is
+ // part of derived classes.
+ virtual Status CheckPayload() = 0;
+ virtual Status CheckValues() = 0;
+
+ Status status_;
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy_;
+ std::unique_ptr<enterprise_management::PolicyData> policy_data_;
+
+ int64_t timestamp_not_before_;
+ ValidateTimestampOption timestamp_option_;
+ ValidateDMTokenOption dm_token_option_;
+ ValidateDeviceIdOption device_id_option_;
+ std::string username_;
+ std::string gaia_id_;
+ bool canonicalize_user_;
+ std::string domain_;
+ std::string dm_token_;
+ std::string device_id_;
+ std::string policy_type_;
+ std::string settings_entity_id_;
+ std::string key_;
+ std::string cached_key_;
+ std::string cached_key_signature_;
+ absl::optional<std::string> verification_key_;
+ std::string owning_domain_;
+ bool allow_key_rotation_;
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+};
+
+// A simple type-parameterized extension of CloudPolicyValidator that
+// facilitates working with the actual protobuf payload type.
+template <typename PayloadProto>
+class POLICY_EXPORT CloudPolicyValidator final
+ : public CloudPolicyValidatorBase {
+ public:
+ using CompletionCallback = base::OnceCallback<void(CloudPolicyValidator*)>;
+
+ // Creates a new validator.
+ // |background_task_runner| is optional; if RunValidation() is used directly
+ // and StartValidation() is not used then it can be nullptr.
+ CloudPolicyValidator(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_response,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+ : CloudPolicyValidatorBase(std::move(policy_response),
+ background_task_runner) {}
+ CloudPolicyValidator(const CloudPolicyValidator&) = delete;
+ CloudPolicyValidator& operator=(const CloudPolicyValidator&) = delete;
+
+ void ValidateValues(
+ std::unique_ptr<PolicyValueValidator<PayloadProto>> value_validator) {
+ validation_flags_ |= VALIDATE_VALUES;
+ value_validators_.push_back(std::move(value_validator));
+ }
+
+ std::unique_ptr<PayloadProto>& payload() { return payload_; }
+
+ // Kicks off asynchronous validation through |validator|.
+ // |completion_callback| is invoked when done.
+ static void StartValidation(std::unique_ptr<CloudPolicyValidator> validator,
+ CompletionCallback completion_callback) {
+ CloudPolicyValidator* const validator_ptr = validator.get();
+ PostValidationTask(
+ std::move(validator),
+ base::BindOnce(std::move(completion_callback), validator_ptr));
+ }
+
+ private:
+ // CloudPolicyValidatorBase:
+ Status CheckPayload() override { return CheckProtoPayload(payload_.get()); }
+ Status CheckValues() override {
+ for (const std::unique_ptr<PolicyValueValidator<PayloadProto>>&
+ value_validator : value_validators_) {
+ value_validator->ValidateValues(*payload_, &value_validation_issues_);
+ }
+ // TODO(hendrich,pmarko): https://crbug.com/794848
+ // Always return OK independent of value validation results for now. We only
+ // want to reject policy blobs on failed value validation sometime in the
+ // future.
+ return VALIDATION_OK;
+ }
+
+ std::unique_ptr<PayloadProto> payload_ = std::make_unique<PayloadProto>();
+
+ std::vector<std::unique_ptr<PolicyValueValidator<PayloadProto>>>
+ value_validators_;
+};
+
+using UserCloudPolicyValidator =
+ CloudPolicyValidator<enterprise_management::CloudPolicySettings>;
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+using ComponentCloudPolicyValidator =
+ CloudPolicyValidator<enterprise_management::ExternalPolicyData>;
+#endif
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_VALIDATOR_H_
diff --git a/chromium/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc b/chromium/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc
new file mode 100644
index 00000000000..c2dfeacc8b0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc
@@ -0,0 +1,533 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/rsa_private_key.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "base/command_line.h"
+#include "base/system/sys_info.h"
+#include "base/test/scoped_chromeos_version_info.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest-death-test.h"
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+namespace em = enterprise_management;
+
+using testing::Invoke;
+using testing::Mock;
+
+namespace policy {
+
+namespace {
+
+ACTION_P(CheckStatus, expected_status) {
+ EXPECT_EQ(expected_status, arg0->status());
+}
+
+const char kPolicyName[] = "fake-policy-name";
+const ValueValidationIssue::Severity kSeverity = ValueValidationIssue::kError;
+const char kMessage[] = "fake-message";
+
+class FakeUserPolicyValueValidator
+ : public PolicyValueValidator<em::CloudPolicySettings> {
+ public:
+ bool ValidateValues(
+ const enterprise_management::CloudPolicySettings& policy_payload,
+ std::vector<ValueValidationIssue>* validation_issues) const override {
+ validation_issues->push_back({kPolicyName, kSeverity, kMessage});
+ return false;
+ }
+};
+
+class CloudPolicyValidatorTest : public testing::Test {
+ public:
+ CloudPolicyValidatorTest()
+ : task_environment_(
+ base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
+ timestamp_(base::Time::FromJavaTime(PolicyBuilder::kFakeTimestamp)),
+ timestamp_option_(CloudPolicyValidatorBase::TIMESTAMP_VALIDATED),
+ dm_token_option_(CloudPolicyValidatorBase::DM_TOKEN_REQUIRED),
+ device_id_option_(CloudPolicyValidatorBase::DEVICE_ID_REQUIRED),
+ allow_key_rotation_(true),
+ existing_dm_token_(PolicyBuilder::kFakeToken),
+ existing_device_id_(PolicyBuilder::kFakeDeviceId),
+ owning_domain_(PolicyBuilder::kFakeDomain),
+ cached_key_signature_(PolicyBuilder::GetTestSigningKeySignature()),
+ validate_by_gaia_id_(true),
+ validate_values_(false) {
+ policy_.SetDefaultNewSigningKey();
+ }
+ CloudPolicyValidatorTest(const CloudPolicyValidatorTest&) = delete;
+ CloudPolicyValidatorTest& operator=(const CloudPolicyValidatorTest&) = delete;
+
+ void Validate(testing::Action<void(UserCloudPolicyValidator*)> check_action) {
+ policy_.Build();
+ ValidatePolicy(check_action, policy_.GetCopy());
+ }
+
+ void ValidatePolicy(
+ testing::Action<void(UserCloudPolicyValidator*)> check_action,
+ std::unique_ptr<em::PolicyFetchResponse> policy_response) {
+ // Create a validator.
+ std::unique_ptr<UserCloudPolicyValidator> validator =
+ CreateValidator(std::move(policy_response));
+
+ // Run validation and check the result.
+ EXPECT_CALL(*this, ValidationCompletion(validator.get()))
+ .WillOnce(check_action);
+ UserCloudPolicyValidator::StartValidation(
+ std::move(validator),
+ base::BindOnce(&CloudPolicyValidatorTest::ValidationCompletion,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
+ std::unique_ptr<em::PolicyFetchResponse> policy_response) {
+ std::string public_key = PolicyBuilder::GetPublicTestKeyAsString();
+ EXPECT_FALSE(public_key.empty());
+
+ auto validator = std::make_unique<UserCloudPolicyValidator>(
+ std::move(policy_response), base::ThreadTaskRunnerHandle::Get());
+ validator->ValidateTimestamp(timestamp_, timestamp_option_);
+ if (validate_by_gaia_id_) {
+ validator->ValidateUsernameAndGaiaId(
+ /*expected_user=*/std::string(), PolicyBuilder::kFakeGaiaId);
+ } else {
+ validator->ValidateUsername(PolicyBuilder::kFakeUsername);
+ }
+ if (!owning_domain_.empty())
+ validator->ValidateDomain(owning_domain_);
+ validator->ValidateDMToken(existing_dm_token_, dm_token_option_);
+ validator->ValidateDeviceId(existing_device_id_, device_id_option_);
+ validator->ValidatePolicyType(dm_protocol::kChromeUserPolicyType);
+ validator->ValidatePayload();
+ validator->ValidateCachedKey(public_key, cached_key_signature_,
+ owning_domain_);
+ if (allow_key_rotation_) {
+ validator->ValidateSignatureAllowingRotation(public_key, owning_domain_);
+ validator->ValidateInitialKey(owning_domain_);
+ } else {
+ validator->ValidateSignature(public_key);
+ }
+
+ if (validate_values_) {
+ validator->ValidateValues(
+ std::make_unique<FakeUserPolicyValueValidator>());
+ }
+
+ return validator;
+ }
+
+ void CheckSuccessfulValidation(UserCloudPolicyValidator* validator) {
+ EXPECT_TRUE(validator->success());
+ EXPECT_EQ(policy_.policy().SerializeAsString(),
+ validator->policy()->SerializeAsString());
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ validator->policy_data()->SerializeAsString());
+ EXPECT_EQ(policy_.payload().SerializeAsString(),
+ validator->payload()->SerializeAsString());
+ }
+
+ void CheckValueValidation(UserCloudPolicyValidator* validator) {
+ std::unique_ptr<CloudPolicyValidatorBase::ValidationResult>
+ validation_result = validator->GetValidationResult();
+ ASSERT_EQ(1u, validation_result->value_validation_issues.size());
+ const ValueValidationIssue& result =
+ validation_result->value_validation_issues[0];
+ EXPECT_EQ(kPolicyName, result.policy_name);
+ EXPECT_EQ(kSeverity, result.severity);
+ EXPECT_EQ(kMessage, result.message);
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ base::Time timestamp_;
+ CloudPolicyValidatorBase::ValidateTimestampOption timestamp_option_;
+ CloudPolicyValidatorBase::ValidateDMTokenOption dm_token_option_;
+ CloudPolicyValidatorBase::ValidateDeviceIdOption device_id_option_;
+ std::string signing_key_;
+ bool allow_key_rotation_;
+ std::string existing_dm_token_;
+ std::string existing_device_id_;
+ std::string owning_domain_;
+ std::string cached_key_signature_;
+ bool validate_by_gaia_id_;
+ bool validate_values_;
+
+ UserPolicyBuilder policy_;
+
+ private:
+ MOCK_METHOD1(ValidationCompletion, void(UserCloudPolicyValidator* validator));
+};
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(CloudPolicyValidatorTest,
+ SuccessfulValidationWithDisableKeyVerificationOnTestImage) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitch(switches::kDisablePolicyKeyVerification);
+ const char kLsbRelease[] =
+ "CHROMEOS_RELEASE_NAME=Chrome OS\n"
+ "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_TRACK=testimage-channel\n";
+ base::test::ScopedChromeOSVersionInfo version(kLsbRelease, base::Time());
+ EXPECT_TRUE(base::SysInfo::IsRunningOnChromeOS());
+
+ // Should not crash when creating a CloudPolicyValidator. Runs validation
+ // successfully.
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest,
+ CrashIfDisableKeyVerificationWithoutTestImage) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitch(switches::kDisablePolicyKeyVerification);
+ const char kLsbRelease[] =
+ "CHROMEOS_RELEASE_NAME=Chrome OS\n"
+ "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n";
+ base::test::ScopedChromeOSVersionInfo version(kLsbRelease, base::Time());
+ EXPECT_TRUE(base::SysInfo::IsRunningOnChromeOS());
+
+ // Should crash when creating a CloudPolicyValidator.
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ policy_.Build();
+ std::unique_ptr<UserCloudPolicyValidator> validator =
+ CreateValidator(policy_.GetCopy());
+ },
+ "");
+}
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulValidation) {
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulRunValidation) {
+ policy_.Build();
+ std::unique_ptr<UserCloudPolicyValidator> validator =
+ CreateValidator(policy_.GetCopy());
+ // Run validation immediately (no background tasks).
+ validator->RunValidation();
+ CheckSuccessfulValidation(validator.get());
+}
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulRunValidationWithNoExistingDMToken) {
+ existing_dm_token_.clear();
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulRunValidationWithNoDMTokens) {
+ existing_dm_token_.clear();
+ policy_.policy_data().clear_request_token();
+ dm_token_option_ = CloudPolicyValidatorBase::DM_TOKEN_NOT_REQUIRED;
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest,
+ SuccessfulRunValidationWithNoExistingDeviceId) {
+ existing_device_id_.clear();
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulRunValidationWithNoDeviceId) {
+ existing_device_id_.clear();
+ policy_.policy_data().clear_device_id();
+ device_id_option_ = CloudPolicyValidatorBase::DEVICE_ID_NOT_REQUIRED;
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest,
+ SuccessfulRunValidationWithTimestampFromTheFuture) {
+ base::Time timestamp(timestamp_ + base::Hours(3));
+ policy_.policy_data().set_timestamp(
+ (timestamp - base::Time::UnixEpoch()).InMilliseconds());
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_OK));
+}
+
+TEST_F(CloudPolicyValidatorTest, UsernameCanonicalization) {
+ policy_.policy_data().set_username(
+ base::ToUpperASCII(PolicyBuilder::kFakeUsername));
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_OK));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoPolicyType) {
+ policy_.policy_data().clear_policy_type();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_WRONG_POLICY_TYPE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorWrongPolicyType) {
+ policy_.policy_data().set_policy_type("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_WRONG_POLICY_TYPE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoTimestamp) {
+ policy_.policy_data().clear_timestamp();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_TIMESTAMP));
+}
+
+TEST_F(CloudPolicyValidatorTest, IgnoreMissingTimestamp) {
+ timestamp_option_ = CloudPolicyValidatorBase::TIMESTAMP_NOT_VALIDATED;
+ policy_.policy_data().clear_timestamp();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_OK));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorOldTimestamp) {
+ base::Time timestamp(timestamp_ - base::Minutes(5));
+ policy_.policy_data().set_timestamp(timestamp.ToJavaTime());
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_TIMESTAMP));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDMToken) {
+ policy_.policy_data().clear_request_token();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DM_TOKEN));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDMTokenNotRequired) {
+ // Even though DM tokens are not required, if the existing policy has a token,
+ // we should still generate an error if the new policy has none.
+ policy_.policy_data().clear_request_token();
+ dm_token_option_ = CloudPolicyValidatorBase::DM_TOKEN_NOT_REQUIRED;
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DM_TOKEN));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDMTokenNoTokenPassed) {
+ // Mimic the first fetch of policy (no existing DM token) - should still
+ // complain about not having any DM token.
+ existing_dm_token_.clear();
+ policy_.policy_data().clear_request_token();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DM_TOKEN));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidDMToken) {
+ policy_.policy_data().set_request_token("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DM_TOKEN));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDeviceId) {
+ policy_.policy_data().clear_device_id();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DEVICE_ID));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDeviceIdNotRequired) {
+ // Even though device ids are not required, if the existing policy has a
+ // device id, we should still generate an error if the new policy has none.
+ policy_.policy_data().clear_device_id();
+ device_id_option_ = CloudPolicyValidatorBase::DEVICE_ID_NOT_REQUIRED;
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DEVICE_ID));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoDeviceIdNoDeviceIdPassed) {
+ // Mimic the first fetch of policy (no existing device id) - should still
+ // complain about not having any device id.
+ existing_device_id_.clear();
+ policy_.policy_data().clear_device_id();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DEVICE_ID));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidDeviceId) {
+ policy_.policy_data().set_device_id("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_DEVICE_ID));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoPolicyValue) {
+ policy_.clear_payload();
+ Validate(
+ CheckStatus(CloudPolicyValidatorBase::VALIDATION_POLICY_PARSE_ERROR));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidPolicyValue) {
+ policy_.clear_payload();
+ policy_.policy_data().set_policy_value("invalid");
+ Validate(
+ CheckStatus(CloudPolicyValidatorBase::VALIDATION_POLICY_PARSE_ERROR));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoUsername) {
+ validate_by_gaia_id_ = false;
+ policy_.policy_data().clear_username();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_USER));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidUsername) {
+ validate_by_gaia_id_ = false;
+ policy_.policy_data().set_username("invalid@example.com");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_USER));
+}
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulByUsername) {
+ validate_by_gaia_id_ = false;
+ policy_.policy_data().clear_gaia_id();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_OK));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoGaiaId) {
+ policy_.policy_data().clear_gaia_id();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_USER));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidGaiaId) {
+ policy_.policy_data().set_gaia_id("other-gaia-id");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_USER));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorErrorMessage) {
+ policy_.policy().set_error_message("error");
+ Validate(
+ CheckStatus(CloudPolicyValidatorBase::VALIDATION_ERROR_CODE_PRESENT));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorErrorCode) {
+ policy_.policy().set_error_code(42);
+ Validate(
+ CheckStatus(CloudPolicyValidatorBase::VALIDATION_ERROR_CODE_PRESENT));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoSignature) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().clear_policy_data_signature();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidSignature) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().set_policy_data_signature("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoPublicKey) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().clear_new_public_key();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidPublicKey) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().set_new_public_key("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoPublicKeySignature) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().clear_new_public_key_signature();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidPublicKeySignature) {
+ policy_.UnsetSigningKey();
+ policy_.UnsetNewSigningKey();
+ policy_.policy().set_new_public_key_signature("invalid");
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+// Validation key is not currently checked on Chrome OS
+// (http://crbug.com/328038).
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidPublicKeyVerificationSignature) {
+ policy_.Build();
+ policy_.policy().set_new_public_key_verification_signature_deprecated(
+ "invalid");
+ ValidatePolicy(
+ CheckStatus(
+ CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE),
+ policy_.GetCopy());
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorDomainMismatchForKeyVerification) {
+ policy_.Build();
+ // Generate a non-matching owning_domain, which should cause a validation
+ // failure.
+ owning_domain_ = "invalid.com";
+ ValidatePolicy(
+ CheckStatus(
+ CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE),
+ policy_.GetCopy());
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorDomainExtractedFromUsernameMismatch) {
+ // Generate a non-matching username domain, which should cause a validation
+ // failure when we try to verify the signing key with it.
+ policy_.policy_data().set_username("wonky@invalid.com");
+ policy_.Build();
+ // Pass an empty domain to tell validator to extract the domain from the
+ // policy's |username| field.
+ owning_domain_ = "";
+ ValidatePolicy(
+ CheckStatus(
+ CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE),
+ policy_.GetCopy());
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoCachedKeySignature) {
+ // Generate an empty cached_key_signature_ and this should cause a validation
+ // error when we try to verify the signing key with it.
+ cached_key_signature_ = "";
+ Validate(CheckStatus(
+ CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorInvalidCachedKeySignature) {
+ // Generate a key signature for a different key (one that does not match
+ // the signing key) and this should cause a validation error when we try to
+ // verify the signing key with it.
+ cached_key_signature_ = PolicyBuilder::GetTestOtherSigningKeySignature();
+ Validate(CheckStatus(
+ CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE));
+}
+#endif
+
+TEST_F(CloudPolicyValidatorTest, SuccessfulNoDomainValidation) {
+ // Don't pass in a domain - this tells the validation code to instead
+ // extract the domain from the username.
+ owning_domain_ = "";
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckSuccessfulValidation));
+}
+
+TEST_F(CloudPolicyValidatorTest, ErrorNoRotationAllowed) {
+ allow_key_rotation_ = false;
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_BAD_SIGNATURE));
+}
+
+TEST_F(CloudPolicyValidatorTest, NoRotation) {
+ allow_key_rotation_ = false;
+ policy_.UnsetNewSigningKey();
+ Validate(CheckStatus(CloudPolicyValidatorBase::VALIDATION_OK));
+}
+
+TEST_F(CloudPolicyValidatorTest, ValueValidation) {
+ validate_values_ = true;
+ Validate(Invoke(this, &CloudPolicyValidatorTest::CheckValueValidation));
+}
+
+} // namespace
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_service.cc
new file mode 100644
index 00000000000..1961deb1fa0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service.cc
@@ -0,0 +1,548 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_updater.h"
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+using ComponentPolicyMap =
+ policy::ComponentCloudPolicyServiceObserver::ComponentPolicyMap;
+using ScopedResponseMap = std::unordered_map<policy::PolicyNamespace,
+ em::PolicyFetchResponse,
+ policy::PolicyNamespaceHash>;
+
+namespace policy {
+
+namespace {
+
+bool NotInResponseMap(const ScopedResponseMap& map,
+ PolicyDomain domain,
+ const std::string& component_id) {
+ return map.find(PolicyNamespace(domain, component_id)) == map.end();
+}
+
+bool ToPolicyNamespace(const std::pair<std::string, std::string>& key,
+ PolicyNamespace* ns) {
+ if (!ComponentCloudPolicyStore::GetPolicyDomain(key.first, &ns->domain))
+ return false;
+ ns->component_id = key.second;
+ return true;
+}
+
+} // namespace
+
+ComponentCloudPolicyService::Delegate::~Delegate() = default;
+
+// Owns the objects that live on the background thread, and posts back to the
+// thread that the ComponentCloudPolicyService runs on whenever the policy
+// changes.
+class ComponentCloudPolicyService::Backend
+ : public ComponentCloudPolicyStore::Delegate {
+ public:
+ // This class can be instantiated on any thread but from then on, may be
+ // accessed via the |task_runner_| only. Policy changes are posted to the
+ // |service| via the |service_task_runner|. The |cache| is used to load and
+ // store local copies of the downloaded policies.
+ Backend(
+ base::WeakPtr<ComponentCloudPolicyService> service,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<base::SequencedTaskRunner> service_task_runner,
+ std::unique_ptr<ResourceCache> cache,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ const std::string& policy_type);
+ Backend(const Backend&) = delete;
+ Backend& operator=(const Backend&) = delete;
+
+ ~Backend() override;
+
+ // Deletes all cached component policies from the store.
+ void ClearCache();
+
+ // The passed credentials will be used to validate the policies.
+ void SetCredentials(const std::string& username,
+ const std::string& gaia_id,
+ const std::string& dm_token,
+ const std::string& device_id,
+ const std::string& public_key,
+ int public_key_version);
+
+ // When called for the first time, loads the |store_| and exposes the loaded
+ // cached policies.
+ void InitIfNeeded();
+
+ // Passes a map with all the PolicyFetchResponses for components currently
+ // set at the server. Any components without an entry in |responses|
+ // will have their cache purged after this call.
+ // Otherwise the backend will start the validation and eventual download of
+ // the policy data for each PolicyFetchResponse in |responses|.
+ void SetFetchedPolicy(std::unique_ptr<ScopedResponseMap> responses);
+
+ // ComponentCloudPolicyStore::Delegate implementation:
+ void OnComponentCloudPolicyStoreUpdated() override;
+
+ private:
+ // Triggers an update of the policies from the last policy fetch response
+ // stored in |last_fetched_policies_|.
+ void UpdateWithLastFetchedPolicy();
+
+ // The ComponentCloudPolicyService that owns |this|. Used to inform the
+ // |service_| when policy changes.
+ base::WeakPtr<ComponentCloudPolicyService> service_;
+
+ // The thread that |this| runs on. Used to post tasks to be run by |this|.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // The thread that the |service_| runs on. Used to post policy changes to the
+ // right thread.
+ scoped_refptr<base::SequencedTaskRunner> service_task_runner_;
+
+ std::unique_ptr<ResourceCache> cache_;
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher_;
+ ComponentCloudPolicyStore store_;
+ std::unique_ptr<ComponentCloudPolicyUpdater> updater_;
+ bool initialized_ = false;
+ bool has_credentials_set_ = false;
+ std::unique_ptr<ScopedResponseMap> last_fetched_policy_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+ComponentCloudPolicyService::Backend::Backend(
+ base::WeakPtr<ComponentCloudPolicyService> service,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ scoped_refptr<base::SequencedTaskRunner> service_task_runner,
+ std::unique_ptr<ResourceCache> cache,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ const std::string& policy_type)
+ : service_(service),
+ task_runner_(task_runner),
+ service_task_runner_(service_task_runner),
+ cache_(std::move(cache)),
+ external_policy_data_fetcher_(std::move(external_policy_data_fetcher)),
+ store_(this, cache_.get(), policy_type) {
+ // This class is allowed to be instantiated on any thread.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+ComponentCloudPolicyService::Backend::~Backend() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void ComponentCloudPolicyService::Backend::ClearCache() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(1) << "Clearing cache";
+ store_.Clear();
+ has_credentials_set_ = false;
+}
+
+void ComponentCloudPolicyService::Backend::SetCredentials(
+ const std::string& username,
+ const std::string& gaia_id,
+ const std::string& dm_token,
+ const std::string& device_id,
+ const std::string& public_key,
+ int public_key_version) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!username.empty());
+ DCHECK(!dm_token.empty());
+ DVLOG(1) << "Updating credentials: username = " << username
+ << ", public_key_version = " << public_key_version;
+ store_.SetCredentials(username, gaia_id, dm_token, device_id, public_key,
+ public_key_version);
+ has_credentials_set_ = true;
+ // Trigger an additional update against the last fetched policies. This helps
+ // to deal with transient validation errors during signing key rotation, if
+ // the component cloud policy validation begins before the superior policy is
+ // validated and stored.
+ UpdateWithLastFetchedPolicy();
+}
+
+void ComponentCloudPolicyService::Backend::InitIfNeeded() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (initialized_)
+ return;
+
+ DVLOG(2) << "Initializing backend";
+
+ // Load the cached policy. Note that this does not trigger notifications
+ // through OnComponentCloudPolicyStoreUpdated. Note also that the cached
+ // data may contain names or values that don't match the schema for that
+ // component; the data must be cached without modifications so that its
+ // integrity can be verified using the hash, but it must also be filtered
+ // right after a Load().
+ store_.Load();
+
+ // Start downloading any pending data.
+ updater_ = std::make_unique<ComponentCloudPolicyUpdater>(
+ task_runner_, std::move(external_policy_data_fetcher_), &store_);
+
+ std::unique_ptr<PolicyBundle> bundle(std::make_unique<PolicyBundle>());
+ bundle->CopyFrom(store_.policy());
+ service_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &ComponentCloudPolicyService::SetPolicy, service_, std::move(bundle),
+ std::make_unique<ComponentPolicyMap>(store_.serialized_policy())));
+
+ initialized_ = true;
+
+ // Start processing the fetched policy, when there is some already.
+ UpdateWithLastFetchedPolicy();
+}
+
+void ComponentCloudPolicyService::Backend::SetFetchedPolicy(
+ std::unique_ptr<ScopedResponseMap> responses) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(2) << "Updating last fetched policies (count = " << responses->size()
+ << ")";
+ last_fetched_policy_ = std::move(responses);
+ UpdateWithLastFetchedPolicy();
+}
+
+void ComponentCloudPolicyService::Backend::
+ OnComponentCloudPolicyStoreUpdated() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!initialized_) {
+ // Ignore notifications triggered by the initial Purge or Clear.
+ return;
+ }
+ DVLOG(2) << "Installing updated policy from the component policy store";
+
+ std::unique_ptr<PolicyBundle> bundle(std::make_unique<PolicyBundle>());
+ bundle->CopyFrom(store_.policy());
+ service_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &ComponentCloudPolicyService::SetPolicy, service_, std::move(bundle),
+ std::make_unique<ComponentPolicyMap>(store_.serialized_policy())));
+}
+
+void ComponentCloudPolicyService::Backend::UpdateWithLastFetchedPolicy() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!has_credentials_set_ || !last_fetched_policy_ || !initialized_)
+ return;
+
+ DVLOG(1) << "Processing the last fetched policies (count = "
+ << last_fetched_policy_->size() << ")";
+
+ // Purge any components that don't have a policy configured at the server.
+ // Note that this is less secure than the data integrity validation, since
+ // at this point we can only rely on the TLS to prevent the tampering. The
+ // MITM attacker can trick the client into dropping policies for extensions
+ // (even though they can't inject malicious policies). See crbug.com/668733.
+ store_.Purge(
+ base::BindRepeating(&NotInResponseMap, std::cref(*last_fetched_policy_)));
+
+ for (auto it = last_fetched_policy_->begin();
+ it != last_fetched_policy_->end(); ++it) {
+ updater_->UpdateExternalPolicy(
+ it->first, std::make_unique<em::PolicyFetchResponse>(it->second));
+ }
+}
+
+ComponentCloudPolicyService::ComponentCloudPolicyService(
+ const std::string& policy_type,
+ Delegate* delegate,
+ SchemaRegistry* schema_registry,
+ CloudPolicyCore* core,
+ CloudPolicyClient* client,
+ std::unique_ptr<ResourceCache> cache,
+ scoped_refptr<base::SequencedTaskRunner> backend_task_runner)
+ : policy_type_(policy_type),
+ delegate_(delegate),
+ schema_registry_(schema_registry),
+ core_(core),
+ backend_task_runner_(backend_task_runner) {
+ DCHECK(policy_type == dm_protocol::kChromeExtensionPolicyType ||
+ policy_type ==
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType ||
+ policy_type == dm_protocol::kChromeSigninExtensionPolicyType);
+ CHECK(!core_->client());
+
+ backend_ = std::make_unique<Backend>(
+ weak_ptr_factory_.GetWeakPtr(), backend_task_runner_,
+ base::ThreadTaskRunnerHandle::Get(), std::move(cache),
+ std::make_unique<ExternalPolicyDataFetcher>(client->GetURLLoaderFactory(),
+ backend_task_runner_),
+ policy_type);
+
+ // Observe the schema registry for keeping |current_schema_map_| up to date.
+ schema_registry_->AddObserver(this);
+ UpdateFromSchemaRegistry();
+
+ // Observe the superior store load, so that the backend can get the cached
+ // credentials to validate the cached policies.
+ core_->store()->AddObserver(this);
+ if (core_->store()->is_initialized())
+ UpdateFromSuperiorStore();
+
+ core_->AddObserver(this);
+ client->AddObserver(this);
+
+ // Register the supported policy domain for being downloaded in future policy
+ // fetches.
+ client->AddPolicyTypeToFetch(policy_type_,
+ std::string() /* settings_entity_id */);
+}
+
+ComponentCloudPolicyService::~ComponentCloudPolicyService() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ for (auto& observer : observers_)
+ observer.OnComponentPolicyServiceDestruction(this);
+
+ schema_registry_->RemoveObserver(this);
+ core_->store()->RemoveObserver(this);
+ core_->RemoveObserver(this);
+ if (core_->client())
+ Disconnect();
+
+ backend_task_runner_->DeleteSoon(FROM_HERE, std::move(backend_));
+}
+
+// static
+bool ComponentCloudPolicyService::SupportsDomain(PolicyDomain domain) {
+ return ComponentCloudPolicyStore::SupportsDomain(domain);
+}
+
+void ComponentCloudPolicyService::ClearCache() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ backend_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Backend::ClearCache, base::Unretained(backend_.get())));
+}
+
+void ComponentCloudPolicyService::AddObserver(
+ ComponentCloudPolicyServiceObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+ // Pretend that the ComponentPolicyStore was updated so Backend triggers
+ // notification of all observers, including the newly added one.
+ backend_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&Backend::OnComponentCloudPolicyStoreUpdated,
+ base::Unretained(backend_.get())));
+}
+
+void ComponentCloudPolicyService::RemoveObserver(
+ ComponentCloudPolicyServiceObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+void ComponentCloudPolicyService::OnSchemaRegistryReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UpdateFromSchemaRegistry();
+}
+
+void ComponentCloudPolicyService::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UpdateFromSchemaRegistry();
+}
+
+void ComponentCloudPolicyService::OnCoreConnected(CloudPolicyCore* core) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(core_, core);
+ // Immediately update with any PolicyFetchResponses that the client may
+ // already have.
+ UpdateFromClient();
+}
+
+void ComponentCloudPolicyService::OnCoreDisconnecting(CloudPolicyCore* core) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(core_, core);
+ Disconnect();
+}
+
+void ComponentCloudPolicyService::OnRefreshSchedulerStarted(
+ CloudPolicyCore* core) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Ignored.
+}
+
+void ComponentCloudPolicyService::OnStoreLoaded(CloudPolicyStore* store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(core_->store(), store);
+ UpdateFromSuperiorStore();
+}
+
+void ComponentCloudPolicyService::OnStoreError(CloudPolicyStore* store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(core_->store(), store);
+ UpdateFromSuperiorStore();
+}
+
+void ComponentCloudPolicyService::OnPolicyFetched(CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(core_->client(), client);
+ UpdateFromClient();
+}
+
+void ComponentCloudPolicyService::OnRegistrationStateChanged(
+ CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Ignored; the registration state is tracked by looking at the
+ // CloudPolicyStore instead.
+}
+
+void ComponentCloudPolicyService::OnClientError(CloudPolicyClient* client) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Ignored.
+}
+
+void ComponentCloudPolicyService::UpdateFromSuperiorStore() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DVLOG(2) << "Obtaining credentials from the superior policy store";
+
+ const em::PolicyData* policy = core_->store()->policy();
+ if (!policy || !policy->has_username() || !policy->has_request_token()) {
+ // Clear the cache in case there is no policy or there are no credentials -
+ // e.g. when the user signs out.
+ backend_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Backend::ClearCache, base::Unretained(backend_.get())));
+ } else {
+ // Send the current credentials to the backend; do this whenever the store
+ // updates, to handle the case of the user registering for policy after the
+ // session starts.
+ std::string username = policy->username();
+ std::string gaia_id = policy->gaia_id();
+ std::string request_token = policy->request_token();
+ std::string device_id =
+ policy->has_device_id() ? policy->device_id() : std::string();
+ std::string public_key = core_->store()->policy_signature_public_key();
+ int public_key_version =
+ policy->has_public_key_version() ? policy->public_key_version() : -1;
+ backend_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&Backend::SetCredentials,
+ base::Unretained(backend_.get()), username,
+ gaia_id, request_token, device_id, public_key,
+ public_key_version));
+ }
+
+ // Initialize the backend to load the initial policy if not done yet,
+ // regardless of the signin state.
+ backend_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Backend::InitIfNeeded, base::Unretained(backend_.get())));
+}
+
+void ComponentCloudPolicyService::UpdateFromClient() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (core_->client()->responses().empty()) {
+ // The client's responses will be empty if it hasn't fetched policy from the
+ // DMServer yet. Make sure we don't purge the caches in this case.
+ return;
+ }
+
+ DVLOG(2) << "Obtaining fetched policies from the policy client";
+
+ std::unique_ptr<ScopedResponseMap> valid_responses =
+ std::make_unique<ScopedResponseMap>();
+ for (const auto& response : core_->client()->responses()) {
+ PolicyNamespace ns;
+ if (!ToPolicyNamespace(response.first, &ns)) {
+ DVLOG(1) << "Ignored policy with type = " << response.first.first;
+ continue;
+ }
+ (*valid_responses)[ns] = response.second;
+ }
+
+ backend_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&Backend::SetFetchedPolicy,
+ base::Unretained(backend_.get()),
+ std::move(valid_responses)));
+}
+
+void ComponentCloudPolicyService::UpdateFromSchemaRegistry() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!schema_registry_->IsReady()) {
+ // Ignore notifications from the registry which is not ready yet.
+ return;
+ }
+ DVLOG(2) << "Updating schema map";
+ current_schema_map_ = schema_registry_->schema_map();
+ FilterAndInstallPolicy();
+}
+
+void ComponentCloudPolicyService::Disconnect() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ core_->client()->RemoveObserver(this);
+
+ // Unregister the policy domain from being downloaded in the future policy
+ // fetches.
+ core_->client()->RemovePolicyTypeToFetch(
+ policy_type_, std::string() /* settings_entity_id */);
+}
+
+void ComponentCloudPolicyService::SetPolicy(
+ std::unique_ptr<PolicyBundle> policy,
+ std::unique_ptr<ComponentPolicyMap> serialized_policy) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Store the current unfiltered policies.
+ unfiltered_policy_ = std::move(policy);
+
+ NotifyComponentPolicyUpdated(std::move(serialized_policy));
+ FilterAndInstallPolicy();
+}
+
+void ComponentCloudPolicyService::FilterAndInstallPolicy() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!unfiltered_policy_ || !current_schema_map_)
+ return;
+
+ // Make a copy in |policy_| and filter it and validate against the schemas;
+ // this is what's passed to the outside world.
+ policy_.CopyFrom(*unfiltered_policy_);
+ current_schema_map_->FilterBundle(&policy_,
+ /*drop_invalid_component_policies=*/false);
+
+ policy_installed_ = true;
+ DVLOG(1) << "Installed policy (count = "
+ << std::distance(policy_.begin(), policy_.end()) << ")";
+ delegate_->OnComponentCloudPolicyUpdated();
+}
+
+void ComponentCloudPolicyService::NotifyComponentPolicyUpdated(
+ std::unique_ptr<ComponentPolicyMap> serialized_policy) {
+ for (auto& observer : observers_) {
+ observer.OnComponentPolicyUpdated(*serialized_policy);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service.h b/chromium/components/policy/core/common/cloud/component_cloud_policy_service.h
new file mode 100644
index 00000000000..b705ad47e52
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service.h
@@ -0,0 +1,198 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_core.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_service_observer.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class ResourceCache;
+class SchemaMap;
+
+// Manages cloud policy for components (currently used for device local accounts
+// and policy for extensions --> go/cros-ent-p4ext-dd).
+//
+// This class takes care of fetching, validating, storing and updating policy
+// for components.
+//
+// Note that the policies for all components, as returned by the server, are
+// downloaded and cached, regardless of the current state of the schema
+// registry. However, exposed are only the policies whose components are present
+// in the schema registry.
+//
+// The exposed policies are guaranteed to be conformant to the corresponding
+// schemas. Values that do not pass validation against the schema are dropped.
+class POLICY_EXPORT ComponentCloudPolicyService
+ : public CloudPolicyClient::Observer,
+ public CloudPolicyCore::Observer,
+ public CloudPolicyStore::Observer,
+ public SchemaRegistry::Observer {
+ public:
+ class POLICY_EXPORT Delegate {
+ public:
+ virtual ~Delegate();
+
+ // Invoked whenever the policy served by policy() changes. This is also
+ // invoked for the first time once the backend is initialized, and
+ // is_initialized() becomes true.
+ virtual void OnComponentCloudPolicyUpdated() = 0;
+ };
+
+ // |policy_type| specifies the policy type that should be fetched. The only
+ // allowed values are: |dm_protocol::kChromeExtensionPolicyType|,
+ // |dm_protocol::kChromeSigninExtensionPolicyType|.
+ //
+ // The |delegate| is notified of updates to the downloaded policies and must
+ // outlive this object.
+ //
+ // |schema_registry| is used to filter the fetched policies against the list
+ // of installed extensions and to validate the policies against corresponding
+ // schemas. It must outlive this object.
+ //
+ // |core| is used to obtain the CloudPolicyStore and CloudPolicyClient used
+ // by this service. The store will be the source of the registration status
+ // and registration credentials; the client will be used to fetch cloud
+ // policy. It must outlive this object.
+ //
+ // The |core| MUST not be connected yet when this service is created;
+ // |client| must be the client that will be connected to the |core|. This
+ // is important to make sure that this service appends any necessary policy
+ // fetch types to the |client| before the |core| gets connected and before
+ // the initial policy fetch request is sent out.
+ //
+ // |cache| is used to load and store local copies of the downloaded policies.
+ //
+ // Download scheduling, validation and caching of policies are done via the
+ // |backend_task_runner|, which must support file I/O.
+ ComponentCloudPolicyService(
+ const std::string& policy_type,
+ Delegate* delegate,
+ SchemaRegistry* schema_registry,
+ CloudPolicyCore* core,
+ CloudPolicyClient* client,
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+ std::unique_ptr<ResourceCache> cache,
+#endif
+ scoped_refptr<base::SequencedTaskRunner> backend_task_runner);
+ ComponentCloudPolicyService(const ComponentCloudPolicyService&) = delete;
+ ComponentCloudPolicyService& operator=(const ComponentCloudPolicyService&) =
+ delete;
+ ~ComponentCloudPolicyService() override;
+
+ // Returns true if |domain| is supported by the service.
+ static bool SupportsDomain(PolicyDomain domain);
+
+ // Returns true if the backend is initialized, and the initial policies are
+ // being served.
+ bool is_initialized() const { return policy_installed_; }
+
+ // Returns the current policies for components.
+ const PolicyBundle& policy() const { return policy_; }
+
+ // Add/Remove observer to notify about component policy changes. AddObserver
+ // triggers an OnComponentPolicyUpdated notification to be posted to the newly
+ // added observer.
+ void AddObserver(ComponentCloudPolicyServiceObserver* observer);
+ void RemoveObserver(ComponentCloudPolicyServiceObserver* observer);
+
+ // Deletes all the cached component policy.
+ void ClearCache();
+
+ // SchemaRegistry::Observer implementation:
+ void OnSchemaRegistryReady() override;
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
+ // CloudPolicyCore::Observer implementation:
+ void OnCoreConnected(CloudPolicyCore* core) override;
+ void OnCoreDisconnecting(CloudPolicyCore* core) override;
+ void OnRefreshSchedulerStarted(CloudPolicyCore* core) override;
+
+ // CloudPolicyStore::Observer implementation:
+ void OnStoreLoaded(CloudPolicyStore* store) override;
+ void OnStoreError(CloudPolicyStore* store) override;
+
+ // CloudPolicyClient::Observer implementation:
+ void OnPolicyFetched(CloudPolicyClient* client) override;
+ void OnRegistrationStateChanged(CloudPolicyClient* client) override;
+ void OnClientError(CloudPolicyClient* client) override;
+
+ private:
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+ class Backend;
+
+ void UpdateFromSuperiorStore();
+ void UpdateFromClient();
+ void UpdateFromSchemaRegistry();
+ void Disconnect();
+ void SetPolicy(
+ std::unique_ptr<PolicyBundle> policy,
+ std::unique_ptr<ComponentCloudPolicyServiceObserver::ComponentPolicyMap>
+ serialized_policy);
+ void FilterAndInstallPolicy();
+ void NotifyComponentPolicyUpdated(
+ std::unique_ptr<ComponentCloudPolicyServiceObserver::ComponentPolicyMap>
+ serialized_policy);
+
+ std::string policy_type_;
+ raw_ptr<Delegate> delegate_;
+ raw_ptr<SchemaRegistry> schema_registry_;
+ raw_ptr<CloudPolicyCore> core_;
+ scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;
+
+ // The |backend_| handles all download scheduling, validation and caching of
+ // policies. It is instantiated on the thread |this| runs on but after that,
+ // must only be accessed and eventually destroyed via the
+ // |backend_task_runner_|.
+ std::unique_ptr<Backend> backend_;
+
+ // The currently registered components for each policy domain. Used for
+ // filtering and validation of the component policies.
+ scoped_refptr<SchemaMap> current_schema_map_;
+#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+
+ // Contains all the policies loaded from the store, before having been
+ // filtered and validated by the |current_schema_map_|.
+ std::unique_ptr<PolicyBundle> unfiltered_policy_;
+
+ // Contains all the current policies for components, filtered and validated by
+ // the |current_schema_map_|.
+ PolicyBundle policy_;
+
+ // Whether policies are being served.
+ bool policy_installed_ = false;
+
+ base::ObserverList<ComponentCloudPolicyServiceObserver> observers_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Must be the last member.
+ base::WeakPtrFactory<ComponentCloudPolicyService> weak_ptr_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service_observer.h b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_observer.h
new file mode 100644
index 00000000000..80562a53cc8
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_observer.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_OBSERVER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_OBSERVER_H_
+
+#include "components/policy/core/common/policy_namespace.h"
+
+namespace policy {
+
+class ComponentCloudPolicyService;
+
+// Callbacks for policy store events used by ComponentCloudPolicyService.
+class POLICY_EXPORT ComponentCloudPolicyServiceObserver
+ : public base::CheckedObserver {
+ public:
+ using ComponentPolicyMap =
+ base::flat_map<policy::PolicyNamespace, std::vector<uint8_t>>;
+
+ ~ComponentCloudPolicyServiceObserver() override = default;
+
+ // Called on changes to store->policy() and/or store->policy_map(). The
+ // values in the `serialized_policy` map are the serialized
+ // PolicyFetchResponse objects received from the server.
+ virtual void OnComponentPolicyUpdated(
+ const ComponentPolicyMap& serialized_policy) = 0;
+ virtual void OnComponentPolicyServiceDestruction(
+ ComponentCloudPolicyService* service) = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_SERVICE_OBSERVER_H_
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc
new file mode 100644
index 00000000000..abbaf24fbbd
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc
@@ -0,0 +1,52 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+
+#include "base/task/sequenced_task_runner.h"
+
+namespace policy {
+
+ComponentCloudPolicyService::Delegate::~Delegate() {}
+
+ComponentCloudPolicyService::ComponentCloudPolicyService(
+ const std::string& policy_type,
+ Delegate* delegate,
+ SchemaRegistry* schema_registry,
+ CloudPolicyCore* core,
+ CloudPolicyClient* client,
+ scoped_refptr<base::SequencedTaskRunner> backend_task_runner)
+ : policy_installed_(true), weak_ptr_factory_(this) {}
+
+ComponentCloudPolicyService::~ComponentCloudPolicyService() {}
+
+// static
+bool ComponentCloudPolicyService::SupportsDomain(PolicyDomain domain) {
+ return false;
+}
+
+void ComponentCloudPolicyService::OnSchemaRegistryReady() {}
+
+void ComponentCloudPolicyService::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {}
+
+void ComponentCloudPolicyService::OnCoreConnected(CloudPolicyCore* core) {}
+
+void ComponentCloudPolicyService::OnCoreDisconnecting(CloudPolicyCore* core) {}
+
+void ComponentCloudPolicyService::OnRefreshSchedulerStarted(
+ CloudPolicyCore* core) {}
+
+void ComponentCloudPolicyService::OnStoreLoaded(CloudPolicyStore* store) {}
+
+void ComponentCloudPolicyService::OnStoreError(CloudPolicyStore* store) {}
+
+void ComponentCloudPolicyService::OnPolicyFetched(CloudPolicyClient* client) {}
+
+void ComponentCloudPolicyService::OnRegistrationStateChanged(
+ CloudPolicyClient* client) {}
+
+void ComponentCloudPolicyService::OnClientError(CloudPolicyClient* client) {}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc
new file mode 100644
index 00000000000..1dd0581e4c9
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc
@@ -0,0 +1,687 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/containers/contains.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/sha2.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace em = enterprise_management;
+
+using testing::AtLeast;
+using testing::Mock;
+using testing::Return;
+
+namespace policy {
+
+namespace {
+
+const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char kTestExtension2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+const char kTestDownload[] = "http://example.com/getpolicy?id=123";
+
+const char kTestPolicy[] =
+ "{"
+ " \"Name\": {"
+ " \"Value\": \"disabled\""
+ " },"
+ " \"Second\": {"
+ " \"Value\": \"maybe\","
+ " \"Level\": \"Recommended\""
+ " }"
+ "}";
+
+const char kInvalidTestPolicy[] =
+ "{"
+ " \"Name\": {"
+ " \"Value\": \"published\""
+ " },"
+ " \"Undeclared Name\": {"
+ " \"Value\": \"not published\""
+ " }"
+ "}";
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"Name\": { \"type\": \"string\" },"
+ " \"Second\": { \"type\": \"string\" }"
+ " }"
+ "}";
+
+class MockComponentCloudPolicyDelegate
+ : public ComponentCloudPolicyService::Delegate {
+ public:
+ ~MockComponentCloudPolicyDelegate() override {}
+
+ MOCK_METHOD0(OnComponentCloudPolicyUpdated, void());
+};
+
+} // namespace
+
+class ComponentCloudPolicyServiceTest : public testing::Test {
+ protected:
+ ComponentCloudPolicyServiceTest()
+ : cache_(nullptr),
+ client_(nullptr),
+ core_(dm_protocol::kChromeUserPolicyType,
+ std::string(),
+ &store_,
+ base::ThreadTaskRunnerHandle::Get(),
+ network::TestNetworkConnectionTracker::CreateGetter()) {
+ builder_.SetDefaultSigningKey();
+ builder_.policy_data().set_policy_type(
+ dm_protocol::kChromeExtensionPolicyType);
+ builder_.policy_data().set_settings_entity_id(kTestExtension);
+ builder_.payload().set_download_url(kTestDownload);
+ builder_.payload().set_secure_hash(crypto::SHA256HashString(kTestPolicy));
+
+ public_key_ = builder_.GetPublicSigningKeyAsString();
+
+ expected_policy_.Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("disabled"), nullptr);
+ expected_policy_.Set("Second", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("maybe"), nullptr);
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ owned_cache_ = std::make_unique<ResourceCache>(
+ temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ /* max_cache_size */ absl::nullopt);
+ cache_ = owned_cache_.get();
+ }
+
+ void TearDown() override {
+ // The service cleans up its backend on the background thread.
+ service_.reset();
+ RunUntilIdle();
+ }
+
+ void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
+
+ void Connect() {
+ client_ = new MockCloudPolicyClient(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &loader_factory_));
+ service_ = std::make_unique<ComponentCloudPolicyService>(
+ dm_protocol::kChromeExtensionPolicyType, &delegate_, &registry_, &core_,
+ client_, std::move(owned_cache_), base::ThreadTaskRunnerHandle::Get());
+
+ client_->SetDMToken(ComponentCloudPolicyBuilder::kFakeToken);
+ EXPECT_EQ(1u, client_->types_to_fetch_.size());
+ core_.Connect(std::unique_ptr<CloudPolicyClient>(client_));
+ EXPECT_EQ(2u, client_->types_to_fetch_.size());
+
+ // Also initialize the refresh scheduler, so that calls to
+ // core()->RefreshSoon() trigger a FetchPolicy() call on the mock |client_|.
+ // The |service_| should never trigger new fetches.
+ EXPECT_CALL(*client_, FetchPolicy());
+ core_.StartRefreshScheduler();
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ }
+
+ void LoadStore() {
+ auto policy_data = std::make_unique<em::PolicyData>();
+ policy_data->set_username(PolicyBuilder::kFakeUsername);
+ policy_data->set_request_token(PolicyBuilder::kFakeToken);
+ policy_data->set_device_id(PolicyBuilder::kFakeDeviceId);
+ policy_data->set_public_key_version(PolicyBuilder::kFakePublicKeyVersion);
+ store_.set_policy_data_for_testing(std::move(policy_data));
+ store_.policy_signature_public_key_ = public_key_;
+ store_.NotifyStoreLoaded();
+ RunUntilIdle();
+ EXPECT_TRUE(store_.is_initialized());
+ }
+
+ void InitializeRegistry() {
+ registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension),
+ CreateTestSchema());
+ registry_.SetAllDomainsReady();
+ }
+
+ void PopulateCache() {
+ EXPECT_FALSE(cache_
+ ->Store("extension-policy", kTestExtension,
+ CreateSerializedResponse())
+ .empty());
+ EXPECT_FALSE(
+ cache_->Store("extension-policy-data", kTestExtension, kTestPolicy)
+ .empty());
+
+ builder_.policy_data().set_settings_entity_id(kTestExtension2);
+ EXPECT_FALSE(cache_
+ ->Store("extension-policy", kTestExtension2,
+ CreateSerializedResponse())
+ .empty());
+ EXPECT_FALSE(
+ cache_->Store("extension-policy-data", kTestExtension2, kTestPolicy)
+ .empty());
+ builder_.policy_data().set_settings_entity_id(kTestExtension);
+ }
+
+ std::unique_ptr<em::PolicyFetchResponse> CreateResponse() {
+ builder_.Build();
+ return std::make_unique<em::PolicyFetchResponse>(builder_.policy());
+ }
+
+ std::string CreateSerializedResponse() {
+ builder_.Build();
+ return builder_.GetBlob();
+ }
+
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ EXPECT_TRUE(schema.valid()) << error;
+ return schema;
+ }
+
+ const PolicyNamespace kTestExtensionNS =
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension);
+ const PolicyNamespace kTestExtensionNS2 =
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension2);
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+ network::TestURLLoaderFactory loader_factory_;
+ MockComponentCloudPolicyDelegate delegate_;
+ // |cache_| is owned by the |service_| and is invalid once the |service_|
+ // is destroyed.
+ std::unique_ptr<ResourceCache> owned_cache_;
+ raw_ptr<ResourceCache> cache_;
+ raw_ptr<MockCloudPolicyClient> client_;
+ MockCloudPolicyStore store_;
+ CloudPolicyCore core_;
+ SchemaRegistry registry_;
+ std::unique_ptr<ComponentCloudPolicyService> service_;
+ ComponentCloudPolicyBuilder builder_;
+ PolicyMap expected_policy_;
+ std::string public_key_;
+};
+
+TEST_F(ComponentCloudPolicyServiceTest, InitializeStoreThenRegistry) {
+ Connect();
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(0);
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_FALSE(service_->is_initialized());
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ InitializeRegistry();
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_TRUE(service_->is_initialized());
+
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(service_->policy().Equals(empty_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, InitializeRegistryThenStore) {
+ Connect();
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(0);
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ InitializeRegistry();
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_FALSE(service_->is_initialized());
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_TRUE(service_->is_initialized());
+ EXPECT_EQ(2u, client_->types_to_fetch_.size());
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(service_->policy().Equals(empty_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, InitializeWithCachedPolicy) {
+ PopulateCache();
+ Connect();
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ InitializeRegistry();
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ EXPECT_TRUE(service_->is_initialized());
+ EXPECT_EQ(2u, client_->types_to_fetch_.size());
+
+ // Policies for both extensions are stored in the cache.
+ std::map<std::string, std::string> contents;
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ ASSERT_EQ(2u, contents.size());
+ EXPECT_TRUE(base::Contains(contents, kTestExtension));
+ EXPECT_TRUE(base::Contains(contents, kTestExtension2));
+
+ // Policy for extension 1 is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+
+ // Register extension 2. Its policy gets loaded without any additional
+ // policy fetches.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema());
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // Policies for both extensions are being served now.
+ expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, FetchPolicy) {
+ Connect();
+ // Initialize the store. A refresh is not needed, because no components are
+ // registered yet.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ registry_.SetAllDomainsReady();
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_TRUE(service_->is_initialized());
+
+ // Register the components to fetch. The |service_| issues a new update
+ // because the new schema may filter different policies from the store.
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // Send back a fake policy fetch response.
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+
+ // That should have triggered the download fetch.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The policy is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, FetchPolicyBeforeStoreLoaded) {
+ Connect();
+ // A fake policy is fetched.
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+
+ // Initialize the store. A refresh is not needed, because no components are
+ // registered yet.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ registry_.SetAllDomainsReady();
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_TRUE(service_->is_initialized());
+
+ // Register the components to fetch. The |service_| issues a new update
+ // because the new schema may filter different policies from the store.
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The download started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The policy is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest,
+ FetchPolicyWithCachedPolicyBeforeStoreLoaded) {
+ PopulateCache();
+ Connect();
+ // A fake policy is fetched.
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+
+ // Initialize the store.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated()).Times(AtLeast(1));
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ InitializeRegistry();
+ LoadStore();
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+ EXPECT_TRUE(service_->is_initialized());
+
+ // Only policy for extension 1 is served. Policy for extension 2, which was in
+ // the cache initially, is now dropped.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, LoadCacheAndDeleteExtensions) {
+ Connect();
+ // Insert data in the cache.
+ PopulateCache();
+ registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema());
+ InitializeRegistry();
+
+ // Load the initial cache.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+
+ // Now purge one of the extensions. This generates a notification after an
+ // immediate filtering.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ registry_.UnregisterComponent(kTestExtensionNS);
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The policy served for extension 1 becomes empty.
+ expected_bundle.Get(kTestExtensionNS).Clear();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+
+ // The cache still keeps policies for both extensions.
+ std::map<std::string, std::string> contents;
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_TRUE(base::Contains(contents, kTestExtension));
+ EXPECT_TRUE(base::Contains(contents, kTestExtension2));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, SignInAfterStartup) {
+ registry_.SetAllDomainsReady();
+
+ // Initialize the store without credentials.
+ EXPECT_FALSE(store_.is_initialized());
+ store_.NotifyStoreLoaded();
+ RunUntilIdle();
+
+ // Register an extension.
+ registry_.RegisterComponent(kTestExtensionNS, CreateTestSchema());
+ RunUntilIdle();
+
+ // Now signin. The service will finish loading its backend (which is empty
+ // for now, because there are no credentials) and issue a notification.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ Connect();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // Send the response to the service. The response data will be ignored,
+ // because the store doesn't have the updated credentials yet.
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+
+ // The policy was ignored and no download is started because the store
+ // doesn't have credentials.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+
+ // Now update the |store_| with the updated policy, which includes
+ // credentials. The responses in the |client_| will be reloaded.
+ LoadStore();
+
+ // The extension policy was validated this time, and the download is started.
+ ASSERT_EQ(1, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The policy is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, SignOut) {
+ // Initialize everything and serve policy for a component.
+ PopulateCache();
+ LoadStore();
+ InitializeRegistry();
+
+ // The initial, cached policy will be served once the backend is initialized.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ Connect();
+ Mock::VerifyAndClearExpectations(&delegate_);
+ // Policy for extension 1 is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+ // The cache still contains policies for both extensions.
+ std::map<std::string, std::string> contents;
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ ASSERT_EQ(2u, contents.size());
+
+ // Signing out removes all of the component policies from the service and
+ // from the cache. It does not trigger a refresh.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ core_.Disconnect();
+ store_.set_policy_data_for_testing(std::make_unique<em::PolicyData>());
+ Mock::VerifyAndClearExpectations(&store_);
+ store_.NotifyStoreLoaded();
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(service_->policy().Equals(empty_bundle));
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ ASSERT_EQ(0u, contents.size());
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, LoadInvalidPolicyFromCache) {
+ // Put the invalid test policy in the cache. One of its policies will be
+ // loaded, the other should be filtered out by the schema.
+ builder_.payload().set_secure_hash(
+ crypto::SHA256HashString(kInvalidTestPolicy));
+ EXPECT_FALSE(cache_
+ ->Store("extension-policy", kTestExtension,
+ CreateSerializedResponse())
+ .empty());
+ EXPECT_FALSE(
+ cache_->Store("extension-policy-data", kTestExtension, kInvalidTestPolicy)
+ .empty());
+
+ LoadStore();
+ InitializeRegistry();
+
+ // The initial, cached policy will be served once the backend is initialized.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ Connect();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS)
+ .Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("published"), nullptr);
+ // The second policy should be invalid.
+ expected_bundle.Get(kTestExtensionNS)
+ .Set("Undeclared Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("not published"), nullptr);
+ expected_bundle.Get(kTestExtensionNS)
+ .GetMutable("Undeclared Name")
+ ->SetInvalid();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, PurgeWhenServerRemovesPolicy) {
+ // Initialize with cached policy.
+ PopulateCache();
+ Connect();
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ EXPECT_CALL(*client_, FetchPolicy()).Times(0);
+ registry_.RegisterComponent(kTestExtensionNS2, CreateTestSchema());
+ InitializeRegistry();
+ LoadStore();
+ Mock::VerifyAndClearExpectations(client_);
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ EXPECT_TRUE(service_->is_initialized());
+ EXPECT_EQ(2u, client_->types_to_fetch_.size());
+
+ // Verify that policy for 2 extensions has been loaded from the cache.
+ std::map<std::string, std::string> contents;
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ ASSERT_EQ(2u, contents.size());
+ EXPECT_TRUE(base::Contains(contents, kTestExtension));
+ EXPECT_TRUE(base::Contains(contents, kTestExtension2));
+
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ expected_bundle.Get(kTestExtensionNS2) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+
+ // Receive an updated fetch response from the server. There is no response for
+ // extension 2, so it will be dropped from the cache. This triggers an
+ // immediate notification to the delegate.
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The cache should have dropped the entries for the second extension.
+ contents.clear();
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_TRUE(base::Contains(contents, kTestExtension));
+ EXPECT_FALSE(base::Contains(contents, kTestExtension2));
+
+ // And the service isn't publishing policy for the second extension anymore.
+ expected_bundle.Clear();
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+TEST_F(ComponentCloudPolicyServiceTest, KeyRotation) {
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ Connect();
+ LoadStore();
+ InitializeRegistry();
+ RunUntilIdle();
+ EXPECT_TRUE(service_->is_initialized());
+
+ // Send back a fake policy fetch response with the new signing key.
+ const int kNewPublicKeyVersion = PolicyBuilder::kFakePublicKeyVersion + 1;
+ std::unique_ptr<crypto::RSAPrivateKey> new_signing_key =
+ PolicyBuilder::CreateTestOtherSigningKey();
+ builder_.SetSigningKey(*new_signing_key);
+ builder_.policy_data().set_public_key_version(kNewPublicKeyVersion);
+ client_->SetPolicy(dm_protocol::kChromeExtensionPolicyType, kTestExtension,
+ *CreateResponse());
+ service_->OnPolicyFetched(client_);
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_);
+
+ // The download fetch shouldn't have been triggered, as the component policy
+ // validation should fail due to the old key returned by the store.
+ ASSERT_EQ(0, loader_factory_.NumPending());
+
+ // Update the signing key in the store.
+ store_.policy_signature_public_key_ = builder_.GetPublicSigningKeyAsString();
+ auto policy_data = std::make_unique<em::PolicyData>(*store_.policy());
+ policy_data->set_public_key_version(kNewPublicKeyVersion);
+ store_.set_policy_data_for_testing(std::move(policy_data));
+ store_.NotifyStoreLoaded();
+ RunUntilIdle();
+
+ // The validation of the component policy should have finished successfully
+ // and trigger the download fetch.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+
+ EXPECT_CALL(delegate_, OnComponentCloudPolicyUpdated());
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&delegate_);
+
+ // The policy is now being served.
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(kTestExtensionNS) = expected_policy_.Clone();
+ EXPECT_TRUE(service_->policy().Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_store.cc
new file mode 100644
index 00000000000..60d7925c4bb
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_store.cc
@@ -0,0 +1,466 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_store.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/sha2.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "url/gurl.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+struct ComponentCloudPolicyStore::DomainConstants {
+ const char* policy_type;
+ const PolicyDomain domain;
+ const PolicyScope scope;
+ const char* proto_cache_key;
+ const char* data_cache_key;
+};
+
+namespace {
+
+const char kValue[] = "Value";
+const char kLevel[] = "Level";
+const char kRecommended[] = "Recommended";
+
+const ComponentCloudPolicyStore::DomainConstants kDomains[] = {
+ {
+ dm_protocol::kChromeExtensionPolicyType,
+ POLICY_DOMAIN_EXTENSIONS,
+ POLICY_SCOPE_USER,
+ "extension-policy",
+ "extension-policy-data",
+ },
+ {
+ dm_protocol::kChromeSigninExtensionPolicyType,
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS,
+ POLICY_SCOPE_USER,
+ "signinextension-policy",
+ "signinextension-policy-data",
+ },
+ {
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+ POLICY_DOMAIN_EXTENSIONS,
+ POLICY_SCOPE_MACHINE,
+ "extension-policy",
+ "extension-policy-data",
+ },
+};
+
+const ComponentCloudPolicyStore::DomainConstants* GetDomainConstantsForType(
+ const std::string& type) {
+ for (const ComponentCloudPolicyStore::DomainConstants& constants : kDomains) {
+ if (constants.policy_type == type)
+ return &constants;
+ }
+ return nullptr;
+}
+
+} // namespace
+
+ComponentCloudPolicyStore::Delegate::~Delegate() = default;
+
+ComponentCloudPolicyStore::ComponentCloudPolicyStore(
+ Delegate* delegate,
+ ResourceCache* cache,
+ const std::string& policy_type)
+ : delegate_(delegate),
+ cache_(cache),
+ domain_constants_(GetDomainConstantsForType(policy_type)) {
+ // Allow the store to be created on a different thread than the thread that
+ // will end up using it.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ DCHECK(domain_constants_);
+}
+
+ComponentCloudPolicyStore::~ComponentCloudPolicyStore() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+// static
+bool ComponentCloudPolicyStore::SupportsDomain(PolicyDomain domain) {
+ for (const DomainConstants& constants : kDomains) {
+ if (constants.domain == domain)
+ return true;
+ }
+ return false;
+}
+
+// static
+bool ComponentCloudPolicyStore::GetPolicyDomain(const std::string& policy_type,
+ PolicyDomain* domain) {
+ const DomainConstants* constants = GetDomainConstantsForType(policy_type);
+ if (!constants)
+ return false;
+ *domain = constants->domain;
+ return true;
+}
+
+const std::string& ComponentCloudPolicyStore::GetCachedHash(
+ const PolicyNamespace& ns) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = cached_hashes_.find(ns);
+ return it == cached_hashes_.end() ? base::EmptyString() : it->second;
+}
+
+void ComponentCloudPolicyStore::SetCredentials(const std::string& username,
+ const std::string& gaia_id,
+ const std::string& dm_token,
+ const std::string& device_id,
+ const std::string& public_key,
+ int public_key_version) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(username_.empty() || username == username_);
+ DCHECK(dm_token_.empty() || dm_token == dm_token_);
+ DCHECK(device_id_.empty() || device_id == device_id_);
+ username_ = username;
+ gaia_id_ = gaia_id;
+ dm_token_ = dm_token;
+ device_id_ = device_id;
+ public_key_ = public_key;
+ public_key_version_ = public_key_version;
+}
+
+void ComponentCloudPolicyStore::Load() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ typedef std::map<std::string, std::string> ContentMap;
+
+ // Load cached policy protobuf for the assoicated domain.
+ ContentMap protos;
+ cache_->LoadAllSubkeys(domain_constants_->proto_cache_key, &protos);
+ for (auto it = protos.begin(); it != protos.end(); ++it) {
+ const std::string& id(it->first);
+ const PolicyNamespace ns(domain_constants_->domain, id);
+
+ // Validate the protobuf.
+ auto proto = std::make_unique<em::PolicyFetchResponse>();
+ if (!proto->ParseFromString(it->second)) {
+ LOG(ERROR) << "Failed to parse the cached policy fetch response.";
+ Delete(ns);
+ continue;
+ }
+ em::ExternalPolicyData payload;
+ em::PolicyData policy_data;
+ std::string policy_error;
+ if (!ValidatePolicy(ns, std::move(proto), &policy_data, &payload,
+ &policy_error)) {
+ // The policy fetch response is corrupted.
+ LOG(ERROR) << "Discarding policy for component " << ns.component_id
+ << " due to policy validation failure: " << policy_error;
+ Delete(ns);
+ continue;
+ }
+
+ // The protobuf looks good; load the policy data.
+ std::string data;
+ if (cache_->Load(domain_constants_->data_cache_key, id, &data).empty()) {
+ LOG(ERROR) << "Failed to load the cached policy data.";
+ Delete(ns);
+ continue;
+ }
+ PolicyMap policy;
+ std::string data_error;
+ if (!ValidateData(data, payload.secure_hash(), &policy, &data_error)) {
+ // The data for this proto is corrupted.
+ LOG(ERROR) << "Discarding policy for component " << ns.component_id
+ << " due to data validation failure: " << data_error;
+ Delete(ns);
+ continue;
+ }
+
+ // The data is also good; expose the policies.
+ policy_bundle_.Get(ns).Swap(&policy);
+ cached_hashes_[ns] = payload.secure_hash();
+ stored_policy_times_[ns] =
+ base::Time::FromJavaTime(policy_data.timestamp());
+
+ serialized_policy_[ns] =
+ std::vector<uint8_t>(it->second.begin(), it->second.end());
+ }
+ delegate_->OnComponentCloudPolicyStoreUpdated();
+}
+
+bool ComponentCloudPolicyStore::Store(const PolicyNamespace& ns,
+ const std::string& serialized_policy,
+ const em::PolicyData* policy_data,
+ const std::string& secure_hash,
+ const std::string& data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (domain_constants_->domain != ns.domain) {
+ return false;
+ }
+
+ // |serialized_policy| has already been validated; validate the data now.
+ PolicyMap policy;
+ std::string error;
+ if (!ValidateData(data, secure_hash, &policy, &error)) {
+ LOG(ERROR) << "Discarding policy for component " << ns.component_id
+ << " due to data validation failure: " << error;
+ return false;
+ }
+
+ // Flush the proto and the data to the cache.
+ cache_->Store(domain_constants_->proto_cache_key, ns.component_id,
+ serialized_policy);
+ cache_->Store(domain_constants_->data_cache_key, ns.component_id, data);
+ // And expose the policy.
+ policy_bundle_.Get(ns).Swap(&policy);
+ cached_hashes_[ns] = secure_hash;
+ stored_policy_times_[ns] = base::Time::FromJavaTime(policy_data->timestamp());
+ serialized_policy_[ns] =
+ std::vector<uint8_t>(serialized_policy.begin(), serialized_policy.end());
+ delegate_->OnComponentCloudPolicyStoreUpdated();
+ return true;
+}
+
+void ComponentCloudPolicyStore::Delete(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (domain_constants_->domain != ns.domain) {
+ return;
+ }
+
+ cache_->Delete(domain_constants_->proto_cache_key, ns.component_id);
+ cache_->Delete(domain_constants_->data_cache_key, ns.component_id);
+
+ if (!policy_bundle_.Get(ns).empty()) {
+ policy_bundle_.Get(ns).Clear();
+ serialized_policy_.erase(ns);
+ delegate_->OnComponentCloudPolicyStoreUpdated();
+ }
+}
+
+void ComponentCloudPolicyStore::Purge(const PurgeFilter& filter) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ auto subkey_filter = base::BindRepeating(filter, domain_constants_->domain);
+ cache_->FilterSubkeys(domain_constants_->proto_cache_key, subkey_filter);
+ cache_->FilterSubkeys(domain_constants_->data_cache_key, subkey_filter);
+
+ // Stop serving policies for purged namespaces.
+ bool purged_current_policies = false;
+ for (PolicyBundle::const_iterator it = policy_bundle_.begin();
+ it != policy_bundle_.end(); ++it) {
+ DCHECK_EQ(it->first.domain, domain_constants_->domain);
+ if (filter.Run(domain_constants_->domain, it->first.component_id) &&
+ !policy_bundle_.Get(it->first).empty()) {
+ policy_bundle_.Get(it->first).Clear();
+ purged_current_policies = true;
+ }
+ }
+
+ // Purge cached hashes, so that those namespaces can be fetched again if the
+ // policy state changes.
+ auto it = cached_hashes_.begin();
+ while (it != cached_hashes_.end()) {
+ const PolicyNamespace ns(it->first);
+ DCHECK_EQ(ns.domain, domain_constants_->domain);
+ if (filter.Run(domain_constants_->domain, ns.component_id)) {
+ auto prev = it;
+ ++it;
+ cached_hashes_.erase(prev);
+ DCHECK(stored_policy_times_.count(ns));
+ stored_policy_times_.erase(ns);
+ serialized_policy_.erase(ns);
+ } else {
+ ++it;
+ }
+ }
+
+ if (purged_current_policies) {
+ delegate_->OnComponentCloudPolicyStoreUpdated();
+ }
+}
+
+void ComponentCloudPolicyStore::Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ cache_->Clear(domain_constants_->proto_cache_key);
+ cache_->Clear(domain_constants_->data_cache_key);
+
+ cached_hashes_.clear();
+ stored_policy_times_.clear();
+ serialized_policy_.clear();
+ const PolicyBundle empty_bundle;
+ if (!policy_bundle_.Equals(empty_bundle)) {
+ policy_bundle_.Clear();
+ delegate_->OnComponentCloudPolicyStoreUpdated();
+ }
+}
+
+bool ComponentCloudPolicyStore::ValidatePolicy(
+ const PolicyNamespace& ns,
+ std::unique_ptr<em::PolicyFetchResponse> proto,
+ em::PolicyData* policy_data,
+ em::ExternalPolicyData* payload,
+ std::string* error) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (domain_constants_->domain != ns.domain) {
+ *error = "Domains do not match.";
+ return false;
+ }
+
+ if (ns.component_id.empty()) {
+ *error = "Empty component id.";
+ return false;
+ }
+
+ if (username_.empty() || dm_token_.empty() || device_id_.empty() ||
+ public_key_.empty() || public_key_version_ == -1) {
+ *error = "Credentials are not loaded yet.";
+ return false;
+ }
+
+ // A valid policy should be not older than the currently stored policy, which
+ // allows to prevent the rollback of the policy.
+ base::Time time_not_before;
+ const auto stored_policy_times_iter = stored_policy_times_.find(ns);
+ if (stored_policy_times_iter != stored_policy_times_.end())
+ time_not_before = stored_policy_times_iter->second;
+
+ auto validator = std::make_unique<ComponentCloudPolicyValidator>(
+ std::move(proto), scoped_refptr<base::SequencedTaskRunner>());
+ validator->ValidateTimestamp(time_not_before,
+ CloudPolicyValidatorBase::TIMESTAMP_VALIDATED);
+ validator->ValidateUsernameAndGaiaId(username_, gaia_id_);
+ validator->ValidateDMToken(dm_token_,
+ ComponentCloudPolicyValidator::DM_TOKEN_REQUIRED);
+ validator->ValidateDeviceId(device_id_,
+ CloudPolicyValidatorBase::DEVICE_ID_REQUIRED);
+ validator->ValidatePolicyType(domain_constants_->policy_type);
+ validator->ValidateSettingsEntityId(ns.component_id);
+ validator->ValidatePayload();
+ validator->ValidateSignature(public_key_);
+ validator->RunValidation();
+ if (!validator->success()) {
+ *error = base::StrCat(
+ {"Unsuccessful validation with Status ",
+ CloudPolicyValidatorBase::StatusToString(validator->status()), "."});
+ return false;
+ }
+
+ if (!validator->policy_data()->has_public_key_version()) {
+ *error = "Public key version missing.";
+ return false;
+ }
+ if (validator->policy_data()->public_key_version() != public_key_version_) {
+ *error = base::StrCat(
+ {"Wrong public key version ",
+ base::NumberToString(validator->policy_data()->public_key_version()),
+ " - expected ", base::NumberToString(public_key_version_), "."});
+ return false;
+ }
+
+ em::ExternalPolicyData* data = validator->payload().get();
+ // The download URL must be empty, or must be a valid URL.
+ // An empty download URL signals that this component doesn't have cloud
+ // policy, or that the policy has been removed.
+ if (data->has_download_url() && !data->download_url().empty()) {
+ if (!GURL(data->download_url()).is_valid()) {
+ *error = base::StrCat({"Invalid URL: ", data->download_url(), " ."});
+ return false;
+ }
+ if (!data->has_secure_hash() || data->secure_hash().empty()) {
+ *error = "Secure hash missing.";
+ return false;
+ }
+ } else if (data->has_secure_hash()) {
+ *error = "URL missing.";
+ return false;
+ }
+
+ if (policy_data)
+ policy_data->Swap(validator->policy_data().get());
+ if (payload)
+ payload->Swap(validator->payload().get());
+ return true;
+}
+
+bool ComponentCloudPolicyStore::ValidateData(const std::string& data,
+ const std::string& secure_hash,
+ PolicyMap* policy,
+ std::string* error) {
+ if (crypto::SHA256HashString(data) != secure_hash) {
+ *error = "The received data doesn't match the expected hash.";
+ return false;
+ }
+ return ParsePolicy(data, policy, error);
+}
+
+bool ComponentCloudPolicyStore::ParsePolicy(const std::string& data,
+ PolicyMap* policy,
+ std::string* error) {
+ base::JSONReader::ValueWithError value_with_error =
+ base::JSONReader::ReadAndReturnValueWithError(
+ data, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ if (!value_with_error.value) {
+ *error =
+ base::StrCat({"Invalid JSON blob: ", value_with_error.error_message});
+ return false;
+ }
+ base::Value json = std::move(value_with_error.value.value());
+ if (!json.is_dict()) {
+ *error = "The JSON blob is not a dictionary.";
+ return false;
+ }
+
+ // Each top-level key maps a policy name to its description.
+ //
+ // Each description is an object that contains the policy value under the
+ // "Value" key. The optional "Level" key is either "Mandatory" (default) or
+ // "Recommended".
+ for (auto it : json.DictItems()) {
+ const std::string& policy_name = it.first;
+ base::Value description = std::move(it.second);
+ if (!description.is_dict()) {
+ *error = "The JSON blob dictionary value is not a dictionary.";
+ return false;
+ }
+
+ absl::optional<base::Value> value = description.ExtractKey(kValue);
+ if (!value.has_value()) {
+ *error = base::StrCat(
+ {"The JSON blob dictionary value doesn't contain the required ",
+ kValue, " field."});
+ return false;
+ }
+
+ PolicyLevel level = POLICY_LEVEL_MANDATORY;
+ const std::string* level_string = description.FindStringKey(kLevel);
+ if (level_string && *level_string == kRecommended)
+ level = POLICY_LEVEL_RECOMMENDED;
+
+ policy->Set(policy_name, level, domain_constants_->scope,
+ POLICY_SOURCE_CLOUD, std::move(value.value()), nullptr);
+ }
+
+ return true;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_store.h b/chromium/components/policy/core/common/cloud/component_cloud_policy_store.h
new file mode 100644
index 00000000000..a1f59299fb6
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_store.h
@@ -0,0 +1,188 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_STORE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace enterprise_management {
+class ExternalPolicyData;
+class PolicyData;
+class PolicyFetchResponse;
+}
+
+namespace policy {
+
+class ResourceCache;
+
+// Validates protobufs for external policy data, validates the data itself, and
+// caches both locally.
+//
+// The policy data is validated using the credentials that have to be passed
+// beforehand using |SetCredentials|. The expectation is that these credentials
+// should be the same as used for validating the superior policy (e.g. the user
+// policy, the device-local account policy, etc.).
+class POLICY_EXPORT ComponentCloudPolicyStore {
+ public:
+ class POLICY_EXPORT Delegate {
+ public:
+ virtual ~Delegate();
+
+ // Invoked whenever the policies served by policy() have changed, except
+ // for the initial Load().
+ virtual void OnComponentCloudPolicyStoreUpdated() = 0;
+ };
+
+ struct DomainConstants;
+
+ using PurgeFilter =
+ base::RepeatingCallback<bool(const PolicyDomain domain,
+ const std::string& component_id)>;
+
+ // Both the |delegate| and the |cache| must outlive this object.
+ // |policy_type| only supports kChromeSigninExtensionPolicyType,
+ // kChromeExtensionPolicyType, kChromeMachineLevelExtensionCloudPolicyType.
+ // Please update component_cloud_policy_store.cc in case there is new policy
+ // type added.
+ ComponentCloudPolicyStore(Delegate* delegate,
+ ResourceCache* cache,
+ const std::string& policy_type);
+ ComponentCloudPolicyStore(const ComponentCloudPolicyStore&) = delete;
+ ComponentCloudPolicyStore& operator=(const ComponentCloudPolicyStore&) =
+ delete;
+ ~ComponentCloudPolicyStore();
+
+ // Helper that returns true for PolicyDomains that can be managed by this
+ // store.
+ static bool SupportsDomain(PolicyDomain domain);
+
+ // Returns true if |policy_type| corresponds to a policy domain that can be
+ // managed by this store; in that case, the domain constants is assigned to
+ // |domain|. Otherwise returns false.
+ static bool GetPolicyDomain(const std::string& policy_type,
+ PolicyDomain* domain);
+
+ // The current list of policies.
+ const PolicyBundle& policy() const { return policy_bundle_; }
+
+ // Returns the map of serialized policy for each namespace.
+ const base::flat_map<PolicyNamespace, std::vector<uint8_t>>&
+ serialized_policy() {
+ return serialized_policy_;
+ }
+
+ // The cached hash for namespace |ns|, or the empty string if |ns| is not
+ // cached.
+ const std::string& GetCachedHash(const PolicyNamespace& ns) const;
+
+ // The passed credentials are used to validate the cached data, and data
+ // stored later.
+ // All ValidatePolicy() requests without credentials fail.
+ void SetCredentials(const std::string& username,
+ const std::string& gaia_id,
+ const std::string& dm_token,
+ const std::string& device_id,
+ const std::string& public_key,
+ int public_key_version);
+
+ // Loads and validates the currently cached protobufs and policy data that are
+ // owned by this PolicyStore.
+ // This is performed synchronously, and policy() will return the cached
+ // policies after this call.
+ void Load();
+
+ // Stores the protobuf and |data| for namespace |ns|. The protobuf is passed
+ // serialized in |serialized_policy_proto|, and must have been validated
+ // before. The protobuf |policy_data| contain the corresponding policy data.
+ // The |data| is validated during this call, and its secure hash must match
+ // |secure_hash|.
+ // Returns false if |data| failed validation, otherwise returns true and the
+ // data was stored in the cache.
+ bool Store(const PolicyNamespace& ns,
+ const std::string& serialized_policy_proto,
+ const enterprise_management::PolicyData* policy_data,
+ const std::string& secure_hash,
+ const std::string& data);
+
+ // Deletes the storage of namespace |ns| and stops serving its policies.
+ void Delete(const PolicyNamespace& ns);
+
+ // Deletes the storage of all components that pass for the given
+ // |filter|, and stops serving their policies.
+ void Purge(const PurgeFilter& filter);
+
+ // Deletes the storage of every component that is owned by this PolicyStore.
+ void Clear();
+
+ // Validates |proto| and returns the parsed PolicyData in |policy_data| and
+ // parsed ExternalPolicyData in |payload|. It is also validated that |proto|
+ // has the policy namespace equal to |ns|.
+ // If |proto| validates successfully then its |payload| can be trusted, and
+ // the data referenced there can be downloaded. A |proto| must be validated
+ // before attempting to download the data, and before storing both.
+ bool ValidatePolicy(
+ const PolicyNamespace& ns,
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> proto,
+ enterprise_management::PolicyData* policy_data,
+ enterprise_management::ExternalPolicyData* payload,
+ std::string* error);
+
+ private:
+ // Validates the JSON policy serialized in |data|, and verifies its hash
+ // with |secure_hash|. Returns true on success, and in that case stores the
+ // parsed policies in |policy|.
+ bool ValidateData(const std::string& data,
+ const std::string& secure_hash,
+ PolicyMap* policy,
+ std::string* error);
+
+ // Parses the JSON policy in |data| into |policy|, and returns true if the
+ // parse was successful.
+ bool ParsePolicy(const std::string& data,
+ PolicyMap* policy,
+ std::string* error);
+
+ const raw_ptr<Delegate> delegate_;
+ const raw_ptr<ResourceCache> cache_;
+
+ // The following fields contain credentials used for validating the policy.
+ std::string username_;
+ std::string gaia_id_;
+ std::string dm_token_;
+ std::string device_id_;
+ std::string public_key_;
+ int public_key_version_ = -1;
+
+ // The current list of policies.
+ PolicyBundle policy_bundle_;
+ // Mapping from policy namespace to data hashes for each currently exposed
+ // component.
+ std::map<PolicyNamespace, std::string> cached_hashes_;
+ // Mapping from policy namespace to policy timestamp for each currently
+ // exposed component.
+ std::map<PolicyNamespace, base::Time> stored_policy_times_;
+ // Mapping from policy namespace to serialized policy data for each currently
+ // exposed component. Only the policy that passed the validation is stored.
+ base::flat_map<PolicyNamespace, std::vector<uint8_t>> serialized_policy_;
+
+ raw_ptr<const DomainConstants> domain_constants_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc
new file mode 100644
index 00000000000..08befefabd0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc
@@ -0,0 +1,675 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_store.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/sha2.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace em = enterprise_management;
+
+using testing::Mock;
+
+namespace policy {
+
+namespace {
+
+const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char kTestDownload[] = "http://example.com/getpolicy?id=123";
+const char kTestPolicy[] =
+ "{"
+ " \"Name\": {"
+ " \"Value\": \"disabled\""
+ " },"
+ " \"Second\": {"
+ " \"Value\": \"maybe\","
+ " \"Level\": \"Recommended\""
+ " }"
+ "}";
+
+std::string TestPolicyHash() {
+ return crypto::SHA256HashString(kTestPolicy);
+}
+
+bool NotEqual(const std::string& expected,
+ const PolicyDomain domain,
+ const std::string& key) {
+ return key != expected;
+}
+
+bool True(const PolicyDomain domain, const std::string& ignored) {
+ return true;
+}
+
+class MockComponentCloudPolicyStoreDelegate
+ : public ComponentCloudPolicyStore::Delegate {
+ public:
+ ~MockComponentCloudPolicyStoreDelegate() override {}
+
+ MOCK_METHOD0(OnComponentCloudPolicyStoreUpdated, void());
+};
+
+} // namespace
+
+class ComponentCloudPolicyStoreTest : public testing::Test {
+ protected:
+ ComponentCloudPolicyStoreTest()
+ : kTestPolicyNS(POLICY_DOMAIN_EXTENSIONS, kTestExtension) {
+ builder_.SetDefaultSigningKey();
+ builder_.policy_data().set_policy_type(
+ dm_protocol::kChromeExtensionPolicyType);
+ builder_.policy_data().set_settings_entity_id(kTestExtension);
+ builder_.payload().set_download_url(kTestDownload);
+ builder_.payload().set_secure_hash(TestPolicyHash());
+
+ public_key_ = builder_.GetPublicSigningKeyAsString();
+
+ SetupExpectBundleWithScope(POLICY_SCOPE_USER);
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ cache_ = std::make_unique<ResourceCache>(
+ temp_dir_.GetPath(), base::MakeRefCounted<base::TestSimpleTaskRunner>(),
+ /* max_cache_size */ absl::nullopt);
+ store_ = CreateStore();
+ store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ }
+
+ void SetupExpectBundleWithScope(const PolicyScope& scope) {
+ PolicyMap& policy = expected_bundle_.Get(kTestPolicyNS);
+ policy.Clear();
+ policy.Set("Name", POLICY_LEVEL_MANDATORY, scope, POLICY_SOURCE_CLOUD,
+ base::Value("disabled"), nullptr);
+ policy.Set("Second", POLICY_LEVEL_RECOMMENDED, scope, POLICY_SOURCE_CLOUD,
+ base::Value("maybe"), nullptr);
+ }
+
+ std::unique_ptr<em::PolicyFetchResponse> CreateResponse() {
+ builder_.Build();
+ return std::make_unique<em::PolicyFetchResponse>(builder_.policy());
+ }
+
+ std::unique_ptr<em::PolicyData> CreatePolicyData() {
+ builder_.Build();
+ return std::make_unique<em::PolicyData>(builder_.policy_data());
+ }
+
+ std::string CreateSerializedResponse() {
+ builder_.Build();
+ return builder_.GetBlob();
+ }
+
+ std::unique_ptr<ComponentCloudPolicyStore> CreateStore(
+ const std::string& policy_type =
+ dm_protocol::kChromeExtensionPolicyType) {
+ return std::make_unique<ComponentCloudPolicyStore>(
+ &store_delegate_, cache_.get(), policy_type);
+ }
+
+ // Returns true if the policy exposed by the |store| is empty.
+ bool IsStoreEmpty(const ComponentCloudPolicyStore& store) {
+ return store.policy().Equals(PolicyBundle());
+ }
+
+ void StoreTestPolicy(ComponentCloudPolicyStore* store) {
+ StoreTestPolicyWithNamespace(store, kTestPolicyNS);
+ }
+ void StoreTestPolicyWithNamespace(ComponentCloudPolicyStore* store,
+ const PolicyNamespace& ns) {
+ std::string error;
+ EXPECT_TRUE(store->ValidatePolicy(ns, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(store->Store(ns, CreateSerializedResponse(),
+ CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+ EXPECT_EQ(std::string(), error);
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ EXPECT_TRUE(store->policy().Equals(expected_bundle_));
+ EXPECT_FALSE(LoadCacheExtensionsSubkeys().empty());
+ }
+
+ std::map<std::string, std::string> LoadCacheExtensionsSubkeys() {
+ std::map<std::string, std::string> contents;
+ cache_->LoadAllSubkeys("extension-policy", &contents);
+ return contents;
+ }
+
+ const PolicyNamespace kTestPolicyNS;
+ std::unique_ptr<ResourceCache> cache_;
+
+ std::unique_ptr<ComponentCloudPolicyStore> store_;
+ std::unique_ptr<ComponentCloudPolicyStore> another_store_;
+ std::unique_ptr<ComponentCloudPolicyStore> yet_another_store_;
+
+ MockComponentCloudPolicyStoreDelegate store_delegate_;
+ ComponentCloudPolicyBuilder builder_;
+ PolicyBundle expected_bundle_;
+ std::string public_key_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicy) {
+ em::PolicyData policy_data;
+ em::ExternalPolicyData payload;
+ std::string error;
+ EXPECT_TRUE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ &policy_data, &payload, &error));
+ EXPECT_EQ(dm_protocol::kChromeExtensionPolicyType, policy_data.policy_type());
+ EXPECT_EQ(kTestExtension, policy_data.settings_entity_id());
+ EXPECT_EQ(kTestDownload, payload.download_url());
+ EXPECT_EQ(TestPolicyHash(), payload.secure_hash());
+ EXPECT_EQ(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongTimestamp) {
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+
+ const int64_t kPastTimestamp = (base::Time() + base::Days(1)).ToJavaTime();
+ CHECK_GT(PolicyBuilder::kFakeTimestamp, kPastTimestamp);
+ builder_.policy_data().set_timestamp(kPastTimestamp);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongUser) {
+ builder_.policy_data().set_username("anotheruser@example.com");
+ builder_.policy_data().set_gaia_id("another-gaia-id");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongDMToken) {
+ builder_.policy_data().set_request_token("notmytoken");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongDeviceId) {
+ builder_.policy_data().set_device_id("invalid");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadType) {
+ builder_.policy_data().set_policy_type(dm_protocol::kChromeUserPolicyType);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongNamespace) {
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "nosuchid"), CreateResponse(),
+ nullptr /* policy_data */, nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyNoSignature) {
+ builder_.UnsetSigningKey();
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadSignature) {
+ std::unique_ptr<em::PolicyFetchResponse> response = CreateResponse();
+ response->set_policy_data_signature("invalid");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, std::move(response),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyEmptyComponentId) {
+ builder_.policy_data().set_settings_entity_id(std::string());
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, std::string()),
+ CreateResponse(), nullptr /* policy_data */, nullptr /* payload */,
+ &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongPublicKey) {
+ // Test against a policy signed with a wrong key.
+ builder_.SetSigningKey(*PolicyBuilder::CreateTestOtherSigningKey());
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongPublicKeyVersion) {
+ // Test against a policy containing wrong public key version.
+ builder_.policy_data().set_public_key_version(
+ PolicyBuilder::kFakePublicKeyVersion + 1);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyDifferentPublicKey) {
+ // Test against a policy signed with a different key and containing a new
+ // public key version.
+ builder_.SetSigningKey(*PolicyBuilder::CreateTestOtherSigningKey());
+ builder_.policy_data().set_public_key_version(
+ PolicyBuilder::kFakePublicKeyVersion + 1);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadDownloadUrl) {
+ builder_.payload().set_download_url("invalidurl");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyEmptyDownloadUrl) {
+ builder_.payload().clear_download_url();
+ builder_.payload().clear_secure_hash();
+ std::string error;
+ // This is valid; it's how "no policy" is signalled to the client.
+ EXPECT_TRUE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_EQ(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadPayload) {
+ builder_.clear_payload();
+ builder_.policy_data().set_policy_value("broken");
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentials) {
+ store_ = CreateStore();
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentialsUser) {
+ store_ = CreateStore();
+ store_->SetCredentials(/*username=*/std::string(), /*gaia_id=*/std::string(),
+ PolicyBuilder::kFakeToken,
+ PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentialsDMToken) {
+ store_ = CreateStore();
+ store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ std::string() /* dm_token */, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentialsDeviceId) {
+ store_ = CreateStore();
+ store_->SetCredentials(PolicyBuilder::kFakeUsername,
+ PolicyBuilder::kFakeGaiaId, PolicyBuilder::kFakeToken,
+ std::string() /* device_id */, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentialsPublicKey) {
+ store_ = CreateStore();
+ store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId,
+ std::string() /* public_key */, PolicyBuilder::kFakePublicKeyVersion);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(kTestPolicyNS, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentialsPublicKeyVersion) {
+ StoreTestPolicy(store_.get());
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ -1 /* public_key_version */);
+ another_store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*another_store_));
+ EXPECT_TRUE(LoadCacheExtensionsSubkeys().empty());
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateWrongCredentialsDMToken) {
+ StoreTestPolicy(store_.get());
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(PolicyBuilder::kFakeUsername,
+ PolicyBuilder::kFakeGaiaId, "wrongtoken",
+ PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*another_store_));
+ EXPECT_TRUE(LoadCacheExtensionsSubkeys().empty());
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateWrongCredentialsDeviceId) {
+ StoreTestPolicy(store_.get());
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, "wrongdeviceid", public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*another_store_));
+ EXPECT_TRUE(LoadCacheExtensionsSubkeys().empty());
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, ValidateWrongCredentialsPublicKey) {
+ StoreTestPolicy(store_.get());
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, "wrongkey",
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*another_store_));
+ EXPECT_TRUE(LoadCacheExtensionsSubkeys().empty());
+}
+
+TEST_F(ComponentCloudPolicyStoreTest,
+ ValidateWrongCredentialsPublicKeyVersion) {
+ StoreTestPolicy(store_.get());
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion + 1);
+ another_store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*another_store_));
+ EXPECT_TRUE(LoadCacheExtensionsSubkeys().empty());
+}
+
+TEST_F(ComponentCloudPolicyStoreTest,
+ ValidatePolicyWithInvalidCombinationOfDomainAndPolicyType) {
+ PolicyNamespace ns_chrome(POLICY_DOMAIN_CHROME, std::string());
+ PolicyNamespace ns_extension(POLICY_DOMAIN_EXTENSIONS, kTestExtension);
+ PolicyNamespace ns_signin_extension(POLICY_DOMAIN_SIGNIN_EXTENSIONS,
+ kTestExtension);
+
+ store_ =
+ CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+ std::string error;
+ EXPECT_FALSE(store_->ValidatePolicy(ns_chrome, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+ EXPECT_FALSE(store_->ValidatePolicy(ns_signin_extension, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+
+ store_ = CreateStore(dm_protocol::kChromeSigninExtensionPolicyType);
+ EXPECT_FALSE(store_->ValidatePolicy(ns_chrome, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+ EXPECT_FALSE(store_->ValidatePolicy(ns_extension, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+
+ store_ = CreateStore(dm_protocol::kChromeExtensionPolicyType);
+ EXPECT_FALSE(store_->ValidatePolicy(ns_chrome, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+ EXPECT_FALSE(store_->ValidatePolicy(ns_signin_extension, CreateResponse(),
+ nullptr /* policy_data */,
+ nullptr /* payload */, &error));
+ EXPECT_NE(std::string(), error);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, StoreAndLoad) {
+ // Initially empty.
+ EXPECT_TRUE(IsStoreEmpty(*store_));
+ store_->Load();
+ EXPECT_TRUE(IsStoreEmpty(*store_));
+
+ // Store policy for an unsupported domain.
+ builder_.policy_data().set_policy_type(dm_protocol::kChromeUserPolicyType);
+ EXPECT_FALSE(
+ store_->Store(PolicyNamespace(POLICY_DOMAIN_CHROME, kTestExtension),
+ CreateSerializedResponse(), CreatePolicyData().get(),
+ TestPolicyHash(), kTestPolicy));
+
+ // Store policy for an unowned domain.
+ EXPECT_FALSE(store_->Store(
+ PolicyNamespace(POLICY_DOMAIN_SIGNIN_EXTENSIONS, kTestExtension),
+ CreateSerializedResponse(), CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+
+ // Store policy with the wrong hash.
+ builder_.policy_data().set_policy_type(
+ dm_protocol::kChromeExtensionPolicyType);
+ builder_.payload().set_secure_hash("badash");
+ EXPECT_FALSE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), "badash", kTestPolicy));
+
+ // Store policy without a hash.
+ builder_.payload().clear_secure_hash();
+ EXPECT_FALSE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), std::string(),
+ kTestPolicy));
+
+ // Store policy with invalid JSON data.
+ static const char kInvalidData[] = "{ not json }";
+ const std::string invalid_data_hash = crypto::SHA256HashString(kInvalidData);
+ builder_.payload().set_secure_hash(invalid_data_hash);
+ EXPECT_FALSE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), invalid_data_hash,
+ kInvalidData));
+
+ // All of those failed.
+ EXPECT_TRUE(IsStoreEmpty(*store_));
+ EXPECT_EQ(std::string(), store_->GetCachedHash(kTestPolicyNS));
+
+ // Now store a valid policy.
+ builder_.payload().set_secure_hash(TestPolicyHash());
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ EXPECT_FALSE(IsStoreEmpty(*store_));
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+ EXPECT_EQ(TestPolicyHash(), store_->GetCachedHash(kTestPolicyNS));
+
+ // Loading from the cache validates the policy data again.
+ another_store_ = CreateStore();
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(another_store_->policy().Equals(expected_bundle_));
+ EXPECT_EQ(TestPolicyHash(), another_store_->GetCachedHash(kTestPolicyNS));
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, StoreAndLoadMachineLevelUserPolicy) {
+ store_ =
+ CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+ store_->SetCredentials(PolicyBuilder::kFakeUsername,
+ PolicyBuilder::kFakeGaiaId, PolicyBuilder::kFakeToken,
+ PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+
+ builder_.policy_data().set_policy_type(
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+ builder_.payload().set_secure_hash(TestPolicyHash());
+ SetupExpectBundleWithScope(POLICY_SCOPE_MACHINE);
+
+ StoreTestPolicyWithNamespace(store_.get(), kTestPolicyNS);
+
+ another_store_ =
+ CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(another_store_->policy().Equals(expected_bundle_));
+ EXPECT_EQ(TestPolicyHash(), another_store_->GetCachedHash(kTestPolicyNS));
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, Updates) {
+ // Store some policies.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ EXPECT_FALSE(IsStoreEmpty(*store_));
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+
+ // Deleting a non-existant namespace doesn't trigger updates.
+ PolicyNamespace ns_fake(POLICY_DOMAIN_EXTENSIONS, "nosuchid");
+ store_->Delete(ns_fake);
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Deleting a unowned domain doesn't trigger updates.
+ PolicyNamespace ns_fake_2(POLICY_DOMAIN_SIGNIN_EXTENSIONS, kTestExtension);
+ store_->Delete(ns_fake_2);
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Deleting a namespace that has policies triggers an update.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ store_->Delete(kTestPolicyNS);
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, Purge) {
+ // Store a valid policy.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(store_->Store(kTestPolicyNS, CreateSerializedResponse(),
+ CreatePolicyData().get(), TestPolicyHash(),
+ kTestPolicy));
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ EXPECT_FALSE(IsStoreEmpty(*store_));
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+
+ // Purge other components.
+ store_->Purge(base::BindRepeating(&NotEqual, kTestExtension));
+
+ // The policy for |kTestPolicyNS| is still served.
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+
+ // Loading the store again will still see |kTestPolicyNS|.
+ another_store_ = CreateStore();
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(another_store_->policy().Equals(empty_bundle));
+ another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ another_store_->Load();
+ EXPECT_TRUE(another_store_->policy().Equals(expected_bundle_));
+
+ // Now purge everything.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ store_->Purge(base::BindRepeating(&True));
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // No policies are served anymore.
+ EXPECT_TRUE(store_->policy().Equals(empty_bundle));
+
+ // And they aren't loaded anymore either.
+ yet_another_store_ = CreateStore();
+ yet_another_store_->SetCredentials(
+ PolicyBuilder::kFakeUsername, PolicyBuilder::kFakeGaiaId,
+ PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ yet_another_store_->Load();
+ EXPECT_TRUE(yet_another_store_->policy().Equals(empty_bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.cc
new file mode 100644
index 00000000000..49239991de7
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_updater.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+// The maximum size of the serialized policy protobuf.
+const size_t kPolicyProtoMaxSize = 16 * 1024;
+
+// The maximum size of the downloaded policy data.
+const int64_t kPolicyDataMaxSize = 5 * 1024 * 1024;
+
+// Tha maximum number of policy data fetches to run in parallel.
+const int64_t kMaxParallelPolicyDataFetches = 2;
+
+std::string NamespaceToKey(const PolicyNamespace& ns) {
+ const std::string domain = base::NumberToString(ns.domain);
+ const std::string size = base::NumberToString(domain.size());
+ return size + ":" + domain + ":" + ns.component_id;
+}
+
+} // namespace
+
+ComponentCloudPolicyUpdater::ComponentCloudPolicyUpdater(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ ComponentCloudPolicyStore* store)
+ : store_(store),
+ external_policy_data_updater_(task_runner,
+ std::move(external_policy_data_fetcher),
+ kMaxParallelPolicyDataFetches) {}
+
+ComponentCloudPolicyUpdater::~ComponentCloudPolicyUpdater() {
+}
+
+void ComponentCloudPolicyUpdater::UpdateExternalPolicy(
+ const PolicyNamespace& ns,
+ std::unique_ptr<em::PolicyFetchResponse> response) {
+ // Keep a serialized copy of |response|, to cache it later.
+ // The policy is also rejected if it exceeds the maximum size.
+ std::string serialized_response;
+ if (!response->SerializeToString(&serialized_response)) {
+ LOG(ERROR) << "Failed to serialize policy fetch response.";
+ return;
+ }
+ if (serialized_response.size() > kPolicyProtoMaxSize) {
+ LOG(ERROR) << "Policy fetch response too large: "
+ << serialized_response.size() << " bytes (max "
+ << kPolicyProtoMaxSize << ").";
+ return;
+ }
+
+ // Validate the policy before doing anything else.
+ auto policy_data = std::make_unique<em::PolicyData>();
+ em::ExternalPolicyData data;
+ std::string error;
+ if (!store_->ValidatePolicy(ns, std::move(response), policy_data.get(), &data,
+ &error)) {
+ LOG(ERROR) << "Discarding policy for component " << ns.component_id
+ << " due to policy validation failure: " << error;
+ return;
+ }
+
+ // Maybe the data for this hash has already been downloaded and cached.
+ const std::string& cached_hash = store_->GetCachedHash(ns);
+ if (!cached_hash.empty() && data.secure_hash() == cached_hash)
+ return;
+
+ const std::string key = NamespaceToKey(ns);
+
+ if (data.download_url().empty() || !data.has_secure_hash()) {
+ // If there is no policy for this component or the policy has been removed,
+ // cancel any existing request to fetch policy for this component.
+ external_policy_data_updater_.CancelExternalDataFetch(key);
+
+ // Delete any existing policy for this component.
+ store_->Delete(ns);
+ } else {
+ // Make a request to fetch policy for this component. If another fetch
+ // request is already pending for the component, it will be canceled.
+ external_policy_data_updater_.FetchExternalData(
+ key,
+ ExternalPolicyDataUpdater::Request(
+ data.download_url(), data.secure_hash(), kPolicyDataMaxSize),
+ base::BindRepeating(&ComponentCloudPolicyStore::Store,
+ base::Unretained(store_), ns, serialized_response,
+ base::Owned(policy_data.release()),
+ data.secure_hash()));
+ }
+}
+
+void ComponentCloudPolicyUpdater::CancelUpdate(const PolicyNamespace& ns) {
+ external_policy_data_updater_.CancelExternalDataFetch(NamespaceToKey(ns));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.h b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.h
new file mode 100644
index 00000000000..5be6e20579e
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_UPDATER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_UPDATER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/cloud/external_policy_data_updater.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace enterprise_management {
+class PolicyFetchResponse;
+}
+
+namespace policy {
+
+class ComponentCloudPolicyStore;
+class ExternalPolicyDataFetcher;
+
+// This class downloads external policy data, given PolicyFetchResponses.
+// It validates the PolicyFetchResponse and its corresponding data, and caches
+// them in a ComponentCloudPolicyStore. It also enforces size limits on what's
+// cached.
+// It retries to download the policy data periodically when a download fails.
+class POLICY_EXPORT ComponentCloudPolicyUpdater {
+ public:
+ // This class runs on the background thread represented by |task_runner|,
+ // which must support file I/O. All network I/O is delegated to the
+ // |external_policy_data_fetcher|.
+ ComponentCloudPolicyUpdater(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ ComponentCloudPolicyStore* store);
+ ComponentCloudPolicyUpdater(const ComponentCloudPolicyUpdater&) = delete;
+ ComponentCloudPolicyUpdater& operator=(const ComponentCloudPolicyUpdater&) =
+ delete;
+ ~ComponentCloudPolicyUpdater();
+
+ // |response| is the latest policy information fetched for component
+ // represented by namespace |ns|.
+ // This method schedules the download of the policy data, if |response| is
+ // validated. If the downloaded data also passes validation then that data
+ // will be passed to the |store_|.
+ void UpdateExternalPolicy(
+ const PolicyNamespace& ns,
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> response);
+
+ // Cancels any pending operations for the given namespace.
+ void CancelUpdate(const PolicyNamespace& ns);
+
+ private:
+ const raw_ptr<ComponentCloudPolicyStore> store_;
+ ExternalPolicyDataUpdater external_policy_data_updater_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_COMPONENT_CLOUD_POLICY_UPDATER_H_
diff --git a/chromium/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc
new file mode 100644
index 00000000000..20f2d92c857
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc
@@ -0,0 +1,522 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/component_cloud_policy_updater.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+#include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/sha2.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace em = enterprise_management;
+
+using testing::Mock;
+
+namespace policy {
+
+namespace {
+
+const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char kTestExtension2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+const char kTestExtension3[] = "cccccccccccccccccccccccccccccccc";
+const char kTestDownload[] = "http://example.com/getpolicy?id=123";
+const char kTestDownload2[] = "http://example.com/getpolicy?id=456";
+const char kTestDownload3[] = "http://example.com/getpolicy?id=789";
+const char kTestPolicy[] =
+ "{"
+ " \"Name\": {"
+ " \"Value\": \"disabled\""
+ " },"
+ " \"Second\": {"
+ " \"Value\": \"maybe\","
+ " \"Level\": \"Recommended\""
+ " }"
+ "}";
+
+class MockComponentCloudPolicyStoreDelegate
+ : public ComponentCloudPolicyStore::Delegate {
+ public:
+ ~MockComponentCloudPolicyStoreDelegate() override {}
+
+ MOCK_METHOD0(OnComponentCloudPolicyStoreUpdated, void());
+};
+
+} // namespace
+
+class ComponentCloudPolicyUpdaterTest : public testing::Test {
+ protected:
+ ComponentCloudPolicyUpdaterTest();
+ void SetUp() override;
+ void TearDown() override;
+
+ std::unique_ptr<em::PolicyFetchResponse> CreateResponse();
+
+ const PolicyNamespace kTestPolicyNS{POLICY_DOMAIN_EXTENSIONS, kTestExtension};
+ base::test::TaskEnvironment task_env_;
+ std::unique_ptr<ComponentCloudPolicyStore> store_;
+ MockComponentCloudPolicyStoreDelegate store_delegate_;
+ network::TestURLLoaderFactory loader_factory_;
+ std::unique_ptr<ComponentCloudPolicyUpdater> updater_;
+ ComponentCloudPolicyBuilder builder_;
+ PolicyBundle expected_bundle_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ std::unique_ptr<ResourceCache> cache_;
+ std::string public_key_;
+};
+
+ComponentCloudPolicyUpdaterTest::ComponentCloudPolicyUpdaterTest()
+ : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+ builder_.SetDefaultSigningKey();
+ builder_.policy_data().set_policy_type(
+ dm_protocol::kChromeExtensionPolicyType);
+ builder_.policy_data().set_settings_entity_id(kTestExtension);
+ builder_.payload().set_download_url(kTestDownload);
+ builder_.payload().set_secure_hash(crypto::SHA256HashString(kTestPolicy));
+
+ public_key_ = builder_.GetPublicSigningKeyAsString();
+
+ PolicyMap& policy = expected_bundle_.Get(kTestPolicyNS);
+ policy.Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("disabled"), nullptr);
+ policy.Set("Second", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("maybe"), nullptr);
+}
+
+void ComponentCloudPolicyUpdaterTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ cache_ = std::make_unique<ResourceCache>(temp_dir_.GetPath(),
+ task_env_.GetMainThreadTaskRunner(),
+ /* max_cache_size */ absl::nullopt);
+ store_ = std::make_unique<ComponentCloudPolicyStore>(
+ &store_delegate_, cache_.get(), dm_protocol::kChromeExtensionPolicyType);
+ store_->SetCredentials(PolicyBuilder::kFakeUsername,
+ PolicyBuilder::kFakeGaiaId, PolicyBuilder::kFakeToken,
+ PolicyBuilder::kFakeDeviceId, public_key_,
+ PolicyBuilder::kFakePublicKeyVersion);
+ auto url_loader_factory =
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &loader_factory_);
+ updater_ = std::make_unique<ComponentCloudPolicyUpdater>(
+ task_env_.GetMainThreadTaskRunner(),
+ std::make_unique<ExternalPolicyDataFetcher>(
+ std::move(url_loader_factory), task_env_.GetMainThreadTaskRunner()),
+ store_.get());
+ ASSERT_EQ(store_->policy().end(), store_->policy().begin());
+}
+
+void ComponentCloudPolicyUpdaterTest::TearDown() {
+ updater_.reset();
+ task_env_.RunUntilIdle();
+}
+
+std::unique_ptr<em::PolicyFetchResponse>
+ComponentCloudPolicyUpdaterTest::CreateResponse() {
+ builder_.Build();
+ return std::make_unique<em::PolicyFetchResponse>(builder_.policy());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, FetchAndCache) {
+ // Submit a policy fetch response.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that a download has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+ task_env_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Verify that the downloaded policy is being served.
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseTooLarge) {
+ // Submit a policy fetch response that exceeds the allowed maximum size.
+ std::string long_download("http://example.com/get?id=");
+ long_download.append(20 * 1024, '1');
+ builder_.payload().set_download_url(long_download);
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ // Submit two valid policy fetch responses.
+ builder_.policy_data().set_settings_entity_id(kTestExtension2);
+ builder_.payload().set_download_url(kTestDownload2);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension2),
+ CreateResponse());
+ builder_.policy_data().set_settings_entity_id(kTestExtension3);
+ builder_.payload().set_download_url(kTestDownload3);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension3),
+ CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first policy fetch response has been ignored and downloads
+ // have been started for the next two fetch responses instead.
+ EXPECT_EQ(2, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload2));
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload3));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseInvalid) {
+ // Submit an invalid policy fetch response.
+ builder_.policy_data().set_username("wronguser@example.com");
+ builder_.policy_data().set_gaia_id("wrong-gaia-id");
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ // Submit two valid policy fetch responses.
+ builder_.policy_data().set_username(PolicyBuilder::kFakeUsername);
+ builder_.policy_data().set_gaia_id(PolicyBuilder::kFakeGaiaId);
+ builder_.policy_data().set_settings_entity_id(kTestExtension2);
+ builder_.payload().set_download_url(kTestDownload2);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension2),
+ CreateResponse());
+ builder_.policy_data().set_settings_entity_id(kTestExtension3);
+ builder_.payload().set_download_url(kTestDownload3);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension3),
+ CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first policy fetch response has been ignored and downloads
+ // have been started for the next two fetch responses instead.
+ EXPECT_EQ(2, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload2));
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload3));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseNoSignature) {
+ // Submit an invalid policy fetch response.
+ builder_.UnsetSigningKey();
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseBadSignature) {
+ // Submit an invalid policy fetch response.
+ std::unique_ptr<em::PolicyFetchResponse> response = CreateResponse();
+ response->set_policy_data_signature("invalid");
+ updater_->UpdateExternalPolicy(kTestPolicyNS, std::move(response));
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseWrongPublicKey) {
+ // Submit a policy fetch response signed with a wrong signing key.
+ builder_.SetSigningKey(*PolicyBuilder::CreateTestOtherSigningKey());
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest,
+ PolicyFetchResponseWrongPublicKeyVersion) {
+ // Submit a policy fetch response containing different public key version.
+ builder_.policy_data().set_public_key_version(
+ PolicyBuilder::kFakePublicKeyVersion + 1);
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseDifferentPublicKey) {
+ // Submit a policy fetch response signed with a different key and containing a
+ // new public key version.
+ builder_.SetSigningKey(*PolicyBuilder::CreateTestOtherSigningKey());
+ builder_.policy_data().set_public_key_version(
+ PolicyBuilder::kFakePublicKeyVersion + 1);
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, PolicyFetchResponseEmptyComponentId) {
+ // Submit a policy fetch response having an empty component ID.
+ builder_.policy_data().set_settings_entity_id(std::string());
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, std::string()),
+ CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+
+ // Submit a policy fetch response having an empty component ID with empty data
+ // fields, requesting the deletion of policy.
+ builder_.payload().clear_download_url();
+ builder_.payload().clear_secure_hash();
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, std::string()),
+ CreateResponse());
+
+ task_env_.RunUntilIdle();
+
+ // Verify that the policy fetch response has been ignored.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, AlreadyCached) {
+ // Cache policy for an extension.
+ builder_.Build();
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ EXPECT_TRUE(
+ store_->Store(kTestPolicyNS, builder_.GetBlob(), &builder_.policy_data(),
+ crypto::SHA256HashString(kTestPolicy), kTestPolicy));
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Submit a policy fetch response whose extension ID and hash match the
+ // already cached policy.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that no download has been started.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, DataTooLarge) {
+ // Submit three policy fetch responses.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ builder_.payload().set_download_url(kTestDownload2);
+ builder_.policy_data().set_settings_entity_id(kTestExtension2);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension2),
+ CreateResponse());
+ builder_.policy_data().set_settings_entity_id(kTestExtension3);
+ builder_.payload().set_download_url(kTestDownload3);
+ updater_->UpdateExternalPolicy(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kTestExtension3),
+ CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first and second downloads have been started.
+ EXPECT_EQ(2, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload2));
+
+ // Complete the request with a policy data size that exceeds allowed maximum.
+ loader_factory_.AddResponse(kTestDownload2,
+ std::string(6 * 1024 * 1024, 'a'));
+ task_env_.RunUntilIdle();
+
+ // Verify that the third download has been started.
+ EXPECT_EQ(2, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload3));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, FetchUpdatedData) {
+ // Submit a policy fetch response.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first download has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Submit a second policy fetch response for the same extension with an
+ // updated download URL.
+ builder_.payload().set_download_url(kTestDownload2);
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first download is no longer running and the second download
+ // has been started.
+ EXPECT_EQ(1, loader_factory_.NumPending());
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload2));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, FetchUpdatedDataWithoutPolicy) {
+ // Submit a policy fetch response.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the download has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+ task_env_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Verify that the downloaded policy is being served.
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+
+ // Submit a second policy fetch response for the same extension with no
+ // download URL, meaning that no policy should be provided for this extension.
+ builder_.payload().clear_download_url();
+ builder_.payload().clear_secure_hash();
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ task_env_.RunUntilIdle();
+
+ // Verify that no download has been started.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+
+ // Verify that the policy is no longer being served.
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(store_->policy().Equals(kEmptyBundle));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, NoPolicy) {
+ // Submit a policy fetch response with a valid download URL.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the download has been started.
+ EXPECT_EQ(1, loader_factory_.NumPending());
+
+ // Update the policy fetch response before the download has finished. The new
+ // policy fetch response has no download URL.
+ builder_.payload().Clear();
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the download is no longer running.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, CancelUpdate) {
+ // Submit a policy fetch response with a valid download URL.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the download has been started.
+ EXPECT_EQ(1, loader_factory_.NumPending());
+
+ // Now cancel that update before the download completes.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated()).Times(0);
+ updater_->CancelUpdate(kTestPolicyNS);
+ task_env_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+ EXPECT_EQ(0, loader_factory_.NumPending());
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, RetryAfterDataTooLarge) {
+ // Submit a policy fetch response.
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first download has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Indicate that the policy data size will exceed allowed maximum.
+ loader_factory_.AddResponse(kTestDownload, std::string(6 * 1024 * 1024, 'a'));
+ task_env_.RunUntilIdle();
+ loader_factory_.ClearResponses();
+
+ // After 12 hours (minus some random jitter), the next download attempt
+ // happens.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+ task_env_.FastForwardBy(base::Hours(12));
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+ task_env_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Verify that the downloaded policy is being served.
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+}
+
+TEST_F(ComponentCloudPolicyUpdaterTest, RetryAfterDataValidationFails) {
+ // Submit a policy fetch response that is calculated for an empty (that is,
+ // invalid) JSON.
+ builder_.payload().set_secure_hash(crypto::SHA256HashString(std::string()));
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // Verify that the first download has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download with an invalid (empty) JSON.
+ loader_factory_.AddResponse(kTestDownload, std::string());
+ task_env_.RunUntilIdle();
+ loader_factory_.ClearResponses();
+
+ // Verify that no policy is being served.
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(store_->policy().Equals(kEmptyBundle));
+
+ // After 12 hours (minus some random jitter), the next download attempt
+ // happens.
+ EXPECT_EQ(0, loader_factory_.NumPending());
+ task_env_.FastForwardBy(base::Hours(12));
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download with an invalid (empty) JSON. This tests against the
+ // regression that was tracked at https://crbug.com/706781.
+ loader_factory_.AddResponse(kTestDownload, std::string());
+ task_env_.RunUntilIdle();
+ loader_factory_.ClearResponses();
+
+ // Submit a policy fetch response that is calculated for the correct JSON.
+ builder_.payload().set_secure_hash(crypto::SHA256HashString(kTestPolicy));
+ updater_->UpdateExternalPolicy(kTestPolicyNS, CreateResponse());
+ task_env_.RunUntilIdle();
+
+ // The next download attempt has been started.
+ EXPECT_TRUE(loader_factory_.IsPending(kTestDownload));
+
+ // Complete the download.
+ EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
+ loader_factory_.AddResponse(kTestDownload, kTestPolicy);
+ task_env_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&store_delegate_);
+
+ // Verify that the downloaded policy is being served.
+ EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/device_management_service.cc b/chromium/components/policy/core/common/cloud/device_management_service.cc
new file mode 100644
index 00000000000..4128cf74b95
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/device_management_service.cc
@@ -0,0 +1,712 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/device_management_service.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/url_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/gurl.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+const char kPostContentType[] = "application/protobuf";
+
+// Number of times to retry on ERR_NETWORK_CHANGED errors.
+static_assert(DeviceManagementService::kMaxRetries <
+ static_cast<int>(DMServerRequestSuccess::kRequestFailed),
+ "Maximum retries must be less than 10 which is uma sample of "
+ "request failed.");
+
+// Delay after first unsuccessful upload attempt. After each additional failure,
+// the delay increases exponentially. Can be changed for testing to prevent
+// timeouts.
+long g_retry_delay_ms = 10000;
+
+bool IsProxyError(int net_error) {
+ switch (net_error) {
+ case net::ERR_PROXY_CONNECTION_FAILED:
+ case net::ERR_TUNNEL_CONNECTION_FAILED:
+ case net::ERR_PROXY_AUTH_UNSUPPORTED:
+ case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED:
+ case net::ERR_PROXY_CERTIFICATE_INVALID:
+ case net::ERR_SOCKS_CONNECTION_FAILED:
+ case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
+ return true;
+ }
+ return false;
+}
+
+bool IsConnectionError(int net_error) {
+ switch (net_error) {
+ case net::ERR_NETWORK_CHANGED:
+ case net::ERR_NAME_NOT_RESOLVED:
+ case net::ERR_INTERNET_DISCONNECTED:
+ case net::ERR_ADDRESS_UNREACHABLE:
+ case net::ERR_CONNECTION_TIMED_OUT:
+ case net::ERR_NAME_RESOLUTION_FAILED:
+ return true;
+ }
+ return false;
+}
+
+bool IsProtobufMimeType(const std::string& mime_type) {
+ return mime_type == "application/x-protobuffer";
+}
+
+bool FailedWithProxy(const std::string& mime_type,
+ int response_code,
+ int net_error,
+ bool was_fetched_via_proxy) {
+ if (IsProxyError(net_error)) {
+ LOG(WARNING) << "Proxy failed while contacting dmserver.";
+ return true;
+ }
+
+ if (net_error == net::OK &&
+ response_code == DeviceManagementService::kSuccess &&
+ was_fetched_via_proxy && !IsProtobufMimeType(mime_type)) {
+ // The proxy server can be misconfigured but pointing to an existing
+ // server that replies to requests. Try to recover if a successful
+ // request that went through a proxy returns an unexpected mime type.
+ LOG(WARNING) << "Got bad mime-type in response from dmserver that was "
+ << "fetched via a proxy.";
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+// While these are declared as constexpr in the header file, they also need to
+// be defined here so that references can be retrieved when needed. For
+// example, setting one of these constants as an argument to base::BindOnce()
+// requires such a reference.
+const int DeviceManagementService::kSuccess;
+const int DeviceManagementService::kInvalidArgument;
+const int DeviceManagementService::kInvalidAuthCookieOrDMToken;
+const int DeviceManagementService::kMissingLicenses;
+const int DeviceManagementService::kDeviceManagementNotAllowed;
+const int DeviceManagementService::kInvalidURL;
+const int DeviceManagementService::kInvalidSerialNumber;
+const int DeviceManagementService::kDomainMismatch;
+const int DeviceManagementService::kDeviceIdConflict;
+const int DeviceManagementService::kDeviceNotFound;
+const int DeviceManagementService::kPendingApproval;
+const int DeviceManagementService::kRequestTooLarge;
+const int DeviceManagementService::kConsumerAccountWithPackagedLicense;
+const int DeviceManagementService::kTooManyRequests;
+const int DeviceManagementService::kInternalServerError;
+const int DeviceManagementService::kServiceUnavailable;
+const int DeviceManagementService::kPolicyNotFound;
+const int DeviceManagementService::kDeprovisioned;
+const int DeviceManagementService::kArcDisabled;
+const int DeviceManagementService::kInvalidDomainlessCustomer;
+const int DeviceManagementService::kTosHasNotBeenAccepted;
+const int DeviceManagementService::kIllegalAccountForPackagedEDULicense;
+
+// static
+std::string DeviceManagementService::JobConfiguration::GetJobTypeAsString(
+ JobType type) {
+ switch (type) {
+ case DeviceManagementService::JobConfiguration::TYPE_INVALID:
+ return "Invalid";
+ case DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT:
+ return "AutoEnrollment";
+ case DeviceManagementService::JobConfiguration::TYPE_REGISTRATION:
+ return "Registration";
+ case DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH:
+ return "ApiAuthCodeFetch";
+ case DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH:
+ return "PolicyFetch";
+ case DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION:
+ return "Unregistration";
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE:
+ return "UploadCertificate";
+ case DeviceManagementService::JobConfiguration::TYPE_DEVICE_STATE_RETRIEVAL:
+ return "DeviceStateRetrieval";
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS:
+ return "UploadStatus";
+ case DeviceManagementService::JobConfiguration::TYPE_REMOTE_COMMANDS:
+ return "RemoteCommands";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ATTRIBUTE_UPDATE_PERMISSION:
+ return "AttributeUpdatePermission";
+ case DeviceManagementService::JobConfiguration::TYPE_ATTRIBUTE_UPDATE:
+ return "AttributeUpdate";
+ case DeviceManagementService::JobConfiguration::TYPE_GCM_ID_UPDATE:
+ return "GcmIdUpdate";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ANDROID_MANAGEMENT_CHECK:
+ return "AndroidManagementCheck";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_CERT_BASED_REGISTRATION:
+ return "CertBasedRegistration";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ACTIVE_DIRECTORY_ENROLL_PLAY_USER:
+ return "ActiveDirectoryEnrollPlayUser";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ACTIVE_DIRECTORY_PLAY_ACTIVITY:
+ return "ActiveDirectoryPlayActivity";
+ case DeviceManagementService::JobConfiguration::TYPE_TOKEN_ENROLLMENT:
+ return "TokenEnrollment";
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_DESKTOP_REPORT:
+ return "ChromeDesktopReport";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL:
+ return "InitialEnrollmentStateRetrieval";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_POLICY_VALIDATION_REPORT:
+ return "UploadPolicyValidationReport";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_REAL_TIME_REPORT:
+ return "UploadrealtimeReport";
+ case DeviceManagementService::JobConfiguration::TYPE_REQUEST_SAML_URL:
+ return "PublicSamlUserRequest";
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_OS_USER_REPORT:
+ return "ChromeOsUserReport";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_CERT_PROVISIONING_REQUEST:
+ return "CertProvisioningRequest";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_PSM_HAS_DEVICE_STATE_REQUEST:
+ return "PSMDeviceStateRequest";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_ENCRYPTED_REPORT:
+ return "UploadEncryptedReport";
+ case DeviceManagementService::JobConfiguration::TYPE_CHECK_USER_ACCOUNT:
+ return "CheckUserAccount";
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_EUICC_INFO:
+ return "UploadEuiccInfo";
+ case DeviceManagementService::JobConfiguration::
+ TYPE_BROWSER_UPLOAD_PUBLIC_KEY:
+ return "BrowserUploadPublicKey";
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_PROFILE_REPORT:
+ return "ChromeProfileReport";
+ }
+ NOTREACHED() << "Invalid job type " << type;
+ return "";
+}
+
+JobConfigurationBase::JobConfigurationBase(
+ JobType type,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> factory)
+ : type_(type),
+ factory_(factory),
+ auth_data_(std::move(auth_data)),
+ oauth_token_(std::move(oauth_token)) {
+ CHECK(!auth_data_.has_oauth_token()) << "Use |oauth_token| instead";
+
+#if !BUILDFLAG(IS_IOS)
+ if (oauth_token_) {
+ // Put the oauth token in the query parameters for platforms that are not
+ // iOS. On iOS we are trying the oauth token in the request headers
+ // (crbug.com/1312158). We might want to use the iOS approach on all
+ // platforms at some point.
+ AddParameter(dm_protocol::kParamOAuthToken, *oauth_token_);
+ }
+#endif
+}
+
+JobConfigurationBase::~JobConfigurationBase() = default;
+
+JobConfigurationBase::JobType JobConfigurationBase::GetType() {
+ return type_;
+}
+
+const JobConfigurationBase::ParameterMap&
+JobConfigurationBase::GetQueryParams() {
+ return query_params_;
+}
+
+void JobConfigurationBase::AddParameter(const std::string& name,
+ const std::string& value) {
+ query_params_[name] = value;
+}
+
+const DMAuth& JobConfigurationBase::GetAuth() const {
+ return auth_data_;
+}
+
+scoped_refptr<network::SharedURLLoaderFactory>
+JobConfigurationBase::GetUrlLoaderFactory() {
+ return factory_;
+}
+
+net::NetworkTrafficAnnotationTag
+JobConfigurationBase::GetTrafficAnnotationTag() {
+ return net::DefineNetworkTrafficAnnotation("device_management_service", R"(
+ semantics {
+ sender: "Cloud Policy"
+ description:
+ "Communication with the Cloud Policy backend, used to check for "
+ "the existence of cloud policy for the signed-in account, and to "
+ "load/update cloud policy if it exists. Also used to send reports, "
+ "both desktop batch reports and real-time reports."
+ trigger:
+ "Sign in to Chrome, enroll for Chrome Browser Cloud Management, "
+ "periodic refreshes."
+ data:
+ "During initial signin or device enrollment, auth data is sent up "
+ "as part of registration. After initial signin/enrollment, if the "
+ "session or device is managed, a unique device or profile ID is "
+ "sent with every future request. Other diagnostic information can be "
+ "sent up for managed sessions, including which users have used the "
+ "device, device hardware status, connected networks, CPU usage, etc."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature cannot be controlled by Chrome settings, but users "
+ "can sign out of Chrome to disable it."
+ chrome_policy {
+ SigninAllowed {
+ policy_options {mode: MANDATORY}
+ SigninAllowed: false
+ }
+ }
+ })");
+}
+
+std::unique_ptr<network::ResourceRequest>
+JobConfigurationBase::GetResourceRequest(bool bypass_proxy, int last_error) {
+ auto rr = std::make_unique<network::ResourceRequest>();
+
+ // Build the URL for the request, including parameters.
+ GURL url = GetURL(last_error);
+ for (ParameterMap::const_iterator entry(query_params_.begin());
+ entry != query_params_.end(); ++entry) {
+ url = net::AppendQueryParameter(url, entry->first, entry->second);
+ }
+
+ rr->url = url;
+ rr->method = "POST";
+ rr->load_flags =
+ net::LOAD_DISABLE_CACHE | (bypass_proxy ? net::LOAD_BYPASS_PROXY : 0);
+ rr->credentials_mode = network::mojom::CredentialsMode::kOmit;
+ // Disable secure DNS for requests related to device management to allow for
+ // recovery in the event of a misconfigured secure DNS policy.
+ rr->trusted_params = network::ResourceRequest::TrustedParams();
+ rr->trusted_params->disable_secure_dns = true;
+
+#if BUILDFLAG(IS_IOS)
+ // Put the oauth token in the request headers on iOS. We might want
+ // to use this approach on the other platforms at some point. This approach
+ // will be tried first on iOS (crbug.com/1312158). Technically, the
+ // DMServer should already be able to handle the oauth token in the
+ // request headers, but we prefer to try the approach on iOS first to
+ // avoid breaking the other platforms with unexpected issues.
+ if (oauth_token_ && !oauth_token_->empty()) {
+ rr->headers.SetHeader(dm_protocol::kAuthHeader,
+ base::StrCat({"OAuth ", *oauth_token_}));
+ }
+#endif
+
+ // If auth data is specified, use it to build the request.
+ switch (auth_data_.token_type()) {
+ case DMAuthTokenType::kNoAuth:
+ break;
+ case DMAuthTokenType::kDm:
+ rr->headers.SetHeader(dm_protocol::kAuthHeader,
+ std::string(dm_protocol::kDMTokenAuthHeaderPrefix) +
+ auth_data_.dm_token());
+ break;
+ case DMAuthTokenType::kEnrollment:
+ rr->headers.SetHeader(
+ dm_protocol::kAuthHeader,
+ std::string(dm_protocol::kEnrollmentTokenAuthHeaderPrefix) +
+ auth_data_.enrollment_token());
+ break;
+ case DMAuthTokenType::kOauth:
+ // OAuth token is transferred as a HTTP query parameter.
+ break;
+ }
+
+ return rr;
+}
+
+DeviceManagementService::Job::RetryMethod JobConfigurationBase::ShouldRetry(
+ int response_code,
+ const std::string& response_body) {
+ // By default, no need to retry based on the contents of the response.
+ return DeviceManagementService::Job::NO_RETRY;
+}
+
+// A device management service job implementation.
+class DeviceManagementService::JobImpl : public Job {
+ public:
+ JobImpl(const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ std::unique_ptr<JobConfiguration> config)
+ : config_(std::move(config)), task_runner_(task_runner) {}
+ JobImpl(const JobImpl&) = delete;
+ JobImpl& operator=(const JobImpl&) = delete;
+ ~JobImpl() override = default;
+
+ void Start();
+ base::WeakPtr<JobImpl> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
+
+ private:
+ friend class JobForTesting;
+
+ void CreateUrlLoader();
+
+ // Callback for `SimpleURLLoader`. Extracts data from |response_body| and
+ // |url_loader_| and passes it on to |OnURLLoaderCompleteInternal|.
+ void OnURLLoaderComplete(std::unique_ptr<std::string> response_body);
+
+ // Interprets URL loading data and either schedules a retry or hands the data
+ // off to |HandleResponseData|.
+ RetryMethod OnURLLoaderCompleteInternal(const std::string& response_body,
+ const std::string& mime_type,
+ int net_error,
+ int response_code,
+ bool was_fetched_via_proxy,
+ bool is_test = false);
+ // Logs failed jobs an jobs that succeeded after retry.
+ // Then hands the response data off to |config_|.
+ RetryMethod HandleResponseData(const std::string& response_body,
+ const std::string& mime_type,
+ int net_error,
+ int response_code,
+ bool was_fetched_via_proxy);
+ RetryMethod ShouldRetry(const std::string& response_body,
+ const std::string& mime_type,
+ int response_code,
+ int net_error,
+ bool was_fetched_via_proxy);
+ int GetRetryDelay(RetryMethod method);
+
+ std::unique_ptr<JobConfiguration> config_;
+ bool bypass_proxy_ = false;
+
+ // Number of times that this job has been retried due to connection errors.
+ int retries_count_ = 0;
+
+ // Network error code passed of last call to HandleResponseData().
+ int last_error_ = 0;
+
+ int retry_delay_ = 0;
+
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ SEQUENCE_CHECKER(sequence_checker_);
+ base::WeakPtrFactory<JobImpl> weak_ptr_factory_{this};
+};
+
+void DeviceManagementService::JobImpl::CreateUrlLoader() {
+ auto rr = config_->GetResourceRequest(bypass_proxy_, last_error_);
+ auto annotation = config_->GetTrafficAnnotationTag();
+ url_loader_ = network::SimpleURLLoader::Create(std::move(rr), annotation);
+ url_loader_->AttachStringForUpload(config_->GetPayload(), kPostContentType);
+ url_loader_->SetAllowHttpErrorResults(true);
+}
+
+void DeviceManagementService::JobImpl::OnURLLoaderComplete(
+ std::unique_ptr<std::string> response_body) {
+ int response_code = 0;
+ bool was_fetched_via_proxy = false;
+ std::string mime_type;
+ if (url_loader_->ResponseInfo()) {
+ was_fetched_via_proxy =
+ url_loader_->ResponseInfo()->proxy_server.is_valid() &&
+ !url_loader_->ResponseInfo()->proxy_server.is_direct();
+ mime_type = url_loader_->ResponseInfo()->mime_type;
+ if (url_loader_->ResponseInfo()->headers)
+ response_code = url_loader_->ResponseInfo()->headers->response_code();
+ }
+
+ std::string response_body_str;
+ if (response_body.get())
+ response_body_str = std::move(*response_body.get());
+
+ OnURLLoaderCompleteInternal(response_body_str, mime_type,
+ url_loader_->NetError(), response_code,
+ was_fetched_via_proxy);
+}
+
+DeviceManagementService::Job::RetryMethod
+DeviceManagementService::JobImpl::OnURLLoaderCompleteInternal(
+ const std::string& response_body,
+ const std::string& mime_type,
+ int net_error,
+ int response_code,
+ bool was_fetched_via_proxy,
+ bool is_test) {
+ RetryMethod retry_method =
+ ShouldRetry(response_body, mime_type, response_code, net_error,
+ was_fetched_via_proxy);
+
+ if (retry_method == Job::NO_RETRY) {
+ HandleResponseData(response_body, mime_type, net_error, response_code,
+ was_fetched_via_proxy);
+ return retry_method;
+ }
+
+ config_->OnBeforeRetry(response_code, response_body);
+ LOG(WARNING) << "Request of type "
+ << JobConfiguration::GetJobTypeAsString(config_->GetType())
+ << " failed (net_error = " << net_error
+ << ", response_code = " << response_code << "), retrying in "
+ << retry_delay_ << "ms.";
+ if (!is_test) {
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&DeviceManagementService::JobImpl::Start, GetWeakPtr()),
+ base::Milliseconds(GetRetryDelay(retry_method)));
+ }
+ return retry_method;
+}
+
+DeviceManagementService::Job::RetryMethod
+DeviceManagementService::JobImpl::HandleResponseData(
+ const std::string& response_body,
+ const std::string& mime_type,
+ int net_error,
+ int response_code,
+ bool was_fetched_via_proxy) {
+ std::string uma_name = config_->GetUmaName();
+ if (net_error != net::OK) {
+ // Using histogram functions which allows runtime histogram name.
+ base::UmaHistogramEnumeration(uma_name,
+ DMServerRequestSuccess::kRequestFailed);
+ LOG(WARNING) << "Request of type "
+ << JobConfiguration::GetJobTypeAsString(config_->GetType())
+ << " failed (net_error = " << net_error << ").";
+ config_->OnURLLoadComplete(this, net_error, response_code, std::string());
+ return RetryMethod::NO_RETRY;
+ }
+
+ if (response_code != kSuccess) {
+ LOG(WARNING) << "Request of type "
+ << JobConfiguration::GetJobTypeAsString(config_->GetType())
+ << " failed (response_code = " << response_code << ").";
+ base::UmaHistogramEnumeration(uma_name,
+ DMServerRequestSuccess::kRequestError);
+ } else {
+ // Success with retries_count_ retries.
+ if (retries_count_) {
+ LOG(WARNING) << "Request of type "
+ << JobConfiguration::GetJobTypeAsString(config_->GetType())
+ << " succeeded after " << retries_count_ << " retries.";
+ }
+ base::UmaHistogramExactLinear(
+ uma_name, retries_count_,
+ static_cast<int>(DMServerRequestSuccess::kMaxValue) + 1);
+ }
+
+ config_->OnURLLoadComplete(this, net_error, response_code, response_body);
+ return NO_RETRY;
+}
+
+DeviceManagementService::Job::RetryMethod
+DeviceManagementService::JobImpl::ShouldRetry(const std::string& response_body,
+ const std::string& mime_type,
+ int response_code,
+ int net_error,
+ bool was_fetched_via_proxy) {
+ last_error_ = net_error;
+ if (!bypass_proxy_ && FailedWithProxy(mime_type, response_code, net_error,
+ was_fetched_via_proxy)) {
+ // Retry the job immediately if it failed due to a broken proxy, by
+ // bypassing the proxy on the next try.
+ bypass_proxy_ = true;
+ return RETRY_IMMEDIATELY;
+ }
+
+ // Early device policy fetches on ChromeOS and Auto-Enrollment checks are
+ // often interrupted during ChromeOS startup when network is not yet ready.
+ // Allowing the fetcher to retry once after that is enough to recover; allow
+ // it to retry up to 3 times just in case.
+ if (IsConnectionError(net_error) && retries_count_ < kMaxRetries) {
+ ++retries_count_;
+ if (config_->GetType() ==
+ DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH) {
+ // We must not delay when retrying policy fetch, because it is a blocking
+ // call when logging in.
+ return RETRY_IMMEDIATELY;
+ } else {
+ return RETRY_WITH_DELAY;
+ }
+ }
+
+ // The request didn't fail, or the limit of retry attempts has been reached.
+ // Ask the config if this is a valid response.
+ return config_->ShouldRetry(response_code, response_body);
+}
+
+int DeviceManagementService::JobImpl::GetRetryDelay(RetryMethod method) {
+ switch (method) {
+ case RETRY_WITH_DELAY:
+ return g_retry_delay_ms << (retries_count_ - 1);
+ case RETRY_IMMEDIATELY:
+ return 0;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+DeviceManagementService::~DeviceManagementService() = default;
+
+DeviceManagementService::JobForTesting::JobForTesting() = default;
+DeviceManagementService::JobForTesting::JobForTesting(JobImpl* job_impl)
+ : job_impl_(job_impl ? job_impl->GetWeakPtr() : base::WeakPtr<JobImpl>{}) {}
+DeviceManagementService::JobForTesting::JobForTesting(const JobForTesting&) =
+ default;
+DeviceManagementService::JobForTesting::JobForTesting(
+ JobForTesting&&) noexcept = default;
+DeviceManagementService::JobForTesting&
+DeviceManagementService::JobForTesting::operator=(const JobForTesting&) =
+ default;
+DeviceManagementService::JobForTesting&
+DeviceManagementService::JobForTesting::operator=(JobForTesting&&) noexcept =
+ default;
+DeviceManagementService::JobForTesting::~JobForTesting() = default;
+
+bool DeviceManagementService::JobForTesting::IsActive() const {
+ return job_impl_.get();
+}
+
+void DeviceManagementService::JobForTesting::Deactivate() {
+ return job_impl_.reset();
+}
+
+DeviceManagementService::JobConfiguration*
+DeviceManagementService::JobForTesting::GetConfigurationForTesting() const {
+ CHECK(IsActive());
+ return job_impl_.get()->config_.get();
+}
+
+DeviceManagementService::Job::RetryMethod
+DeviceManagementService::JobForTesting::SetResponseForTesting(
+ int net_error,
+ int response_code,
+ const std::string& response_body,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy) {
+ CHECK(IsActive());
+ return job_impl_.get()->OnURLLoaderCompleteInternal(
+ response_body, mime_type, net_error, response_code, was_fetched_via_proxy,
+ /*is_test=*/true);
+}
+
+std::pair<std::unique_ptr<DeviceManagementService::Job>,
+ DeviceManagementService::JobForTesting>
+DeviceManagementService::CreateJobForTesting(
+ std::unique_ptr<JobConfiguration> config) {
+ CHECK(config);
+ auto job = std::make_unique<JobImpl>(task_runner_, std::move(config));
+ JobForTesting job_for_testing(job.get()); // IN-TEST
+ return std::make_pair(std::move(job), std::move(job_for_testing));
+}
+
+std::unique_ptr<DeviceManagementService::Job>
+DeviceManagementService::CreateJob(std::unique_ptr<JobConfiguration> config) {
+ CHECK(config);
+ std::unique_ptr<JobImpl> job =
+ std::make_unique<JobImpl>(task_runner_, std::move(config));
+ AddJob(job.get());
+ return job;
+}
+
+void DeviceManagementService::ScheduleInitialization(
+ int64_t delay_milliseconds) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (initialized_)
+ return;
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&DeviceManagementService::Initialize,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Milliseconds(delay_milliseconds));
+}
+
+void DeviceManagementService::Initialize() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (initialized_)
+ return;
+ initialized_ = true;
+
+ StartQueuedJobs();
+}
+
+void DeviceManagementService::Shutdown() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+DeviceManagementService::DeviceManagementService(
+ std::unique_ptr<Configuration> configuration)
+ : configuration_(std::move(configuration)),
+ initialized_(false),
+ task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+ DCHECK(configuration_);
+}
+
+void DeviceManagementService::JobImpl::Start() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ CreateUrlLoader();
+ url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ config_->GetUrlLoaderFactory().get(),
+ base::BindOnce(&DeviceManagementService::JobImpl::OnURLLoaderComplete,
+ GetWeakPtr()));
+}
+
+// static
+void DeviceManagementService::SetRetryDelayForTesting(long retry_delay_ms) {
+ CHECK_GE(retry_delay_ms, 0);
+ g_retry_delay_ms = retry_delay_ms;
+}
+
+void DeviceManagementService::AddJob(JobImpl* job) {
+ if (initialized_)
+ job->Start();
+ else
+ queued_jobs_.push_back(job->GetWeakPtr());
+}
+
+base::WeakPtr<DeviceManagementService> DeviceManagementService::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void DeviceManagementService::StartQueuedJobs() {
+ DCHECK(initialized_);
+ for (auto& job : queued_jobs_) {
+ if (job.get()) {
+ job.get()->Start();
+ }
+ }
+ queued_jobs_.clear();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/device_management_service.h b/chromium/components/policy/core/common/cloud/device_management_service.h
new file mode 100644
index 00000000000..2b619cc803a
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/device_management_service.h
@@ -0,0 +1,404 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_DEVICE_MANAGEMENT_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_DEVICE_MANAGEMENT_SERVICE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/policy_export.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+// Used in the Enterprise.DMServerRequestSuccess histogram, shows how many
+// retries we had to do to execute the DeviceManagementRequestJob.
+enum class DMServerRequestSuccess {
+ // No retries happened, the request succeeded for the first try.
+ kRequestNoRetry = 0,
+
+ // 1..kMaxRetries: number of retries. kMaxRetries is the maximum number of
+ // retries allowed, defined in the .cc file.
+
+ // The request failed (too many retries or non-retryable error).
+ kRequestFailed = 10,
+ // The server responded with an error.
+ kRequestError = 11,
+
+ kMaxValue = kRequestError,
+};
+
+// The device management service is responsible for everything related to
+// communication with the device management server. It creates the backends
+// objects that the device management policy provider and friends use to issue
+// requests.
+class POLICY_EXPORT DeviceManagementService {
+ public:
+ // HTTP Error Codes of the DM Server with their concrete meanings in the
+ // context of the DM Server communication.
+ static constexpr int kSuccess = 200;
+ static constexpr int kInvalidArgument = 400;
+ static constexpr int kInvalidAuthCookieOrDMToken = 401;
+ static constexpr int kMissingLicenses = 402;
+ static constexpr int kDeviceManagementNotAllowed = 403;
+ static constexpr int kInvalidURL =
+ 404; // This error is not coming from the GFE.
+ static constexpr int kInvalidSerialNumber = 405;
+ static constexpr int kDomainMismatch = 406;
+ static constexpr int kDeviceIdConflict = 409;
+ static constexpr int kDeviceNotFound = 410;
+ static constexpr int kPendingApproval = 412;
+ static constexpr int kRequestTooLarge = 413;
+ static constexpr int kConsumerAccountWithPackagedLicense = 417;
+ static constexpr int kTooManyRequests = 429;
+ static constexpr int kInternalServerError = 500;
+ static constexpr int kServiceUnavailable = 503;
+ static constexpr int kPolicyNotFound = 902;
+ static constexpr int kDeprovisioned = 903;
+ static constexpr int kArcDisabled = 904;
+ static constexpr int kInvalidDomainlessCustomer = 905;
+ static constexpr int kTosHasNotBeenAccepted = 906;
+ static constexpr int kIllegalAccountForPackagedEDULicense = 907;
+
+ // Number of times to retry on ERR_NETWORK_CHANGED errors.
+ static const int kMaxRetries = 3;
+
+ // Obtains the parameters used to contact the server.
+ // This allows creating the DeviceManagementService early and getting these
+ // parameters later. Passing the parameters directly in the ctor isn't
+ // possible because some aren't ready during startup. http://crbug.com/302798
+ class POLICY_EXPORT Configuration {
+ public:
+ virtual ~Configuration() {}
+
+ // Server at which to contact the service (DMServer).
+ virtual std::string GetDMServerUrl() const = 0;
+
+ // Agent reported in the "agent" query parameter.
+ virtual std::string GetAgentParameter() const = 0;
+
+ // The platform reported in the "platform" query parameter.
+ virtual std::string GetPlatformParameter() const = 0;
+
+ // Server at which to contact the real time reporting service.
+ virtual std::string GetRealtimeReportingServerUrl() const = 0;
+
+ // Server endpoint for encrypted events.
+ virtual std::string GetEncryptedReportingServerUrl() const = 0;
+
+ // Server at which to contact the real time reporting service for
+ // enterprise connectors.
+ virtual std::string GetReportingConnectorServerUrl(
+ content::BrowserContext* context) const = 0;
+ };
+
+ // A DeviceManagementService job manages network requests to the device
+ // management and real-time reporting services. Jobs are created by calling
+ // CreateJob() and specifying a JobConfiguration. Jobs can be canceled by
+ // deleting the returned Job object.
+ //
+ // If network requests fail, the Job will retry them.
+ //
+ // JobConfiguration is the interface used by callers to specify parameters
+ // of network requests. This object is not immutable and may be changed after
+ // a call to OnBeforeRetry(). DeviceManagementService calls the GetXXX
+ // methods again to create a new network request for each retry.
+
+ class JobConfiguration;
+
+ class POLICY_EXPORT Job {
+ public:
+ enum RetryMethod {
+ // No retry required for this request.
+ NO_RETRY,
+ // Should retry immediately (no delay).
+ RETRY_IMMEDIATELY,
+ // Should retry after a delay.
+ RETRY_WITH_DELAY
+ };
+
+ virtual ~Job() {}
+ };
+
+ class JobImpl;
+
+ // JobForTesting is a test API to access jobs.
+ // See also |FakeDeviceManagementService|.
+ class POLICY_EXPORT JobForTesting {
+ public:
+ JobForTesting();
+ explicit JobForTesting(JobImpl* job_impl);
+ JobForTesting(const JobForTesting&);
+ JobForTesting(JobForTesting&&) noexcept;
+ JobForTesting& operator=(const JobForTesting&);
+ JobForTesting& operator=(JobForTesting&&) noexcept;
+ ~JobForTesting();
+
+ bool IsActive() const;
+ void Deactivate();
+
+ // TODO(rbock) make return type const.
+ JobConfiguration* GetConfigurationForTesting() const;
+
+ Job::RetryMethod SetResponseForTesting(
+ // TODO(rbock) change type to net::Error
+ int net_error,
+ int response_code,
+ const std::string& response_body,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy);
+
+ private:
+ base::WeakPtr<JobImpl> job_impl_;
+ };
+
+ class POLICY_EXPORT JobConfiguration {
+ public:
+ // Describes the job type. (Integer values are stated explicitly to
+ // facilitate reading of logs.)
+ // TYPE_INVALID is used only in tests so that they can EXPECT the correct
+ // job type has been used. Otherwise, tests would need to initially set
+ // the type to somehing like TYPE_AUTO_ENROLLMENT, and then it would not
+ // be possible to EXPECT the job type in auto enrollment tests.
+ enum JobType {
+ TYPE_INVALID = -1,
+ TYPE_AUTO_ENROLLMENT = 0,
+ TYPE_REGISTRATION = 1,
+ TYPE_API_AUTH_CODE_FETCH = 2,
+ TYPE_POLICY_FETCH = 3,
+ TYPE_UNREGISTRATION = 4,
+ TYPE_UPLOAD_CERTIFICATE = 5,
+ TYPE_DEVICE_STATE_RETRIEVAL = 6,
+ TYPE_UPLOAD_STATUS = 7,
+ TYPE_REMOTE_COMMANDS = 8,
+ TYPE_ATTRIBUTE_UPDATE_PERMISSION = 9,
+ TYPE_ATTRIBUTE_UPDATE = 10,
+ TYPE_GCM_ID_UPDATE = 11,
+ TYPE_ANDROID_MANAGEMENT_CHECK = 12,
+ TYPE_CERT_BASED_REGISTRATION = 13,
+ TYPE_ACTIVE_DIRECTORY_ENROLL_PLAY_USER = 14,
+ TYPE_ACTIVE_DIRECTORY_PLAY_ACTIVITY = 15,
+ /* TYPE_REQUEST_LICENSE_TYPES = 16, */
+ /*Deprecated, CloudPolicyClient no longer uses it.
+ TYPE_UPLOAD_APP_INSTALL_REPORT = 17,*/
+ TYPE_TOKEN_ENROLLMENT = 18,
+ TYPE_CHROME_DESKTOP_REPORT = 19,
+ TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL = 20,
+ TYPE_UPLOAD_POLICY_VALIDATION_REPORT = 21,
+ TYPE_UPLOAD_REAL_TIME_REPORT = 22,
+ TYPE_REQUEST_SAML_URL = 23,
+ TYPE_CHROME_OS_USER_REPORT = 24,
+ TYPE_CERT_PROVISIONING_REQUEST = 25,
+ TYPE_PSM_HAS_DEVICE_STATE_REQUEST = 26,
+ TYPE_UPLOAD_ENCRYPTED_REPORT = 27,
+ TYPE_CHECK_USER_ACCOUNT = 28,
+ TYPE_UPLOAD_EUICC_INFO = 29,
+ TYPE_BROWSER_UPLOAD_PUBLIC_KEY = 30,
+ TYPE_CHROME_PROFILE_REPORT = 31,
+ };
+
+ // The set of HTTP query parameters of the request.
+ using ParameterMap = std::map<std::string, std::string>;
+
+ // Convert the job type into a string.
+ static std::string GetJobTypeAsString(JobType type);
+
+ virtual ~JobConfiguration() {}
+
+ virtual JobType GetType() = 0;
+
+ virtual const DMAuth& GetAuth() const = 0;
+
+ virtual const ParameterMap& GetQueryParams() = 0;
+
+ // Gets the factory to create URL fetchers for requests.
+ virtual scoped_refptr<network::SharedURLLoaderFactory>
+ GetUrlLoaderFactory() = 0;
+
+ // Gets the payload to send in requests.
+ virtual std::string GetPayload() = 0;
+
+ // Returns the network annotation to assign to requests.
+ virtual net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() = 0;
+
+ // Returns a properly initialized resource for requests.
+ virtual std::unique_ptr<network::ResourceRequest> GetResourceRequest(
+ bool bypass_proxy,
+ int last_error) = 0;
+
+ // Returns the the UMA histogram to record stats about the network request.
+ virtual std::string GetUmaName() = 0;
+
+ // Returns the RetryMethod the configuration considers appropriate given the
+ // response from the server. The response_code is the http response, and the
+ // response_body is the response returned by the server (it may be empty
+ // depending on the response_code).
+ // Note this method will not be called on a net_error, because the
+ // assumption is that this configuration is deciding to retry based on a
+ // server response, and there is no server response in that case.
+ virtual Job::RetryMethod ShouldRetry(int response_code,
+ const std::string& response_body) = 0;
+
+ // Prepare this job for a network request retry.
+ virtual void OnBeforeRetry(int response_code,
+ const std::string& response_body) = 0;
+
+ // Called when a result is available for the request (possibly after
+ // retries). If |net_error| is net::OK, |response_code| will be set to the
+ // HTTP response code.
+ virtual void OnURLLoadComplete(Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) = 0;
+ };
+
+ explicit DeviceManagementService(
+ std::unique_ptr<Configuration> configuration);
+ DeviceManagementService(const DeviceManagementService&) = delete;
+ DeviceManagementService& operator=(const DeviceManagementService&) = delete;
+ virtual ~DeviceManagementService();
+
+ // Creates and queues/starts a new Job.
+ virtual std::unique_ptr<Job> CreateJob(
+ std::unique_ptr<JobConfiguration> config);
+
+ // Schedules a task to run |Initialize| after |delay_milliseconds| had passed.
+ void ScheduleInitialization(int64_t delay_milliseconds);
+
+ // Makes the service stop all requests.
+ void Shutdown();
+
+ const Configuration* configuration() const { return configuration_.get(); }
+
+ // Sets the retry delay to a shorter time to prevent browser tests from
+ // timing out.
+ static void SetRetryDelayForTesting(long retryDelayMs);
+
+ protected:
+ // Creates a new Job without starting it.
+ // Used by `FakeDeviceManagementService` to avoid queueing/starting of
+ // jobs in tests.
+ std::pair<std::unique_ptr<Job>, JobForTesting> CreateJobForTesting(
+ std::unique_ptr<JobConfiguration> config);
+
+ const scoped_refptr<base::SequencedTaskRunner> GetTaskRunnerForTesting() {
+ return task_runner_;
+ }
+
+ private:
+ using JobQueue = std::vector<base::WeakPtr<JobImpl>>;
+
+ // Starts processing any queued jobs.
+ void Initialize();
+
+ // If called before |Initialize| this queues job.
+ // Otherwise it starts the job.
+ void AddJob(JobImpl* job);
+
+ base::WeakPtr<DeviceManagementService> GetWeakPtr();
+
+ // Moves jobs from the queued state to the pending state and starts them.
+ // This should only be called when DeviceManagementService is already
+ // initialized.
+ void StartQueuedJobs();
+
+ // A Configuration implementation that is used to obtain various parameters
+ // used to talk to the device management server.
+ std::unique_ptr<Configuration> configuration_;
+
+ // Jobs that are added, but not started yet.
+ JobQueue queued_jobs_;
+
+ // If this service is initialized, incoming requests get started instantly.
+ // If it is not initialized, incoming requests are queued.
+ bool initialized_;
+
+ // TaskRunner used to schedule retry attempts.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Used to run delayed tasks (e.g. |Initialize()|).
+ base::WeakPtrFactory<DeviceManagementService> weak_ptr_factory_{this};
+};
+
+// Base class used to implement job configurations.
+class POLICY_EXPORT JobConfigurationBase
+ : public DeviceManagementService::JobConfiguration {
+ public:
+ JobConfigurationBase(const JobConfigurationBase&) = delete;
+ JobConfigurationBase& operator=(const JobConfigurationBase&) = delete;
+
+ // DeviceManagementService::JobConfiguration:
+ JobType GetType() override;
+ const ParameterMap& GetQueryParams() override;
+ scoped_refptr<network::SharedURLLoaderFactory> GetUrlLoaderFactory() override;
+ net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() override;
+ std::unique_ptr<network::ResourceRequest> GetResourceRequest(
+ bool bypass_proxy,
+ int last_error) override;
+ DeviceManagementService::Job::RetryMethod ShouldRetry(
+ int response_code,
+ const std::string& response_body) override;
+
+ protected:
+ JobConfigurationBase(JobType type,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> factory);
+ ~JobConfigurationBase() override;
+
+ // Adds the query parameter to the network request's URL. If the parameter
+ // already exists its value is replaced.
+ void AddParameter(const std::string& name, const std::string& value);
+
+ const DMAuth& GetAuth() const override;
+
+ // Derived classes should return the base URL for the request.
+ virtual GURL GetURL(int last_error) const = 0;
+
+ private:
+ JobType type_;
+ scoped_refptr<network::SharedURLLoaderFactory> factory_;
+
+ // Auth data that will be passed as 'Authorization' header. Both |auth_data_|
+ // and |oauth_token_| can be specified for one request.
+ DMAuth auth_data_;
+
+ // OAuth token that will be passed as a query parameter. Both |auth_data_|
+ // and |oauth_token_| can be specified for one request.
+ absl::optional<std::string> oauth_token_;
+
+ // Query parameters for the network request.
+ ParameterMap query_params_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_DEVICE_MANAGEMENT_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/device_management_service_unittest.cc b/chromium/components/policy/core/common/cloud/device_management_service_unittest.cc
new file mode 100644
index 00000000000..3525da64855
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/device_management_service_unittest.cc
@@ -0,0 +1,1222 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/device_management_service.h"
+
+#include <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/run_loop.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/mock_device_management_service.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Mock;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+const char kServiceUrl[] = "https://example.com/management_service";
+
+// Encoded empty response messages for testing the error code paths.
+const char kResponseEmpty[] = "\x08\x00";
+
+#define PROTO_STRING(name) (std::string(name, std::size(name) - 1))
+
+// Some helper constants.
+const char kOAuthToken[] = "oauth-token";
+const char kDMToken[] = "device-management-token";
+const char kClientID[] = "device-id";
+const char kRobotAuthCode[] = "robot-oauth-auth-code";
+const char kEnrollmentToken[] = "enrollment_token";
+#if BUILDFLAG(IS_IOS)
+const char kOAuthAuthorizationHeaderPrefix[] = "OAuth ";
+#endif
+
+// Unit tests for the device management policy service. The tests are run
+// against a TestURLLoaderFactory that is used to short-circuit the request
+// without calling into the actual network stack.
+class DeviceManagementServiceTestBase : public testing::Test {
+ protected:
+ DeviceManagementServiceTestBase() {
+ // Set retry delay to prevent timeouts.
+ policy::DeviceManagementService::SetRetryDelayForTesting(0);
+
+ shared_url_loader_factory_ =
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &url_loader_factory_);
+ ResetService();
+ InitializeService();
+ }
+
+ ~DeviceManagementServiceTestBase() override {
+ service_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void SetUp() override {
+ // Verify the metrics when job is done.
+ ON_CALL(*this, OnJobDone(_, _, _, _))
+ .WillByDefault(Invoke(
+ [this](DeviceManagementService::Job*, DeviceManagementStatus status,
+ int net_error,
+ const std::string&) { VerifyMetrics(status, net_error); }));
+ }
+
+ void TearDown() override {
+ // Metrics data is always reset after verification so there shouldn't be any
+ // data point left.
+ EXPECT_EQ(
+ 0u, histogram_tester_.GetTotalCountsForPrefix(request_uma_name_prefix_)
+ .size());
+ }
+
+ void ResetService() {
+ std::unique_ptr<DeviceManagementService::Configuration> configuration(
+ new MockDeviceManagementServiceConfiguration(kServiceUrl));
+ service_ =
+ std::make_unique<DeviceManagementService>(std::move(configuration));
+ }
+
+ void InitializeService() {
+ service_->ScheduleInitialization(0);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ network::TestURLLoaderFactory::PendingRequest* GetPendingRequest(
+ size_t index = 0) {
+ if (index >= url_loader_factory_.pending_requests()->size())
+ return nullptr;
+ return &(*url_loader_factory_.pending_requests())[index];
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartJob(
+ DeviceManagementService::JobConfiguration::JobType type,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ last_job_type_ =
+ DeviceManagementService::JobConfiguration::GetJobTypeAsString(type);
+ std::unique_ptr<FakeJobConfiguration> config =
+ std::make_unique<FakeJobConfiguration>(
+ service_.get(), type, kClientID, critical, std::move(auth_data),
+ oauth_token, shared_url_loader_factory_,
+ base::BindOnce(&DeviceManagementServiceTestBase::OnJobDone,
+ base::Unretained(this)),
+ base::BindRepeating(&DeviceManagementServiceTestBase::OnJobRetry,
+ base::Unretained(this)),
+ base::BindRepeating(
+ &DeviceManagementServiceTestBase::OnShouldJobRetry,
+ base::Unretained(this)));
+ config->SetRequestPayload(payload);
+ config->SetShouldRetryResponse(method);
+ return service_->CreateJob(std::move(config));
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartRegistrationJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_REGISTRATION,
+ /*critical=*/false, DMAuth::NoAuth(), kOAuthToken, payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartCertBasedRegistrationJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_CERT_BASED_REGISTRATION,
+ /*critical=*/false, DMAuth::NoAuth(), std::string(), payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartTokenEnrollmentJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_TOKEN_ENROLLMENT,
+ /*critical=*/false, DMAuth::FromEnrollmentToken(kEnrollmentToken),
+ std::string(), payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartApiAuthCodeFetchJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH,
+ /*critical=*/false, DMAuth::NoAuth(), kOAuthToken, payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartUnregistrationJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION,
+ /*critical=*/false, DMAuth::FromDMToken(kDMToken), std::string(),
+ payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartPolicyFetchJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ /*critical=*/false, DMAuth::NoAuth(), kOAuthToken, payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartCriticalPolicyFetchJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ /*critical=*/true, DMAuth::NoAuth(), kOAuthToken, payload, method);
+ }
+
+ std::unique_ptr<DeviceManagementService::Job> StartAutoEnrollmentJob(
+ const std::string& payload = std::string(),
+ DeviceManagementService::Job::RetryMethod method =
+ DeviceManagementService::Job::NO_RETRY) {
+ return StartJob(
+ DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
+ /*critical=*/false, DMAuth::NoAuth(), std::string(), payload, method);
+ }
+
+ void SendResponse(net::Error net_error,
+ int http_status,
+ const std::string& response,
+ size_t request_index = 0u,
+ const std::string& mime_type = std::string(),
+ bool was_fetched_via_proxy = false) {
+ const auto* request = GetPendingRequest(request_index);
+ ASSERT_TRUE(request);
+
+ // Note: We cannot use `network::CreateURLResponseHead` because we are using
+ // some unconventional HTTP status codes, which trigger NOTREACHED in
+ // `net::GetHttpReasonPhrase`.
+ auto head = network::mojom::URLResponseHead::New();
+ std::string status_line(
+ base::StringPrintf("HTTP/1.1 %d Something", http_status));
+ std::string headers = status_line + "\n" +
+ net::HttpRequestHeaders::kContentType +
+ ": text/html\n\n";
+ head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ net::HttpUtil::AssembleRawHeaders(headers));
+
+ if (was_fetched_via_proxy) {
+ head->proxy_server = net::ProxyServer(
+ net::ProxyServer::Scheme::SCHEME_HTTPS, /*host_port_pair=*/{});
+ }
+ head->mime_type = mime_type;
+ network::URLLoaderCompletionStatus status(net_error);
+
+ // This wakes up pending url loaders on this url.
+ url_loader_factory_.AddResponse(request->request.url, std::move(head),
+ response, status);
+
+ // Unset the response to allow for potential retry attempts
+ url_loader_factory_.ClearResponses();
+
+ // Finish SimpleURLLoader::DownloadToStringOfUnboundedSizeUntilCrashAndDie
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void VerifyMetrics(DeviceManagementStatus status, int net_error) {
+ EXPECT_LE(expected_retry_count_, 10);
+ DCHECK_NE(last_job_type_, "");
+ EXPECT_EQ(
+ 1u, histogram_tester_.GetTotalCountsForPrefix(request_uma_name_prefix_)
+ .size());
+ int expected_sample;
+ if (net_error != net::OK) {
+ expected_sample = static_cast<int>(
+ DMServerRequestSuccess::kRequestFailed); // network error sample
+ } else if (status != DM_STATUS_SUCCESS &&
+ status != DM_STATUS_RESPONSE_DECODING_ERROR) {
+ expected_sample = static_cast<int>(
+ DMServerRequestSuccess::kRequestError); // server error sample
+ } else {
+ expected_sample = expected_retry_count_; // Success without retry sample
+ }
+ histogram_tester_.ExpectUniqueSample(
+ request_uma_name_prefix_ + last_job_type_, expected_sample, 1);
+
+ // Reset metrics data for next request.
+ statistics_recorder_.reset();
+ statistics_recorder_ =
+ base::StatisticsRecorder::CreateTemporaryForTesting();
+ }
+
+ MOCK_METHOD4(OnJobDone,
+ void(DeviceManagementService::Job*,
+ DeviceManagementStatus,
+ int,
+ const std::string&));
+
+ MOCK_METHOD2(OnJobRetry,
+ void(int response_code, const std::string& response_body));
+
+ MOCK_METHOD2(OnShouldJobRetry,
+ void(int response_code, const std::string& response_body));
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ network::TestURLLoaderFactory url_loader_factory_;
+ scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
+ shared_url_loader_factory_;
+ std::unique_ptr<DeviceManagementService> service_;
+
+ std::string last_job_type_;
+ int expected_retry_count_ = 0;
+ const std::string request_uma_name_prefix_ =
+ "Enterprise.DMServerRequestSuccess.";
+ std::unique_ptr<base::StatisticsRecorder> statistics_recorder_ =
+ base::StatisticsRecorder::CreateTemporaryForTesting();
+ base::HistogramTester histogram_tester_;
+};
+
+struct FailedRequestParams {
+ FailedRequestParams(DeviceManagementStatus expected_status,
+ net::Error error,
+ int http_status,
+ const std::string& response)
+ : expected_status_(expected_status),
+ error_(error),
+ http_status_(http_status),
+ response_(response) {}
+
+ DeviceManagementStatus expected_status_;
+ net::Error error_;
+ int http_status_;
+ std::string response_;
+};
+
+void PrintTo(const FailedRequestParams& params, std::ostream* os) {
+ *os << "FailedRequestParams " << params.expected_status_ << " "
+ << params.error_ << " " << params.http_status_;
+}
+
+// A parameterized test case for erroneous response situations, they're mostly
+// the same for all kinds of requests.
+class DeviceManagementServiceFailedRequestTest
+ : public DeviceManagementServiceTestBase,
+ public testing::WithParamInterface<FailedRequestParams> {};
+
+TEST_P(DeviceManagementServiceFailedRequestTest, RegisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, CertBasedRegisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartCertBasedRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, TokenEnrollmentRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartTokenEnrollmentJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, ApiAuthCodeFetchRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartApiAuthCodeFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, UnregisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartUnregistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, PolicyRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartPolicyFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+TEST_P(DeviceManagementServiceFailedRequestTest, AutoEnrollmentRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, GetParam().expected_status_, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this,
+ OnShouldJobRetry(GetParam().http_status_, GetParam().response_));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartAutoEnrollmentJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ SendResponse(GetParam().error_, GetParam().http_status_,
+ GetParam().response_);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ DeviceManagementServiceFailedRequestTestInstance,
+ DeviceManagementServiceFailedRequestTest,
+ testing::Values(
+ FailedRequestParams(DM_STATUS_REQUEST_FAILED, net::ERR_FAILED, 0, ""),
+ FailedRequestParams(DM_STATUS_HTTP_STATUS_ERROR,
+ net::OK,
+ 666,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED,
+ net::OK,
+ 403,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER,
+ net::OK,
+ 405,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT,
+ net::OK,
+ 409,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_DEVICE_NOT_FOUND,
+ net::OK,
+ 410,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID,
+ net::OK,
+ 401,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_REQUEST_INVALID,
+ net::OK,
+ 400,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_TEMPORARY_UNAVAILABLE,
+ net::OK,
+ 404,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_ACTIVATION_PENDING,
+ net::OK,
+ 412,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_MISSING_LICENSES,
+ net::OK,
+ 402,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(
+ DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE,
+ net::OK,
+ 417,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(
+ DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL,
+ net::OK,
+ 905,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(
+ DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED,
+ net::OK,
+ 906,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(
+ DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE,
+ net::OK,
+ 907,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_REQUEST_TOO_LARGE,
+ net::OK,
+ 413,
+ PROTO_STRING(kResponseEmpty)),
+ FailedRequestParams(DM_STATUS_SERVICE_TOO_MANY_REQUESTS,
+ net::OK,
+ 429,
+ PROTO_STRING(kResponseEmpty))));
+
+// Simple query parameter parser for testing.
+class QueryParams {
+ public:
+ explicit QueryParams(const std::string& query) {
+ base::SplitStringIntoKeyValuePairs(query, '=', '&', &params_);
+ }
+
+ // Returns true if there is exactly one query parameter with |name| and its
+ // value is equal to |expected_value|.
+ bool Check(const std::string& name, const std::string& expected_value) {
+ std::vector<std::string> params = GetParams(name);
+ return params.size() == 1 && params[0] == expected_value;
+ }
+
+ // Returns vector containing all values for the query param with |name|.
+ std::vector<std::string> GetParams(const std::string& name) {
+ std::vector<std::string> results;
+ for (const auto& param : params_) {
+ std::string unescaped_name = net::UnescapeBinaryURLComponent(
+ param.first, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+ if (unescaped_name == name) {
+ std::string value = net::UnescapeBinaryURLComponent(
+ param.second, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+ results.push_back(value);
+ }
+ }
+ return results;
+ }
+
+ private:
+ typedef base::StringPairs ParamMap;
+ ParamMap params_;
+};
+
+class DeviceManagementServiceTest
+ : public DeviceManagementServiceTestBase {
+ protected:
+ void CheckURLAndQueryParams(
+ const network::TestURLLoaderFactory::PendingRequest* request,
+ const std::string& request_type,
+ const std::string& device_id,
+ const std::string& last_error,
+ bool critical = false) {
+ const GURL service_url(kServiceUrl);
+ const auto& request_url = request->request.url;
+ EXPECT_EQ(service_url.scheme(), request_url.scheme());
+ EXPECT_EQ(service_url.host(), request_url.host());
+ EXPECT_EQ(service_url.port(), request_url.port());
+ EXPECT_EQ(service_url.path(), request_url.path());
+
+ QueryParams query_params(request_url.query());
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamRequest, request_type));
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamDeviceID, device_id));
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamDeviceType,
+ dm_protocol::kValueDeviceType));
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamAppType,
+ dm_protocol::kValueAppType));
+ EXPECT_EQ(critical,
+ query_params.Check(dm_protocol::kParamCritical, "true"));
+ if (last_error == "") {
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamRetry, "false"));
+ } else {
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamRetry, "true"));
+ EXPECT_TRUE(query_params.Check(dm_protocol::kParamLastError, last_error));
+ }
+ }
+};
+
+TEST_F(DeviceManagementServiceTest, RegisterRequest) {
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_register_response()->
+ set_device_management_token(kDMToken);
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob(expected_data));
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister, kClientID,
+ "");
+
+ EXPECT_EQ(expected_data, network::GetUploadData(request->request));
+
+ // Generate the response.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, CriticalRequest) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartCriticalPolicyFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestPolicy, kClientID,
+ "", true);
+}
+
+TEST_F(DeviceManagementServiceTest, CertBasedRegisterRequest) {
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_register_response()->
+ set_device_management_token(kDMToken);
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartCertBasedRegistrationJob(expected_data));
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestCertBasedRegister,
+ kClientID, "");
+
+ EXPECT_EQ(expected_data, network::GetUploadData(request->request));
+
+ // Generate the response.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, TokenEnrollmentRequest) {
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_register_response()->set_device_management_token(
+ kDMToken);
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartTokenEnrollmentJob(expected_data));
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestTokenEnrollment,
+ kClientID, "");
+
+ // Make sure request is properly authorized.
+ std::string header;
+ ASSERT_TRUE(request->request.headers.GetHeader("Authorization", &header));
+ EXPECT_EQ("GoogleEnrollmentToken token=enrollment_token", header);
+
+ EXPECT_EQ(expected_data, network::GetUploadData(request->request));
+
+ // Generate the response.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, ApiAuthCodeFetchRequest) {
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_service_api_access_response()->set_auth_code(
+ kRobotAuthCode);
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartApiAuthCodeFetchJob(expected_data));
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestApiAuthorization,
+ kClientID, "");
+
+ EXPECT_EQ(expected_data, network::GetUploadData(request->request));
+
+ // Generate the response.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, UnregisterRequest) {
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_unregister_response();
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartUnregistrationJob(expected_data));
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // Check the data the fetcher received.
+ const GURL& request_url(request->request.url);
+ const GURL service_url(kServiceUrl);
+ EXPECT_EQ(service_url.scheme(), request_url.scheme());
+ EXPECT_EQ(service_url.host(), request_url.host());
+ EXPECT_EQ(service_url.port(), request_url.port());
+ EXPECT_EQ(service_url.path(), request_url.path());
+
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestUnregister,
+ kClientID, "");
+
+ EXPECT_EQ(expected_data, network::GetUploadData(request->request));
+
+ // Generate the response.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, CancelRegisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelCertBasedRegisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartCertBasedRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelTokenEnrollmentRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartTokenEnrollmentJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelApiAuthCodeFetch) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartApiAuthCodeFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelUnregisterRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartUnregistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelPolicyRequest) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartPolicyFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // There shouldn't be any callbacks.
+ request_job.reset();
+}
+
+TEST_F(DeviceManagementServiceTest, JobQueueing) {
+ // Start with a non-initialized service.
+ ResetService();
+
+ em::DeviceManagementResponse expected_response;
+ expected_response.mutable_register_response()->
+ set_device_management_token(kDMToken);
+ std::string expected_data;
+ ASSERT_TRUE(expected_response.SerializeToString(&expected_data));
+
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, expected_data));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, expected_data));
+
+ // Make a request. We should not see any fetchers being created.
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_FALSE(request);
+
+ // Now initialize the service. That should start the job.
+ InitializeService();
+ request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // Check that the request is processed as expected.
+ SendResponse(net::OK, 200, expected_data);
+}
+
+TEST_F(DeviceManagementServiceTest, CancelRequestAfterShutdown) {
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartPolicyFetchJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ // Shutdown the service and cancel the job afterwards.
+ service_->Shutdown();
+ request_job.reset();
+}
+
+ACTION_P(ResetPointer, pointer) {
+ pointer->reset();
+}
+
+TEST_F(DeviceManagementServiceTest, CancelDuringCallback) {
+ // Make a request.
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _))
+ .WillOnce(DoAll(ResetPointer(&request_job),
+ Invoke([this](DeviceManagementService::Job*,
+ DeviceManagementStatus status,
+ int net_error, const std::string&) {
+ VerifyMetrics(status, net_error);
+ })));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(500, std::string()));
+
+ // Generate a callback.
+ SendResponse(net::OK, 500, std::string());
+
+ // Job should have been reset.
+ EXPECT_FALSE(request_job);
+}
+
+TEST_F(DeviceManagementServiceTest, RetryOnProxyError) {
+ // Make a request.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(0, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ EXPECT_EQ(0, request->request.load_flags & net::LOAD_BYPASS_PROXY);
+ // Not a retry.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister, kClientID,
+ "");
+ const std::string upload_data(network::GetUploadData(request->request));
+
+ // Generate a callback with a proxy failure.
+ SendResponse(net::ERR_PROXY_CONNECTION_FAILED, 0, std::string());
+
+ // Verify that a new fetch was started that bypasses the proxy.
+ request = GetPendingRequest();
+ ASSERT_TRUE(request);
+ EXPECT_TRUE(request->request.load_flags & net::LOAD_BYPASS_PROXY);
+ EXPECT_EQ(upload_data, network::GetUploadData(request->request));
+ // Retry with last error net::ERR_PROXY_CONNECTION_FAILED.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister, kClientID,
+ std::to_string(net::ERR_PROXY_CONNECTION_FAILED));
+}
+
+TEST_F(DeviceManagementServiceTest, RetryOnBadResponseFromProxy) {
+ // Make a request and expect that it will not succeed.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(200, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ EXPECT_EQ(0, request->request.load_flags & net::LOAD_BYPASS_PROXY);
+ const GURL original_url(request->request.url);
+ const std::string upload_data(network::GetUploadData(request->request));
+
+ // Generate a callback with a valid http response, that was generated by
+ // a bad/wrong proxy.
+ SendResponse(net::OK, 200, std::string(), 0u, "bad/type",
+ true /* was_fetched_via_proxy */);
+
+ // Verify that a new fetch was started that bypasses the proxy.
+ request = GetPendingRequest();
+ ASSERT_TRUE(request);
+ EXPECT_NE(0, request->request.load_flags & net::LOAD_BYPASS_PROXY);
+ EXPECT_EQ(original_url, request->request.url);
+ EXPECT_EQ(upload_data, network::GetUploadData(request->request));
+}
+
+TEST_F(DeviceManagementServiceTest, AcceptMimeTypeFromProxy) {
+ // Make a request and expect that it will succeed.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(1);
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ EXPECT_EQ(0, request->request.load_flags & net::LOAD_BYPASS_PROXY);
+ const GURL original_url(request->request.url);
+ const std::string upload_data(network::GetUploadData(request->request));
+
+ // Generate a callback with a valid http response, containing a charset in the
+ // Content-type header.
+ SendResponse(net::OK, 200, std::string(), 0u, "application/x-protobuffer",
+ true /* was_fetched_via_proxy */);
+}
+
+TEST_F(DeviceManagementServiceTest, RetryOnNetworkChanges) {
+ // Make a request.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(0, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+ auto* request = GetPendingRequest();
+ // Not a retry.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister, kClientID,
+ "");
+ const std::string original_upload_data(
+ network::GetUploadData(request->request));
+
+ // Make it fail with ERR_NETWORK_CHANGED.
+ SendResponse(net::ERR_NETWORK_CHANGED, 0, std::string());
+
+ // Verify that a new fetch was started that retries this job, after
+ // having called OnJobRetry.
+ Mock::VerifyAndClearExpectations(this);
+ request = GetPendingRequest();
+ ASSERT_TRUE(request);
+ EXPECT_EQ(original_upload_data, network::GetUploadData(request->request));
+ // Retry with last error net::ERR_NETWORK_CHANGED.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister, kClientID,
+ std::to_string(net::ERR_NETWORK_CHANGED));
+}
+
+TEST_F(DeviceManagementServiceTest, PolicyFetchRetryImmediately) {
+ // We must not wait before a policy fetch retry, so this must not time out.
+ policy::DeviceManagementService::SetRetryDelayForTesting(60000);
+
+ // Make a request.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(0, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartPolicyFetchJob());
+ auto* request = GetPendingRequest();
+ // Not a retry.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestPolicy, kClientID,
+ "");
+ const std::string original_upload_data(
+ network::GetUploadData(request->request));
+
+ // Make it fail with ERR_NETWORK_CHANGED.
+ SendResponse(net::ERR_NETWORK_CHANGED, 0, std::string());
+
+ // Verify that a new fetch was started that retries this job, after
+ // having called OnJobRetry.
+ Mock::VerifyAndClearExpectations(this);
+ request = GetPendingRequest();
+ ASSERT_TRUE(request);
+ EXPECT_EQ(original_upload_data, network::GetUploadData(request->request));
+ // Retry with last error net::ERR_NETWORK_CHANGED.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestPolicy, kClientID,
+ std::to_string(net::ERR_NETWORK_CHANGED));
+
+ // Request is succeeded with retry.
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _));
+ expected_retry_count_ = 1;
+ SendResponse(net::OK, 200, std::string());
+ Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(DeviceManagementServiceTest, RetryLimit) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+
+ // Simulate `DeviceManagementService::kMaxRetries` failed network requests.
+ for (int i = 0; i < DeviceManagementService::kMaxRetries; ++i) {
+ // Make the current fetcher fail with ERR_NETWORK_CHANGED.
+ auto* request = GetPendingRequest();
+ ASSERT_TRUE(request);
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(0, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+ if (i == 0) {
+ // Not a retry.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister,
+ kClientID, "");
+ } else {
+ // Retry with last error net::ERR_NETWORK_CHANGED.
+ CheckURLAndQueryParams(request, dm_protocol::kValueRequestRegister,
+ kClientID,
+ std::to_string(net::ERR_NETWORK_CHANGED));
+ }
+ SendResponse(net::ERR_NETWORK_CHANGED, 0, std::string());
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ // At the next failure the DeviceManagementService should give up retrying and
+ // pass the error code to the job's owner.
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_REQUEST_FAILED, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+ SendResponse(net::ERR_NETWORK_CHANGED, 0, std::string());
+ Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(DeviceManagementServiceTest, CancelDuringRetry) {
+ // Make a request.
+ EXPECT_CALL(*this, OnJobDone(_, _, _, _)).Times(0);
+ EXPECT_CALL(*this, OnJobRetry(0, std::string()));
+ EXPECT_CALL(*this, OnShouldJobRetry(_, _)).Times(0);
+
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartRegistrationJob());
+
+ // Make it fail with ERR_NETWORK_CHANGED.
+ SendResponse(net::ERR_NETWORK_CHANGED, 0, std::string());
+
+ // Before we retry, cancel the job
+ request_job.reset();
+
+ // We must not crash
+ base::RunLoop().RunUntilIdle();
+}
+
+// Tests that authorization data is correctly added to the request.
+class DeviceManagementRequestAuthTest : public DeviceManagementServiceTestBase {
+ public:
+ DeviceManagementRequestAuthTest(const DeviceManagementRequestAuthTest&) =
+ delete;
+ DeviceManagementRequestAuthTest& operator=(
+ const DeviceManagementRequestAuthTest&) = delete;
+
+ protected:
+ DeviceManagementRequestAuthTest() = default;
+ ~DeviceManagementRequestAuthTest() override = default;
+
+ std::unique_ptr<DeviceManagementService::Job> StartJobWithAuthData(
+ DMAuth auth,
+ absl::optional<std::string> oauth_token) {
+ EXPECT_CALL(*this, OnJobDone(_, DM_STATUS_SUCCESS, _, _));
+ EXPECT_CALL(*this, OnJobRetry(_, _)).Times(0);
+
+ // Job type is not really relevant for the test.
+ std::unique_ptr<DeviceManagementService::Job> job =
+ StartJob(DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH,
+ /*critical=*/false, std::move(auth),
+ oauth_token ? *oauth_token : absl::optional<std::string>());
+ return job;
+ }
+
+ // Returns vector containing all values for the OAuth query param.
+ std::vector<std::string> GetOAuthParams(
+ const network::TestURLLoaderFactory::PendingRequest& request) {
+ QueryParams query_params(request.request.url.query());
+ return query_params.GetParams(dm_protocol::kParamOAuthToken);
+ }
+
+ // Returns the value of 'Authorization' header if found.
+ absl::optional<std::string> GetAuthHeader(
+ const network::TestURLLoaderFactory::PendingRequest& request) {
+ std::string header;
+ bool result =
+ request.request.headers.GetHeader(dm_protocol::kAuthHeader, &header);
+ return result ? absl::optional<std::string>(header) : absl::nullopt;
+ }
+};
+
+TEST_F(DeviceManagementRequestAuthTest, OnlyOAuthToken) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartJobWithAuthData(DMAuth::NoAuth(), kOAuthToken));
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ const network::TestURLLoaderFactory::PendingRequest* request =
+ GetPendingRequest();
+ ASSERT_TRUE(request);
+
+#if BUILDFLAG(IS_IOS)
+ EXPECT_EQ(base::StrCat({kOAuthAuthorizationHeaderPrefix, kOAuthToken}),
+ GetAuthHeader(*request));
+ EXPECT_TRUE(GetOAuthParams(*request).empty());
+#else
+ const std::vector<std::string> params = GetOAuthParams(*request);
+ ASSERT_EQ(1u, params.size());
+ EXPECT_EQ(kOAuthToken, params[0]);
+ EXPECT_FALSE(GetAuthHeader(*request));
+#endif
+
+ SendResponse(net::OK, 200, std::string());
+}
+
+TEST_F(DeviceManagementRequestAuthTest, OnlyDMToken) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartJobWithAuthData(DMAuth::FromDMToken(kDMToken),
+ absl::nullopt /* oauth_token */));
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ const network::TestURLLoaderFactory::PendingRequest* request =
+ GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ const std::vector<std::string> params = GetOAuthParams(*request);
+ EXPECT_EQ(0u, params.size());
+ EXPECT_EQ(base::StrCat({dm_protocol::kDMTokenAuthHeaderPrefix, kDMToken}),
+ GetAuthHeader(*request));
+
+ SendResponse(net::OK, 200, std::string());
+}
+
+TEST_F(DeviceManagementRequestAuthTest, OnlyEnrollmentToken) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartJobWithAuthData(DMAuth::FromEnrollmentToken(kEnrollmentToken),
+ absl::nullopt /* oauth_token */));
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ const network::TestURLLoaderFactory::PendingRequest* request =
+ GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ const std::vector<std::string> params = GetOAuthParams(*request);
+ EXPECT_EQ(0u, params.size());
+ EXPECT_EQ(base::StrCat({dm_protocol::kEnrollmentTokenAuthHeaderPrefix,
+ kEnrollmentToken}),
+ GetAuthHeader(*request));
+
+ SendResponse(net::OK, 200, std::string());
+}
+
+#if !BUILDFLAG(IS_IOS)
+// Cannot test requests with an oauth token and another authorization token on
+// iOS because they both use the "Authorization" header.
+
+TEST_F(DeviceManagementRequestAuthTest, OAuthAndDMToken) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartJobWithAuthData(DMAuth::FromDMToken(kDMToken), kOAuthToken));
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ const network::TestURLLoaderFactory::PendingRequest* request =
+ GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ std::vector<std::string> params = GetOAuthParams(*request);
+ ASSERT_EQ(1u, params.size());
+ EXPECT_EQ(kOAuthToken, params[0]);
+ EXPECT_EQ(base::StrCat({dm_protocol::kDMTokenAuthHeaderPrefix, kDMToken}),
+ GetAuthHeader(*request));
+
+ SendResponse(net::OK, 200, std::string());
+}
+
+TEST_F(DeviceManagementRequestAuthTest, OAuthAndEnrollmentToken) {
+ std::unique_ptr<DeviceManagementService::Job> request_job(
+ StartJobWithAuthData(DMAuth::FromEnrollmentToken(kEnrollmentToken),
+ kOAuthToken));
+ EXPECT_CALL(*this, OnShouldJobRetry(200, std::string()));
+
+ const network::TestURLLoaderFactory::PendingRequest* request =
+ GetPendingRequest();
+ ASSERT_TRUE(request);
+
+ std::vector<std::string> params = GetOAuthParams(*request);
+ ASSERT_EQ(1u, params.size());
+ EXPECT_EQ(kOAuthToken, params[0]);
+ EXPECT_EQ(base::StrCat({dm_protocol::kEnrollmentTokenAuthHeaderPrefix,
+ kEnrollmentToken}),
+ GetAuthHeader(*request));
+
+ SendResponse(net::OK, 200, std::string());
+}
+
+#endif
+
+#if defined(GTEST_HAS_DEATH_TEST)
+TEST_F(DeviceManagementRequestAuthTest, CannotUseOAuthTokenAsAuthData) {
+ // Job type is not really relevant for the test.
+ ASSERT_DEATH(StartJobWithAuthData(DMAuth::FromOAuthToken(kOAuthToken), ""),
+ "");
+}
+#endif // GTEST_HAS_DEATH_TEST
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/dm_auth.cc b/chromium/components/policy/core/common/cloud/dm_auth.cc
new file mode 100644
index 00000000000..fa52402b57b
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dm_auth.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/dm_auth.h"
+
+#include "base/memory/ptr_util.h"
+
+namespace policy {
+
+DMAuth::DMAuth() = default;
+DMAuth::~DMAuth() = default;
+
+DMAuth::DMAuth(DMAuth&& other) = default;
+DMAuth& DMAuth::operator=(DMAuth&& other) = default;
+
+// static
+DMAuth DMAuth::FromDMToken(const std::string& dm_token) {
+ return DMAuth(dm_token, DMAuthTokenType::kDm);
+}
+
+// static
+DMAuth DMAuth::FromOAuthToken(const std::string& oauth_token) {
+ return DMAuth(oauth_token, DMAuthTokenType::kOauth);
+}
+
+// static
+DMAuth DMAuth::FromEnrollmentToken(const std::string& enrollment_token) {
+ return DMAuth(enrollment_token, DMAuthTokenType::kEnrollment);
+}
+
+// static
+DMAuth DMAuth::NoAuth() {
+ return {};
+}
+
+DMAuth::DMAuth(const std::string& token, DMAuthTokenType token_type)
+ : token_(token), token_type_(token_type) {}
+
+bool DMAuth::operator==(const DMAuth& other) const {
+ return token_ == other.token_ && token_type_ == other.token_type_;
+}
+
+bool DMAuth::operator!=(const DMAuth& other) const {
+ return !(*this == other);
+}
+
+DMAuth DMAuth::Clone() const {
+ return DMAuth(token_, token_type_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/dm_auth.h b/chromium/components/policy/core/common/cloud/dm_auth.h
new file mode 100644
index 00000000000..8226dc16cbf
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dm_auth.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_AUTH_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_AUTH_H_
+
+#include <memory>
+#include <string>
+
+#include "base/check_op.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// The enum type for the token used for authentication. Explicit values are
+// set to allow easy identification of value from the logs.
+enum class DMAuthTokenType {
+ kNoAuth = 0,
+ // Skipping obsolete kGaia = 1
+ kDm = 2,
+ kEnrollment = 3,
+ kOauth = 4,
+};
+
+// Class that encapsulates different authentication methods to interact with
+// device management service.
+// We currently have 3 methods for authentication:
+// * OAuth token, that is passed as a part of URL
+// * Enrollment token, provided by installation configuration, passed as
+// Authorization: GoogleEnrollmentToken header
+// * DMToken, created during Register request, passed as
+// Authorization: GoogleDMToken header
+// Also, several requests require no authentication (e.g. enterprise_check) or
+// embed some authentication in the payload (e.g. certificate_based_register).
+class POLICY_EXPORT DMAuth {
+ public:
+ // Static methods for creating DMAuth instances:
+ static DMAuth FromDMToken(const std::string& dm_token);
+ static DMAuth FromOAuthToken(const std::string& oauth_token);
+ static DMAuth FromEnrollmentToken(const std::string& token);
+ static DMAuth NoAuth();
+
+ DMAuth();
+ ~DMAuth();
+
+ DMAuth(const DMAuth& other) = delete;
+ DMAuth& operator=(const DMAuth& other) = delete;
+
+ DMAuth(DMAuth&& other);
+ DMAuth& operator=(DMAuth&& other);
+
+ bool operator==(const DMAuth& other) const;
+ bool operator!=(const DMAuth& other) const;
+
+ // Creates a copy of DMAuth.
+ DMAuth Clone() const;
+
+ // Checks if no authentication is provided.
+ bool empty() const { return token_type_ == DMAuthTokenType::kNoAuth; }
+
+ std::string dm_token() const {
+ DCHECK_EQ(DMAuthTokenType::kDm, token_type_);
+ return token_;
+ }
+ bool has_dm_token() const { return token_type_ == DMAuthTokenType::kDm; }
+ std::string enrollment_token() const {
+ DCHECK_EQ(DMAuthTokenType::kEnrollment, token_type_);
+ return token_;
+ }
+ bool has_enrollment_token() const {
+ return token_type_ == DMAuthTokenType::kEnrollment;
+ }
+ std::string oauth_token() const {
+ DCHECK_EQ(DMAuthTokenType::kOauth, token_type_);
+ return token_;
+ }
+ bool has_oauth_token() const {
+ return token_type_ == DMAuthTokenType::kOauth;
+ }
+ DMAuthTokenType token_type() const { return token_type_; }
+
+ private:
+ DMAuth(const std::string& token, DMAuthTokenType token_type);
+
+ std::string token_;
+ DMAuthTokenType token_type_ = DMAuthTokenType::kNoAuth;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_AUTH_H_
diff --git a/chromium/components/policy/core/common/cloud/dm_token.cc b/chromium/components/policy/core/common/cloud/dm_token.cc
new file mode 100644
index 00000000000..0b9aa782720
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dm_token.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/dm_token.h"
+
+namespace policy {
+
+// static
+DMToken DMToken::CreateValidTokenForTesting(const std::string& value) {
+ return DMToken(Status::kValid, value);
+}
+
+// static
+DMToken DMToken::CreateInvalidTokenForTesting() {
+ return DMToken(Status::kInvalid, "");
+}
+
+// static
+DMToken DMToken::CreateEmptyTokenForTesting() {
+ return DMToken(Status::kEmpty, "");
+}
+
+DMToken::DMToken() : DMToken(Status::kEmpty, "") {}
+
+DMToken::DMToken(Status status, const base::StringPiece value)
+ : status_(status), value_(value) {}
+
+const std::string& DMToken::value() const {
+ DCHECK(is_valid());
+ return value_;
+}
+
+bool DMToken::is_valid() const {
+ return status_ == Status::kValid;
+}
+
+bool DMToken::is_invalid() const {
+ return status_ == Status::kInvalid;
+}
+
+bool DMToken::is_empty() const {
+ return status_ == Status::kEmpty;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/dm_token.h b/chromium/components/policy/core/common/cloud/dm_token.h
new file mode 100644
index 00000000000..550f7b9d887
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dm_token.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_TOKEN_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_TOKEN_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Represents a DM token with a status, which can be:
+// Valid:
+// A valid token to be used to make requests. Its value cannot be empty or
+// equal to |kInvalidTokenValue|.
+// Invalid:
+// The token explicitly marks this browser as unenrolled. The browser
+// should not sync policies or try to get a new DM token if it is set to
+// this value.
+// Empty:
+// The token is empty. The browser will try to get a valid token if an
+// enrollment token is present.
+class POLICY_EXPORT DMToken {
+ public:
+ enum class Status { kValid, kInvalid, kEmpty };
+
+ static DMToken CreateValidTokenForTesting(const std::string& value);
+ static DMToken CreateInvalidTokenForTesting();
+ static DMToken CreateEmptyTokenForTesting();
+
+ DMToken();
+ DMToken(Status status, const base::StringPiece value);
+
+ DMToken(const DMToken& other) = default;
+ DMToken(DMToken&& other) = default;
+
+ DMToken& operator=(const DMToken& other) = default;
+ DMToken& operator=(DMToken&& other) = default;
+ ~DMToken() = default;
+
+ // Returns the DM token string value. Should only be called on a valid token.
+ const std::string& value() const;
+
+ // Helpers that check the status of the token. Theses states are mutually
+ // exclusive, so one function returning true implies all other return false.
+ bool is_valid() const;
+ bool is_invalid() const;
+ bool is_empty() const;
+
+ private:
+ Status status_;
+ std::string value_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_DM_TOKEN_H_
diff --git a/chromium/components/policy/core/common/cloud/dmserver_job_configurations.cc b/chromium/components/policy/core/common/cloud/dmserver_job_configurations.cc
new file mode 100644
index 00000000000..a654ad3e54e
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dmserver_job_configurations.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/dmserver_job_configurations.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "net/base/url_util.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+const char* JobTypeToRequestType(
+ DeviceManagementService::JobConfiguration::JobType type) {
+ switch (type) {
+ case DeviceManagementService::JobConfiguration::TYPE_INVALID:
+ NOTREACHED() << "Not a DMServer request type" << type;
+ return "Invalid";
+ case DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT:
+ return dm_protocol::kValueRequestAutoEnrollment;
+ case DeviceManagementService::JobConfiguration::TYPE_REGISTRATION:
+ return dm_protocol::kValueRequestRegister;
+ case DeviceManagementService::JobConfiguration::TYPE_POLICY_FETCH:
+ return dm_protocol::kValueRequestPolicy;
+ case DeviceManagementService::JobConfiguration::TYPE_API_AUTH_CODE_FETCH:
+ return dm_protocol::kValueRequestApiAuthorization;
+ case DeviceManagementService::JobConfiguration::TYPE_UNREGISTRATION:
+ return dm_protocol::kValueRequestUnregister;
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_CERTIFICATE:
+ return dm_protocol::kValueRequestUploadCertificate;
+ case DeviceManagementService::JobConfiguration::TYPE_DEVICE_STATE_RETRIEVAL:
+ return dm_protocol::kValueRequestDeviceStateRetrieval;
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_STATUS:
+ return dm_protocol::kValueRequestUploadStatus;
+ case DeviceManagementService::JobConfiguration::TYPE_REMOTE_COMMANDS:
+ return dm_protocol::kValueRequestRemoteCommands;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ATTRIBUTE_UPDATE_PERMISSION:
+ return dm_protocol::kValueRequestDeviceAttributeUpdatePermission;
+ case DeviceManagementService::JobConfiguration::TYPE_ATTRIBUTE_UPDATE:
+ return dm_protocol::kValueRequestDeviceAttributeUpdate;
+ case DeviceManagementService::JobConfiguration::TYPE_GCM_ID_UPDATE:
+ return dm_protocol::kValueRequestGcmIdUpdate;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ANDROID_MANAGEMENT_CHECK:
+ return dm_protocol::kValueRequestCheckAndroidManagement;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_CERT_BASED_REGISTRATION:
+ return dm_protocol::kValueRequestCertBasedRegister;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ACTIVE_DIRECTORY_ENROLL_PLAY_USER:
+ return dm_protocol::kValueRequestActiveDirectoryEnrollPlayUser;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_ACTIVE_DIRECTORY_PLAY_ACTIVITY:
+ return dm_protocol::kValueRequestActiveDirectoryPlayActivity;
+ case DeviceManagementService::JobConfiguration::TYPE_TOKEN_ENROLLMENT:
+ return dm_protocol::kValueRequestTokenEnrollment;
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_DESKTOP_REPORT:
+ return dm_protocol::kValueRequestChromeDesktopReport;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL:
+ return dm_protocol::kValueRequestInitialEnrollmentStateRetrieval;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_POLICY_VALIDATION_REPORT:
+ return dm_protocol::kValueRequestUploadPolicyValidationReport;
+ case DeviceManagementService::JobConfiguration::TYPE_REQUEST_SAML_URL:
+ return dm_protocol::kValueRequestPublicSamlUser;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_REAL_TIME_REPORT:
+ NOTREACHED() << "Not a DMServer request type " << type;
+ break;
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_OS_USER_REPORT:
+ return dm_protocol::kValueRequestChromeOsUserReport;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_CERT_PROVISIONING_REQUEST:
+ return dm_protocol::kValueRequestCertProvisioningRequest;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_PSM_HAS_DEVICE_STATE_REQUEST:
+ return dm_protocol::kValueRequestPsmHasDeviceState;
+ case DeviceManagementService::JobConfiguration::TYPE_CHECK_USER_ACCOUNT:
+ return dm_protocol::kValueCheckUserAccount;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_BROWSER_UPLOAD_PUBLIC_KEY:
+ return dm_protocol::kValueBrowserUploadPublicKey;
+ case DeviceManagementService::JobConfiguration::
+ TYPE_UPLOAD_ENCRYPTED_REPORT:
+ NOTREACHED() << "Not a DMServer request type " << type;
+ break;
+ case DeviceManagementService::JobConfiguration::TYPE_UPLOAD_EUICC_INFO:
+ return dm_protocol::kValueRequestUploadEuiccInfo;
+ case DeviceManagementService::JobConfiguration::TYPE_CHROME_PROFILE_REPORT:
+ return dm_protocol::kValueRequestChromeProfileReport;
+ }
+ NOTREACHED() << "Invalid job type " << type;
+ return "";
+}
+
+} // namespace
+
+DMServerJobConfiguration::DMServerJobConfiguration(
+ DeviceManagementService* service,
+ JobType type,
+ const std::string& client_id,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> factory,
+ Callback callback)
+ : JobConfigurationBase(type, std::move(auth_data), oauth_token, factory),
+ server_url_(service->configuration()->GetDMServerUrl()),
+ callback_(std::move(callback)) {
+ DCHECK(!callback_.is_null());
+ AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type));
+ AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType);
+ AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType);
+ AddParameter(dm_protocol::kParamAgent,
+ service->configuration()->GetAgentParameter());
+ AddParameter(dm_protocol::kParamPlatform,
+ service->configuration()->GetPlatformParameter());
+ AddParameter(dm_protocol::kParamDeviceID, client_id);
+
+ if (critical)
+ AddParameter(dm_protocol::kParamCritical, "true");
+}
+
+DMServerJobConfiguration::DMServerJobConfiguration(
+ JobType type,
+ CloudPolicyClient* client,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ Callback callback)
+ : DMServerJobConfiguration(client->service(),
+ type,
+ client->client_id(),
+ critical,
+ std::move(auth_data),
+ oauth_token,
+ client->GetURLLoaderFactory(),
+ std::move(callback)) {}
+
+DMServerJobConfiguration::~DMServerJobConfiguration() {}
+
+DeviceManagementStatus
+DMServerJobConfiguration::MapNetErrorAndResponseCodeToDMStatus(
+ int net_error,
+ int response_code) {
+ DeviceManagementStatus code;
+ if (net_error != net::OK) {
+ code = DM_STATUS_REQUEST_FAILED;
+ } else {
+ switch (response_code) {
+ case DeviceManagementService::kSuccess:
+ code = DM_STATUS_SUCCESS;
+ break;
+ case DeviceManagementService::kInvalidArgument:
+ code = DM_STATUS_REQUEST_INVALID;
+ break;
+ case DeviceManagementService::kInvalidAuthCookieOrDMToken:
+ code = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+ break;
+ case DeviceManagementService::kMissingLicenses:
+ code = DM_STATUS_SERVICE_MISSING_LICENSES;
+ break;
+ case DeviceManagementService::kDeviceManagementNotAllowed:
+ code = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
+ break;
+ case DeviceManagementService::kPendingApproval:
+ code = DM_STATUS_SERVICE_ACTIVATION_PENDING;
+ break;
+ case DeviceManagementService::kRequestTooLarge:
+ code = DM_STATUS_REQUEST_TOO_LARGE;
+ break;
+ case DeviceManagementService::kConsumerAccountWithPackagedLicense:
+ code = DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE;
+ break;
+ case DeviceManagementService::kInvalidURL:
+ case DeviceManagementService::kInternalServerError:
+ case DeviceManagementService::kServiceUnavailable:
+ code = DM_STATUS_TEMPORARY_UNAVAILABLE;
+ break;
+ case DeviceManagementService::kDeviceNotFound:
+ code = DM_STATUS_SERVICE_DEVICE_NOT_FOUND;
+ break;
+ case DeviceManagementService::kPolicyNotFound:
+ code = DM_STATUS_SERVICE_POLICY_NOT_FOUND;
+ break;
+ case DeviceManagementService::kInvalidSerialNumber:
+ code = DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER;
+ break;
+ case DeviceManagementService::kTooManyRequests:
+ code = DM_STATUS_SERVICE_TOO_MANY_REQUESTS;
+ break;
+ case DeviceManagementService::kDomainMismatch:
+ code = DM_STATUS_SERVICE_DOMAIN_MISMATCH;
+ break;
+ case DeviceManagementService::kDeprovisioned:
+ code = DM_STATUS_SERVICE_DEPROVISIONED;
+ break;
+ case DeviceManagementService::kDeviceIdConflict:
+ code = DM_STATUS_SERVICE_DEVICE_ID_CONFLICT;
+ break;
+ case DeviceManagementService::kArcDisabled:
+ code = DM_STATUS_SERVICE_ARC_DISABLED;
+ break;
+ case DeviceManagementService::kInvalidDomainlessCustomer:
+ code = DM_STATUS_SERVICE_ENTERPRISE_ACCOUNT_IS_NOT_ELIGIBLE_TO_ENROLL;
+ break;
+ case DeviceManagementService::kTosHasNotBeenAccepted:
+ code = DM_STATUS_SERVICE_ENTERPRISE_TOS_HAS_NOT_BEEN_ACCEPTED;
+ break;
+ case DeviceManagementService::kIllegalAccountForPackagedEDULicense:
+ code = DM_STATUS_SERVICE_ILLEGAL_ACCOUNT_FOR_PACKAGED_EDU_LICENSE;
+ break;
+ default:
+ // Handle all unknown 5xx HTTP error codes as temporary and any other
+ // unknown error as one that needs more time to recover.
+ if (response_code >= 500 && response_code <= 599)
+ code = DM_STATUS_TEMPORARY_UNAVAILABLE;
+ else
+ code = DM_STATUS_HTTP_STATUS_ERROR;
+ break;
+ }
+ }
+ return code;
+}
+
+std::string DMServerJobConfiguration::GetPayload() {
+ std::string payload;
+ CHECK(request_.SerializeToString(&payload));
+ return payload;
+}
+
+std::string DMServerJobConfiguration::GetUmaName() {
+ return "Enterprise.DMServerRequestSuccess." + GetJobTypeAsString(GetType());
+}
+
+void DMServerJobConfiguration::OnURLLoadComplete(
+ DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) {
+ DeviceManagementStatus code =
+ MapNetErrorAndResponseCodeToDMStatus(net_error, response_code);
+
+ em::DeviceManagementResponse response;
+ if (code == DM_STATUS_SUCCESS && !response.ParseFromString(response_body)) {
+ code = DM_STATUS_RESPONSE_DECODING_ERROR;
+ LOG(WARNING) << "DMServer sent an invalid response";
+ } else if (response_code != DeviceManagementService::kSuccess) {
+ if (response.ParseFromString(response_body)) {
+ LOG(WARNING) << "DMServer sent an error response: " << response_code
+ << ". " << response.error_message();
+ } else {
+ LOG(WARNING) << "DMServer sent an error response: " << response_code;
+ }
+ }
+
+ std::move(callback_).Run(job, code, net_error, response);
+}
+
+GURL DMServerJobConfiguration::GetURL(int last_error) const {
+ // DM server requests always expect a dm_protocol::kParamRetry URL parameter
+ // to indicate if this request is a retry. Furthermore, if so then the
+ // dm_protocol::kParamLastError URL parameter is also expected with the value
+ // of the last error.
+
+ GURL url(server_url_);
+ url = net::AppendQueryParameter(url, dm_protocol::kParamRetry,
+ last_error == 0 ? "false" : "true");
+
+ if (last_error != 0) {
+ url = net::AppendQueryParameter(url, dm_protocol::kParamLastError,
+ base::NumberToString(last_error).c_str());
+ }
+
+ return url;
+}
+
+RegistrationJobConfiguration::RegistrationJobConfiguration(
+ JobType type,
+ CloudPolicyClient* client,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ Callback callback)
+ : DMServerJobConfiguration(type,
+ client,
+ /*critical=*/false,
+ std::move(auth_data),
+ oauth_token,
+ std::move(callback)) {}
+
+void RegistrationJobConfiguration::OnBeforeRetry(
+ int response_code,
+ const std::string& response_body) {
+ // If the initial request managed to get to the server but the response
+ // didn't arrive at the client then retrying with the same client ID will
+ // fail. Set the re-registration flag so that the server accepts it.
+ // If the server hasn't seen the client ID before then it will also accept
+ // the re-registration.
+ request()->mutable_register_request()->set_reregister(true);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/dmserver_job_configurations.h b/chromium/components/policy/core/common/cloud/dmserver_job_configurations.h
new file mode 100644
index 00000000000..1411db8f616
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/dmserver_job_configurations.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_DMSERVER_JOB_CONFIGURATIONS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_DMSERVER_JOB_CONFIGURATIONS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class CloudPolicyClient;
+
+// A configuration for sending enterprise_management::DeviceManagementRequest to
+// the DM server.
+class POLICY_EXPORT DMServerJobConfiguration : public JobConfigurationBase {
+ public:
+ typedef base::OnceCallback<void(
+ DeviceManagementService::Job* job,
+ DeviceManagementStatus code,
+ int net_error,
+ const enterprise_management::DeviceManagementResponse&)>
+ Callback;
+
+ DMServerJobConfiguration(
+ DeviceManagementService* service,
+ JobType type,
+ const std::string& client_id,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> factory,
+ Callback callback);
+
+ // This constructor is a convenience if the caller already hsa a pointer to
+ // a CloudPolicyClient.
+ DMServerJobConfiguration(JobType type,
+ CloudPolicyClient* client,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ Callback callback);
+
+ DMServerJobConfiguration(const DMServerJobConfiguration&) = delete;
+ DMServerJobConfiguration& operator=(const DMServerJobConfiguration&) = delete;
+
+ ~DMServerJobConfiguration() override;
+
+ enterprise_management::DeviceManagementRequest* request() {
+ return &request_;
+ }
+
+ protected:
+ DeviceManagementStatus MapNetErrorAndResponseCodeToDMStatus(
+ int net_error,
+ int response_code);
+
+ private:
+ // JobConfiguration interface.
+ std::string GetPayload() override;
+ std::string GetUmaName() override;
+ void OnBeforeRetry(int response_code,
+ const std::string& response_body) override {}
+ void OnURLLoadComplete(DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) override;
+
+ // JobConfigurationBase overrides.
+ GURL GetURL(int last_error) const override;
+
+ std::string server_url_;
+ enterprise_management::DeviceManagementRequest request_;
+ Callback callback_;
+};
+
+// A configuration for sending registration requests to the DM server. These
+// are requests of type TYPE_REGISTRATION, TYPE_TOKEN_ENROLLMENT, and
+// TYPE_CERT_BASED_REGISTRATION. This class extends DMServerJobConfiguration
+// by adjusting the enterprise_management::DeviceManagementRequest message in
+// registration retry requests.
+class POLICY_EXPORT RegistrationJobConfiguration
+ : public DMServerJobConfiguration {
+ public:
+ RegistrationJobConfiguration(JobType type,
+ CloudPolicyClient* client,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ Callback callback);
+ RegistrationJobConfiguration(const RegistrationJobConfiguration&) = delete;
+ RegistrationJobConfiguration& operator=(const RegistrationJobConfiguration&) =
+ delete;
+
+ private:
+ // JobConfiguration interface.
+ void OnBeforeRetry(int response_code,
+ const std::string& response_body) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_DMSERVER_JOB_CONFIGURATIONS_H_
diff --git a/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
new file mode 100644
index 00000000000..45d5535630f
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
+
+#include "base/base64.h"
+#include "base/containers/contains.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+namespace {
+
+// EncryptedReportingJobConfiguration strings
+constexpr char kEncryptedRecordListKey[] = "encryptedRecord";
+constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
+constexpr char kDeviceKey[] = "device";
+constexpr char kBrowserKey[] = "browser";
+
+} // namespace
+
+EncryptedReportingJobConfiguration::EncryptedReportingJobConfiguration(
+ CloudPolicyClient* client,
+ const std::string& server_url,
+ const base::Value::Dict& merging_payload,
+ UploadCompleteCallback complete_cb)
+ : ReportingJobConfigurationBase(TYPE_UPLOAD_ENCRYPTED_REPORT,
+ client->GetURLLoaderFactory(),
+ client,
+ server_url,
+ /*include_device_info*/ true,
+ std::move(complete_cb)) {
+ // Merge it into the base class payload.
+ payload_.Merge(merging_payload);
+}
+
+EncryptedReportingJobConfiguration::~EncryptedReportingJobConfiguration() {
+ if (!callback_.is_null()) {
+ // The job either wasn't tried, or failed in some unhandled way. Report
+ // failure to the callback.
+ std::move(callback_).Run(/*job=*/nullptr,
+ DeviceManagementStatus::DM_STATUS_REQUEST_FAILED,
+ /*net_error=*/418,
+ /*response_body=*/absl::nullopt);
+ }
+}
+
+void EncryptedReportingJobConfiguration::UpdatePayloadBeforeGetInternal() {
+ for (auto it = payload_.begin(); it != payload_.end();) {
+ const auto& [key, value] = *it;
+ if (!base::Contains(GetTopLevelKeyAllowList(), key)) {
+ it = payload_.erase(it);
+ continue;
+ }
+ ++it;
+ }
+}
+
+void EncryptedReportingJobConfiguration::UpdateContext(
+ base::Value::Dict context) {
+ context_ = std::move(context);
+}
+
+std::string EncryptedReportingJobConfiguration::GetUmaString() const {
+ return "Enterprise.EncryptedReportingSuccess";
+}
+
+std::set<std::string>
+EncryptedReportingJobConfiguration::GetTopLevelKeyAllowList() {
+ static std::set<std::string> kTopLevelKeyAllowList{
+ kEncryptedRecordListKey, kAttachEncryptionSettingsKey, kDeviceKey,
+ kBrowserKey};
+ return kTopLevelKeyAllowList;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
new file mode 100644
index 00000000000..be6efc526a2
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -0,0 +1,109 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENCRYPTED_REPORTING_JOB_CONFIGURATION_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENCRYPTED_REPORTING_JOB_CONFIGURATION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
+#include "components/policy/policy_export.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+
+namespace policy {
+
+// {{{Note}}} ERP Payload Overview
+//
+// EncryptedReportingJobConfiguration configures a payload for the Encrypted
+// server endpoint. A JSON version of the payload looks like this:
+// {
+// "encryptedRecord": [
+// {
+// "encryptedWrappedRecord": "EncryptedMessage",
+// "encryptionInfo" : {
+// "encryptionKey": "LocalPublicValue",
+// "publicKeyId": 1
+// },
+// "sequenceInformation": {
+// "sequencingId": 1,
+// "generationId": 123456789,
+// "priority": 1
+// },
+// "compressionInformation": {
+// "compressionAlgorithm": 1
+// }
+// },
+// {
+// "encryptedWrappedRecord": "EncryptedMessage",
+// "encryptionInfo" : {
+// "encryptionKey": "LocalPublicValue",
+// "publicKeyId": 2
+// },
+// "sequenceInformation": {
+// "sequencingId": 2,
+// "generationId": 123456789,
+// "priority": 1
+// },
+// "compressionInformation": {
+// "compressionAlgorithm": 1
+// }
+// }
+// ],
+// "attachEncryptionSettings": true, // optional field
+// "requestId": "SomeString",
+// "device": {
+// "client_id": "abcdef1234",
+// "dmToken": "abcdef1234",
+// "name": "George",
+// "osPlatform": "Windows",
+// "osVersion": "10.0.0.0"
+// },
+// "browser": {
+// "browserId": "abcdef1234",
+// "chromeVersion": "10.0.0.0",
+// "machineUser": "abcdef1234",
+// "userAgent": "abcdef1234"
+// }
+// }
+// "device" and "browser" are populated by the base class,
+// the rest needs to be provided as |merging_payload|.
+//
+// Details other than the "device" and "browser" fields are documented at note
+// "ERP Encrypted Record".
+
+class POLICY_EXPORT EncryptedReportingJobConfiguration
+ : public ReportingJobConfigurationBase {
+ public:
+ EncryptedReportingJobConfiguration(CloudPolicyClient* client,
+ const std::string& server_url,
+ const base::Value::Dict& merging_payload,
+ UploadCompleteCallback complete_cb);
+ ~EncryptedReportingJobConfiguration() override;
+
+ // |context| is a base::Value::Dict item as generated by
+ // |reporting::GetContext|. |context| will be stored and applied to the
+ // payload on the proceeding |GetPayload| call. |context| mostly fills out the
+ // profile dictionary, but does overwrite a few of the device and browser
+ // fields (check reporting::GetContext for specifics).
+ void UpdateContext(base::Value::Dict context);
+
+ protected:
+ void UpdatePayloadBeforeGetInternal() override;
+
+ std::string GetUmaString() const override;
+
+ private:
+ friend class EncryptedReportingJobConfigurationTest;
+
+ std::set<std::string> GetTopLevelKeyAllowList();
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENCRYPTED_REPORTING_JOB_CONFIGURATION_H_
diff --git a/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
new file mode 100644
index 00000000000..ff0f3729ac1
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
@@ -0,0 +1,532 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/encrypted_reporting_job_configuration.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_device_management_service.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "components/version_info/version_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/system/fake_statistics_provider.h"
+#endif
+
+using ::testing::_;
+using ::testing::ByRef;
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::MockFunction;
+using ::testing::NotNull;
+using ::testing::ReturnPointee;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+namespace policy {
+
+namespace {
+constexpr uint64_t kGenerationId = 4321;
+constexpr ::reporting::Priority kPriority = ::reporting::Priority::IMMEDIATE;
+
+// Default values for EncryptionInfo
+constexpr char kEncryptionKeyValue[] = "abcdef";
+constexpr uint64_t kPublicKeyIdValue = 9876;
+
+// Keys for response internal dictionaries
+constexpr char kLastSucceedUploadedRecordKey[] = "lastSucceedUploadedRecord";
+constexpr char kFirstFailedUploadedRecordKey[] = "firstFailedUploadedRecord";
+
+// UploadEncryptedReportingRequest list key
+constexpr char kEncryptedRecordListKey[] = "encryptedRecord";
+
+// Encryption settings request key
+constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
+
+// Keys for EncrypedRecord
+constexpr char kEncryptedWrappedRecordKey[] = "encryptedWrappedRecord";
+constexpr char kSequenceInformationKey[] = "sequencingInformation";
+constexpr char kEncryptionInfoKey[] = "encryptionInfo";
+
+// Keys for internal encryption information dictionaries.
+constexpr char kEncryptionKey[] = "encryptionKey";
+constexpr char kPublicKeyIdKey[] = "publicKeyId";
+
+// Keys for internal SequenceInformation dictionaries.
+constexpr char kSequencingIdKey[] = "sequencingId";
+constexpr char kGenerationIdKey[] = "generationId";
+constexpr char kPriorityKey[] = "priority";
+
+// Keys for FirstFailedUploadRecord values.
+constexpr char kFailedUploadedRecord[] = "failedUploadedRecord";
+constexpr char kFailureStatus[] = "failureStatus";
+
+// Keys for FirstFailedUploadRecord Status dictionary
+constexpr char kCodeKey[] = "code";
+constexpr char kMessageKey[] = "message";
+
+uint64_t GetNextSequenceId() {
+ static uint64_t kSequencingId = 0;
+ return kSequencingId++;
+}
+
+base::Value GenerateSingleRecord(base::StringPiece encrypted_wrapped_record) {
+ base::Value record_dictionary{base::Value::Type::DICTIONARY};
+ std::string base64_encode;
+ base::Base64Encode(encrypted_wrapped_record, &base64_encode);
+ record_dictionary.SetStringKey(kEncryptedWrappedRecordKey, base64_encode);
+
+ base::Value* const sequencing_dictionary = record_dictionary.SetKey(
+ kSequenceInformationKey, base::Value{base::Value::Type::DICTIONARY});
+ sequencing_dictionary->SetStringKey(
+ kSequencingIdKey, base::NumberToString(GetNextSequenceId()));
+ sequencing_dictionary->SetStringKey(kGenerationIdKey,
+ base::NumberToString(kGenerationId));
+ sequencing_dictionary->SetIntKey(kPriorityKey, kPriority);
+
+ base::Value* const encryption_info_dictionary = record_dictionary.SetKey(
+ kEncryptionInfoKey, base::Value{base::Value::Type::DICTIONARY});
+ encryption_info_dictionary->SetStringKey(kEncryptionKey, kEncryptionKeyValue);
+ encryption_info_dictionary->SetStringKey(
+ kPublicKeyIdKey, base::NumberToString(kPublicKeyIdValue));
+
+ return record_dictionary;
+}
+
+class RequestPayloadBuilder {
+ public:
+ explicit RequestPayloadBuilder(bool attach_encryption_settings = false) {
+ if (attach_encryption_settings) {
+ payload_.Set(kAttachEncryptionSettingsKey, true);
+ }
+ payload_.Set(kEncryptedRecordListKey, base::Value::List());
+ }
+
+ RequestPayloadBuilder& AddRecord(const base::Value& record) {
+ base::Value::List* records_list =
+ payload_.FindList(kEncryptedRecordListKey);
+ records_list->Append(record.Clone());
+ return *this;
+ }
+
+ base::Value::Dict Build() { return std::move(payload_); }
+
+ private:
+ base::Value::Dict payload_;
+};
+
+class ResponseValueBuilder {
+ public:
+ static absl::optional<base::Value> CreateUploadFailure(
+ const ::reporting::SequenceInformation& sequence_information) {
+ if (!sequence_information.has_sequencing_id() ||
+ !sequence_information.has_generation_id() ||
+ !sequence_information.has_priority()) {
+ return absl::nullopt;
+ }
+
+ base::Value upload_failure{base::Value::Type::DICTIONARY};
+ upload_failure.SetKey(kFailedUploadedRecord,
+ BuildSequenceInformationValue(sequence_information));
+
+ // Set to internal error (error::INTERNAL == 13).
+ upload_failure.SetIntKey(GetFailureStatusCodePath(), 13);
+ upload_failure.SetStringKey(GetFailureStatusMessagePath(),
+ "FailingForTests");
+ return upload_failure;
+ }
+
+ static base::Value::Dict CreateResponse(
+ const base::Value& sequence_information,
+ absl::optional<base::Value> upload_failure) {
+ base::Value::Dict response;
+
+ response.Set(kLastSucceedUploadedRecordKey, sequence_information.Clone());
+
+ if (upload_failure.has_value()) {
+ response.Set(kFirstFailedUploadedRecordKey,
+ std::move(upload_failure.value()));
+ }
+ return response;
+ }
+
+ static std::string CreateResponseString(const base::Value::Dict& response) {
+ std::string response_string;
+ base::JSONWriter::Write(response, &response_string);
+ return response_string;
+ }
+
+ static std::string GetUploadFailureFailedUploadSequencingIdPath() {
+ return GetPath(kFirstFailedUploadedRecordKey,
+ GetFailedUploadSequencingIdPath());
+ }
+
+ static std::string GetUploadFailureFailedUploadGenerationIdPath() {
+ return GetPath(kFirstFailedUploadedRecordKey,
+ GetFailedUploadGenerationIdPath());
+ }
+
+ static std::string GetUploadFailureFailedUploadPriorityPath() {
+ return GetPath(kFirstFailedUploadedRecordKey,
+ GetFailedUploadPriorityPath());
+ }
+
+ static std::string GetUploadFailureStatusCodePath() {
+ return GetPath(kFirstFailedUploadedRecordKey, GetFailureStatusCodePath());
+ }
+
+ private:
+ static base::Value BuildSequenceInformationValue(
+ const ::reporting::SequenceInformation& sequence_information) {
+ base::Value sequence_information_value{base::Value::Type::DICTIONARY};
+ sequence_information_value.SetIntKey(kSequencingIdKey,
+ sequence_information.sequencing_id());
+ sequence_information_value.SetIntKey(kGenerationIdKey,
+ sequence_information.generation_id());
+ sequence_information_value.SetIntKey(kPriorityKey,
+ sequence_information.priority());
+ return sequence_information_value;
+ }
+
+ static std::string GetPath(base::StringPiece base, base::StringPiece leaf) {
+ return base::JoinString({base, leaf}, ".");
+ }
+
+ static std::string GetFailedUploadSequencingIdPath() {
+ return GetPath(kFailedUploadedRecord, kSequencingIdKey);
+ }
+
+ static std::string GetFailedUploadGenerationIdPath() {
+ return GetPath(kFailedUploadedRecord, kGenerationIdKey);
+ }
+
+ static std::string GetFailedUploadPriorityPath() {
+ return GetPath(kFailedUploadedRecord, kPriorityKey);
+ }
+
+ static std::string GetFailureStatusCodePath() {
+ return GetPath(kFailureStatus, kCodeKey);
+ }
+
+ static std::string GetFailureStatusMessagePath() {
+ return GetPath(kFailureStatus, kMessageKey);
+ }
+};
+
+} // namespace
+
+class EncryptedReportingJobConfigurationTest : public testing::Test {
+ public:
+ EncryptedReportingJobConfigurationTest()
+ :
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ fake_serial_number_(&fake_statistics_provider_),
+#endif
+ client_(&service_) {
+ }
+
+ protected:
+ using MockCompleteCb = MockFunction<void(DeviceManagementService::Job* job,
+ DeviceManagementStatus code,
+ int net_error,
+ absl::optional<base::Value::Dict>)>;
+ static base::Value::Dict GenerateContext(base::StringPiece key,
+ base::StringPiece value) {
+ base::Value::Dict context;
+ context.SetByDottedPath(key, value);
+ return context;
+ }
+
+ void GetRecordList(EncryptedReportingJobConfiguration* configuration,
+ base::Value** record_list) {
+ base::Value* const payload = GetPayload(configuration);
+ *record_list = payload->FindListKey(kEncryptedRecordListKey);
+ ASSERT_TRUE(*record_list);
+ }
+
+ bool GetAttachEncryptionSettings(
+ EncryptedReportingJobConfiguration* configuration) {
+ base::Value* const payload = GetPayload(configuration);
+ const auto attach_encryption_settings =
+ payload->FindBoolKey(kAttachEncryptionSettingsKey);
+ return attach_encryption_settings.has_value() &&
+ attach_encryption_settings.value();
+ }
+
+ base::Value* GetPayload(EncryptedReportingJobConfiguration* configuration) {
+ absl::optional<base::Value> payload_result =
+ base::JSONReader::Read(configuration->GetPayload());
+
+ EXPECT_TRUE(payload_result.has_value());
+ payload_ = std::move(payload_result.value());
+ return &payload_;
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+ class ScopedFakeSerialNumber {
+ public:
+ explicit ScopedFakeSerialNumber(
+ chromeos::system::ScopedFakeStatisticsProvider*
+ fake_statistics_provider) {
+ // The fake serial number must be set before |configuration| is
+ // constructed below.
+ fake_statistics_provider->SetMachineStatistic(
+ chromeos::system::kSerialNumberKeyForTest, "fake_serial_number");
+ }
+ };
+ ScopedFakeSerialNumber fake_serial_number_;
+#endif
+
+ StrictMock<MockJobCreationHandler> job_creation_handler_;
+ FakeDeviceManagementService service_{&job_creation_handler_};
+ MockCloudPolicyClient client_;
+ StrictMock<MockCompleteCb> complete_cb_;
+
+ DeviceManagementService::Job job_;
+
+ private:
+ base::Value payload_;
+};
+
+// Validates that the non-Record portions of the payload are generated
+// correctly.
+TEST_F(EncryptedReportingJobConfigurationTest, ValidatePayload) {
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ RequestPayloadBuilder().Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+ auto* payload = GetPayload(&configuration);
+ EXPECT_FALSE(GetDeviceName().empty());
+ EXPECT_EQ(
+ *payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetNamePath()),
+ GetDeviceName());
+ EXPECT_EQ(
+ *payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetClientIdPath()),
+ client_.client_id());
+ EXPECT_EQ(*payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSPlatformPath()),
+ GetOSPlatform());
+ EXPECT_EQ(
+ *payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetOSVersionPath()),
+ GetOSVersion());
+
+ EXPECT_EQ(*payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetMachineUserPath()),
+ GetOSUsername());
+ EXPECT_EQ(*payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetChromeVersionPath()),
+ version_info::GetVersionNumber());
+}
+
+// Ensures that records are added correctly and that the payload is Base64
+// encoded.
+TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddEncryptedRecord) {
+ const std::string kEncryptedWrappedRecord = "TEST_INFO";
+ base::Value record_value = GenerateSingleRecord(kEncryptedWrappedRecord);
+
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ RequestPayloadBuilder().AddRecord(record_value).Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ base::Value* record_list = nullptr;
+ GetRecordList(&configuration, &record_list);
+ EXPECT_EQ(record_list->GetListDeprecated().size(), 1u);
+ EXPECT_EQ(record_list->GetListDeprecated()[0], record_value);
+
+ std::string* encrypted_wrapped_record =
+ record_list->GetListDeprecated()[0].FindStringKey(
+ kEncryptedWrappedRecordKey);
+ ASSERT_THAT(encrypted_wrapped_record, NotNull());
+
+ std::string decoded_record;
+ ASSERT_TRUE(base::Base64Decode(*encrypted_wrapped_record, &decoded_record));
+ EXPECT_THAT(decoded_record, StrEq(kEncryptedWrappedRecord));
+}
+
+// Ensures that multiple records can be added to the request.
+TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddsMultipleRecords) {
+ const std::vector<std::string> kEncryptedWrappedRecords{
+ "T", "E", "S", "T", "_", "I", "N", "F", "O"};
+ std::vector<base::Value> records;
+ RequestPayloadBuilder builder;
+ for (auto value : kEncryptedWrappedRecords) {
+ records.push_back(GenerateSingleRecord(value));
+ builder.AddRecord(records.back());
+ }
+
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ builder.Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ base::Value* record_list = nullptr;
+ GetRecordList(&configuration, &record_list);
+
+ EXPECT_EQ(record_list->GetListDeprecated().size(), records.size());
+
+ size_t counter = 0;
+ for (const auto& record : records) {
+ EXPECT_EQ(record_list->GetListDeprecated()[counter++], record);
+ }
+
+ EXPECT_FALSE(GetAttachEncryptionSettings(&configuration));
+}
+
+// Ensures that attach encryption settings request is included when no records
+// are present.
+TEST_F(EncryptedReportingJobConfigurationTest,
+ AllowsAttachEncryptionSettingsAlone) {
+ RequestPayloadBuilder builder{/*attach_encryption_settings=*/true};
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ builder.Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ base::Value* record_list = nullptr;
+ GetRecordList(&configuration, &record_list);
+
+ EXPECT_TRUE(record_list->GetListDeprecated().empty());
+
+ EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
+}
+
+TEST_F(EncryptedReportingJobConfigurationTest,
+ CorrectlyAddsMultipleRecordsWithAttachEncryptionSettings) {
+ const std::vector<std::string> kEncryptedWrappedRecords{
+ "T", "E", "S", "T", "_", "I", "N", "F", "O"};
+ std::vector<base::Value> records;
+ RequestPayloadBuilder builder{/*attach_encryption_settings=*/true};
+ for (auto value : kEncryptedWrappedRecords) {
+ records.push_back(GenerateSingleRecord(value));
+ builder.AddRecord(records.back());
+ }
+
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ builder.Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ base::Value* record_list = nullptr;
+ GetRecordList(&configuration, &record_list);
+
+ EXPECT_EQ(record_list->GetListDeprecated().size(), records.size());
+
+ size_t counter = 0;
+ for (const auto& record : records) {
+ EXPECT_EQ(record_list->GetListDeprecated()[counter++], record);
+ }
+
+ EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
+}
+
+// Ensures that the context can be updated.
+TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddsAndUpdatesContext) {
+ EXPECT_CALL(complete_cb_, Call(_, _, _, _)).Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ RequestPayloadBuilder().Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ const std::string kTestKey = "device.name";
+ const std::string kTestValue = "1701-A";
+ base::Value::Dict context = GenerateContext(kTestKey, kTestValue);
+ configuration.UpdateContext(std::move(context));
+
+ // Ensure the payload includes the path and value.
+ base::Value* payload = GetPayload(&configuration);
+ std::string* good_result = payload->FindStringPath(kTestKey);
+ ASSERT_THAT(good_result, NotNull());
+ EXPECT_EQ(*good_result, kTestValue);
+
+ // Add a path that isn't in the allow list.
+ const std::string kBadTestKey = "profile.string";
+ context = GenerateContext(kBadTestKey, kTestValue);
+ configuration.UpdateContext(std::move(context));
+
+ // Ensure that the path is removed from the payload.
+ payload = GetPayload(&configuration);
+ const std::string* bad_result = payload->FindStringPath(kBadTestKey);
+ EXPECT_THAT(bad_result, IsNull());
+
+ // Ensure that adding a bad path hasn't destroyed the good path.
+ good_result = payload->FindStringPath(kTestKey);
+ EXPECT_THAT(good_result, NotNull());
+ EXPECT_EQ(*good_result, kTestValue);
+
+ // Ensure that a good path can be overriden.
+ const std::string kUpdatedTestValue = "1701-B";
+ context = GenerateContext(kTestKey, kUpdatedTestValue);
+ configuration.UpdateContext(std::move(context));
+ payload = GetPayload(&configuration);
+ good_result = payload->FindStringPath(kTestKey);
+ ASSERT_THAT(good_result, NotNull());
+ EXPECT_EQ(*good_result, kUpdatedTestValue);
+}
+
+// Ensures that upload success is handled correctly.
+TEST_F(EncryptedReportingJobConfigurationTest, OnURLLoadComplete_Success) {
+ const std::string kEncryptedWrappedRecord = "TEST_INFO";
+ base::Value record_value = GenerateSingleRecord(kEncryptedWrappedRecord);
+
+ base::Value::Dict response = ResponseValueBuilder::CreateResponse(
+ *record_value.FindDictKey(kSequenceInformationKey), absl::nullopt);
+
+ EXPECT_CALL(complete_cb_,
+ Call(&job_, DM_STATUS_SUCCESS, net::OK, Eq(ByRef(response))))
+ .Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ RequestPayloadBuilder().AddRecord(record_value).Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+
+ const std::string kTestString = "device.clientId";
+ const std::string kTestInt = "1701-A";
+ base::Value::Dict context = GenerateContext(kTestString, kTestInt);
+ configuration.UpdateContext(std::move(context));
+
+ configuration.OnURLLoadComplete(
+ &job_, net::OK, DeviceManagementService::kSuccess,
+ ResponseValueBuilder::CreateResponseString(response));
+}
+
+// Ensures that upload failure is handled correctly.
+TEST_F(EncryptedReportingJobConfigurationTest, OnURLLoadComplete_NetError) {
+ int net_error = net::ERR_CONNECTION_RESET;
+ EXPECT_CALL(complete_cb_, Call(&job_, DM_STATUS_REQUEST_FAILED, net_error,
+ testing::Eq(absl::nullopt)))
+ .Times(1);
+ EncryptedReportingJobConfiguration configuration(
+ &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
+ RequestPayloadBuilder().Build(),
+ base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
+ configuration.OnURLLoadComplete(&job_, net_error, 0, "");
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/enterprise_metrics.cc b/chromium/components/policy/core/common/cloud/enterprise_metrics.cc
new file mode 100644
index 00000000000..1d725637a55
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/enterprise_metrics.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/enterprise_metrics.h"
+
+namespace policy {
+
+const char kMetricUserPolicyRefresh[] = "Enterprise.PolicyRefresh2";
+const char kMetricUserPolicyRefreshFcm[] =
+ "Enterprise.FCMInvalidationService.PolicyRefresh2";
+
+const char kMetricUserPolicyInvalidations[] = "Enterprise.PolicyInvalidations";
+const char kMetricUserPolicyInvalidationsFcm[] =
+ "Enterprise.FCMInvalidationService.PolicyInvalidations";
+
+const char kMetricDevicePolicyRefresh[] = "Enterprise.DevicePolicyRefresh3";
+const char kMetricDevicePolicyRefreshFcm[] =
+ "Enterprise.FCMInvalidationService.DevicePolicyRefresh3";
+
+const char kMetricDevicePolicyInvalidations[] =
+ "Enterprise.DevicePolicyInvalidations2";
+const char kMetricDevicePolicyInvalidationsFcm[] =
+ "Enterprise.FCMInvalidationService.DevicePolicyInvalidations2";
+
+const char kMetricDeviceLocalAccountPolicyRefresh[] =
+ "Enterprise.DeviceLocalAccountPolicyRefresh3";
+const char kMetricDeviceLocalAccountPolicyRefreshFcm[] =
+ "Enterprise.FCMInvalidationService.DeviceLocalAccountPolicyRefresh3";
+
+const char kMetricDeviceLocalAccountPolicyInvalidations[] =
+ "Enterprise.DeviceLocalAccountPolicyInvalidations2";
+const char kMetricDeviceLocalAccountPolicyInvalidationsFcm[] =
+ "Enterprise.FCMInvalidationService.DeviceLocalAccountPolicyInvalidations2";
+
+const char kMetricCBCMPolicyRefresh[] = "Enterprise.CBCMPolicyRefresh";
+const char kMetricCBCMPolicyRefreshFcm[] =
+ "Enterprise.FCMInvalidationService.CBCMPolicyRefresh";
+
+const char kMetricCBCMPolicyInvalidations[] =
+ "Enterprise.CBCMPolicyInvalidations";
+const char kMetricCBCMPolicyInvalidationsFcm[] =
+ "Enterprise.FCMInvalidationService.CBCMPolicyInvalidations";
+
+const char kMetricPolicyInvalidationRegistration[] =
+ "Enterprise.PolicyInvalidationsRegistrationResult";
+const char kMetricPolicyInvalidationRegistrationFcm[] =
+ "Enterprise.FCMInvalidationService.PolicyInvalidationsRegistrationResult";
+
+const char kMetricUserRemoteCommandInvalidations[] =
+ "Enterprise.UserRemoteCommandInvalidations";
+const char kMetricDeviceRemoteCommandInvalidations[] =
+ "Enterprise.DeviceRemoteCommandInvalidations";
+const char kMetricCBCMRemoteCommandInvalidations[] =
+ "Enterprise.CBCMRemoteCommandInvalidations";
+
+const char kMetricRemoteCommandInvalidationsRegistrationResult[] =
+ "Enterprise.RemoteCommandInvalidationsRegistrationResult";
+
+const char kMetricUserRemoteCommandReceived[] =
+ "Enterprise.UserRemoteCommand.Received";
+
+// Expands to:
+// Enterprise.UserRemoteCommand.Executed.CommandEchoTest
+// Enterprise.UserRemoteCommand.Executed.DeviceReboot
+// Enterprise.UserRemoteCommand.Executed.DeviceScreenshot
+// Enterprise.UserRemoteCommand.Executed.DeviceSetVolume
+// Enterprise.UserRemoteCommand.Executed.DeviceStartCrdSession
+// Enterprise.UserRemoteCommand.Executed.DeviceFetchStatus
+// Enterprise.UserRemoteCommand.Executed.UserArcCommand
+// Enterprise.UserRemoteCommand.Executed.DeviceWipeUsers
+// Enterprise.UserRemoteCommand.Executed.DeviceRefreshEnterpriseMachineCertificate
+// Enterprise.UserRemoteCommand.Executed.DeviceRemotePowerwash
+// Enterprise.UserRemoteCommand.Executed.DeviceGetAvailableDiagnosticRoutines
+// Enterprise.UserRemoteCommand.Executed.DeviceRunDiagnosticRoutine
+// Enterprise.UserRemoteCommand.Executed.DeviceGetDiagnosticRoutineUpdate
+// Enterprise.UserRemoteCommand.Executed.BrowserClearBrowsingData
+// Enterprise.UserRemoteCommand.Executed.DeviceResetEuicc
+// Enterprise.UserRemoteCommand.Executed.BrowserRotateAttestationCredential
+const char kMetricUserRemoteCommandExecutedTemplate[] =
+ "Enterprise.UserRemoteCommand.Executed.%s";
+
+const char kMetricDeviceRemoteCommandReceived[] =
+ "Enterprise.DeviceRemoteCommand.Received";
+
+// Expands to:
+// Enterprise.DeviceRemoteCommand.Executed.CommandEchoTest
+// Enterprise.DeviceRemoteCommand.Executed.DeviceReboot
+// Enterprise.DeviceRemoteCommand.Executed.DeviceScreenshot
+// Enterprise.DeviceRemoteCommand.Executed.DeviceSetVolume
+// Enterprise.DeviceRemoteCommand.Executed.DeviceStartCrdSession
+// Enterprise.DeviceRemoteCommand.Executed.DeviceFetchStatus
+// Enterprise.DeviceRemoteCommand.Executed.UserArcCommand
+// Enterprise.DeviceRemoteCommand.Executed.DeviceWipeUsers
+// Enterprise.DeviceRemoteCommand.Executed.DeviceRefreshEnterpriseMachineCertificate
+// Enterprise.DeviceRemoteCommand.Executed.DeviceRemotePowerwash
+// Enterprise.DeviceRemoteCommand.Executed.DeviceGetAvailableDiagnosticRoutines
+// Enterprise.DeviceRemoteCommand.Executed.DeviceRunDiagnosticRoutine
+// Enterprise.DeviceRemoteCommand.Executed.DeviceGetDiagnosticRoutineUpdate
+// Enterprise.DeviceRemoteCommand.Executed.BrowserClearBrowsingData
+// Enterprise.DeviceRemoteCommand.Executed.DeviceResetEuicc
+// Enterprise.DeviceRemoteCommand.Executed.BrowserRotateAttestationCredential
+const char kMetricDeviceRemoteCommandExecutedTemplate[] =
+ "Enterprise.DeviceRemoteCommand.Executed.%s";
+
+const char kMetricCBCMRemoteCommandReceived[] =
+ "Enterprise.CBCMRemoteCommand.Received";
+
+// Expands to:
+// Enterprise.CBCMRemoteCommand.Executed.CommandEchoTest
+// Enterprise.CBCMRemoteCommand.Executed.DeviceReboot
+// Enterprise.CBCMRemoteCommand.Executed.DeviceScreenshot
+// Enterprise.CBCMRemoteCommand.Executed.DeviceSetVolume
+// Enterprise.CBCMRemoteCommand.Executed.DeviceStartCrdSession
+// Enterprise.CBCMRemoteCommand.Executed.DeviceFetchStatus
+// Enterprise.CBCMRemoteCommand.Executed.UserArcCommand
+// Enterprise.CBCMRemoteCommand.Executed.DeviceWipeUsers
+// Enterprise.CBCMRemoteCommand.Executed.DeviceRefreshEnterpriseMachineCertificate
+// Enterprise.CBCMRemoteCommand.Executed.DeviceRemotePowerwash
+// Enterprise.CBCMRemoteCommand.Executed.DeviceGetAvailableDiagnosticRoutines
+// Enterprise.CBCMRemoteCommand.Executed.DeviceRunDiagnosticRoutine
+// Enterprise.CBCMRemoteCommand.Executed.DeviceGetDiagnosticRoutineUpdate
+// Enterprise.CBCMRemoteCommand.Executed.BrowserClearBrowsingData
+// Enterprise.CBCMRemoteCommand.Executed.DeviceResetEuicc
+// Enterprise.CBCMRemoteCommand.Executed.BrowserRotateAttestationCredential
+const char kMetricCBCMRemoteCommandExecutedTemplate[] =
+ "Enterprise.CBCMRemoteCommand.Executed.%s";
+
+const char kUMAPsmSuccessTime[] =
+ "Enterprise.AutoEnrollmentPrivateSetMembershipSuccessTime";
+const char kUMAPsmResult[] = "Enterprise.AutoEnrollmentPsmResult";
+const char kUMAPsmNetworkErrorCode[] =
+ "Enterprise.AutoEnrollmentPsmRequestNetworkErrorCode";
+const char kUMAPsmDmServerRequestStatus[] =
+ "Enterprise.AutoEnrollmentPsmDmServerRequestStatus";
+
+const char kUMAHashDanceSuccessTime[] =
+ "Enterprise.AutoEnrollmentHashDanceSuccessTime";
+const char kUMAHashDanceProtocolTime[] =
+ "Enterprise.AutoEnrollmentProtocolTime";
+const char kUMAHashDanceBucketDownloadTime[] =
+ "Enterprise.AutoEnrollmentBucketDownloadTime";
+const char kUMAHashDanceExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
+const char kUMAHashDanceRequestStatus[] =
+ "Enterprise.AutoEnrollmentRequestStatus";
+const char kUMAHashDanceNetworkErrorCode[] =
+ "Enterprise.AutoEnrollmentRequestNetworkErrorCode";
+
+const char kUMASuffixInitialEnrollment[] = ".InitialEnrollment";
+const char kUMASuffixFRE[] = ".ForcedReenrollment";
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/enterprise_metrics.h b/chromium/components/policy/core/common/cloud/enterprise_metrics.h
new file mode 100644
index 00000000000..80c4d133478
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/enterprise_metrics.h
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENTERPRISE_METRICS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENTERPRISE_METRICS_H_
+
+#include "build/build_config.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Metrics collected for enterprise events.
+
+// Events related to device enrollment.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration
+// (update tools/metrics/histograms/enums.xml as well).
+enum MetricEnrollment {
+ // User pressed 'Cancel' during the enrollment process.
+ kMetricEnrollmentCancelled = 0,
+ // User started enrollment process by submitting valid credentials.
+ kMetricEnrollmentStarted = 1,
+ // OAuth token fetch failed: network error.
+ kMetricEnrollmentNetworkFailed = 2,
+ // OAuth token fetch failed: login error.
+ kMetricEnrollmentLoginFailed = 3,
+ // Registration / policy fetch failed: DM server reports management not
+ // supported.
+ kMetricEnrollmentNotSupported = 4,
+ /* kMetricEnrollmentPolicyFailed = 5 REMOVED */
+ /* kMetricEnrollmentOtherFailed = 6 REMOVED */
+ // Enrollment was successful.
+ kMetricEnrollmentOK = 7,
+ // Registration / policy fetch failed: DM server reports that the serial
+ // number we try to register is not assigned to the domain used.
+ kMetricEnrollmentRegisterPolicyInvalidSerial = 8,
+ /* kMetricEnrollmentAutoStarted = 9 REMOVED */
+ /* kMetricEnrollmentAutoFailed = 10 REMOVED */
+ /* kMetricEnrollmentAutoRestarted = 11 REMOVED */
+ /* kMetricEnrollmentAutoCancelled = 12 REMOVED */
+ /* kMetricEnrollmentAutoOK = 13 REMOVED */
+ // Registration failed: DM server returns unknown/disallowed enrollment mode.
+ kMetricEnrollmentInvalidEnrollmentMode = 14,
+ /* kMetricEnrollmentAutoEnrollmentNotSupported = 15 REMOVED */
+ // Lockbox initialization took too long to complete.
+ kMetricEnrollmentLockboxTimeoutError = 16,
+ // Lockbox error at re-enrollment: domain does not match install attributes.
+ kMetricEnrollmentLockDomainMismatch = 17,
+ // Registration / policy fetch failed: DM server reports licenses expired or
+ // exhausted.
+ kMetricEnrollmentRegisterPolicyMissingLicenses = 18,
+ // Failed to fetch device robot authorization code from DM Server.
+ kMetricEnrollmentRobotAuthCodeFetchFailed = 19,
+ // Failed to fetch device robot refresh token from GAIA.
+ kMetricEnrollmentRobotRefreshTokenFetchFailed = 20,
+ // Failed to persist robot account refresh token on device.
+ kMetricEnrollmentRobotRefreshTokenStoreFailed = 21,
+ // Registration / policy fetch failed: DM server reports administrator
+ // deprovisioned the device.
+ kMetricEnrollmentRegisterPolicyDeprovisioned = 22,
+ // Registration / policy fetch failed: DM server reports domain mismatch.
+ kMetricEnrollmentRegisterPolicyDomainMismatch = 23,
+ // Enrollment has been triggered, the webui login screen has been shown.
+ kMetricEnrollmentTriggered = 24,
+ // The user submitted valid credentials to start the enrollment process
+ // for the second (or further) time.
+ kMetricEnrollmentRestarted = 25,
+ /* kMetricEnrollmentStoreTokenAndIdFailed = 26 REMOVED */
+ // Failed to obtain FRE state keys.
+ kMetricEnrollmentNoStateKeys = 27,
+ // Failed to validate policy.
+ kMetricEnrollmentPolicyValidationFailed = 28,
+ // Failed due to error in CloudPolicyStore.
+ kMetricEnrollmentCloudPolicyStoreError = 29,
+ /* kMetricEnrollmentLockBackendError = 30 REMOVED */
+ // Registration / policy fetch failed: DM server reports invalid request
+ // payload.
+ kMetricEnrollmentRegisterPolicyPayloadInvalid = 31,
+ // Registration / policy fetch failed: DM server reports device not found.
+ kMetricEnrollmentRegisterPolicyDeviceNotFound = 32,
+ // Registration / policy fetch failed: DM server reports DM token invalid.
+ kMetricEnrollmentRegisterPolicyDMTokenInvalid = 33,
+ // Registration / policy fetch failed: DM server reports activation pending.
+ kMetricEnrollmentRegisterPolicyActivationPending = 34,
+ // Registration / policy fetch failed: DM server reports device ID conflict.
+ kMetricEnrollmentRegisterPolicyDeviceIdConflict = 35,
+ // Registration / policy fetch failed: DM server can't find policy.
+ kMetricEnrollmentRegisterPolicyNotFound = 36,
+ // Registration / policy fetch failed: HTTP request failed.
+ kMetricEnrollmentRegisterPolicyRequestFailed = 37,
+ // Registration / policy fetch failed: DM server reports temporary problem.
+ kMetricEnrollmentRegisterPolicyTempUnavailable = 38,
+ // Registration / policy fetch failed: DM server returns non-success HTTP
+ // status code.
+ kMetricEnrollmentRegisterPolicyHttpError = 39,
+ // Registration / policy fetch failed: can't decode DM server response.
+ kMetricEnrollmentRegisterPolicyResponseInvalid = 40,
+ // OAuth token fetch failed: account not signed up.
+ kMetricEnrollmentAccountNotSignedUp = 41,
+ /* kMetricEnrollmentAccountDeleted = 42 REMOVED */
+ /* kMetricEnrollmentAccountDisabled = 43 REMOVED */
+ // Re-enrollment pre-check failed: domain does not match install attributes.
+ kMetricEnrollmentPrecheckDomainMismatch = 44,
+ // Lockbox backend failed to initialize.
+ kMetricEnrollmentLockBackendInvalid = 45,
+ // Lockbox backend (TPM) already locked.
+ kMetricEnrollmentLockAlreadyLocked = 46,
+ // Lockbox failure setting attributes.
+ kMetricEnrollmentLockSetError = 47,
+ // Lockbox failure during locking.
+ kMetricEnrollmentLockFinalizeError = 48,
+ // Lockbox read back is inconsistent.
+ kMetricEnrollmentLockReadbackError = 49,
+ // Failed to update device attributes.
+ kMetricEnrollmentAttributeUpdateFailed = 50,
+ // Enrollment mode does not match already locked install attributes.
+ kMetricEnrollmentLockModeMismatch = 51,
+ // A registration certificate could not be fetched from the PCA.
+ kMetricEnrollmentRegistrationCertificateFetchFailed = 52,
+ // The request to enroll could not be signed.
+ kMetricEnrollmentRegisterCannotSignRequest = 53,
+ // Device model or serial number missing from VPD.
+ kMetricEnrollmentNoDeviceIdentification = 54,
+ // Active Directory policy fetch failed.
+ kMetricEnrollmentActiveDirectoryPolicyFetchFailed = 55,
+ // Failed to store DM token into the local state.
+ kMetricEnrollmentStoreDMTokenFailed = 56,
+ // Failed to get available licenses.
+ kMetricEnrollmentLicenseRequestFailed = 57,
+ // Registration failed: Consumer account with packaged license.
+ kMetricEnrollmentRegisterConsumerAccountWithPackagedLicense = 58,
+ // Device was not pre-provisioned for Zero-Touch.
+ kMetricEnrollmentDeviceNotPreProvisioned = 59,
+ // Enrollment failed: Enterprise account is not eligible to enroll.
+ kMetricEnrollmentRegisterEnterpriseAccountIsNotEligibleToEnroll = 60,
+ // Enrollment failed: Enterprise TOS has not been accepted.
+ kMetricEnrollmentRegisterEnterpriseTosHasNotBeenAccepted = 61,
+ // Too many requests are uploadede within a short time.
+ kMetricEnrollmentTooManyRequests = 62,
+ // Enrollment failed: illegal account for packaged EDU license.
+ kMetricEnrollmentIllegalAccountForPackagedEDULicense = 63,
+ // Enrollment failed: dev mode would be blocked but this is prevented by a
+ // command-line switch.
+ kMetricEnrollmentMayNotBlockDevMode = 64,
+ // Max value for use with enumeration histogram UMA functions.
+ kMaxValue = kMetricEnrollmentIllegalAccountForPackagedEDULicense
+};
+
+// Events related to policy refresh.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration
+// (update tools/metrics/histograms/enums.xml as well).
+enum MetricPolicyRefresh {
+ // A refresh occurred while the policy was not invalidated and the policy was
+ // changed. Invalidations were enabled.
+ METRIC_POLICY_REFRESH_CHANGED = 0,
+ // A refresh occurred while the policy was not invalidated and the policy was
+ // changed. Invalidations were disabled.
+ METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS = 1,
+ // A refresh occurred while the policy was not invalidated and the policy was
+ // unchanged.
+ METRIC_POLICY_REFRESH_UNCHANGED = 2,
+ // A refresh occurred while the policy was invalidated and the policy was
+ // changed.
+ METRIC_POLICY_REFRESH_INVALIDATED_CHANGED = 3,
+ // A refresh occurred while the policy was invalidated and the policy was
+ // unchanged.
+ METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED = 4,
+
+ METRIC_POLICY_REFRESH_SIZE // Must be the last.
+};
+
+// Types of policy invalidations.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration
+// (update tools/metrics/histograms/enums.xml as well).
+enum PolicyInvalidationType {
+ // The invalidation contained no payload.
+ POLICY_INVALIDATION_TYPE_NO_PAYLOAD = 0,
+ // A normal invalidation containing a payload.
+ POLICY_INVALIDATION_TYPE_NORMAL = 1,
+ // The invalidation contained no payload and was considered expired.
+ POLICY_INVALIDATION_TYPE_NO_PAYLOAD_EXPIRED = 3,
+ // The invalidation contained a payload and was considered expired.
+ POLICY_INVALIDATION_TYPE_EXPIRED = 4,
+
+ POLICY_INVALIDATION_TYPE_SIZE // Must be the last.
+};
+
+// Result of the Device ID field validation in policy protobufs.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class PolicyDeviceIdValidity {
+ kValid = 0,
+ kActualIdUnknown = 1,
+ kMissing = 2,
+ kInvalid = 3,
+ kMaxValue = kInvalid, // Must be the last.
+};
+
+// Names for the UMA counters. They are shared from here since the events
+// from the same enum above can be triggered in different files, and must use
+// the same UMA histogram name.
+// Metrics name from UMA dashboard cloud be used in codesearch as is, so please
+// keep the names without format specifiers (e.g. %s) or add a comment how the
+// name could be expanded.
+POLICY_EXPORT extern const char kMetricUserPolicyRefresh[];
+POLICY_EXPORT extern const char kMetricUserPolicyRefreshFcm[];
+POLICY_EXPORT extern const char kMetricUserPolicyInvalidations[];
+POLICY_EXPORT extern const char kMetricUserPolicyInvalidationsFcm[];
+
+POLICY_EXPORT extern const char kMetricDevicePolicyRefresh[];
+POLICY_EXPORT extern const char kMetricDevicePolicyRefreshFcm[];
+POLICY_EXPORT extern const char kMetricDevicePolicyInvalidations[];
+POLICY_EXPORT extern const char kMetricDevicePolicyInvalidationsFcm[];
+
+POLICY_EXPORT extern const char kMetricDeviceLocalAccountPolicyRefresh[];
+POLICY_EXPORT extern const char kMetricDeviceLocalAccountPolicyRefreshFcm[];
+POLICY_EXPORT extern const char kMetricDeviceLocalAccountPolicyInvalidations[];
+POLICY_EXPORT extern const char
+ kMetricDeviceLocalAccountPolicyInvalidationsFcm[];
+
+POLICY_EXPORT extern const char kMetricCBCMPolicyRefresh[];
+POLICY_EXPORT extern const char kMetricCBCMPolicyRefreshFcm[];
+POLICY_EXPORT extern const char kMetricCBCMPolicyInvalidations[];
+POLICY_EXPORT extern const char kMetricCBCMPolicyInvalidationsFcm[];
+
+POLICY_EXPORT extern const char kMetricPolicyInvalidationRegistration[];
+POLICY_EXPORT extern const char kMetricPolicyInvalidationRegistrationFcm[];
+
+POLICY_EXPORT extern const char kMetricUserRemoteCommandInvalidations[];
+POLICY_EXPORT extern const char kMetricDeviceRemoteCommandInvalidations[];
+POLICY_EXPORT extern const char kMetricCBCMRemoteCommandInvalidations[];
+
+POLICY_EXPORT extern const char
+ kMetricRemoteCommandInvalidationsRegistrationResult[];
+
+POLICY_EXPORT extern const char kMetricUserRemoteCommandReceived[];
+POLICY_EXPORT extern const char kMetricUserRemoteCommandExecutedTemplate[];
+
+POLICY_EXPORT extern const char kMetricDeviceRemoteCommandReceived[];
+POLICY_EXPORT extern const char kMetricDeviceRemoteCommandExecutedTemplate[];
+
+POLICY_EXPORT extern const char kMetricCBCMRemoteCommandReceived[];
+POLICY_EXPORT extern const char kMetricCBCMRemoteCommandExecutedTemplate[];
+
+// Private set membership UMA histogram names.
+POLICY_EXPORT extern const char kUMAPsmSuccessTime[];
+POLICY_EXPORT extern const char kUMAPsmResult[];
+POLICY_EXPORT extern const char kUMAPsmNetworkErrorCode[];
+POLICY_EXPORT extern const char kUMAPsmDmServerRequestStatus[];
+
+// DeviceAutoEnrollmentRequest i.e. hash dance request UMA histogram names.
+POLICY_EXPORT extern const char kUMAHashDanceSuccessTime[];
+// The following histogram names where added before PSM (private set membership)
+// existed. They are only recorded for hash dance.
+POLICY_EXPORT extern const char kUMAHashDanceProtocolTime[];
+POLICY_EXPORT extern const char kUMAHashDanceBucketDownloadTime[];
+POLICY_EXPORT extern const char kUMAHashDanceExtraTime[];
+POLICY_EXPORT extern const char kUMAHashDanceRequestStatus[];
+POLICY_EXPORT extern const char kUMAHashDanceNetworkErrorCode[];
+
+// The following UMA suffixes are used by Hash dance and PSM protocols.
+// Suffix for initial enrollment.
+POLICY_EXPORT extern const char kUMASuffixInitialEnrollment[];
+// Suffix for Forced Re-Enrollment.
+POLICY_EXPORT extern const char kUMASuffixFRE[];
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_ENTERPRISE_METRICS_H_
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.cc b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.cc
new file mode 100644
index 00000000000..1542996d79b
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.cc
@@ -0,0 +1,295 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace policy {
+
+class ExternalPolicyDataFetcher::Job
+ : public network::SimpleURLLoaderStreamConsumer {
+ public:
+ Job(std::unique_ptr<network::PendingSharedURLLoaderFactory>
+ pending_url_loader_factory,
+ base::WeakPtr<ExternalPolicyDataFetcher> fetcher,
+ scoped_refptr<base::SequencedTaskRunner> fetcher_task_runner,
+ ExternalPolicyDataFetcher::FetchCallback callback);
+ Job(const Job&) = delete;
+ Job& operator=(const Job&) = delete;
+
+ void Start(const GURL& url, int64_t max_size);
+ void Cancel();
+ void OnResponseStarted(const GURL& final_url,
+ const network::mojom::URLResponseHead& response_head);
+
+ // network::SimpleURLLoaderStreamConsumer implementation
+ void OnDataReceived(base::StringPiece string_piece,
+ base::OnceClosure resume) override;
+ void OnComplete(bool success) override;
+ void OnRetry(base::OnceClosure start_retry) override;
+
+ private:
+ void ReportFinished(Result result, std::unique_ptr<std::string> data);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ std::unique_ptr<network::PendingSharedURLLoaderFactory>
+ pending_url_loader_factory_;
+ base::WeakPtr<ExternalPolicyDataFetcher> fetcher_;
+ scoped_refptr<base::SequencedTaskRunner> fetcher_task_runner_;
+ ExternalPolicyDataFetcher::FetchCallback callback_;
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+ std::string response_body_;
+ int64_t max_size_ = 0;
+};
+
+ExternalPolicyDataFetcher::Job::Job(
+ std::unique_ptr<network::PendingSharedURLLoaderFactory>
+ pending_url_loader_factory,
+ base::WeakPtr<ExternalPolicyDataFetcher> fetcher,
+ scoped_refptr<base::SequencedTaskRunner> fetcher_task_runner,
+ ExternalPolicyDataFetcher::FetchCallback callback)
+ : pending_url_loader_factory_(std::move(pending_url_loader_factory)),
+ fetcher_(std::move(fetcher)),
+ fetcher_task_runner_(std::move(fetcher_task_runner)),
+ callback_(std::move(callback)) {
+ // A job is created on the fetcher sequence but it then lives on the separate
+ // job sequence.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+void ExternalPolicyDataFetcher::Job::Start(
+ const GURL& url,
+ int64_t max_size) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DCHECK_GE(max_size, 0);
+ max_size_ = max_size;
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+ resource_request->load_flags =
+ net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("external_policy_fetcher", R"(
+ semantics {
+ sender: "Cloud Policy"
+ description:
+ "Used to fetch policy for extensions, policy-controlled wallpaper, "
+ "and custom terms of service."
+ trigger:
+ "Periodically loaded when a managed user is signed in to Chrome."
+ data:
+ "This request does not send any data. It loads external resources "
+ "by a unique URL provided by the admin."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature cannot be controlled by Chrome settings, but users "
+ "can sign out of Chrome to disable it."
+ policy_exception_justification:
+ "Not implemented, considered not useful. This request is part of "
+ "the policy fetcher itself."
+ })");
+
+ url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ url_loader_->SetRetryOptions(
+ 3, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+ url_loader_->SetOnResponseStartedCallback(
+ base::BindOnce(&ExternalPolicyDataFetcher::Job::OnResponseStarted,
+ base::Unretained(this)));
+ url_loader_->DownloadAsStream(network::SharedURLLoaderFactory::Create(
+ std::move(pending_url_loader_factory_))
+ .get(),
+ this);
+}
+
+void ExternalPolicyDataFetcher::Job::Cancel() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ url_loader_.reset();
+}
+
+void ExternalPolicyDataFetcher::Job::OnResponseStarted(
+ const GURL& /* final_url */,
+ const network::mojom::URLResponseHead& response_head) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (response_head.content_length != -1 &&
+ response_head.content_length > max_size_) {
+ url_loader_.reset();
+ ReportFinished(MAX_SIZE_EXCEEDED, nullptr);
+ return;
+ }
+}
+
+void ExternalPolicyDataFetcher::Job::OnDataReceived(
+ base::StringPiece string_piece,
+ base::OnceClosure resume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (response_body_.length() + string_piece.length() >
+ static_cast<uint64_t>(max_size_)) {
+ url_loader_.reset();
+ ReportFinished(MAX_SIZE_EXCEEDED, nullptr);
+ return;
+ }
+
+ response_body_.append(string_piece.data(), string_piece.length());
+ std::move(resume).Run();
+}
+
+void ExternalPolicyDataFetcher::Job::OnComplete(bool /* success */) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(url_loader_);
+
+ int response_code = 0;
+ if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers)
+ response_code = url_loader->ResponseInfo()->headers->response_code();
+
+ Result result;
+ std::unique_ptr<std::string> data;
+
+ if (url_loader->NetError() == net::ERR_CONNECTION_RESET ||
+ url_loader->NetError() == net::ERR_TEMPORARILY_THROTTLED ||
+ url_loader->NetError() == net::ERR_CONNECTION_CLOSED) {
+ // The connection was interrupted.
+ result = CONNECTION_INTERRUPTED;
+ } else if (url_loader->NetError() == net::ERR_HTTP_RESPONSE_CODE_FAILURE) {
+ // net::ERR_HTTP_RESPONSE_CODE_FAILURE signals that a non-2xx HTTP response
+ // has been received.
+ if (response_code >= 500) {
+ // Problem at the server.
+ result = SERVER_ERROR;
+ } else if (response_code >= 400) {
+ // Client error.
+ result = CLIENT_ERROR;
+ } else {
+ // Any other type of HTTP failure.
+ result = HTTP_ERROR;
+ }
+ } else if (url_loader->NetError() != net::OK) {
+ // Another network error occurred.
+ result = NETWORK_ERROR;
+ } else {
+ result = SUCCESS;
+ data = std::make_unique<std::string>(std::move(response_body_));
+ }
+
+ ReportFinished(result, std::move(data));
+}
+
+void ExternalPolicyDataFetcher::Job::OnRetry(base::OnceClosure start_retry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ response_body_.clear();
+ std::move(start_retry).Run();
+}
+
+void ExternalPolicyDataFetcher::Job::ReportFinished(
+ Result result,
+ std::unique_ptr<std::string> data) {
+ fetcher_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ExternalPolicyDataFetcher::OnJobFinished, fetcher_,
+ std::move(callback_), this, result, std::move(data)));
+}
+
+ExternalPolicyDataFetcher::ExternalPolicyDataFetcher(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)),
+ job_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+ // |url_loader_factory| is null in some tests.
+ if (url_loader_factory)
+ pending_url_loader_factory_ = url_loader_factory->Clone();
+}
+
+ExternalPolicyDataFetcher::~ExternalPolicyDataFetcher() {
+ // No RunsTasksInCurrentSequence() check to avoid unit tests failures.
+ // In unit tests the browser process instance is deleted only after test ends
+ // and test task scheduler is shutted down. Therefore we need to delete some
+ // components of BrowserPolicyConnector (ResourceCache and
+ // CloudExternalDataManagerBase::Backend) manually when task runner doesn't
+ // accept new tasks (DeleteSoon in this case). This leads to the situation
+ // when this destructor is called not on |task_runner|.
+
+ for (auto it = jobs_.begin(); it != jobs_.end(); ++it)
+ CancelJob(*it);
+}
+
+ExternalPolicyDataFetcher::Job* ExternalPolicyDataFetcher::StartJob(
+ const GURL& url,
+ int64_t max_size,
+ FetchCallback callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (!cloned_url_loader_factory_) {
+ cloned_url_loader_factory_ = network::SharedURLLoaderFactory::Create(
+ std::move(pending_url_loader_factory_));
+ }
+ Job* job =
+ new Job(cloned_url_loader_factory_->Clone(), weak_factory_.GetWeakPtr(),
+ task_runner_, std::move(callback));
+ jobs_.insert(job);
+ job_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Job::Start, base::Unretained(job), url, max_size));
+ return job;
+}
+
+void ExternalPolicyDataFetcher::CancelJob(Job* job) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(jobs_.find(job) != jobs_.end());
+ jobs_.erase(job);
+ // Post a task that will cancel the |job| in the |job_task_runner_|. The |job|
+ // is removed from |jobs_| immediately to indicate that it has been canceled
+ // but is not actually deleted until the cancellation has reached the
+ // |job_task_runner_| and a confirmation has been posted back. This ensures
+ // that no new job can be allocated at the same address while an
+ // OnJobFinished() callback may still be pending for the canceled |job|.
+ job_task_runner_->PostTaskAndReply(
+ FROM_HERE, base::BindOnce(&Job::Cancel, base::Unretained(job)),
+ base::BindOnce([](Job*) {}, base::Owned(job)));
+}
+
+void ExternalPolicyDataFetcher::OnJobFinished(
+ FetchCallback callback,
+ Job* job,
+ Result result,
+ std::unique_ptr<std::string> data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ auto it = jobs_.find(job);
+ if (it == jobs_.end()) {
+ // The |job| has been canceled and removed from |jobs_| already. This can
+ // happen because the jobs run on a different sequence and a |job| may
+ // finish before the cancellation has reached that sequence.
+ return;
+ }
+ std::move(callback).Run(result, std::move(data));
+ jobs_.erase(it);
+ delete job;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.h b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.h
new file mode 100644
index 00000000000..4869a035749
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher.h
@@ -0,0 +1,118 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_FETCHER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_FETCHER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/policy/policy_export.h"
+#include "url/gurl.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace network {
+class PendingSharedURLLoaderFactory;
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+// This class handles network fetch jobs for the ExternalPolicyDataUpdater by
+// forwarding them to a job running on a different thread.
+// The class can be instantiated on any thread but from then on, it must be
+// accessed and destroyed on the background thread that the
+// ExternalPolicyDataUpdater runs on only.
+class POLICY_EXPORT ExternalPolicyDataFetcher {
+ public:
+ // The result of a fetch job.
+ enum Result {
+ // Successful fetch.
+ SUCCESS,
+ // The connection was interrupted.
+ CONNECTION_INTERRUPTED,
+ // Another network error occurred.
+ NETWORK_ERROR,
+ // Problem at the server.
+ SERVER_ERROR,
+ // Client error.
+ CLIENT_ERROR,
+ // Any other type of HTTP failure.
+ HTTP_ERROR,
+ // Received data exceeds maximum allowed size.
+ MAX_SIZE_EXCEEDED,
+ };
+
+ // Encapsulates the state for a fetch job.
+ class Job;
+
+ // Callback invoked when a fetch job finishes. If the fetch was successful,
+ // the Result is SUCCESS and the scoped_ptr contains the retrieved data.
+ // Otherwise, Result indicates the type of error that occurred and the
+ // scoped_ptr is NULL.
+ using FetchCallback =
+ base::OnceCallback<void(Result, std::unique_ptr<std::string>)>;
+
+ // |task_runner| represents the background thread that |this| runs on.
+ ExternalPolicyDataFetcher(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ ExternalPolicyDataFetcher(const ExternalPolicyDataFetcher&) = delete;
+ ExternalPolicyDataFetcher& operator=(const ExternalPolicyDataFetcher&) =
+ delete;
+ ~ExternalPolicyDataFetcher();
+
+ // Fetch data from |url| and invoke |callback| with the result. See the
+ // documentation of FetchCallback and Result for more details. If a fetch
+ // should be retried after an error, it is the caller's responsibility to call
+ // StartJob() again. Returns an opaque job identifier. Ownership of the job
+ // identifier is retained by |this|.
+ Job* StartJob(const GURL& url, int64_t max_size, FetchCallback callback);
+
+ // Cancel the fetch job identified by |job|. The job is canceled silently,
+ // without invoking the |callback| that was passed to StartJob().
+ void CancelJob(Job* job);
+
+ private:
+ // Invoked when a fetch job finishes in the |backend_|.
+ void OnJobFinished(FetchCallback callback,
+ Job* job,
+ Result result,
+ std::unique_ptr<std::string> data);
+
+ // Task runner representing the thread that |this| runs on.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ // Task runner for running the fetch jobs. It's the task runner on which this
+ // instance was created.
+ const scoped_refptr<base::SequencedTaskRunner> job_task_runner_;
+
+ // The information for the lazy creation of |cloned_url_loader_factory_|.
+ std::unique_ptr<network::PendingSharedURLLoaderFactory>
+ pending_url_loader_factory_;
+ // The cloned factory that can be used from |task_runner_|. It's created
+ // lazily, as our constructor runs on a difference sequence.
+ scoped_refptr<network::SharedURLLoaderFactory> cloned_url_loader_factory_;
+
+ // Set that owns all currently running Jobs.
+ typedef std::set<Job*> JobSet;
+ JobSet jobs_;
+
+ base::WeakPtrFactory<ExternalPolicyDataFetcher> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_FETCHER_H_
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_fetcher_unittest.cc b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher_unittest.cc
new file mode 100644
index 00000000000..30bb9ba0d85
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_fetcher_unittest.cc
@@ -0,0 +1,449 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/run_loop.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char* kExternalPolicyDataURLs[] = {
+ "http://localhost/data_1",
+ "http://localhost/data_2"
+};
+
+const int64_t kExternalPolicyDataMaxSize = 20;
+
+const char* kExternalPolicyDataPayload = "External policy data";
+const char* kExternalPolicyDataOverflowPayload = "External policy data+++++++";
+
+} // namespace
+
+class ExternalPolicyDataFetcherTest : public testing::Test {
+ public:
+ ExternalPolicyDataFetcherTest(const ExternalPolicyDataFetcherTest&) = delete;
+ ExternalPolicyDataFetcherTest& operator=(
+ const ExternalPolicyDataFetcherTest&) = delete;
+
+ protected:
+ ExternalPolicyDataFetcherTest();
+ ~ExternalPolicyDataFetcherTest() override;
+
+ // testing::Test:
+ void SetUp() override;
+
+ void StartJob(int index);
+ void CancelJob(int index);
+
+ void OnJobFinished(int job_index,
+ ExternalPolicyDataFetcher::Result result,
+ std::unique_ptr<std::string> data);
+ int GetAndResetCallbackCount();
+
+ base::test::TaskEnvironment task_environment_;
+ scoped_refptr<base::TestSimpleTaskRunner> owner_task_runner_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ std::unique_ptr<ExternalPolicyDataFetcher> fetcher_;
+
+ std::map<int, ExternalPolicyDataFetcher::Job*> jobs_; // Not owned.
+
+ int callback_count_;
+ int callback_job_index_;
+ ExternalPolicyDataFetcher::Result callback_result_;
+ std::unique_ptr<std::string> callback_data_;
+};
+
+ExternalPolicyDataFetcherTest::ExternalPolicyDataFetcherTest()
+ : callback_count_(0) {}
+
+ExternalPolicyDataFetcherTest::~ExternalPolicyDataFetcherTest() {
+}
+
+void ExternalPolicyDataFetcherTest::SetUp() {
+ auto url_loader_factory =
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_);
+ owner_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+ fetcher_ = std::make_unique<ExternalPolicyDataFetcher>(
+ std::move(url_loader_factory), owner_task_runner_);
+}
+
+void ExternalPolicyDataFetcherTest::StartJob(int index) {
+ jobs_[index] = fetcher_->StartJob(
+ GURL(kExternalPolicyDataURLs[index]), kExternalPolicyDataMaxSize,
+ base::BindOnce(&ExternalPolicyDataFetcherTest::OnJobFinished,
+ base::Unretained(this), index));
+ base::RunLoop().RunUntilIdle();
+}
+
+void ExternalPolicyDataFetcherTest::CancelJob(int index) {
+ auto it = jobs_.find(index);
+ ASSERT_TRUE(it != jobs_.end());
+ ExternalPolicyDataFetcher::Job* job = it->second;
+ jobs_.erase(it);
+ fetcher_->CancelJob(job);
+}
+
+void ExternalPolicyDataFetcherTest::OnJobFinished(
+ int job_index,
+ ExternalPolicyDataFetcher::Result result,
+ std::unique_ptr<std::string> data) {
+ ++callback_count_;
+ callback_job_index_ = job_index;
+ callback_result_ = result;
+ callback_data_ = std::move(data);
+ jobs_.erase(job_index);
+}
+
+int ExternalPolicyDataFetcherTest::GetAndResetCallbackCount() {
+ const int callback_count = callback_count_;
+ callback_count_ = 0;
+ return callback_count;
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, Success) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, MaxSizeExceeded) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch with more data than allowed.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataOverflowPayload);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED, callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ConnectionInterrupted) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail due to an interrupted connection.
+ test_url_loader_factory_.AddResponse(
+ GURL(kExternalPolicyDataURLs[0]), network::mojom::URLResponseHead::New(),
+ std::string(),
+ network::URLLoaderCompletionStatus(net::ERR_CONNECTION_RESET));
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED,
+ callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, NetworkError) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail due to a network error.
+ test_url_loader_factory_.AddResponse(
+ GURL(kExternalPolicyDataURLs[0]), network::mojom::URLResponseHead::New(),
+ std::string(),
+ network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED));
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::NETWORK_ERROR, callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ServerError) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with a server error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(),
+ net::HTTP_INTERNAL_SERVER_ERROR);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SERVER_ERROR, callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ClientError) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with a client error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(), net::HTTP_BAD_REQUEST);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::CLIENT_ERROR, callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, HTTPError) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with an HTTP error.
+ test_url_loader_factory_.AddResponse(
+ kExternalPolicyDataURLs[0], std::string(), net::HTTP_MULTIPLE_CHOICES);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the correct error code.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::HTTP_ERROR, callback_result_);
+ EXPECT_FALSE(callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, Canceled) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Cancel the fetch job.
+ CancelJob(0);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is not invoked.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(0, GetAndResetCallbackCount());
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, SuccessfulCanceled) {
+ // Start a fetch job.
+ StartJob(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Cancel the fetch job before the successful fetch result has arrived from
+ // the job.
+ CancelJob(0);
+
+ // Verify that the callback is not invoked.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(0, GetAndResetCallbackCount());
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ParallelJobs) {
+ // Start two fetch jobs.
+ StartJob(0);
+ StartJob(1);
+
+ // Verify that the first and second fetches have been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the first fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Verify that the first fetch is no longer running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+
+ // Verify that the second fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Verify that the second fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(1, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ParallelJobsFinishingOutOfOrder) {
+ // Start two fetch jobs.
+ StartJob(0);
+ StartJob(1);
+
+ // Verify that the first and second fetches have been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Verify that the second fetch is no longer running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(1, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+
+ // Verify that the first fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the first fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Verify that the first fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(0, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+}
+
+TEST_F(ExternalPolicyDataFetcherTest, ParallelJobsWithCancel) {
+ // Start two fetch jobs.
+ StartJob(0);
+ StartJob(1);
+
+ // Verify that the first and second fetches have been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Cancel the first fetch job.
+ CancelJob(0);
+
+ // Verify that the first fetch is no longer running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is not invoked.
+ EXPECT_EQ(0, GetAndResetCallbackCount());
+
+ // Verify that the second fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Verify that the second fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that the callback is invoked with the retrieved data.
+ owner_task_runner_->RunUntilIdle();
+ EXPECT_EQ(1, GetAndResetCallbackCount());
+ EXPECT_EQ(1, callback_job_index_);
+ EXPECT_EQ(ExternalPolicyDataFetcher::SUCCESS, callback_result_);
+ ASSERT_TRUE(callback_data_);
+ EXPECT_EQ(kExternalPolicyDataPayload, *callback_data_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_updater.cc b/chromium/components/policy/core/common/cloud/external_policy_data_updater.cc
new file mode 100644
index 00000000000..c50a645031d
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_updater.cc
@@ -0,0 +1,426 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/external_policy_data_updater.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+#include "crypto/sha2.h"
+#include "net/base/backoff_entry.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+namespace {
+
+// Policies for exponential backoff of failed requests. There are 3 policies for
+// different classes of errors.
+
+// For temporary errors (HTTP 500, RST, etc).
+const net::BackoffEntry::Policy kRetrySoonPolicy = {
+ // Number of initial errors to ignore before starting to back off.
+ 0,
+
+ // Initial delay in ms: 15 seconds.
+ 1000 * 15,
+
+ // Factor by which the waiting time is multiplied.
+ 2,
+
+ // Fuzzing percentage; this spreads delays randomly between 80% and 100%
+ // of the calculated time.
+ 0.20,
+
+ // Maximum delay in ms: 12 hours.
+ 1000 * 60 * 60 * 12,
+
+ // When to discard an entry: never.
+ -1,
+
+ // |always_use_initial_delay|; false means that the initial delay is
+ // applied after the first error, and starts backing off from there.
+ false,
+};
+
+// For other errors (request failed, server errors).
+const net::BackoffEntry::Policy kRetryLaterPolicy = {
+ // Number of initial errors to ignore before starting to back off.
+ 0,
+
+ // Initial delay in ms: 1 minute.
+ 1000 * 60,
+
+ // Factor by which the waiting time is multiplied.
+ 2,
+
+ // Fuzzing percentage; this spreads delays randomly between 80% and 100%
+ // of the calculated time.
+ 0.20,
+
+ // Maximum delay in ms: 12 hours.
+ 1000 * 60 * 60 * 12,
+
+ // When to discard an entry: never.
+ -1,
+
+ // |always_use_initial_delay|; false means that the initial delay is
+ // applied after the first error, and starts backing off from there.
+ false,
+};
+
+// When the data fails validation (maybe because the policy URL and the data
+// served at that URL are out of sync). This essentially retries every 12 hours,
+// with some random jitter.
+const net::BackoffEntry::Policy kRetryMuchLaterPolicy = {
+ // Number of initial errors to ignore before starting to back off.
+ 0,
+
+ // Initial delay in ms: 12 hours.
+ 1000 * 60 * 60 * 12,
+
+ // Factor by which the waiting time is multiplied.
+ 2,
+
+ // Fuzzing percentage; this spreads delays randomly between 80% and 100%
+ // of the calculated time.
+ 0.20,
+
+ // Maximum delay in ms: 12 hours.
+ 1000 * 60 * 60 * 12,
+
+ // When to discard an entry: never.
+ -1,
+
+ // |always_use_initial_delay|; false means that the initial delay is
+ // applied after the first error, and starts backing off from there.
+ false,
+};
+
+// Maximum number of retries for requests that aren't likely to get a
+// different response (e.g. HTTP 4xx replies).
+const int kMaxLimitedRetries = 3;
+
+} // namespace
+
+class ExternalPolicyDataUpdater::FetchJob
+ : public base::SupportsWeakPtr<FetchJob> {
+ public:
+ FetchJob(ExternalPolicyDataUpdater* updater,
+ const std::string& key,
+ const ExternalPolicyDataUpdater::Request& request,
+ const ExternalPolicyDataUpdater::FetchSuccessCallback& callback);
+ FetchJob(const FetchJob&) = delete;
+ FetchJob& operator=(const FetchJob&) = delete;
+ virtual ~FetchJob();
+
+ const std::string& key() const;
+ const ExternalPolicyDataUpdater::Request& request() const;
+
+ void Start();
+
+ void OnFetchFinished(ExternalPolicyDataFetcher::Result result,
+ std::unique_ptr<std::string> data);
+
+ bool IsRescheduleWithDelayRunning() const {
+ return is_reschedule_with_delay_running_;
+ }
+
+ private:
+ void OnFailed(net::BackoffEntry* backoff_entry);
+ void Reschedule();
+
+ // Always valid as long as |this| is alive.
+ const raw_ptr<ExternalPolicyDataUpdater> updater_;
+
+ const std::string key_;
+ const ExternalPolicyDataUpdater::Request request_;
+ const ExternalPolicyDataUpdater::FetchSuccessCallback callback_;
+
+ // If the job is currently running, a corresponding |fetch_job_| exists in the
+ // |external_policy_data_fetcher_|. The job must eventually call back to the
+ // |updater_|'s OnJobSucceeded() or OnJobFailed() method in this case.
+ // If the job is currently not running, |fetch_job_| is NULL and no callbacks
+ // should be invoked.
+ raw_ptr<ExternalPolicyDataFetcher::Job> fetch_job_ = nullptr; // Not owned.
+
+ // Some errors should trigger a limited number of retries, even with backoff.
+ // This counts down the number of such retries to stop retrying once the limit
+ // is reached.
+ int limited_retries_remaining_ = kMaxLimitedRetries;
+
+ // Indicates that job rescheduling task is running. In this state the
+ // job is not fetching any data.
+ int is_reschedule_with_delay_running_ = false;
+
+ // Various delays to retry a failed download, depending on the failure reason.
+ net::BackoffEntry retry_soon_entry_{&kRetrySoonPolicy};
+ net::BackoffEntry retry_later_entry_{&kRetryLaterPolicy};
+ net::BackoffEntry retry_much_later_entry_{&kRetryMuchLaterPolicy};
+};
+
+ExternalPolicyDataUpdater::Request::Request() = default;
+
+ExternalPolicyDataUpdater::Request::Request(const std::string& url,
+ const std::string& hash,
+ int64_t max_size)
+ : url(url), hash(hash), max_size(max_size) {}
+
+bool ExternalPolicyDataUpdater::Request::operator==(
+ const Request& other) const {
+ return url == other.url && hash == other.hash && max_size == other.max_size;
+}
+
+ExternalPolicyDataUpdater::FetchJob::FetchJob(
+ ExternalPolicyDataUpdater* updater,
+ const std::string& key,
+ const ExternalPolicyDataUpdater::Request& request,
+ const ExternalPolicyDataUpdater::FetchSuccessCallback& callback)
+ : updater_(updater), key_(key), request_(request), callback_(callback) {}
+
+ExternalPolicyDataUpdater::FetchJob::~FetchJob() {
+ if (fetch_job_) {
+ // Cancel the fetch job in the |external_policy_data_fetcher_|.
+ updater_->external_policy_data_fetcher_->CancelJob(fetch_job_);
+ // Inform the |updater_| that the job was canceled.
+ updater_->OnJobFailed(this);
+ }
+}
+
+const std::string& ExternalPolicyDataUpdater::FetchJob::key() const {
+ return key_;
+}
+
+const ExternalPolicyDataUpdater::Request&
+ExternalPolicyDataUpdater::FetchJob::request() const {
+ return request_;
+}
+
+void ExternalPolicyDataUpdater::FetchJob::Start() {
+ DCHECK(!fetch_job_);
+ DVLOG(1) << "Fetching data for " << key_ << " from " << request_.url << " .";
+ // Start a fetch job in the |external_policy_data_fetcher_|. This will
+ // eventually call back to OnFetchFinished() with the result.
+ // Passing |this| as base::Unretained() is safe here because the |FetchJob|
+ // destructor cancels the fetcher job if one is still running.
+ fetch_job_ = updater_->external_policy_data_fetcher_->StartJob(
+ GURL(request_.url), request_.max_size,
+ base::BindOnce(&ExternalPolicyDataUpdater::FetchJob::OnFetchFinished,
+ base::Unretained(this)));
+}
+
+void ExternalPolicyDataUpdater::FetchJob::OnFetchFinished(
+ ExternalPolicyDataFetcher::Result result,
+ std::unique_ptr<std::string> data) {
+ // The fetch job in the |external_policy_data_fetcher_| is finished.
+ fetch_job_ = nullptr;
+
+ switch (result) {
+ case ExternalPolicyDataFetcher::CONNECTION_INTERRUPTED:
+ // The connection was interrupted. Try again soon.
+ DVLOG(1) << "Failed to fetch the data due to the interrupted connection.";
+ OnFailed(&retry_soon_entry_);
+ return;
+ case ExternalPolicyDataFetcher::NETWORK_ERROR:
+ // Another network error occurred. Try again later.
+ DVLOG(1) << "Failed to fetch the data due to a network error.";
+ OnFailed(&retry_later_entry_);
+ return;
+ case ExternalPolicyDataFetcher::SERVER_ERROR:
+ // Problem at the server. Try again soon.
+ LOG(WARNING) << "Failed to fetch the data due to a server HTTP error.";
+ OnFailed(&retry_soon_entry_);
+ return;
+ case ExternalPolicyDataFetcher::CLIENT_ERROR:
+ // Client error. This is unlikely to go away. Try again later, and give up
+ // retrying after 3 attempts.
+ LOG(WARNING) << "Failed to fetch the data due to a client HTTP error.";
+ OnFailed(limited_retries_remaining_ ? &retry_later_entry_ : nullptr);
+ if (limited_retries_remaining_)
+ --limited_retries_remaining_;
+ return;
+ case ExternalPolicyDataFetcher::HTTP_ERROR:
+ // Any other type of HTTP failure. Try again later.
+ LOG(WARNING) << "Failed to fetch the data due to an HTTP error.";
+ OnFailed(&retry_later_entry_);
+ return;
+ case ExternalPolicyDataFetcher::MAX_SIZE_EXCEEDED:
+ // Received |data| exceeds maximum allowed size. This may be because the
+ // data being served is stale. Try again much later.
+ LOG(WARNING) << "Failed to fetch the data due to the excessive size (max "
+ << request_.max_size << " bytes).";
+ OnFailed(&retry_much_later_entry_);
+ return;
+ case ExternalPolicyDataFetcher::SUCCESS:
+ break;
+ }
+
+ if (crypto::SHA256HashString(*data) != request_.hash) {
+ // Received |data| does not match expected hash. This may be because the
+ // data being served is stale. Try again much later.
+ LOG(ERROR) << "The fetched data doesn't match the expected hash.";
+ OnFailed(&retry_much_later_entry_);
+ return;
+ }
+
+ // If the callback rejects the data, try again much later.
+ if (!callback_.Run(*data)) {
+ OnFailed(&retry_much_later_entry_);
+ return;
+ }
+
+ // Signal success.
+ updater_->OnJobSucceeded(this);
+}
+
+void ExternalPolicyDataUpdater::FetchJob::OnFailed(net::BackoffEntry* entry) {
+ if (entry) {
+ entry->InformOfRequest(false);
+ const base::TimeDelta delay = entry->GetTimeUntilRelease();
+ DVLOG(1) << "Rescheduling the fetch in " << delay << ".";
+
+ is_reschedule_with_delay_running_ = true;
+ // This function may have been invoked because the job was obsoleted and is
+ // in the process of being deleted. If this is the case, the WeakPtr will
+ // become invalid and the delayed task will never run.
+ updater_->task_runner_->PostDelayedTask(
+ FROM_HERE, base::BindOnce(&FetchJob::Reschedule, AsWeakPtr()), delay);
+ }
+
+ updater_->OnJobFailed(this);
+}
+
+void ExternalPolicyDataUpdater::FetchJob::Reschedule() {
+ is_reschedule_with_delay_running_ = false;
+ updater_->ScheduleJob(this);
+}
+
+ExternalPolicyDataUpdater::ExternalPolicyDataUpdater(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ size_t max_parallel_fetches)
+ : task_runner_(task_runner),
+ external_policy_data_fetcher_(std::move(external_policy_data_fetcher)),
+ max_parallel_jobs_(max_parallel_fetches) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+}
+
+ExternalPolicyDataUpdater::~ExternalPolicyDataUpdater() {
+ // No RunsTasksInCurrentSequence() check to avoid unit tests failures.
+ // In unit tests the browser process instance is deleted only after test ends
+ // and test task scheduler is shutted down. Therefore we need to delete some
+ // components of BrowserPolicyConnector (ResourceCache and
+ // CloudExternalDataManagerBase::Backend) manually when task runner doesn't
+ // accept new tasks (DeleteSoon in this case). This leads to the situation
+ // when this destructor is called not on |task_runner|.
+
+ // Raise the flag to prevent jobs from being started during the destruction of
+ // |job_map_|.
+ shutting_down_ = true;
+}
+
+void ExternalPolicyDataUpdater::FetchExternalData(
+ const std::string& key,
+ const Request& request,
+ const FetchSuccessCallback& callback) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // Check whether a job exists for this |key| already.
+ FetchJob* job = job_map_[key].get();
+ if (job) {
+ // We should cancel the job which has been rescheduled for the future to
+ // avoid potentially long delays in data fetching.
+ if (!job->IsRescheduleWithDelayRunning()) {
+ // If the current |job| is handling the given |request| already, nothing
+ // needs to be done.
+ if (job->request() == request) {
+ DVLOG(2) << "Fetching job already scheduled for " << key
+ << " with the same parameters.";
+ return;
+ }
+ }
+
+ // Otherwise, the current |job| is obsolete. If the |job| is on the queue,
+ // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job|
+ // is currently running, it will call OnJobFailed() immediately.
+ DVLOG(2) << "Removing the old job for " << key << ".";
+ job_map_.erase(key);
+ }
+
+ // Start a new job to handle |request|.
+ job = new FetchJob(this, key, request, callback);
+ job_map_[key] = base::WrapUnique(job);
+ ScheduleJob(job);
+}
+
+void ExternalPolicyDataUpdater::CancelExternalDataFetch(
+ const std::string& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // If a |job| exists for this |key|, delete it. If the |job| is on the queue,
+ // its WeakPtr will be invalidated and skipped by StartNextJobs(). If |job| is
+ // currently running, it will call OnJobFailed() immediately.
+ auto job = job_map_.find(key);
+ if (job != job_map_.end()) {
+ DVLOG(1) << "Cancelling the job for " << key << ".";
+ job_map_.erase(job);
+ }
+}
+
+void ExternalPolicyDataUpdater::StartNextJobs() {
+ if (shutting_down_)
+ return;
+
+ while (running_jobs_ < max_parallel_jobs_ && !job_queue_.empty()) {
+ FetchJob* job = job_queue_.front().get();
+ job_queue_.pop();
+
+ // Some of the jobs may have been invalidated, and have to be skipped.
+ if (job) {
+ ++running_jobs_;
+ // A started job will always call OnJobSucceeded() or OnJobFailed().
+ job->Start();
+ }
+ }
+}
+
+void ExternalPolicyDataUpdater::ScheduleJob(FetchJob* job) {
+ DCHECK_EQ(job_map_[job->key()].get(), job);
+
+ job_queue_.push(job->AsWeakPtr());
+
+ StartNextJobs();
+}
+
+void ExternalPolicyDataUpdater::OnJobSucceeded(FetchJob* job) {
+ DCHECK(running_jobs_);
+ DCHECK_EQ(job_map_[job->key()].get(), job);
+
+ --running_jobs_;
+ job_map_.erase(job->key());
+
+ StartNextJobs();
+}
+
+void ExternalPolicyDataUpdater::OnJobFailed(FetchJob* job) {
+ DCHECK(running_jobs_);
+ --running_jobs_;
+
+ // Don't touch |job_map_|; deletion of |FetchJob|s causes a call to this
+ // method, so |job_map_| is possibly in an inconsistent state.
+
+ // The job is not deleted when it fails because a retry attempt may have been
+ // scheduled.
+ StartNextJobs();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_updater.h b/chromium/components/policy/core/common/cloud/external_policy_data_updater.h
new file mode 100644
index 00000000000..97aa420ea79
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_updater.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_UPDATER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_UPDATER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/containers/queue.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class ExternalPolicyDataFetcher;
+
+// This class downloads external policy data. Given a |Request|, data is fetched
+// from the |url|, verified to not exceed |max_size| and to match the expected
+// |hash| and then handed to a callback that can do further verification before
+// finally deciding whether the fetched data is valid.
+// If a fetch is not successful or retrieves invalid data, retries are scheduled
+// with exponential backoff.
+// The actual fetching is handled by an ExternalPolicyDataFetcher, allowing this
+// class to run on a background thread where network I/O is not possible.
+class POLICY_EXPORT ExternalPolicyDataUpdater {
+ public:
+ struct POLICY_EXPORT Request {
+ public:
+ Request();
+ Request(const std::string& url, const std::string& hash, int64_t max_size);
+
+ bool operator==(const Request& other) const;
+
+ std::string url;
+ std::string hash;
+ int64_t max_size;
+ };
+
+ // This callback is invoked when a fetch has successfully retrieved |data|
+ // that does not exceed |max_size| and matches the expected |hash|. The
+ // callback can do further verification to decide whether the fetched data is
+ // valid.
+ // If the callback returns |true|, the data is accepted and the |Request| is
+ // finished. If the callback returns |false|, the data is rejected and the
+ // fetch is retried after a long backoff. Note that in this case, the callback
+ // may be invoked multiple times as the fetch is repeated. Make sure to not
+ // bind base::Passed() scoped_ptrs to the callback in such cases as these
+ // become invalid after a callback has been run once. base::Owned() can be
+ // used in all cases.
+ typedef base::RepeatingCallback<bool(const std::string&)>
+ FetchSuccessCallback;
+
+ // This class runs on the background thread represented by |task_runner|,
+ // which must support file I/O. All network I/O is forwarded to a different
+ // thread by the |external_policy_data_fetcher|.
+ ExternalPolicyDataUpdater(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
+ size_t max_parallel_fetches);
+ ExternalPolicyDataUpdater(const ExternalPolicyDataUpdater&) = delete;
+ ExternalPolicyDataUpdater& operator=(const ExternalPolicyDataUpdater&) =
+ delete;
+ ~ExternalPolicyDataUpdater();
+
+ // Fetches the external data specified in the |request|. The |key| is an
+ // opaque identifier. If another request for the same |key| is still pending,
+ // it will be canceled and replaced with the new |request|. The callback will
+ // be invoked after a successful fetch. See the documentation of
+ // |FetchSuccessCallback| for more details.
+ void FetchExternalData(const std::string& key,
+ const Request& request,
+ const FetchSuccessCallback& callback);
+
+ // Cancels the pending request identified by |key|. If no such request is
+ // pending, does nothing.
+ void CancelExternalDataFetch(const std::string& key);
+
+ private:
+ class FetchJob;
+
+ // Starts jobs from the |job_queue_| until |max_parallel_jobs_| are running or
+ // the queue is depleted.
+ void StartNextJobs();
+
+ // Appends |job| to the |job_queue_| and starts it immediately if less than
+ // |max_parallel_jobs_| are running.
+ void ScheduleJob(FetchJob* job);
+
+ // Callback for jobs that succeeded.
+ void OnJobSucceeded(FetchJob* job);
+
+ // Callback for jobs that failed.
+ void OnJobFailed(FetchJob* job);
+
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ const std::unique_ptr<ExternalPolicyDataFetcher>
+ external_policy_data_fetcher_;
+
+ // The maximum number of jobs to run in parallel.
+ const size_t max_parallel_jobs_;
+
+ // The number of jobs currently running.
+ size_t running_jobs_ = 0;
+
+ // Queue of jobs waiting to be run. Jobs are taken off the queue and started
+ // by StartNextJobs().
+ base::queue<base::WeakPtr<FetchJob>> job_queue_;
+
+ // Map that owns all existing jobs, regardless of whether they are currently
+ // queued, running or waiting for a retry.
+ std::map<std::string, std::unique_ptr<FetchJob>> job_map_;
+
+ // |true| once the destructor starts. Prevents jobs from being started during
+ // shutdown.
+ bool shutting_down_ = false;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_EXTERNAL_POLICY_DATA_UPDATER_H_
diff --git a/chromium/components/policy/core/common/cloud/external_policy_data_updater_unittest.cc b/chromium/components/policy/core/common/cloud/external_policy_data_updater_unittest.cc
new file mode 100644
index 00000000000..3cdf3e9a6e8
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/external_policy_data_updater_unittest.cc
@@ -0,0 +1,863 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/external_policy_data_updater.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_pending_task.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using testing::Mock;
+using testing::Return;
+
+namespace policy {
+
+namespace {
+
+const char* kExternalPolicyDataKeys[] = {"external_policy_data_1",
+ "external_policy_data_2",
+ "external_policy_data_3"};
+const char* kExternalPolicyDataURLs[] = {"http://example.com/data_1",
+ "http://example.com/data_2",
+ "http://example.com/data_3"};
+
+const int64_t kExternalPolicyDataMaxSize = 20;
+
+const char* kExternalPolicyDataPayload = "External policy data";
+const char* kExternalPolicyDataOverflowPayload = "External policy data+++++++";
+
+class MockFetchSuccessCallbackListener {
+ public:
+ MOCK_METHOD2(OnFetchSuccess, bool(const std::string&, const std::string&));
+
+ ExternalPolicyDataUpdater::FetchSuccessCallback CreateCallback(
+ const std::string& key);
+};
+
+ExternalPolicyDataUpdater::FetchSuccessCallback
+ MockFetchSuccessCallbackListener::CreateCallback(const std::string& key) {
+ return base::BindRepeating(&MockFetchSuccessCallbackListener::OnFetchSuccess,
+ base::Unretained(this), key);
+}
+
+} // namespace
+
+class ExternalPolicyDataUpdaterTest : public testing::Test {
+ protected:
+ void SetUp() override;
+
+ void CreateUpdater(size_t max_parallel_fetches);
+ ExternalPolicyDataUpdater::Request CreateRequest(
+ const std::string& url) const;
+ void RequestExternalDataFetchAndWait(int index);
+ void RequestExternalDataFetchAndWait(int key_index, int url_index);
+ void RequestExternalDataFetch(int index);
+ void RequestExternalDataFetch(int key_index, int url_index);
+ base::test::TaskEnvironment task_environment_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ MockFetchSuccessCallbackListener callback_listener_;
+ scoped_refptr<base::TestSimpleTaskRunner> backend_task_runner_;
+ std::unique_ptr<ExternalPolicyDataUpdater> updater_;
+};
+
+void ExternalPolicyDataUpdaterTest::SetUp() {
+ backend_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+}
+
+void ExternalPolicyDataUpdaterTest::CreateUpdater(size_t max_parallel_fetches) {
+ auto url_loader_factory =
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_);
+ updater_ = std::make_unique<ExternalPolicyDataUpdater>(
+ backend_task_runner_,
+ std::make_unique<ExternalPolicyDataFetcher>(std::move(url_loader_factory),
+ backend_task_runner_),
+ max_parallel_fetches);
+}
+
+void ExternalPolicyDataUpdaterTest::RequestExternalDataFetchAndWait(int index) {
+ RequestExternalDataFetchAndWait(index, index);
+}
+
+void ExternalPolicyDataUpdaterTest::RequestExternalDataFetchAndWait(
+ int key_index,
+ int url_index) {
+ RequestExternalDataFetch(key_index, url_index);
+
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+}
+
+void ExternalPolicyDataUpdaterTest::RequestExternalDataFetch(int index) {
+ RequestExternalDataFetch(index, index);
+}
+
+void ExternalPolicyDataUpdaterTest::RequestExternalDataFetch(int key_index,
+ int url_index) {
+ updater_->FetchExternalData(
+ kExternalPolicyDataKeys[key_index],
+ CreateRequest(kExternalPolicyDataURLs[url_index]),
+ callback_listener_.CreateCallback(kExternalPolicyDataKeys[key_index]));
+}
+
+ExternalPolicyDataUpdater::Request
+ ExternalPolicyDataUpdaterTest::CreateRequest(const std::string& url) const {
+ return ExternalPolicyDataUpdater::Request(
+ url,
+ crypto::SHA256HashString(kExternalPolicyDataPayload),
+ kExternalPolicyDataMaxSize);
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, FetchSuccess) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the first fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the second fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, PayloadSizeExceedsLimit) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch with more data than allowed.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataOverflowPayload);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that first fetch is no longer running and the second fetch has been
+ // started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that a retry has been scheduled for the first fetch.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, FetchFailure) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the first fetch fail due to an interrupted connection.
+ test_url_loader_factory_.AddResponse(
+ GURL(kExternalPolicyDataURLs[0]), network::mojom::URLResponseHead::New(),
+ std::string(),
+ network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the first fetch is no longer running and the second fetch has
+ // been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that a retry has been scheduled for the first fetch.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ServerFailure) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the first fetch fail with a server error.
+ test_url_loader_factory_.AddResponse(
+ GURL(kExternalPolicyDataURLs[0]), network::mojom::URLResponseHead::New(),
+ std::string(),
+ network::URLLoaderCompletionStatus(net::HTTP_INTERNAL_SERVER_ERROR));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the first fetch is no longer running and the second fetch has
+ // been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that a retry has been scheduled for the first fetch.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, RetryLimit) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ // Verify that client failures cause the fetch to be retried three times.
+ for (int i = 0; i < 3; ++i) {
+ // Verify that the fetch has been (re)started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with a client error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(), net::HTTP_BAD_REQUEST);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ test_url_loader_factory_.ClearResponses();
+
+ // Verify that the fetch is no longer running.
+ ASSERT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that a retry has been scheduled.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Fast-forward time to the scheduled retry.
+ backend_task_runner_->RunPendingTasks();
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+ }
+
+ // Verify that the fetch has been restarted.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail once more.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(), net::HTTP_BAD_REQUEST);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that no further retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, RetryWithBackoff) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ base::TimeDelta expected_delay = base::Seconds(15);
+ const base::TimeDelta delay_cap = base::Hours(12);
+
+ // The backoff delay is capped at 12 hours, which is reached after 12 retries:
+ // 15 * 2^12 == 61440 > 43200 == 12 * 60 * 60
+ for (int i = 0; i < 20; ++i) {
+ // Verify that the fetch has been (re)started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with a server error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(),
+ net::HTTP_INTERNAL_SERVER_ERROR);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ test_url_loader_factory_.ClearResponses();
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that a retry has been scheduled.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Verify that the retry delay has been doubled, with random jitter from 80%
+ // to 100%.
+ base::TimeDelta delay = backend_task_runner_->NextPendingTaskDelay();
+ EXPECT_GT(delay,
+ base::Milliseconds(0.799 * expected_delay.InMilliseconds()));
+ EXPECT_LE(delay, expected_delay);
+
+ if (i < 12) {
+ // The delay cap has not been reached yet.
+ EXPECT_LT(expected_delay, delay_cap);
+ expected_delay *= 2;
+
+ if (i == 11) {
+ // The last doubling reached the cap.
+ EXPECT_GT(expected_delay, delay_cap);
+ expected_delay = delay_cap;
+ }
+ }
+
+ // Fast-forward time to the scheduled retry.
+ backend_task_runner_->RunPendingTasks();
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+ }
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, CancelDelayedTsaks) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the fetch fail with a server error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(),
+ net::HTTP_INTERNAL_SERVER_ERROR);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ test_url_loader_factory_.ClearResponses();
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that a retry has been scheduled.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Make second fetch request.
+ RequestExternalDataFetch(0);
+
+ // Verify that the fetch has been started and retry is still scheduled.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Fast-forward time to the scheduled retry.
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that retry has been canceled and no additional requests started.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+
+ // Complete the fetch successfully.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+ EXPECT_CALL(callback_listener_, OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that all tasks are completed.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, HashInvalid) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the first fetch retrieve data whose hash does not match the expected
+ // value.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ "Invalid data");
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the first fetch is no longer running and the second fetch has
+ // been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that a retry has been scheduled for the first fetch.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, DataRejectedByCallback) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Reject the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(false));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that a retry has been scheduled.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Fast-forward time to the scheduled retry.
+ test_url_loader_factory_.ClearResponses();
+ backend_task_runner_->RunPendingTasks();
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+
+ // Verify that the fetch has been restarted.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Complete the fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked this time.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, URLChanged) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make another fetch request with the same key but an updated URL.
+ RequestExternalDataFetchAndWait(0, 1);
+
+ // Verify that the original fetch is no longer running and a new fetch has
+ // been started with the updated URL.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, JobInvalidated) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that only the first fetch has been started.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make another fetch request with the same key as the second request but an
+ // updated URL.
+ RequestExternalDataFetchAndWait(1, 2);
+
+ // Verify that the first fetch is still the only one running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Make the first fetch fail with a server error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(),
+ net::HTTP_INTERNAL_SERVER_ERROR);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the second fetch was invalidated and the third fetch has been
+ // started instead.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, FetchCanceled) {
+ // Create an updater that runs one fetch at a time.
+ CreateUpdater(1);
+
+ // Make a fetch request.
+ RequestExternalDataFetchAndWait(0);
+
+ // Verify that the fetch has been started.
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+
+ // Cancel the fetch request.
+ updater_->CancelExternalDataFetch(kExternalPolicyDataKeys[0]);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ParallelJobs) {
+ // Create an updater that runs up to two fetches in parallel.
+ CreateUpdater(2);
+
+ // Make three fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+ RequestExternalDataFetchAndWait(2);
+
+ // Verify that only the first and second fetches have been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the first fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the second fetch is still running and the third has started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[1],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that only the third fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the third fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[2],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[2],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the third fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ParallelJobsFinishingOutOfOrder) {
+ // Create an updater that runs up to two fetches in parallel.
+ CreateUpdater(2);
+
+ // Make three fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+ RequestExternalDataFetchAndWait(2);
+
+ // Verify that only the first and second fetches have been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[1],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the first fetch is still running and the third has started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the first fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[0],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that only the third fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the third fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[2],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[2],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the third fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ParallelJobsWithRetry) {
+ // Create an updater that runs up to two fetches in parallel.
+ CreateUpdater(2);
+
+ // Make three fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+ RequestExternalDataFetchAndWait(2);
+
+ // Verify that only the first and second fetches have been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Make the first fetch fail with a client error.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[0],
+ std::string(), net::HTTP_BAD_REQUEST);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ test_url_loader_factory_.ClearResponses();
+
+ // Verify that the first fetch is no longer running and the third fetch has
+ // been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Verify that a retry has been scheduled for the first fetch.
+ EXPECT_EQ(1u, backend_task_runner_->NumPendingTasks());
+
+ // Fast-forward time to the scheduled retry.
+ backend_task_runner_->RunPendingTasks();
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+
+ // Verify that the first fetch has not been restarted yet.
+ ASSERT_EQ(2, test_url_loader_factory_.NumPending());
+
+ // Complete the third fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[2],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[2],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the second fetch is still running and the first fetch has been
+ // restarted.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Verify that no further retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ParallelJobsWithCancel) {
+ // Create an updater that runs up to two fetches in parallel.
+ CreateUpdater(2);
+
+ // Make three fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+ RequestExternalDataFetchAndWait(2);
+
+ // Verify that only the first and second fetches have been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Cancel the fetch request.
+ updater_->CancelExternalDataFetch(kExternalPolicyDataKeys[0]);
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+
+ // Verify that the second fetch is still running and the third request has
+ // been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the second fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[1],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[1],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that only the third fetch is still running.
+ EXPECT_EQ(1, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+
+ // Complete the third fetch.
+ test_url_loader_factory_.AddResponse(kExternalPolicyDataURLs[2],
+ kExternalPolicyDataPayload);
+
+ // Accept the data when the callback is invoked.
+ EXPECT_CALL(callback_listener_,
+ OnFetchSuccess(kExternalPolicyDataKeys[2],
+ kExternalPolicyDataPayload))
+ .Times(1)
+ .WillOnce(Return(true));
+ base::RunLoop().RunUntilIdle();
+ backend_task_runner_->RunPendingTasks();
+ Mock::VerifyAndClearExpectations(&callback_listener_);
+
+ // Verify that the third fetch is no longer running.
+ EXPECT_EQ(0, test_url_loader_factory_.NumPending());
+
+ // Verify that no retries have been scheduled.
+ EXPECT_FALSE(backend_task_runner_->HasPendingTask());
+}
+
+TEST_F(ExternalPolicyDataUpdaterTest, ParallelJobsWithInvalidatedJob) {
+ // Create an updater that runs up to two fetches in parallel.
+ CreateUpdater(2);
+
+ // Make two fetch requests.
+ RequestExternalDataFetchAndWait(0);
+ RequestExternalDataFetchAndWait(1);
+
+ // Verify that the first and second fetches has been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[1]));
+
+ // Make another fetch request with the same key as the second request but an
+ // updated URL.
+ RequestExternalDataFetchAndWait(1, 2);
+
+ // Verify that the first fetch is still running, the second has been canceled
+ // and a third fetch has been started.
+ EXPECT_EQ(2, test_url_loader_factory_.NumPending());
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[0]));
+ EXPECT_TRUE(test_url_loader_factory_.IsPending(kExternalPolicyDataURLs[2]));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
new file mode 100644
index 00000000000..50aeb7ed779
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
@@ -0,0 +1,128 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
+
+#include <string>
+#include <utility>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace policy {
+namespace {
+
+const base::FilePath::CharType kComponentPolicyCache[] =
+ FILE_PATH_LITERAL("Machine Level User Cloud Component Policy");
+
+} // namespace
+
+MachineLevelUserCloudPolicyManager::MachineLevelUserCloudPolicyManager(
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store,
+ std::unique_ptr<CloudExternalDataManager> external_data_manager,
+ const base::FilePath& policy_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter)
+ : CloudPolicyManager(GetMachineLevelUserCloudPolicyTypeForCurrentOS(),
+ std::string(),
+ store.get(),
+ task_runner,
+ std::move(network_connection_tracker_getter)),
+ store_(std::move(store)),
+ external_data_manager_(std::move(external_data_manager)),
+ policy_dir_(policy_dir) {}
+
+MachineLevelUserCloudPolicyManager::~MachineLevelUserCloudPolicyManager() {}
+
+void MachineLevelUserCloudPolicyManager::Connect(
+ PrefService* local_state,
+ std::unique_ptr<CloudPolicyClient> client) {
+ CHECK(!core()->client());
+
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
+ client->GetURLLoaderFactory();
+
+ CreateComponentCloudPolicyService(
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+ policy_dir_.Append(kComponentPolicyCache), client.get(),
+ schema_registry());
+ core()->Connect(std::move(client));
+ core()->StartRefreshScheduler();
+ core()->TrackRefreshDelayPref(local_state,
+ policy_prefs::kUserPolicyRefreshRate);
+ if (external_data_manager_)
+ external_data_manager_->Connect(std::move(url_loader_factory));
+}
+
+void MachineLevelUserCloudPolicyManager::AddClientObserver(
+ CloudPolicyClient::Observer* observer) {
+ if (client())
+ client()->AddObserver(observer);
+}
+
+void MachineLevelUserCloudPolicyManager::RemoveClientObserver(
+ CloudPolicyClient::Observer* observer) {
+ if (client())
+ client()->RemoveObserver(observer);
+}
+
+void MachineLevelUserCloudPolicyManager::DisconnectAndRemovePolicy() {
+ if (external_data_manager_)
+ external_data_manager_->Disconnect();
+
+ core()->Disconnect();
+
+ // store_->Clear() will publish the updated, empty policy. The component
+ // policy service must be cleared before OnStoreLoaded() is issued, so that
+ // component policies are also empty at CheckAndPublishPolicy().
+ ClearAndDestroyComponentCloudPolicyService();
+
+ // When the |store_| is cleared, it informs the |external_data_manager_| that
+ // all external data references have been removed, causing the
+ // |external_data_manager_| to clear its cache as well.
+ store_->Clear();
+}
+
+void MachineLevelUserCloudPolicyManager::Init(SchemaRegistry* registry) {
+ DVLOG(1) << "Machine level cloud policy manager initialized";
+ // Call to grand-parent's Init() instead of parent's is intentional.
+ // NOLINTNEXTLINE(bugprone-parent-virtual-call)
+ ConfigurationPolicyProvider::Init(registry);
+
+ store()->AddObserver(this);
+
+ // Load the policy from disk synchronously once the manager is initalized
+ // during Chrome launch if the cache and the global dm token exist.
+ store()->LoadImmediately();
+}
+
+void MachineLevelUserCloudPolicyManager::Shutdown() {
+ if (external_data_manager_)
+ external_data_manager_->Disconnect();
+ CloudPolicyManager::Shutdown();
+}
+
+void MachineLevelUserCloudPolicyManager::OnStoreLoaded(
+ CloudPolicyStore* cloud_policy_store) {
+ DCHECK_EQ(store(), cloud_policy_store);
+ CloudPolicyManager::OnStoreLoaded(cloud_policy_store);
+
+ // It's possible for |client()| to be null during startup if the store is
+ // loaded before Connect is called. In this case, don't do anything and wait
+ // for the browser to do its startup policy refresh.
+ if (client() && store()->policy() &&
+ store()->policy()->has_service_account_identity()) {
+ std::string service_account_id =
+ store()->policy()->service_account_identity();
+ client()->UpdateServiceAccount(service_account_id);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h
new file mode 100644
index 00000000000..63e59a321f2
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h
@@ -0,0 +1,68 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
+
+#include <memory>
+
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+class PrefService;
+
+namespace policy {
+
+class MachineLevelUserCloudPolicyStore;
+
+// Implements a cloud policy manager that initializes the machine level user
+// cloud policy.
+class POLICY_EXPORT MachineLevelUserCloudPolicyManager
+ : public CloudPolicyManager {
+ public:
+ MachineLevelUserCloudPolicyManager(
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store,
+ std::unique_ptr<CloudExternalDataManager> external_data_manager,
+ const base::FilePath& policy_dir,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+ MachineLevelUserCloudPolicyManager(
+ const MachineLevelUserCloudPolicyManager&) = delete;
+ MachineLevelUserCloudPolicyManager& operator=(
+ const MachineLevelUserCloudPolicyManager&) = delete;
+ ~MachineLevelUserCloudPolicyManager() override;
+
+ // Initializes the cloud connection. |local_state| must stay valid until this
+ // object is deleted or DisconnectAndRemovePolicy gets called.
+ void Connect(PrefService* local_state,
+ std::unique_ptr<CloudPolicyClient> client);
+
+ // Add or remove |observer| to/from the CloudPolicyClient embedded in |core_|.
+ void AddClientObserver(CloudPolicyClient::Observer* observer);
+ void RemoveClientObserver(CloudPolicyClient::Observer* observer);
+
+ MachineLevelUserCloudPolicyStore* store() { return store_.get(); }
+
+ // Shuts down the MachineLevelUserCloudPolicyManager (removes and stops
+ // refreshing the cached cloud policy).
+ void DisconnectAndRemovePolicy();
+
+ // ConfigurationPolicyProvider:
+ void Init(SchemaRegistry* registry) override;
+ void Shutdown() override;
+
+ private:
+ // CloudPolicyStore::Observer:
+ void OnStoreLoaded(CloudPolicyStore* cloud_policy_store) override;
+
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store_;
+ std::unique_ptr<CloudExternalDataManager> external_data_manager_;
+
+ const base::FilePath policy_dir_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc
new file mode 100644
index 00000000000..274444a57ee
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
+
+#include <string>
+#include <utility>
+
+#include "base/memory/raw_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/dm_token.h"
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class MockMachineLevelUserCloudPolicyStore
+ : public MachineLevelUserCloudPolicyStore {
+ public:
+ MockMachineLevelUserCloudPolicyStore()
+ : MachineLevelUserCloudPolicyStore(
+ DMToken::CreateEmptyTokenForTesting(),
+ std::string(),
+ base::FilePath(),
+ base::FilePath(),
+ base::FilePath(),
+ base::FilePath(),
+ scoped_refptr<base::SequencedTaskRunner>()) {}
+
+ MOCK_METHOD0(LoadImmediately, void(void));
+};
+
+class MachineLevelUserCloudPolicyManagerTest : public ::testing::Test {
+ public:
+ MachineLevelUserCloudPolicyManagerTest() = default;
+ MachineLevelUserCloudPolicyManagerTest(
+ const MachineLevelUserCloudPolicyManagerTest&) = delete;
+ MachineLevelUserCloudPolicyManagerTest& operator=(
+ const MachineLevelUserCloudPolicyManagerTest&) = delete;
+ ~MachineLevelUserCloudPolicyManagerTest() override { manager_->Shutdown(); }
+
+ void SetUp() override {
+ auto store = std::make_unique<MockMachineLevelUserCloudPolicyStore>();
+ store_ = store.get();
+ manager_ = std::make_unique<MachineLevelUserCloudPolicyManager>(
+ std::move(store), std::unique_ptr<CloudExternalDataManager>(),
+ base::FilePath(), scoped_refptr<base::SequencedTaskRunner>(),
+ network::TestNetworkConnectionTracker::CreateGetter());
+ }
+
+ SchemaRegistry schema_registry_;
+ raw_ptr<MockMachineLevelUserCloudPolicyStore> store_ = nullptr;
+ std::unique_ptr<MachineLevelUserCloudPolicyManager> manager_;
+};
+
+TEST_F(MachineLevelUserCloudPolicyManagerTest, InitManager) {
+ EXPECT_CALL(*store_, LoadImmediately());
+ manager_->Init(&schema_registry_);
+ ::testing::Mock::VerifyAndClearExpectations(store_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc
new file mode 100644
index 00000000000..80c81664ae6
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc
@@ -0,0 +1,249 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/dm_token.h"
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+namespace {
+
+const base::FilePath::CharType kPolicyCache[] =
+ FILE_PATH_LITERAL("Machine Level User Cloud Policy");
+const base::FilePath::CharType kKeyCache[] =
+ FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key");
+constexpr base::FilePath::StringPieceType kExternalPolicyCache =
+ FILE_PATH_LITERAL("PolicyFetchResponse");
+constexpr base::FilePath::StringPieceType kExternalPolicyInfo =
+ FILE_PATH_LITERAL("CachedPolicyInfo");
+} // namespace
+
+MachineLevelUserCloudPolicyStore::MachineLevelUserCloudPolicyStore(
+ const DMToken& machine_dm_token,
+ const std::string& machine_client_id,
+ const base::FilePath& external_policy_path,
+ const base::FilePath& external_policy_info_path,
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+ : DesktopCloudPolicyStore(
+ policy_path,
+ key_path,
+ base::BindRepeating(
+ &MachineLevelUserCloudPolicyStore::MaybeUseExternalCachedPolicies,
+ external_policy_path,
+ external_policy_info_path),
+ background_task_runner,
+ PolicyScope::POLICY_SCOPE_MACHINE),
+ machine_dm_token_(machine_dm_token),
+ machine_client_id_(machine_client_id) {}
+
+MachineLevelUserCloudPolicyStore::~MachineLevelUserCloudPolicyStore() {}
+
+// static
+std::unique_ptr<MachineLevelUserCloudPolicyStore>
+MachineLevelUserCloudPolicyStore::Create(
+ const DMToken& machine_dm_token,
+ const std::string& machine_client_id,
+ const base::FilePath& external_policy_dir,
+ const base::FilePath& policy_dir,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
+ base::FilePath policy_cache_file = policy_dir.Append(kPolicyCache);
+ base::FilePath key_cache_file = policy_dir.Append(kKeyCache);
+ base::FilePath external_policy_path;
+ base::FilePath external_policy_info_path;
+ if (!external_policy_dir.empty()) {
+ external_policy_path =
+ external_policy_dir
+ .AppendASCII(policy::dm_protocol::
+ kChromeMachineLevelUserCloudPolicyTypeBase64)
+ .Append(kExternalPolicyCache);
+ external_policy_info_path = external_policy_dir.Append(kExternalPolicyInfo);
+ }
+ return std::make_unique<MachineLevelUserCloudPolicyStore>(
+ machine_dm_token, machine_client_id, external_policy_path,
+ external_policy_info_path, policy_cache_file, key_cache_file,
+ background_task_runner);
+}
+
+bool IsResultKeyEqual(const PolicyLoadResult& default_result,
+ const PolicyLoadResult& external_result) {
+ return default_result.key.signing_key() ==
+ external_result.key.signing_key() &&
+ default_result.key.signing_key_signature() ==
+ external_result.key.signing_key_signature() &&
+ default_result.key.verification_key() ==
+ external_result.key.verification_key();
+}
+
+void MachineLevelUserCloudPolicyStore::LoadImmediately() {
+ // There is no global dm token, stop loading the policy cache in order to
+ // avoid an unnecessary disk access. Policies will be fetched after enrollment
+ // succeeded.
+ if (!machine_dm_token_.is_valid()) {
+ VLOG(1) << "LoadImmediately ignored, no DM token present.";
+#if BUILDFLAG(IS_ANDROID)
+ // On Android, some dependencies (e.g. FirstRunActivity) are blocked until
+ // the PolicyService is initialized, which waits on all policy providers to
+ // indicate that policies are available.
+ //
+ // When cloud enrollment is not mandatory, machine-level cloud policies are
+ // loaded asynchronously and will be applied once they are fetched from the
+ // server. To avoid blocking those dependencies on Android, notify that the
+ // PolicyService initialization doesn't need to wait on cloud policies by
+ // sending out an empty policy set.
+ //
+ // The call to |PolicyLoaded| is exactly the same that would happen if this
+ // disk access optimization was not implemented.
+ PolicyLoadResult result;
+ result.status = policy::LOAD_RESULT_NO_POLICY_FILE;
+ PolicyLoaded(/*validate_in_background=*/false, result);
+#endif // BUILDFLAG(IS_ANDROID)
+ return;
+ }
+ VLOG(1) << "Load policy cache Immediately.";
+ DesktopCloudPolicyStore::LoadImmediately();
+}
+
+void MachineLevelUserCloudPolicyStore::Load() {
+ // There is no global dm token, stop loading the policy cache. The policy will
+ // be fetched in the end of enrollment process.
+ if (!machine_dm_token_.is_valid()) {
+ VLOG(1) << "Load ignored, no DM token present.";
+ return;
+ }
+ VLOG(1) << "Load policy cache.";
+ DesktopCloudPolicyStore::Load();
+}
+
+// static
+PolicyLoadResult
+MachineLevelUserCloudPolicyStore::MaybeUseExternalCachedPolicies(
+ const base::FilePath& policy_cache_path,
+ const base::FilePath& policy_info_path,
+ PolicyLoadResult default_cached_policy_load_result) {
+ PolicyLoadResult external_policy_cache_load_result =
+ LoadExternalCachedPolicies(policy_cache_path, policy_info_path);
+ if (external_policy_cache_load_result.status != policy::LOAD_RESULT_SUCCESS)
+ return default_cached_policy_load_result;
+
+ // If default key is missing or not matches the external one, enable key
+ // rotation mode to re-fetch public key again.
+ if (!IsResultKeyEqual(default_cached_policy_load_result,
+ external_policy_cache_load_result)) {
+ external_policy_cache_load_result.doing_key_rotation = true;
+ }
+
+ if (default_cached_policy_load_result.status != policy::LOAD_RESULT_SUCCESS)
+ return external_policy_cache_load_result;
+
+ enterprise_management::PolicyData default_data;
+ enterprise_management::PolicyData external_data;
+ if (default_data.ParseFromString(
+ default_cached_policy_load_result.policy.policy_data()) &&
+ external_data.ParseFromString(
+ external_policy_cache_load_result.policy.policy_data()) &&
+ external_data.timestamp() > default_data.timestamp()) {
+ return external_policy_cache_load_result;
+ }
+ return default_cached_policy_load_result;
+}
+
+// static
+PolicyLoadResult MachineLevelUserCloudPolicyStore::LoadExternalCachedPolicies(
+ const base::FilePath& policy_cache_path,
+ const base::FilePath& policy_info_path) {
+ // Loads cached cloud policies by an external provider.
+ PolicyLoadResult policy_cache_load_result =
+ DesktopCloudPolicyStore::LoadPolicyFromDisk(policy_cache_path,
+ base::FilePath());
+ PolicyLoadResult policy_info_load_result =
+ DesktopCloudPolicyStore::LoadPolicyFromDisk(policy_info_path,
+ base::FilePath());
+
+ // External policy source doesn't provide full components policies data hence
+ // browser will rely on the local cache which requires public key to verify
+ // them.
+ // Load the key and signature of the key from Extennal policy info file and
+ // use it to verify all Chrome and components policies. The browser will
+ // redownload the policeis in case of validation failure.
+ VLOG(1) << (policy_info_load_result.policy.has_new_public_key()
+ ? "External policy has public key."
+ : "External policy doesn't have public key.");
+ policy_cache_load_result.key.set_signing_key(
+ policy_info_load_result.policy.new_public_key());
+ policy_cache_load_result.key.set_signing_key_signature(
+ policy_info_load_result.policy
+ .new_public_key_verification_signature_deprecated());
+ policy_cache_load_result.key.set_verification_key(GetPolicyVerificationKey());
+
+ return policy_cache_load_result;
+}
+
+std::unique_ptr<UserCloudPolicyValidator>
+MachineLevelUserCloudPolicyStore::CreateValidator(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ CloudPolicyValidatorBase::ValidateTimestampOption option) {
+ auto validator = std::make_unique<UserCloudPolicyValidator>(
+ std::move(policy_fetch_response), background_task_runner());
+ validator->ValidatePolicyType(
+ GetMachineLevelUserCloudPolicyTypeForCurrentOS());
+ validator->ValidateDMToken(machine_dm_token_.value(),
+ CloudPolicyValidatorBase::DM_TOKEN_REQUIRED);
+ validator->ValidateDeviceId(machine_client_id_,
+ CloudPolicyValidatorBase::DEVICE_ID_REQUIRED);
+ if (has_policy()) {
+ validator->ValidateTimestamp(
+ base::Time::FromJavaTime(policy()->timestamp()), option);
+ }
+ validator->ValidatePayload();
+ return validator;
+}
+
+void MachineLevelUserCloudPolicyStore::SetupRegistration(
+ const DMToken& machine_dm_token,
+ const std::string& machine_client_id) {
+ machine_dm_token_ = machine_dm_token;
+ machine_client_id_ = machine_client_id;
+}
+
+void MachineLevelUserCloudPolicyStore::InitWithoutToken() {
+ NotifyStoreError();
+}
+
+void MachineLevelUserCloudPolicyStore::Validate(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ std::unique_ptr<enterprise_management::PolicySigningKey> key,
+ bool validate_in_background,
+ UserCloudPolicyValidator::CompletionCallback callback) {
+ std::unique_ptr<UserCloudPolicyValidator> validator = CreateValidator(
+ std::move(policy), CloudPolicyValidatorBase::TIMESTAMP_VALIDATED);
+
+ // Policies cached by the external provider do not require key and signature
+ // validation since they are stored in a secure location.
+ if (key)
+ ValidateKeyAndSignature(validator.get(), key.get(), std::string());
+
+ if (validate_in_background) {
+ UserCloudPolicyValidator::StartValidation(std::move(validator),
+ std::move(callback));
+ } else {
+ validator->RunValidation();
+ std::move(callback).Run(validator.get());
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h
new file mode 100644
index 00000000000..5a385a95b9f
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h
@@ -0,0 +1,89 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/cloud/dm_token.h"
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+
+namespace policy {
+
+// Implements a cloud policy store that stores policy for machine level user
+// cloud policy. This is used on (non-chromeos) platforms that do no have a
+// secure storage implementation.
+class POLICY_EXPORT MachineLevelUserCloudPolicyStore
+ : public DesktopCloudPolicyStore {
+ public:
+ MachineLevelUserCloudPolicyStore(
+ const DMToken& machine_dm_token,
+ const std::string& machine_client_id,
+ const base::FilePath& external_policy_path,
+ const base::FilePath& external_policy_info_path,
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+ MachineLevelUserCloudPolicyStore(const MachineLevelUserCloudPolicyStore&) =
+ delete;
+ MachineLevelUserCloudPolicyStore& operator=(
+ const MachineLevelUserCloudPolicyStore&) = delete;
+ ~MachineLevelUserCloudPolicyStore() override;
+
+ // Creates a MachineLevelUserCloudPolicyStore instance. |external_policy_path|
+ // must be a secure location because no signature validations are made on it.
+ static std::unique_ptr<MachineLevelUserCloudPolicyStore> Create(
+ const DMToken& machine_dm_token,
+ const std::string& machine_client_id,
+ const base::FilePath& external_policy_dir,
+ const base::FilePath& policy_dir,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+
+ // override DesktopCloudPolicyStore
+ void LoadImmediately() override;
+ void Load() override;
+
+ // override UserCloudPolicyStoreBase
+ std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ CloudPolicyValidatorBase::ValidateTimestampOption option) override;
+
+ // Setup global |dm_token| and |client_id| in store for the validation purpose
+ // before policy refresh.
+ void SetupRegistration(const DMToken& machine_dm_token,
+ const std::string& machine_client_id);
+
+ // No DM token can be fetched from server or read from disk. Finish
+ // initialization with empty policy data.
+ void InitWithoutToken();
+
+ private:
+ // Function used as a PolicyLoadFilter to use external policies if they are
+ // newer than the ones previously written by the browser.
+ static PolicyLoadResult MaybeUseExternalCachedPolicies(
+ const base::FilePath& policy_cache_path,
+ const base::FilePath& policy_info_path,
+ PolicyLoadResult default_cached_policy_load_result);
+
+ static PolicyLoadResult LoadExternalCachedPolicies(
+ const base::FilePath& policy_cache_path,
+ const base::FilePath& policy_info_path);
+
+ // override DesktopCloudPolicyStore
+ void Validate(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ std::unique_ptr<enterprise_management::PolicySigningKey> key,
+ bool validate_in_background,
+ UserCloudPolicyValidator::CompletionCallback callback) override;
+
+ DMToken machine_dm_token_;
+ std::string machine_client_id_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc
new file mode 100644
index 00000000000..7c66d1718c0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc
@@ -0,0 +1,431 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/logging.h"
+
+using ::testing::_;
+
+namespace policy {
+namespace {
+constexpr int kPublicKeyVersion = 0;
+}
+
+// The unit test for MachineLevelUserCloudPolicyStore. Note that most of test
+// cases are covered by UserCloudPolicyStoreTest so that the cases here are
+// focus on testing the unique part of MachineLevelUserCloudPolicyStore.
+class MachineLevelUserCloudPolicyStoreTest : public ::testing::Test {
+ public:
+ MachineLevelUserCloudPolicyStoreTest()
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {
+ policy_.SetDefaultInitialSigningKey();
+ policy_.policy_data().set_policy_type(
+ GetMachineLevelUserCloudPolicyTypeForCurrentOS());
+ policy_.payload().mutable_searchsuggestenabled()->set_value(false);
+ policy_.Build();
+ }
+ MachineLevelUserCloudPolicyStoreTest(
+ const MachineLevelUserCloudPolicyStoreTest&) = delete;
+ MachineLevelUserCloudPolicyStoreTest& operator=(
+ const MachineLevelUserCloudPolicyStoreTest&) = delete;
+
+ ~MachineLevelUserCloudPolicyStoreTest() override {}
+
+ void SetUp() override {
+ ASSERT_TRUE(tmp_policy_dir_.CreateUniqueTempDir());
+ updater_policy_dir_ =
+ tmp_policy_dir_.GetPath().AppendASCII("updater_policies");
+ store_ = CreateStore();
+ }
+
+ void SetExpectedPolicyMap(PolicySource source) {
+ expected_policy_map_.Clear();
+ expected_policy_map_.Set("SearchSuggestEnabled", POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, source, base::Value(false),
+ nullptr);
+ }
+
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> CreateStore() {
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store =
+ MachineLevelUserCloudPolicyStore::Create(
+ DMToken::CreateValidTokenForTesting(PolicyBuilder::kFakeToken),
+ PolicyBuilder::kFakeDeviceId, updater_policy_dir_,
+ tmp_policy_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get());
+ store->AddObserver(&observer_);
+ return store;
+ }
+
+ void TearDown() override {
+ store_->RemoveObserver(&observer_);
+ store_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ const base::FilePath updater_policy_cache_path() const {
+ return updater_policy_dir_
+ .AppendASCII(
+ policy::dm_protocol::kChromeMachineLevelUserCloudPolicyTypeBase64)
+ .AppendASCII("PolicyFetchResponse");
+ }
+
+ const base::FilePath updater_policy_info_path() const {
+ return updater_policy_dir_.AppendASCII("CachedPolicyInfo");
+ }
+
+ void StorePolicyInUpdaterPath(
+ const enterprise_management::PolicyFetchResponse& policy) {
+ ASSERT_TRUE(base::CreateDirectory(updater_policy_cache_path().DirName()));
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+ store_->Store(policy);
+ base::RunLoop().RunUntilIdle();
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+ base::FilePath policy_path = tmp_policy_dir_.GetPath().AppendASCII(
+ "Machine Level User Cloud Policy");
+ ASSERT_TRUE(base::CopyFile(policy_path, updater_policy_info_path()));
+ ASSERT_TRUE(base::Move(policy_path, updater_policy_cache_path()));
+ }
+
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store_;
+
+ base::ScopedTempDir tmp_policy_dir_;
+ base::FilePath updater_policy_dir_;
+ UserPolicyBuilder policy_;
+ UserPolicyBuilder updater_policy_;
+ MockCloudPolicyStoreObserver observer_;
+ PolicyMap expected_policy_map_;
+
+ private:
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadWithoutDMToken) {
+ store_->SetupRegistration(DMToken::CreateEmptyTokenForTesting(),
+ std::string());
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ EXPECT_CALL(observer_, OnStoreLoaded(_)).Times(0);
+ EXPECT_CALL(observer_, OnStoreError(_)).Times(0);
+
+ store_->Load();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadImmediatelyWithoutDMToken) {
+ store_->SetupRegistration(DMToken::CreateEmptyTokenForTesting(),
+ std::string());
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+#if BUILDFLAG(IS_ANDROID)
+ EXPECT_CALL(observer_, OnStoreLoaded(_)).Times(1);
+#else
+ EXPECT_CALL(observer_, OnStoreLoaded(_)).Times(0);
+#endif
+ EXPECT_CALL(observer_, OnStoreError(_)).Times(0);
+
+ store_->LoadImmediately();
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadWithNoFile) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+ store_->Load();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, StorePolicy) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+ const base::FilePath policy_path = tmp_policy_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Machine Level User Cloud Policy"));
+ const base::FilePath signing_key_path = tmp_policy_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key"));
+ EXPECT_FALSE(base::PathExists(policy_path));
+ EXPECT_FALSE(base::PathExists(signing_key_path));
+
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+
+ store_->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_TRUE(store_->policy());
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ store_->policy()->SerializeAsString());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
+ EXPECT_TRUE(base::PathExists(policy_path));
+ EXPECT_TRUE(base::PathExists(signing_key_path));
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, StoreThenLoadPolicy) {
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+ store_->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ SetExpectedPolicyMap(POLICY_SOURCE_CLOUD);
+ ASSERT_TRUE(loader->policy());
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ loader->policy()->SerializeAsString());
+ EXPECT_TRUE(expected_policy_map_.Equals(loader->policy_map()));
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
+ loader->RemoveObserver(&observer_);
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadOlderExternalPolicies) {
+ // Create a fake updater policy file.
+ policy_.policy_data().set_timestamp(1000);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ policy_.Build();
+ StorePolicyInUpdaterPath(policy_.policy());
+
+ // Create a policy file made by the browser.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(store.get()));
+ policy_.policy_data().set_timestamp(2000);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(false);
+ policy_.Build();
+ store->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+ store->RemoveObserver(&observer_);
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ // Load the policies and expect to have the updater ones.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ SetExpectedPolicyMap(POLICY_SOURCE_CLOUD);
+ ASSERT_TRUE(loader->policy());
+ EXPECT_TRUE(expected_policy_map_.Equals(loader->policy_map()));
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
+ loader->RemoveObserver(&observer_);
+
+ // Always keep the key when using the browser policy cache.
+ EXPECT_TRUE(loader->policy()->has_public_key_version());
+ EXPECT_EQ(kPublicKeyVersion, loader->policy()->public_key_version());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadRecentExternalPolicies) {
+ // Create a fake updater policy file.
+ policy_.policy_data().set_timestamp(2000);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.Build();
+ StorePolicyInUpdaterPath(policy_.policy());
+
+ // Create a policy file made by the browser.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(store.get()));
+ policy_.policy_data().set_timestamp(1000);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(false);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.Build();
+ store->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+ store->RemoveObserver(&observer_);
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ // Load the policies and expect to have the updater ones.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ PolicyMap expected_updater_policy_map;
+ expected_updater_policy_map.Set(
+ "SearchSuggestEnabled", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+
+ ASSERT_TRUE(loader->policy());
+ EXPECT_TRUE(expected_updater_policy_map.Equals(loader->policy_map()));
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
+ loader->RemoveObserver(&observer_);
+
+ // Using updater's key with policy data. However, we won't refresh the
+ // key as they are same.
+ EXPECT_TRUE(loader->policy()->has_public_key_version());
+ EXPECT_EQ(kPublicKeyVersion, loader->policy()->public_key_version());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest,
+ LoadExternalPoliciesAndRefreshKey) {
+ // Create a fake updater policy file.
+ policy_.policy_data().set_timestamp(2000);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.SetDefaultNewSigningKey();
+ policy_.Build();
+ StorePolicyInUpdaterPath(policy_.policy());
+
+ // Create a policy file made by the browser.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> store = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(store.get()));
+ policy_.policy_data().set_timestamp(1000);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(false);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.SetDefaultInitialSigningKey();
+ policy_.Build();
+ store->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+ store->RemoveObserver(&observer_);
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ // Load the policies and expect to have the updater ones.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ PolicyMap expected_updater_policy_map;
+ expected_updater_policy_map.Set(
+ "SearchSuggestEnabled", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+
+ ASSERT_TRUE(loader->policy());
+ EXPECT_TRUE(expected_updater_policy_map.Equals(loader->policy_map()));
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
+ loader->RemoveObserver(&observer_);
+
+ // Using updater's key with policy data and requires a refresh.
+ EXPECT_FALSE(loader->policy()->has_public_key_version());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadOnlyExternalPolicies) {
+ // Create a fake updater policy file.
+ policy_.policy_data().set_timestamp(2000);
+ policy_.policy_data().set_public_key_version(kPublicKeyVersion);
+ policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ policy_.Build();
+ StorePolicyInUpdaterPath(policy_.policy());
+
+ // Load the policies and expect to have the updater ones.
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ PolicyMap expected_updater_policy_map;
+ expected_updater_policy_map.Set(
+ "SearchSuggestEnabled", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+
+ ASSERT_TRUE(loader->policy());
+ EXPECT_TRUE(expected_updater_policy_map.Equals(loader->policy_map()));
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
+
+ // Always requires the key refresh when browser policy cache is not available.
+ EXPECT_FALSE(loader->policy()->has_public_key_version());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+ loader->RemoveObserver(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest,
+ StoreAndLoadWithInvalidTokenPolicy) {
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+ store_->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
+ loader->SetupRegistration(DMToken(DMToken::Status::kValid, "bad_token"),
+ "invalid_client_id");
+ EXPECT_CALL(observer_, OnStoreError(loader.get()));
+ loader->Load();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(loader->policy());
+ EXPECT_TRUE(loader->policy_map().empty());
+ EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, loader->status());
+ loader->RemoveObserver(&observer_);
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(MachineLevelUserCloudPolicyStoreTest, KeyRotation) {
+ EXPECT_FALSE(policy_.policy().has_new_public_key_signature());
+ std::string original_policy_key = policy_.policy().new_public_key();
+
+ // Store the original key
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+
+ store_->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_TRUE(store_->policy());
+ EXPECT_EQ(original_policy_key, store_->policy_signature_public_key());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+
+ // Store the new key.
+ policy_.SetDefaultSigningKey();
+ policy_.SetDefaultNewSigningKey();
+ policy_.Build();
+ EXPECT_TRUE(policy_.policy().has_new_public_key_signature());
+ EXPECT_NE(original_policy_key, policy_.policy().new_public_key());
+
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
+
+ store_->Store(policy_.policy());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(policy_.policy().new_public_key(),
+ store_->policy_signature_public_key());
+
+ ::testing::Mock::VerifyAndClearExpectations(&observer_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.cc b/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.cc
new file mode 100644
index 00000000000..1da4060fb48
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.cc
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_cloud_external_data_manager.h"
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+
+namespace policy {
+
+MockCloudExternalDataManager::MockCloudExternalDataManager() {
+}
+
+MockCloudExternalDataManager::~MockCloudExternalDataManager() {
+}
+
+std::unique_ptr<ExternalDataFetcher>
+MockCloudExternalDataManager::CreateExternalDataFetcher(
+ const std::string& policy) {
+ return std::make_unique<ExternalDataFetcher>(weak_factory_.GetWeakPtr(),
+ policy);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.h b/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.h
new file mode 100644
index 00000000000..ae11112a8a8
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_external_data_manager.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_EXTERNAL_DATA_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_EXTERNAL_DATA_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class ExternalDataFetcher;
+
+class MockCloudExternalDataManager : public CloudExternalDataManager {
+ public:
+ MockCloudExternalDataManager();
+ MockCloudExternalDataManager(const MockCloudExternalDataManager&) = delete;
+ MockCloudExternalDataManager& operator=(const MockCloudExternalDataManager&) =
+ delete;
+ ~MockCloudExternalDataManager() override;
+
+ MOCK_METHOD0(OnPolicyStoreLoaded, void(void));
+ MOCK_METHOD1(Connect, void(scoped_refptr<network::SharedURLLoaderFactory>));
+ MOCK_METHOD0(Disconnect, void(void));
+ MOCK_METHOD3(Fetch,
+ void(const std::string&,
+ const std::string&,
+ ExternalDataFetcher::FetchCallback));
+
+ std::unique_ptr<ExternalDataFetcher> CreateExternalDataFetcher(
+ const std::string& policy);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_EXTERNAL_DATA_MANAGER_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.cc b/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.cc
new file mode 100644
index 00000000000..0ba53600ccc
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+MockCloudPolicyClient::MockCloudPolicyClient()
+ : MockCloudPolicyClient(nullptr, nullptr) {}
+
+MockCloudPolicyClient::MockCloudPolicyClient(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+ : MockCloudPolicyClient(url_loader_factory, nullptr) {}
+
+MockCloudPolicyClient::MockCloudPolicyClient(DeviceManagementService* service)
+ : MockCloudPolicyClient(nullptr, service) {}
+
+MockCloudPolicyClient::MockCloudPolicyClient(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceManagementService* service)
+ : CloudPolicyClient(service,
+ std::move(url_loader_factory),
+ CloudPolicyClient::DeviceDMTokenCallback()) {}
+
+MockCloudPolicyClient::~MockCloudPolicyClient() {}
+
+void MockCloudPolicyClient::SetDMToken(const std::string& token) {
+ dm_token_ = token;
+}
+
+void MockCloudPolicyClient::SetPolicy(const std::string& policy_type,
+ const std::string& settings_entity_id,
+ const em::PolicyFetchResponse& policy) {
+ responses_[std::make_pair(policy_type, settings_entity_id)] = policy;
+}
+
+void MockCloudPolicyClient::SetFetchedInvalidationVersion(
+ int64_t fetched_invalidation_version) {
+ fetched_invalidation_version_ = fetched_invalidation_version;
+}
+
+void MockCloudPolicyClient::SetStatus(DeviceManagementStatus status) {
+ status_ = status;
+}
+
+MockCloudPolicyClientObserver::MockCloudPolicyClientObserver() = default;
+
+MockCloudPolicyClientObserver::~MockCloudPolicyClientObserver() = default;
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.h b/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.h
new file mode 100644
index 00000000000..d629f8e2fef
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_client.h
@@ -0,0 +1,224 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_CLIENT_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_CLIENT_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/reporting/proto/synced/record.pb.h"
+#include "device_management_backend.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+ACTION_P(ScheduleStatusCallback, status) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(arg0), status));
+}
+
+class MockCloudPolicyClient : public CloudPolicyClient {
+ public:
+ MockCloudPolicyClient();
+ explicit MockCloudPolicyClient(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+ explicit MockCloudPolicyClient(DeviceManagementService* service);
+ MockCloudPolicyClient(
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ DeviceManagementService* service);
+ MockCloudPolicyClient(const MockCloudPolicyClient&) = delete;
+ MockCloudPolicyClient& operator=(const MockCloudPolicyClient&) = delete;
+ ~MockCloudPolicyClient() override;
+
+ MOCK_METHOD3(SetupRegistration,
+ void(const std::string&,
+ const std::string&,
+ const std::vector<std::string>&));
+ MOCK_METHOD3(Register,
+ void(const RegistrationParameters&,
+ const std::string&,
+ const std::string&));
+ MOCK_METHOD0(FetchPolicy, void(void));
+ MOCK_METHOD0(Unregister, void(void));
+ MOCK_METHOD2(UploadEnterpriseMachineCertificate,
+ void(const std::string&, StatusCallback));
+ MOCK_METHOD2(UploadEnterpriseEnrollmentCertificate,
+ void(const std::string&, StatusCallback));
+ MOCK_METHOD2(UploadEnterpriseEnrollmentId,
+ void(const std::string&, StatusCallback));
+ void UploadDeviceStatus(
+ const enterprise_management::DeviceStatusReportRequest* device_status,
+ const enterprise_management::SessionStatusReportRequest* session_status,
+ const enterprise_management::ChildStatusReportRequest* child_status,
+ StatusCallback callback) override {
+ UploadDeviceStatus_(device_status, session_status, child_status, callback);
+ }
+
+ MOCK_METHOD4(UploadDeviceStatus_,
+ void(const enterprise_management::DeviceStatusReportRequest*,
+ const enterprise_management::SessionStatusReportRequest*,
+ const enterprise_management::ChildStatusReportRequest*,
+ StatusCallback&));
+ MOCK_METHOD0(CancelAppInstallReportUpload, void(void));
+ MOCK_METHOD0(CancelExtensionInstallReportUpload, void(void));
+ void UpdateGcmId(const std::string& id, StatusCallback callback) override {
+ UpdateGcmId_(id, callback);
+ }
+ MOCK_METHOD2(UpdateGcmId_, void(const std::string&, StatusCallback&));
+ MOCK_METHOD4(UploadPolicyValidationReport,
+ void(CloudPolicyValidatorBase::Status,
+ const std::vector<ValueValidationIssue>&,
+ const std::string&,
+ const std::string&));
+
+ void UploadChromeDesktopReport(
+ std::unique_ptr<enterprise_management::ChromeDesktopReportRequest>
+ request,
+ StatusCallback callback) override {
+ UploadChromeDesktopReportProxy(request.get(), callback);
+ }
+ // Use Proxy function because unique_ptr can't be used in mock function.
+ MOCK_METHOD2(UploadChromeDesktopReportProxy,
+ void(enterprise_management::ChromeDesktopReportRequest*,
+ StatusCallback&));
+
+ void UploadChromeOsUserReport(
+ std::unique_ptr<enterprise_management::ChromeOsUserReportRequest> request,
+ StatusCallback callback) override {
+ UploadChromeOsUserReportProxy(request.get(), callback);
+ }
+ // Use Proxy function because unique_ptr can't be used in mock function.
+ MOCK_METHOD2(UploadChromeOsUserReportProxy,
+ void(enterprise_management::ChromeOsUserReportRequest*,
+ StatusCallback&));
+
+ void UploadChromeProfileReport(
+ std::unique_ptr<enterprise_management::ChromeProfileReportRequest>
+ request,
+ StatusCallback callback) override {
+ UploadChromeProfileReportProxy(request.get(), callback);
+ }
+ // Use Proxy function because unique_ptr can't be used in mock function.
+ MOCK_METHOD2(UploadChromeProfileReportProxy,
+ void(enterprise_management::ChromeProfileReportRequest*,
+ StatusCallback&));
+
+ void UploadEuiccInfo(
+ std::unique_ptr<enterprise_management::UploadEuiccInfoRequest> request,
+ StatusCallback callback) override {
+ UploadEuiccInfoProxy(request.get(), callback);
+ }
+ // Use Proxy function because unique_ptr can't be used in mock function.
+ MOCK_METHOD2(UploadEuiccInfoProxy,
+ void(enterprise_management::UploadEuiccInfoRequest*,
+ StatusCallback&));
+
+ void UploadSecurityEventReport(content::BrowserContext* context,
+ bool include_device_info,
+ base::Value::Dict value,
+ StatusCallback callback) override {
+ UploadSecurityEventReport_(context, include_device_info, value, callback);
+ }
+ MOCK_METHOD4(UploadSecurityEventReport_,
+ void(content::BrowserContext* context,
+ bool include_device_info,
+ base::Value::Dict&,
+ StatusCallback&));
+
+ MOCK_METHOD3(UploadEncryptedReport,
+ void(base::Value::Dict,
+ absl::optional<base::Value::Dict>,
+ ResponseCallback));
+
+ void UploadAppInstallReport(base::Value::Dict value,
+ StatusCallback callback) override {
+ UploadAppInstallReport_(value, callback);
+ }
+ MOCK_METHOD2(UploadAppInstallReport_,
+ void(base::Value::Dict&, StatusCallback&));
+ void UploadExtensionInstallReport(base::Value::Dict value,
+ StatusCallback callback) override {
+ UploadExtensionInstallReport_(value, callback);
+ }
+ MOCK_METHOD2(UploadExtensionInstallReport_,
+ void(base::Value::Dict&, StatusCallback&));
+
+ MOCK_METHOD5(ClientCertProvisioningStartCsr,
+ void(const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningStartCsrCallback callback));
+
+ MOCK_METHOD7(ClientCertProvisioningFinishCsr,
+ void(const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ const std::string& va_challenge_response,
+ const std::string& signature,
+ ClientCertProvisioningFinishCsrCallback callback));
+
+ MOCK_METHOD5(ClientCertProvisioningDownloadCert,
+ void(const std::string& cert_scope,
+ const std::string& cert_profile_id,
+ const std::string& cert_profile_version,
+ const std::string& public_key,
+ ClientCertProvisioningDownloadCertCallback callback));
+
+ // Sets the DMToken.
+ void SetDMToken(const std::string& token);
+
+ // Injects policy.
+ void SetPolicy(const std::string& policy_type,
+ const std::string& settings_entity_id,
+ const enterprise_management::PolicyFetchResponse& policy);
+
+ // Inject invalidation version.
+ void SetFetchedInvalidationVersion(int64_t fetched_invalidation_version);
+
+ // Sets the status field.
+ void SetStatus(DeviceManagementStatus status);
+
+ // Make the notification helpers public.
+ using CloudPolicyClient::NotifyClientError;
+ using CloudPolicyClient::NotifyPolicyFetched;
+ using CloudPolicyClient::NotifyRegistrationStateChanged;
+
+ using CloudPolicyClient::client_id_;
+ using CloudPolicyClient::dm_token_;
+ using CloudPolicyClient::fetched_invalidation_version_;
+ using CloudPolicyClient::invalidation_payload_;
+ using CloudPolicyClient::invalidation_version_;
+ using CloudPolicyClient::last_policy_timestamp_;
+ using CloudPolicyClient::public_key_version_;
+ using CloudPolicyClient::public_key_version_valid_;
+ using CloudPolicyClient::types_to_fetch_;
+};
+
+class MockCloudPolicyClientObserver : public CloudPolicyClient::Observer {
+ public:
+ MockCloudPolicyClientObserver();
+ MockCloudPolicyClientObserver(const MockCloudPolicyClientObserver&) = delete;
+ MockCloudPolicyClientObserver& operator=(
+ const MockCloudPolicyClientObserver&) = delete;
+ ~MockCloudPolicyClientObserver() override;
+
+ MOCK_METHOD1(OnPolicyFetched, void(CloudPolicyClient*));
+ MOCK_METHOD1(OnRegistrationStateChanged, void(CloudPolicyClient*));
+ MOCK_METHOD1(OnClientError, void(CloudPolicyClient*));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_CLIENT_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.cc b/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.cc
new file mode 100644
index 00000000000..3bf61bc77ff
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.cc
@@ -0,0 +1,32 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_cloud_policy_service.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+
+namespace policy {
+
+MockCloudPolicyService::MockCloudPolicyService(CloudPolicyClient* client,
+ CloudPolicyStore* store)
+ : CloudPolicyService(std::string(), std::string(), client, store) {
+ // Besides recording the mock call, invoke real RefreshPolicy() method.
+ // That way FetchPolicy() is called on the |client|.
+ ON_CALL(*this, RefreshPolicy(testing::_))
+ .WillByDefault(
+ testing::Invoke(this, &MockCloudPolicyService::InvokeRefreshPolicy));
+}
+
+MockCloudPolicyService::~MockCloudPolicyService() = default;
+
+void MockCloudPolicyService::InvokeRefreshPolicy(
+ RefreshPolicyCallback callback) {
+ CloudPolicyService::RefreshPolicy(std::move(callback));
+}
+
+MockCloudPolicyServiceObserver::MockCloudPolicyServiceObserver() = default;
+
+MockCloudPolicyServiceObserver::~MockCloudPolicyServiceObserver() = default;
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.h b/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.h
new file mode 100644
index 00000000000..87f73a67766
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_service.h
@@ -0,0 +1,44 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_SERVICE_H_
+
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class CloudPolicyClient;
+class CloudPolicyStore;
+
+class MockCloudPolicyService : public CloudPolicyService {
+ public:
+ MockCloudPolicyService(CloudPolicyClient* client, CloudPolicyStore* store);
+ MockCloudPolicyService(const MockCloudPolicyService&) = delete;
+ MockCloudPolicyService& operator=(const MockCloudPolicyService&) = delete;
+ ~MockCloudPolicyService() override;
+
+ MOCK_METHOD1(RefreshPolicy, void(RefreshPolicyCallback));
+
+ private:
+ // Invokes real RefreshPolicy() method.
+ void InvokeRefreshPolicy(RefreshPolicyCallback callback);
+};
+
+class MockCloudPolicyServiceObserver : public CloudPolicyService::Observer {
+ public:
+ MockCloudPolicyServiceObserver();
+ MockCloudPolicyServiceObserver(const MockCloudPolicyServiceObserver&) =
+ delete;
+ MockCloudPolicyServiceObserver& operator=(
+ const MockCloudPolicyServiceObserver&) = delete;
+ ~MockCloudPolicyServiceObserver() override;
+
+ MOCK_METHOD0(OnCloudPolicyServiceInitializationCompleted, void());
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.cc
new file mode 100644
index 00000000000..8b8003915a9
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+MockCloudPolicyStore::MockCloudPolicyStore() = default;
+
+MockCloudPolicyStore::~MockCloudPolicyStore() = default;
+
+MockCloudPolicyStoreObserver::MockCloudPolicyStoreObserver() = default;
+
+MockCloudPolicyStoreObserver::~MockCloudPolicyStoreObserver() = default;
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.h b/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.h
new file mode 100644
index 00000000000..57a433f2e85
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_cloud_policy_store.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_STORE_H_
+
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockCloudPolicyStore : public CloudPolicyStore {
+ public:
+ MockCloudPolicyStore();
+ MockCloudPolicyStore(const MockCloudPolicyStore&) = delete;
+ MockCloudPolicyStore& operator=(const MockCloudPolicyStore&) = delete;
+ ~MockCloudPolicyStore() override;
+
+ MOCK_METHOD1(Store, void(const enterprise_management::PolicyFetchResponse&));
+ MOCK_METHOD0(Load, void(void));
+
+ // Publish the protected members.
+ using CloudPolicyStore::NotifyStoreLoaded;
+ using CloudPolicyStore::NotifyStoreError;
+
+ using CloudPolicyStore::invalidation_version_;
+ using CloudPolicyStore::policy_map_;
+ using CloudPolicyStore::policy_signature_public_key_;
+ using CloudPolicyStore::status_;
+ using CloudPolicyStore::validation_result_;
+};
+
+class MockCloudPolicyStoreObserver : public CloudPolicyStore::Observer {
+ public:
+ MockCloudPolicyStoreObserver();
+ MockCloudPolicyStoreObserver(const MockCloudPolicyStoreObserver&) = delete;
+ MockCloudPolicyStoreObserver& operator=(const MockCloudPolicyStoreObserver&) =
+ delete;
+ ~MockCloudPolicyStoreObserver() override;
+
+ MOCK_METHOD1(OnStoreLoaded, void(CloudPolicyStore* store));
+ MOCK_METHOD1(OnStoreError, void(CloudPolicyStore* store));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_device_management_service.cc b/chromium/components/policy/core/common/cloud/mock_device_management_service.cc
new file mode 100644
index 00000000000..1074baabdc6
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_device_management_service.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_device_management_service.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "base/test/bind.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace policy {
+namespace {
+
+const char kServerUrl[] = "https://example.com/management_service";
+const char kUserAgent[] = "Chrome 1.2.3(456)";
+const char kPlatform[] = "Test|Unit|1.2.3";
+
+std::string Serialize(
+ const enterprise_management::DeviceManagementResponse& response) {
+ // SerializeToString() may fail, that's OK. Some tests explicitly use
+ // malformed responses.
+ std::string payload;
+ if (response.IsInitialized())
+ response.SerializeToString(&payload);
+ return payload;
+}
+
+} // namespace
+
+MockDeviceManagementServiceConfiguration::
+ MockDeviceManagementServiceConfiguration()
+ : server_url_(kServerUrl) {}
+
+MockDeviceManagementServiceConfiguration::
+ MockDeviceManagementServiceConfiguration(const std::string& server_url)
+ : server_url_(server_url) {}
+
+MockDeviceManagementServiceConfiguration::
+ ~MockDeviceManagementServiceConfiguration() = default;
+
+std::string MockDeviceManagementServiceConfiguration::GetDMServerUrl() const {
+ return server_url_;
+}
+
+std::string MockDeviceManagementServiceConfiguration::GetAgentParameter()
+ const {
+ return kUserAgent;
+}
+
+std::string MockDeviceManagementServiceConfiguration::GetPlatformParameter()
+ const {
+ return kPlatform;
+}
+
+std::string
+MockDeviceManagementServiceConfiguration::GetRealtimeReportingServerUrl()
+ const {
+ return server_url_;
+}
+
+std::string
+MockDeviceManagementServiceConfiguration::GetEncryptedReportingServerUrl()
+ const {
+ return server_url_;
+}
+
+std::string
+MockDeviceManagementServiceConfiguration::GetReportingConnectorServerUrl(
+ content::BrowserContext* context) const {
+ return server_url_;
+}
+
+MockJobCreationHandler::MockJobCreationHandler() = default;
+MockJobCreationHandler::~MockJobCreationHandler() = default;
+
+FakeDeviceManagementService::FakeDeviceManagementService(
+ MockJobCreationHandler* creation_handler)
+ : FakeDeviceManagementService(
+ std::make_unique<MockDeviceManagementServiceConfiguration>(),
+ creation_handler) {}
+
+FakeDeviceManagementService::FakeDeviceManagementService(
+ std::unique_ptr<Configuration> config,
+ MockJobCreationHandler* creation_handler)
+ : DeviceManagementService(std::move(config)),
+ creation_handler_(creation_handler) {
+ CHECK(creation_handler_);
+}
+
+FakeDeviceManagementService::~FakeDeviceManagementService() = default;
+
+std::unique_ptr<DeviceManagementService::Job>
+FakeDeviceManagementService::CreateJob(
+ std::unique_ptr<JobConfiguration> config) {
+ auto job_pair = CreateJobForTesting(std::move(config));
+ creation_handler_->OnJobCreation(job_pair.second);
+ return std::move(job_pair.first);
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::CaptureAuthData(DMAuth* auth_data) {
+ return [auth_data](DeviceManagementService::JobForTesting job) mutable {
+ if (job.IsActive())
+ *auth_data = job.GetConfigurationForTesting()->GetAuth().Clone();
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::CaptureJobType(
+ DeviceManagementService::JobConfiguration::JobType* job_type) {
+ return [job_type](DeviceManagementService::JobForTesting job) mutable {
+ if (job.IsActive())
+ *job_type = job.GetConfigurationForTesting()->GetType();
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::CapturePayload(std::string* payload) {
+ return [payload](DeviceManagementService::JobForTesting job) mutable {
+ if (job.IsActive())
+ *payload = job.GetConfigurationForTesting()->GetPayload();
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::CaptureQueryParams(
+ std::map<std::string, std::string>* query_params) {
+ return [query_params](DeviceManagementService::JobForTesting job) mutable {
+ if (job.IsActive())
+ *query_params = job.GetConfigurationForTesting()->GetQueryParams();
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::CaptureRequest(
+ enterprise_management::DeviceManagementRequest* request) {
+ return [request](DeviceManagementService::JobForTesting job) mutable {
+ if (job.IsActive()) {
+ const std::string payload =
+ job.GetConfigurationForTesting()->GetPayload();
+ CHECK(request->ParseFromString(payload));
+ }
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::SendJobResponseAsync(int net_error,
+ int response_code,
+ const std::string& response,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy) {
+ // Note: We need to use a WeakPtr<Job> here, because some tests might destroy
+ // pending jobs, e.g. CloudPolicyClientTest, CancelUploadAppInstallReport.
+ // And base::WeakPtr cannot bind to non-void functions.
+ // Thus, we need the redirect to SendWeakJobResponseNow.
+ return [=](DeviceManagementService::JobForTesting job) {
+ this->GetTaskRunnerForTesting()->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([=]() mutable {
+ if (job.IsActive()) {
+ job.SetResponseForTesting(net_error, response_code, response,
+ mime_type, was_fetched_via_proxy);
+ } else
+ LOG(WARNING) << "job inactive";
+ }));
+ };
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::SendJobResponseAsync(
+ int net_error,
+ int response_code,
+ const enterprise_management::DeviceManagementResponse& response,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy) {
+ return SendJobResponseAsync(net_error, response_code, Serialize(response),
+ mime_type, was_fetched_via_proxy);
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::SendJobOKAsync(const std::string& response) {
+ return SendJobResponseAsync(net::OK, DeviceManagementService::kSuccess,
+ response);
+}
+
+FakeDeviceManagementService::JobAction
+FakeDeviceManagementService::SendJobOKAsync(
+ const enterprise_management::DeviceManagementResponse& response) {
+ return SendJobOKAsync(Serialize(response));
+}
+
+void FakeDeviceManagementService::SendJobResponseNow(
+ DeviceManagementService::JobForTesting* job,
+ int net_error,
+ int response_code,
+ const std::string& response,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy) {
+ CHECK(job);
+ if (job->SetResponseForTesting(net_error, response_code, response, mime_type,
+ was_fetched_via_proxy) ==
+ Job::RetryMethod::NO_RETRY) {
+ job->Deactivate();
+ }
+}
+
+void FakeDeviceManagementService::SendJobResponseNow(
+ DeviceManagementService::JobForTesting* job,
+ int net_error,
+ int response_code,
+ const enterprise_management::DeviceManagementResponse& response,
+ const std::string& mime_type,
+ bool was_fetched_via_proxy) {
+ SendJobResponseNow(job, net_error, response_code, Serialize(response),
+ mime_type, was_fetched_via_proxy);
+}
+
+void FakeDeviceManagementService::SendJobOKNow(
+ DeviceManagementService::JobForTesting* job,
+ const std::string& response) {
+ SendJobResponseNow(job, net::OK, kSuccess, response);
+}
+
+void FakeDeviceManagementService::SendJobOKNow(
+ DeviceManagementService::JobForTesting* job,
+ const enterprise_management::DeviceManagementResponse& response) {
+ SendJobOKNow(job, Serialize(response));
+}
+
+FakeJobConfiguration::FakeJobConfiguration(
+ DeviceManagementService* service,
+ JobType type,
+ const std::string& client_id,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ FakeCallback callback,
+ RetryCallback retry_callback,
+ RetryCallback should_retry_callback)
+ : DMServerJobConfiguration(service,
+ type,
+ client_id,
+ critical,
+ std::move(auth_data),
+ oauth_token,
+ url_loader_factory,
+ base::DoNothing()),
+ should_retry_response_(DeviceManagementService::Job::NO_RETRY),
+ callback_(std::move(callback)),
+ retry_callback_(retry_callback),
+ should_retry_callback_(should_retry_callback) {
+ DCHECK(!callback_.is_null());
+ DCHECK(!retry_callback_.is_null());
+}
+
+FakeJobConfiguration::~FakeJobConfiguration() = default;
+
+void FakeJobConfiguration::SetRequestPayload(
+ const std::string& request_payload) {
+ request()->ParseFromString(request_payload);
+}
+
+void FakeJobConfiguration::SetShouldRetryResponse(
+ DeviceManagementService::Job::RetryMethod method) {
+ should_retry_response_ = method;
+}
+
+DeviceManagementService::Job::RetryMethod FakeJobConfiguration::ShouldRetry(
+ int response_code,
+ const std::string& response_body) {
+ should_retry_callback_.Run(response_code, response_body);
+ return should_retry_response_;
+}
+
+void FakeJobConfiguration::OnBeforeRetry(int response_code,
+ const std::string& response_body) {
+ retry_callback_.Run(response_code, response_body);
+}
+
+void FakeJobConfiguration::OnURLLoadComplete(DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) {
+ DeviceManagementStatus code =
+ MapNetErrorAndResponseCodeToDMStatus(net_error, response_code);
+ std::move(callback_).Run(job, code, net_error, response_body);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_device_management_service.h b/chromium/components/policy/core/common/cloud/mock_device_management_service.h
new file mode 100644
index 00000000000..131908f3237
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_device_management_service.h
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_DEVICE_MANAGEMENT_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_DEVICE_MANAGEMENT_SERVICE_H_
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/dmserver_job_configurations.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace policy {
+
+class MockDeviceManagementServiceConfiguration
+ : public DeviceManagementService::Configuration {
+ public:
+ MockDeviceManagementServiceConfiguration();
+ explicit MockDeviceManagementServiceConfiguration(
+ const std::string& server_url);
+ MockDeviceManagementServiceConfiguration(
+ const MockDeviceManagementServiceConfiguration&) = delete;
+ MockDeviceManagementServiceConfiguration& operator=(
+ const MockDeviceManagementServiceConfiguration&) = delete;
+ ~MockDeviceManagementServiceConfiguration() override;
+
+ std::string GetDMServerUrl() const override;
+ std::string GetAgentParameter() const override;
+ std::string GetPlatformParameter() const override;
+ std::string GetRealtimeReportingServerUrl() const override;
+ std::string GetEncryptedReportingServerUrl() const override;
+ std::string GetReportingConnectorServerUrl(
+ content::BrowserContext* context) const override;
+
+ private:
+ const std::string server_url_;
+};
+
+class MockJobCreationHandler {
+ public:
+ MockJobCreationHandler();
+ MockJobCreationHandler(const MockJobCreationHandler&) = delete;
+ MockJobCreationHandler& operator=(const MockJobCreationHandler&) = delete;
+ ~MockJobCreationHandler();
+
+ MOCK_METHOD(void,
+ OnJobCreation,
+ (const DeviceManagementService::JobForTesting&));
+};
+
+class FakeDeviceManagementService : public DeviceManagementService {
+ public:
+ using JobAction = testing::Action<void(const JobForTesting&)>;
+
+ explicit FakeDeviceManagementService(
+ MockJobCreationHandler* creation_handler);
+ FakeDeviceManagementService(std::unique_ptr<Configuration> config,
+ MockJobCreationHandler* creation_handler);
+ FakeDeviceManagementService(const FakeDeviceManagementService&) = delete;
+ FakeDeviceManagementService& operator=(const FakeDeviceManagementService&) =
+ delete;
+ ~FakeDeviceManagementService() override;
+
+ // Convenience actions to obtain the respective data from the job's
+ // configuration.
+ JobAction CaptureAuthData(DMAuth* auth_data);
+ JobAction CaptureJobType(
+ DeviceManagementService::JobConfiguration::JobType* job_type);
+ JobAction CapturePayload(std::string* payload);
+ JobAction CaptureQueryParams(
+ std::map<std::string, std::string>* query_params);
+ JobAction CaptureRequest(
+ enterprise_management::DeviceManagementRequest* request);
+
+ // Convenience actions to post a task which will call |SetResponseForTesting|
+ // on the job.
+ JobAction SendJobResponseAsync(
+ int net_error,
+ int response_code,
+ const std::string& response = "",
+ const std::string& mime_type = "application/x-protobuffer",
+ bool was_fetched_via_proxy = false);
+
+ JobAction SendJobResponseAsync(
+ int net_error,
+ int response_code,
+ const enterprise_management::DeviceManagementResponse& response,
+ const std::string& mime_type = "application/x-protobuffer",
+ bool was_fetched_via_proxy = false);
+
+ JobAction SendJobOKAsync(const std::string& response);
+
+ JobAction SendJobOKAsync(
+ const enterprise_management::DeviceManagementResponse& response);
+
+ // Convenience wrappers around |job->SetResponseForTesting()|
+ void SendJobResponseNow(
+ DeviceManagementService::JobForTesting* job,
+ int net_error,
+ int response_code,
+ const std::string& response = "",
+ const std::string& mime_type = "application/x-protobuffer",
+ bool was_fetched_via_proxy = false);
+
+ void SendJobResponseNow(
+ DeviceManagementService::JobForTesting* job,
+ int net_error,
+ int response_code,
+ const enterprise_management::DeviceManagementResponse& response,
+ const std::string& mime_type = "application/x-protobuffer",
+ bool was_fetched_via_proxy = false);
+
+ void SendJobOKNow(DeviceManagementService::JobForTesting* job,
+ const std::string& response);
+
+ void SendJobOKNow(
+ DeviceManagementService::JobForTesting* job,
+ const enterprise_management::DeviceManagementResponse& response);
+
+ private:
+ std::unique_ptr<Job> CreateJob(
+ std::unique_ptr<JobConfiguration> config) override;
+
+ raw_ptr<MockJobCreationHandler> creation_handler_;
+};
+
+// A fake implementation of DMServerJobConfiguration that can be used in tests
+// to set arbitrary request payloads for network requests.
+class FakeJobConfiguration : public DMServerJobConfiguration {
+ public:
+ typedef base::OnceCallback<void(DeviceManagementService::Job* job,
+ DeviceManagementStatus code,
+ int net_error,
+ const std::string&)>
+ FakeCallback;
+
+ typedef base::RepeatingCallback<void(int response_code,
+ const std::string& response_body)>
+ RetryCallback;
+
+ explicit FakeJobConfiguration(DeviceManagementService* service);
+ FakeJobConfiguration(
+ DeviceManagementService* service,
+ JobType type,
+ const std::string& client_id,
+ bool critical,
+ DMAuth auth_data,
+ absl::optional<std::string> oauth_token,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ FakeCallback callback,
+ RetryCallback retry_callback,
+ RetryCallback should_retry_callback);
+ ~FakeJobConfiguration() override;
+
+ void SetRequestPayload(const std::string& request_payload);
+ void SetShouldRetryResponse(DeviceManagementService::Job::RetryMethod method);
+
+ private:
+ DeviceManagementService::Job::RetryMethod ShouldRetry(
+ int response_code,
+ const std::string& response_body) override;
+ void OnBeforeRetry(int response_code,
+ const std::string& response_body) override;
+ void OnURLLoadComplete(DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) override;
+
+ DeviceManagementService::Job::RetryMethod should_retry_response_;
+ FakeCallback callback_;
+ RetryCallback retry_callback_;
+ RetryCallback should_retry_callback_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_DEVICE_MANAGEMENT_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_signing_service.cc b/chromium/components/policy/core/common/cloud/mock_signing_service.cc
new file mode 100644
index 00000000000..9c33cee742f
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_signing_service.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_signing_service.h"
+
+#include <string>
+
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+const char kSignedDataNonce[] = "+nonce";
+const char kSignature[] = "fake-signature";
+
+FakeSigningService::FakeSigningService() {}
+
+FakeSigningService::~FakeSigningService() {}
+
+void FakeSigningService::SignData(const std::string& data,
+ SigningCallback callback) {
+ em::SignedData signed_data;
+ if (success_) {
+ SignDataSynchronously(data, &signed_data);
+ }
+ std::move(callback).Run(success_, signed_data);
+}
+
+void FakeSigningService::SignDataSynchronously(const std::string& data,
+ em::SignedData* signed_data) {
+ signed_data->set_data(data + kSignedDataNonce);
+ signed_data->set_signature(kSignature);
+ signed_data->set_extra_data_bytes(sizeof(kSignedDataNonce) - 1);
+}
+
+void FakeSigningService::set_success(bool success) {
+ success_ = success;
+}
+
+MockSigningService::MockSigningService() {}
+
+MockSigningService::~MockSigningService() {}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_signing_service.h b/chromium/components/policy/core/common/cloud/mock_signing_service.h
new file mode 100644
index 00000000000..657cbe3125a
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_signing_service.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_SIGNING_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_SIGNING_SERVICE_H_
+
+#include "components/policy/core/common/cloud/signing_service.h"
+
+#include "base/callback.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class FakeSigningService : public SigningService {
+ public:
+ FakeSigningService();
+ ~FakeSigningService() override;
+
+ void SignData(const std::string& data, SigningCallback callback) override;
+
+ // Useful for test setups without having to deal with callbacks.
+ void SignDataSynchronously(const std::string& data,
+ enterprise_management::SignedData* signed_data);
+
+ // Determine whether SignData will appear successful or not.
+ void set_success(bool success);
+
+ private:
+ bool success_ = true;
+};
+
+class MockSigningService : public FakeSigningService {
+ public:
+ MockSigningService();
+ ~MockSigningService() override;
+
+ MOCK_METHOD2(SignRegistrationData,
+ void(const enterprise_management::
+ CertificateBasedDeviceRegistrationData*,
+ enterprise_management::SignedData*));
+ MOCK_METHOD2(SignData, void(const std::string&, SigningCallback));
+};
+
+}
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_SIGNING_SERVICE_H_
diff --git a/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc
new file mode 100644
index 00000000000..b5b2495b448
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/mock_user_cloud_policy_store.h"
+
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+MockUserCloudPolicyStore::MockUserCloudPolicyStore()
+ : UserCloudPolicyStore(base::FilePath(),
+ base::FilePath(),
+ scoped_refptr<base::SequencedTaskRunner>()) {}
+
+MockUserCloudPolicyStore::~MockUserCloudPolicyStore() {}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.h b/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.h
new file mode 100644
index 00000000000..bbac0673ddd
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/mock_user_cloud_policy_store.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_
+
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockUserCloudPolicyStore : public UserCloudPolicyStore {
+ public:
+ MockUserCloudPolicyStore();
+ MockUserCloudPolicyStore(const MockUserCloudPolicyStore&) = delete;
+ MockUserCloudPolicyStore& operator=(const MockUserCloudPolicyStore&) = delete;
+ ~MockUserCloudPolicyStore() override;
+
+ MOCK_METHOD1(Store, void(const enterprise_management::PolicyFetchResponse&));
+ MOCK_METHOD0(Load, void(void));
+ MOCK_METHOD0(LoadImmediately, void(void));
+ MOCK_METHOD0(Clear, void(void));
+
+ // Publish the protected members.
+ using CloudPolicyStore::NotifyStoreLoaded;
+ using CloudPolicyStore::NotifyStoreError;
+
+ using CloudPolicyStore::policy_map_;
+ using CloudPolicyStore::status_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MOCK_USER_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/policy_invalidation_scope.h b/chromium/components/policy/core/common/cloud/policy_invalidation_scope.h
new file mode 100644
index 00000000000..afae6654f98
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/policy_invalidation_scope.h
@@ -0,0 +1,21 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_INVALIDATION_SCOPE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_INVALIDATION_SCOPE_H_
+
+namespace policy {
+
+// Specifies a scope of a policy or remote command which handler is
+// responsible for.
+enum class PolicyInvalidationScope {
+ kUser,
+ kDevice,
+ kDeviceLocalAccount,
+ kCBCM,
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_INVALIDATION_SCOPE_H_
diff --git a/chromium/components/policy/core/common/cloud/policy_value_validator.h b/chromium/components/policy/core/common/cloud/policy_value_validator.h
new file mode 100644
index 00000000000..166cc257db0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/policy_value_validator.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_VALUE_VALIDATOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_VALUE_VALIDATOR_H_
+
+#include <string>
+
+namespace policy {
+
+struct ValueValidationIssue {
+ enum Severity { kWarning, kError };
+
+ std::string policy_name;
+ Severity severity = kWarning;
+ std::string message;
+
+ bool operator==(ValueValidationIssue const& rhs) const {
+ return policy_name == rhs.policy_name && severity == rhs.severity &&
+ message == rhs.message;
+ }
+};
+
+template <typename PayloadProto>
+class PolicyValueValidator {
+ public:
+ PolicyValueValidator() = default;
+ PolicyValueValidator(const PolicyValueValidator&) = delete;
+ PolicyValueValidator& operator=(const PolicyValueValidator&) = delete;
+ virtual ~PolicyValueValidator() = default;
+
+ // Returns false if the value validation failed with errors.
+ virtual bool ValidateValues(
+ const PayloadProto& policy_payload,
+ std::vector<ValueValidationIssue>* out_validation_issues) const = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_POLICY_VALUE_VALIDATOR_H_
diff --git a/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc
new file mode 100644
index 00000000000..929f1f9d105
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.cc
@@ -0,0 +1,142 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "components/enterprise/common/strings.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/version_info/version_info.h"
+#include "google_apis/google_api_keys.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+const char RealtimeReportingJobConfiguration::kContextKey[] = "context";
+const char RealtimeReportingJobConfiguration::kEventListKey[] = "events";
+
+const char RealtimeReportingJobConfiguration::kEventIdKey[] = "eventId";
+const char RealtimeReportingJobConfiguration::kUploadedEventsKey[] =
+ "uploadedEventIds";
+const char RealtimeReportingJobConfiguration::kFailedUploadsKey[] =
+ "failedUploads";
+const char RealtimeReportingJobConfiguration::kPermanentFailedUploadsKey[] =
+ "permanentFailedUploads";
+
+base::Value::Dict RealtimeReportingJobConfiguration::BuildReport(
+ base::Value::List events,
+ base::Value::Dict context) {
+ base::Value::Dict value_report;
+ value_report.Set(kEventListKey, std::move(events));
+ value_report.Set(kContextKey, std::move(context));
+ return value_report;
+}
+
+RealtimeReportingJobConfiguration::RealtimeReportingJobConfiguration(
+ CloudPolicyClient* client,
+ const std::string& server_url,
+ bool include_device_info,
+ bool add_connector_url_params,
+ UploadCompleteCallback callback)
+ : ReportingJobConfigurationBase(TYPE_UPLOAD_REAL_TIME_REPORT,
+ client->GetURLLoaderFactory(),
+ client,
+ server_url,
+ include_device_info,
+ std::move(callback)) {
+ InitializePayloadInternal(client, add_connector_url_params);
+}
+
+RealtimeReportingJobConfiguration::~RealtimeReportingJobConfiguration() {}
+
+bool RealtimeReportingJobConfiguration::AddReport(base::Value::Dict report) {
+ base::Value::Dict* context = report.FindDict(kContextKey);
+ base::Value::List* events = report.FindList(kEventListKey);
+ if (!context || !events) {
+ return false;
+ }
+
+ // Overwrite internal context. |context_| will be merged with |payload_| in
+ // |GetPayload|.
+ if (context_.has_value()) {
+ context_->Merge(*context);
+ } else {
+ context_ = std::move(*context);
+ }
+
+ // Append event_list to the payload.
+ base::Value::List* to = payload_.FindList(kEventListKey);
+ for (auto& event : *events) {
+ to->Append(std::move(event));
+ }
+ return true;
+}
+
+void RealtimeReportingJobConfiguration::InitializePayloadInternal(
+ CloudPolicyClient* client,
+ bool add_connector_url_params) {
+ payload_.Set(kEventListKey, base::Value::List());
+
+ // If specified add extra enterprise connector URL params.
+ if (add_connector_url_params) {
+ AddParameter(enterprise::kUrlParamConnector, "OnSecurityEvent");
+ AddParameter(enterprise::kUrlParamDeviceToken, client->dm_token());
+ }
+}
+
+DeviceManagementService::Job::RetryMethod
+RealtimeReportingJobConfiguration::ShouldRetryInternal(
+ int response_code,
+ const std::string& response_body) {
+ DeviceManagementService::Job::RetryMethod retry_method =
+ DeviceManagementService::Job::NO_RETRY;
+ const auto failedIds = GetFailedUploadIds(response_body);
+ if (!failedIds.empty()) {
+ retry_method = DeviceManagementService::Job::RETRY_WITH_DELAY;
+ }
+ return retry_method;
+}
+
+void RealtimeReportingJobConfiguration::OnBeforeRetryInternal(
+ int response_code,
+ const std::string& response_body) {
+ const auto& failedIds = GetFailedUploadIds(response_body);
+ if (!failedIds.empty()) {
+ auto* events = payload_.FindList(kEventListKey);
+ // Only keep the elements that temporarily failed their uploads.
+ events->EraseIf([&failedIds](const base::Value& entry) {
+ auto* id = entry.FindStringKey(kEventIdKey);
+ return id && failedIds.find(*id) == failedIds.end();
+ });
+ }
+}
+
+std::string RealtimeReportingJobConfiguration::GetUmaString() const {
+ return "Enterprise.RealtimeReportingSuccess";
+}
+
+std::set<std::string> RealtimeReportingJobConfiguration::GetFailedUploadIds(
+ const std::string& response_body) const {
+ std::set<std::string> failedIds;
+ absl::optional<base::Value> response = base::JSONReader::Read(response_body);
+ base::Value response_value = response ? std::move(*response) : base::Value();
+ base::Value* failedUploads = response_value.FindListKey(kFailedUploadsKey);
+ if (failedUploads) {
+ for (const auto& failedUpload : failedUploads->GetListDeprecated()) {
+ auto* id = failedUpload.FindStringKey(kEventIdKey);
+ if (id) {
+ failedIds.insert(*id);
+ }
+ }
+ }
+ return failedIds;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.h b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.h
new file mode 100644
index 00000000000..bcf228a6900
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration.h
@@ -0,0 +1,93 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_REALTIME_REPORTING_JOB_CONFIGURATION_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_REALTIME_REPORTING_JOB_CONFIGURATION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class CloudPolicyClient;
+
+class POLICY_EXPORT RealtimeReportingJobConfiguration
+ : public ReportingJobConfigurationBase {
+ public:
+ // Keys used in report dictionary.
+ static const char kContextKey[];
+ static const char kEventListKey[];
+
+ // Keys used to parse the response.
+ static const char kEventIdKey[];
+ static const char kUploadedEventsKey[];
+ static const char kFailedUploadsKey[];
+ static const char kPermanentFailedUploadsKey[];
+
+ // Combines the info given in |events| that corresponds to Event proto, and
+ // info given in |context| that corresponds to the Device, Browser and Profile
+ // proto, to a UploadEventsRequest proto defined in
+ // google3/google/internal/chrome/reporting/v1/chromereporting.proto.
+ static base::Value::Dict BuildReport(base::Value::List events,
+ base::Value::Dict context);
+
+ // Configures a request to send real-time reports to the |server_url|
+ // endpoint. If |add_connector_url_params| is true then URL parameters
+ // specific to enterprise connectors are added to the request uploading
+ // the report. |callback| is invoked once the report is uploaded.
+ // |add_connector_url_params| will flip whether the service provider endpoint
+ // parameters will be used.
+ RealtimeReportingJobConfiguration(CloudPolicyClient* client,
+ const std::string& server_url,
+ bool include_device_info,
+ bool add_connector_url_params,
+ UploadCompleteCallback callback);
+ RealtimeReportingJobConfiguration(const RealtimeReportingJobConfiguration&) =
+ delete;
+ RealtimeReportingJobConfiguration& operator=(
+ const RealtimeReportingJobConfiguration&) = delete;
+
+ ~RealtimeReportingJobConfiguration() override;
+
+ // Add a new report to the payload. A report is a dictionary that
+ // contains two keys: "events" and "context". The first key is a list of
+ // dictionaries, where dictionary is defined by the Event message described at
+ // google/internal/chrome/reporting/v1/chromereporting.proto.
+ //
+ // The second is context information about this instance of chrome that
+ // is not specific to the event.
+ //
+ // Returns true if the report was added successfully.
+ bool AddReport(base::Value::Dict report);
+
+ protected:
+ // ReportingJobConfigurationBase
+ DeviceManagementService::Job::RetryMethod ShouldRetryInternal(
+ int response_code,
+ const std::string& response) override;
+ void OnBeforeRetryInternal(int response_code,
+ const std::string& response_body) override;
+
+ std::string GetUmaString() const override;
+
+ private:
+ // Does one time initialization of the payload when the configuration is
+ // created.
+ void InitializePayloadInternal(CloudPolicyClient* client,
+ bool add_connector_url_params);
+
+ // Gathers the ids of the uploads that failed
+ std::set<std::string> GetFailedUploadIds(
+ const std::string& response_body) const;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_REALTIME_REPORTING_JOB_CONFIGURATION_H_
diff --git a/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
new file mode 100644
index 00000000000..a2ec2629e87
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
+
+#include <set>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "components/policy/core/common/cloud/mock_device_management_service.h"
+#include "components/version_info/version_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/system/fake_statistics_provider.h"
+#endif
+
+namespace em = enterprise_management;
+
+using testing::_;
+using testing::StrictMock;
+
+namespace policy {
+
+std::vector<std::string> ids = {"id1", "id2", "id3"};
+
+constexpr char kAppPackage[] = "appPackage";
+constexpr char kEventType[] = "eventType";
+constexpr char kAppInstallEvent[] = "androidAppInstallEvent";
+constexpr char kEventId[] = "eventId";
+constexpr char kStatusCode[] = "status";
+
+constexpr char kDummyToken[] = "dm_token";
+constexpr char kPackage[] = "unitTestPackage";
+
+class MockCallbackObserver {
+ public:
+ MockCallbackObserver() = default;
+
+ MOCK_METHOD4(OnURLLoadComplete,
+ void(DeviceManagementService::Job* job,
+ DeviceManagementStatus code,
+ int net_error,
+ absl::optional<base::Value::Dict>));
+};
+
+MATCHER_P(MatchDict, expected, "matches DictionaryValue") {
+ return arg == expected;
+}
+
+class RealtimeReportingJobConfigurationTest : public testing::Test {
+ public:
+ RealtimeReportingJobConfigurationTest()
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ : client_(&service_),
+ fake_serial_number_(&fake_statistics_provider_)
+#else
+ : client_(&service_)
+#endif
+ {
+ }
+
+ void SetUp() override {
+ client_.SetDMToken(kDummyToken);
+ configuration_ = std::make_unique<RealtimeReportingJobConfiguration>(
+ &client_, service_.configuration()->GetRealtimeReportingServerUrl(),
+ /*include_device_info=*/true, /*add_connector_url_params=*/false,
+ base::BindOnce(&MockCallbackObserver::OnURLLoadComplete,
+ base::Unretained(&callback_observer_)));
+ base::Value::Dict context;
+ context.SetByDottedPath("browser.userAgent", "dummyAgent");
+ base::Value::List events;
+ for (size_t i = 0; i < ids.size(); ++i) {
+ base::Value event = CreateEvent(ids[i], i);
+ events.Append(std::move(event));
+ }
+
+ base::Value::Dict report = RealtimeReportingJobConfiguration::BuildReport(
+ std::move(events), std::move(context));
+ configuration_->AddReport(std::move(report));
+ }
+
+ protected:
+ static base::Value CreateEvent(std::string& event_id, int type) {
+ base::Value event(base::Value::Type::DICTIONARY);
+ event.SetStringKey(kAppPackage, kPackage);
+ event.SetIntKey(kEventType, type);
+ base::Value wrapper(base::Value::Type::DICTIONARY);
+ wrapper.SetKey(kAppInstallEvent, std::move(event));
+ wrapper.SetStringKey(kEventId, event_id);
+ return wrapper;
+ }
+
+ static base::Value::Dict CreateResponse(
+ const std::set<std::string>& success_ids,
+ const std::set<std::string>& failed_ids,
+ const std::set<std::string>& permanent_failed_ids) {
+ base::Value::Dict response;
+ if (success_ids.size()) {
+ base::Value* list =
+ response.Set(RealtimeReportingJobConfiguration::kUploadedEventsKey,
+ base::Value(base::Value::Type::LIST));
+ for (const auto& id : success_ids) {
+ list->Append(id);
+ }
+ }
+ if (failed_ids.size()) {
+ base::Value* list =
+ response.Set(RealtimeReportingJobConfiguration::kFailedUploadsKey,
+ base::Value(base::Value::Type::LIST));
+ for (const auto& id : failed_ids) {
+ base::Value failure(base::Value::Type::DICTIONARY);
+ failure.SetStringKey(kEventId, id);
+ failure.SetIntKey(kStatusCode, 8 /* RESOURCE_EXHAUSTED */);
+ list->Append(std::move(failure));
+ }
+ }
+ if (permanent_failed_ids.size()) {
+ base::Value* list = response.Set(
+ RealtimeReportingJobConfiguration::kPermanentFailedUploadsKey,
+ base::Value(base::Value::Type::LIST));
+ for (const auto& id : permanent_failed_ids) {
+ base::Value failure(base::Value::Type::DICTIONARY);
+ failure.SetStringKey(kEventId, id);
+ failure.SetIntKey(kStatusCode, 9 /* FAILED_PRECONDITION */);
+ list->Append(std::move(failure));
+ }
+ }
+
+ return response;
+ }
+
+ static std::string CreateResponseString(const base::Value::Dict& response) {
+ std::string response_string;
+ base::JSONWriter::Write(response, &response_string);
+ return response_string;
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ StrictMock<MockJobCreationHandler> job_creation_handler_;
+ FakeDeviceManagementService service_{&job_creation_handler_};
+ MockCloudPolicyClient client_;
+ StrictMock<MockCallbackObserver> callback_observer_;
+ DeviceManagementService::Job job_;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
+ class ScopedFakeSerialNumber {
+ public:
+ explicit ScopedFakeSerialNumber(
+ chromeos::system::ScopedFakeStatisticsProvider*
+ fake_statistics_provider) {
+ // The fake serial number must be set before |configuration_| is
+ // constructed below.
+ fake_statistics_provider->SetMachineStatistic(
+ chromeos::system::kSerialNumberKeyForTest, "fake_serial_number");
+ }
+ };
+ ScopedFakeSerialNumber fake_serial_number_;
+#endif
+ std::unique_ptr<RealtimeReportingJobConfiguration> configuration_;
+};
+
+TEST_F(RealtimeReportingJobConfigurationTest, ValidatePayload) {
+ absl::optional<base::Value> payload =
+ base::JSONReader::Read(configuration_->GetPayload());
+ EXPECT_TRUE(payload.has_value());
+ EXPECT_EQ(kDummyToken, *payload->FindStringPath(
+ ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetDMTokenPath()));
+ EXPECT_EQ(
+ client_.client_id(),
+ *payload->FindStringPath(ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetClientIdPath()));
+ EXPECT_EQ(GetOSUsername(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetMachineUserPath()));
+ EXPECT_EQ(version_info::GetVersionNumber(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetChromeVersionPath()));
+ EXPECT_EQ(GetOSPlatform(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSPlatformPath()));
+ EXPECT_EQ(GetOSVersion(),
+ *payload->FindStringPath(
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::
+ GetOSVersionPath()));
+ EXPECT_FALSE(GetDeviceName().empty());
+ EXPECT_EQ(GetDeviceName(), *payload->FindStringPath(
+ ReportingJobConfigurationBase::
+ DeviceDictionaryBuilder::GetNamePath()));
+
+ base::Value* events =
+ payload->FindListKey(RealtimeReportingJobConfiguration::kEventListKey);
+ EXPECT_EQ(ids.size(), events->GetListDeprecated().size());
+ int i = -1;
+ for (const auto& event : events->GetListDeprecated()) {
+ auto* id = event.FindStringKey(kEventId);
+ EXPECT_EQ(ids[++i], *id);
+ auto type = event.FindKey(kAppInstallEvent)->FindIntKey(kEventType);
+ EXPECT_EQ(i, *type);
+ }
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_Success) {
+ base::Value::Dict response = CreateResponse({ids[0], ids[1], ids[2]}, {}, {});
+ EXPECT_CALL(callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_SUCCESS, net::OK,
+ testing::Eq(testing::ByRef(response))));
+ configuration_->OnURLLoadComplete(&job_, net::OK,
+ DeviceManagementService::kSuccess,
+ CreateResponseString(response));
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_NetError) {
+ int net_error = net::ERR_CONNECTION_RESET;
+ EXPECT_CALL(callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_REQUEST_FAILED, net_error,
+ testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(&job_, net_error, 0, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest,
+ OnURLLoadComplete_InvalidRequest) {
+ EXPECT_CALL(callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_REQUEST_INVALID, net::OK,
+ testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(
+ &job_, net::OK, DeviceManagementService::kInvalidArgument, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest,
+ OnURLLoadComplete_InvalidDMToken) {
+ EXPECT_CALL(
+ callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID,
+ net::OK, testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(
+ &job_, net::OK, DeviceManagementService::kInvalidAuthCookieOrDMToken, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_NotSupported) {
+ EXPECT_CALL(
+ callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED,
+ net::OK, testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(
+ &job_, net::OK, DeviceManagementService::kDeviceManagementNotAllowed, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_TempError) {
+ EXPECT_CALL(callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_TEMPORARY_UNAVAILABLE, net::OK,
+ testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(
+ &job_, net::OK, DeviceManagementService::kServiceUnavailable, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_UnknownError) {
+ EXPECT_CALL(callback_observer_,
+ OnURLLoadComplete(&job_, DM_STATUS_HTTP_STATUS_ERROR, net::OK,
+ testing::Eq(absl::nullopt)));
+ configuration_->OnURLLoadComplete(&job_, net::OK,
+ DeviceManagementService::kInvalidURL, "");
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, ShouldRetry_Success) {
+ auto response_string =
+ CreateResponseString(CreateResponse({ids[0], ids[1], ids[2]}, {}, {}));
+ auto should_retry = configuration_->ShouldRetry(
+ DeviceManagementService::kSuccess, response_string);
+ EXPECT_EQ(DeviceManagementService::Job::NO_RETRY, should_retry);
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, ShouldRetry_PartialFalure) {
+ // Batch failures are retried
+ auto response_string =
+ CreateResponseString(CreateResponse({ids[0], ids[1]}, {ids[2]}, {}));
+ auto should_retry = configuration_->ShouldRetry(
+ DeviceManagementService::kSuccess, response_string);
+ EXPECT_EQ(DeviceManagementService::Job::RETRY_WITH_DELAY, should_retry);
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, ShouldRetry_PermanentFailure) {
+ // Permanent failures are not retried.
+ auto response_string =
+ CreateResponseString(CreateResponse({ids[0], ids[1]}, {}, {ids[2]}));
+ auto should_retry = configuration_->ShouldRetry(
+ DeviceManagementService::kSuccess, response_string);
+ EXPECT_EQ(DeviceManagementService::Job::NO_RETRY, should_retry);
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnBeforeRetry_HttpFailure) {
+ // No change should be made to the payload in this case.
+ auto original_payload = configuration_->GetPayload();
+ configuration_->OnBeforeRetry(DeviceManagementService::kServiceUnavailable,
+ "");
+ EXPECT_EQ(original_payload, configuration_->GetPayload());
+}
+
+TEST_F(RealtimeReportingJobConfigurationTest, OnBeforeRetry_PartialBatch) {
+ // Only those events whose ids are in failed_uploads should be in the payload
+ // after the OnBeforeRetry call.
+ auto response_string =
+ CreateResponseString(CreateResponse({ids[0]}, {ids[1]}, {ids[2]}));
+ configuration_->OnBeforeRetry(DeviceManagementService::kSuccess,
+ response_string);
+ absl::optional<base::Value> payload =
+ base::JSONReader::Read(configuration_->GetPayload());
+ base::Value* events =
+ payload->FindListKey(RealtimeReportingJobConfiguration::kEventListKey);
+ EXPECT_EQ(1u, events->GetListDeprecated().size());
+ auto& event = events->GetListDeprecated()[0];
+ EXPECT_EQ(ids[1], *event.FindStringKey(kEventId));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.cc b/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.cc
new file mode 100644
index 00000000000..022064e9750
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.cc
@@ -0,0 +1,303 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/reporting_job_configuration_base.h"
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/dm_auth.h"
+#include "components/policy/policy_export.h"
+#include "components/version_info/version_info.h"
+#include "google_apis/google_api_keys.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+// Strings for |DeviceDictionaryBuilder|.
+const char
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::kDeviceKey[] =
+ "device";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kDMToken[] =
+ "dmToken";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kClientId[] =
+ "clientId";
+const char
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::kOSVersion[] =
+ "osVersion";
+const char
+ ReportingJobConfigurationBase::DeviceDictionaryBuilder::kOSPlatform[] =
+ "osPlatform";
+const char ReportingJobConfigurationBase::DeviceDictionaryBuilder::kName[] =
+ "name";
+
+// static
+base::Value::Dict
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::BuildDeviceDictionary(
+ const std::string& dm_token,
+ const std::string& client_id) {
+ base::Value::Dict device_dictionary;
+ device_dictionary.Set(kDMToken, dm_token);
+ device_dictionary.Set(kClientId, client_id);
+ device_dictionary.Set(kOSVersion, GetOSVersion());
+ device_dictionary.Set(kOSPlatform, GetOSPlatform());
+ device_dictionary.Set(kName, GetDeviceName());
+ return device_dictionary;
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetDMTokenPath() {
+ return GetStringPath(kDMToken);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetClientIdPath() {
+ return GetStringPath(kClientId);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetOSVersionPath() {
+ return GetStringPath(kOSVersion);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetOSPlatformPath() {
+ return GetStringPath(kOSPlatform);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetNamePath() {
+ return GetStringPath(kName);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::DeviceDictionaryBuilder::GetStringPath(
+ base::StringPiece leaf_name) {
+ return base::JoinString({kDeviceKey, leaf_name}, ".");
+}
+
+// Strings for |BrowserDictionaryBuilder|.
+const char
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::kBrowserKey[] =
+ "browser";
+const char
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::kBrowserId[] =
+ "browserId";
+const char
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::kUserAgent[] =
+ "userAgent";
+const char
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::kMachineUser[] =
+ "machineUser";
+const char
+ ReportingJobConfigurationBase::BrowserDictionaryBuilder::kChromeVersion[] =
+ "chromeVersion";
+
+// static
+base::Value
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::BuildBrowserDictionary(
+ bool include_device_info) {
+ base::Value browser_dictionary{base::Value::Type::DICTIONARY};
+
+ base::FilePath browser_id;
+ if (base::PathService::Get(base::DIR_EXE, &browser_id)) {
+ browser_dictionary.SetStringKey(kBrowserId, browser_id.AsUTF8Unsafe());
+ }
+
+ if (include_device_info)
+ browser_dictionary.SetStringKey(kMachineUser, GetOSUsername());
+
+ browser_dictionary.SetStringKey(kChromeVersion,
+ version_info::GetVersionNumber());
+ return browser_dictionary;
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetBrowserIdPath() {
+ return GetStringPath(kBrowserId);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetUserAgentPath() {
+ return GetStringPath(kUserAgent);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetMachineUserPath() {
+ return GetStringPath(kMachineUser);
+}
+
+// static
+std::string ReportingJobConfigurationBase::BrowserDictionaryBuilder::
+ GetChromeVersionPath() {
+ return GetStringPath(kChromeVersion);
+}
+
+// static
+std::string
+ReportingJobConfigurationBase::BrowserDictionaryBuilder::GetStringPath(
+ base::StringPiece leaf_name) {
+ return base::JoinString({kBrowserKey, leaf_name}, ".");
+}
+
+std::string ReportingJobConfigurationBase::GetPayload() {
+ // Move context keys to the payload.
+ if (context_.has_value()) {
+ payload_.Merge(*context_);
+ context_.reset();
+ }
+
+ // Allow children to mutate the payload if need be.
+ UpdatePayloadBeforeGetInternal();
+
+ std::string payload_string;
+ base::JSONWriter::Write(payload_, &payload_string);
+ return payload_string;
+}
+
+std::string ReportingJobConfigurationBase::GetUmaName() {
+ return GetUmaString() + GetJobTypeAsString(GetType());
+}
+
+DeviceManagementService::Job::RetryMethod
+ReportingJobConfigurationBase::ShouldRetry(int response_code,
+ const std::string& response_body) {
+ // If the request wasn't successfully processed at all, resending it won't do
+ // anything. Don't retry.
+ if (response_code != DeviceManagementService::kSuccess) {
+ return DeviceManagementService::Job::NO_RETRY;
+ }
+
+ // Allow child to determine if any portion of the message should be retried.
+ return ShouldRetryInternal(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnBeforeRetry(
+ int response_code,
+ const std::string& response_body) {
+ // If the request wasn't successful, don't try to retry.
+ if (response_code != DeviceManagementService::kSuccess) {
+ return;
+ }
+
+ OnBeforeRetryInternal(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnURLLoadComplete(
+ DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) {
+ absl::optional<base::Value> response = base::JSONReader::Read(response_body);
+
+ // Parse the response even if |response_code| is not a success since the
+ // response data may contain an error message.
+ // Map the net_error/response_code to a DeviceManagementStatus.
+ DeviceManagementStatus code;
+ if (net_error != net::OK) {
+ code = DM_STATUS_REQUEST_FAILED;
+ } else {
+ switch (response_code) {
+ case DeviceManagementService::kSuccess:
+ code = DM_STATUS_SUCCESS;
+ break;
+ case DeviceManagementService::kInvalidArgument:
+ code = DM_STATUS_REQUEST_INVALID;
+ break;
+ case DeviceManagementService::kInvalidAuthCookieOrDMToken:
+ code = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+ break;
+ case DeviceManagementService::kDeviceManagementNotAllowed:
+ code = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
+ break;
+ default:
+ // Handle all unknown 5xx HTTP error codes as temporary and any other
+ // unknown error as one that needs more time to recover.
+ if (response_code >= 500 && response_code <= 599)
+ code = DM_STATUS_TEMPORARY_UNAVAILABLE;
+ else
+ code = DM_STATUS_HTTP_STATUS_ERROR;
+ break;
+ }
+ }
+
+ auto response_dict = response && response->is_dict()
+ ? absl::make_optional(std::move(response->GetDict()))
+ : absl::nullopt;
+ std::move(callback_).Run(job, code, net_error, std::move(response_dict));
+}
+
+DeviceManagementService::Job::RetryMethod
+ReportingJobConfigurationBase::ShouldRetryInternal(
+ int response_code,
+ const std::string& response_body) {
+ return JobConfigurationBase::ShouldRetry(response_code, response_body);
+}
+
+void ReportingJobConfigurationBase::OnBeforeRetryInternal(
+ int response_code,
+ const std::string& response_body) {}
+
+void ReportingJobConfigurationBase::UpdatePayloadBeforeGetInternal() {}
+
+GURL ReportingJobConfigurationBase::GetURL(int last_error) const {
+ return GURL(server_url_);
+}
+
+ReportingJobConfigurationBase::ReportingJobConfigurationBase(
+ JobType type,
+ scoped_refptr<network::SharedURLLoaderFactory> factory,
+ CloudPolicyClient* client,
+ const std::string& server_url,
+ bool include_device_info,
+ UploadCompleteCallback callback)
+ : JobConfigurationBase(type,
+ DMAuth::FromDMToken(client->dm_token()),
+ /*oauth_token=*/absl::nullopt,
+ factory),
+ callback_(std::move(callback)),
+ server_url_(server_url) {
+ DCHECK(GetAuth().has_dm_token());
+ InitializePayload(client, include_device_info);
+}
+
+ReportingJobConfigurationBase::~ReportingJobConfigurationBase() = default;
+
+void ReportingJobConfigurationBase::InitializePayload(
+ CloudPolicyClient* client,
+ bool include_device_info) {
+ AddParameter("key", google_apis::GetAPIKey());
+
+ if (include_device_info) {
+ payload_.Set(DeviceDictionaryBuilder::kDeviceKey,
+ DeviceDictionaryBuilder::BuildDeviceDictionary(
+ client->dm_token(), client->client_id()));
+ }
+ payload_.Set(
+ BrowserDictionaryBuilder::kBrowserKey,
+ BrowserDictionaryBuilder::BuildBrowserDictionary(include_device_info));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.h b/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.h
new file mode 100644
index 00000000000..46c568a3c2c
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/reporting_job_configuration_base.h
@@ -0,0 +1,168 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+class CloudPolicyClient;
+
+// Base for common elements in JobConfigurations for the Reporting pipeline.
+// Ensures the following elements are added to each request.
+// Device dictionary:
+// "device": {
+// "dmToken": "abcdef1234",
+// "clientId": "abcdef1234",
+// "osVersion": "10.0.0.0",
+// "osPlatform": "Windows",
+// "name": "George"
+// }
+//
+// Browser dictionary:
+// "browser": {
+// "browserId": "abcdef1234",
+// "chromeVersion": "10.0.0.0",
+// "machineUser": "abcdef1234"
+// }
+class POLICY_EXPORT ReportingJobConfigurationBase
+ : public JobConfigurationBase {
+ public:
+ // Callback used once the job is complete.
+ using UploadCompleteCallback =
+ base::OnceCallback<void(DeviceManagementService::Job* job,
+ DeviceManagementStatus code,
+ int net_error,
+ absl::optional<base::Value::Dict>)>;
+
+ // Builds a Device dictionary for uploading information about the device to
+ // the server.
+ class POLICY_EXPORT DeviceDictionaryBuilder {
+ public:
+ // Dictionary Key Name
+ static const char kDeviceKey[];
+
+ static base::Value::Dict BuildDeviceDictionary(
+ const std::string& dm_token,
+ const std::string& client_id);
+
+ static std::string GetDMTokenPath();
+ static std::string GetClientIdPath();
+ static std::string GetOSVersionPath();
+ static std::string GetOSPlatformPath();
+ static std::string GetNamePath();
+
+ private:
+ static std::string GetStringPath(base::StringPiece leaf_name);
+
+ // Keys used in Device dictionary.
+ static const char kDMToken[];
+ static const char kClientId[];
+ static const char kOSVersion[];
+ static const char kOSPlatform[];
+ static const char kName[];
+ };
+
+ // Builds a Browser dictionary for uploading information about the browser to
+ // the server.
+ class POLICY_EXPORT BrowserDictionaryBuilder {
+ public:
+ // Dictionary Key Name
+ static const char kBrowserKey[];
+
+ static base::Value BuildBrowserDictionary(bool include_device_info);
+
+ static std::string GetBrowserIdPath();
+ static std::string GetUserAgentPath();
+ static std::string GetMachineUserPath();
+ static std::string GetChromeVersionPath();
+
+ private:
+ static std::string GetStringPath(base::StringPiece leaf_name);
+
+ // Keys used in Browser dictionary.
+ static const char kBrowserId[];
+ static const char kUserAgent[];
+ static const char kMachineUser[];
+ static const char kChromeVersion[];
+ };
+
+ ReportingJobConfigurationBase(const ReportingJobConfigurationBase&) = delete;
+ ReportingJobConfigurationBase& operator=(
+ const ReportingJobConfigurationBase&) = delete;
+
+ // DeviceManagementService::JobConfiguration
+ std::string GetPayload() override;
+ std::string GetUmaName() override;
+ DeviceManagementService::Job::RetryMethod ShouldRetry(
+ int response_code,
+ const std::string& response_body) override;
+ void OnBeforeRetry(int reponse_code,
+ const std::string& response_body) override;
+ void OnURLLoadComplete(DeviceManagementService::Job* job,
+ int net_error,
+ int response_code,
+ const std::string& response_body) override;
+ GURL GetURL(int last_error) const override;
+
+ protected:
+ // |type| indicates which type of job.
+ // |callback| will be called on upload completion.
+ ReportingJobConfigurationBase(
+ JobType type,
+ scoped_refptr<network::SharedURLLoaderFactory> factory,
+ CloudPolicyClient* client,
+ const std::string& server_url,
+ bool include_device_info,
+ UploadCompleteCallback callback);
+ ~ReportingJobConfigurationBase() override;
+
+ // Allows children to determine if a retry should be done.
+ virtual DeviceManagementService::Job::RetryMethod ShouldRetryInternal(
+ int response_code,
+ const std::string& response_body);
+
+ // Allows children to perform actions before a retry.
+ virtual void OnBeforeRetryInternal(int response_code,
+ const std::string& response_body);
+
+ // Allows children to provide final mutations to |payload_| before completion
+ // of |GetPayload| call.
+ virtual void UpdatePayloadBeforeGetInternal();
+
+ // Returns an identifying string for UMA.
+ virtual std::string GetUmaString() const = 0;
+
+ base::Value::Dict payload_;
+
+ // Available to set additional fields by the child. An example of a context
+ // being generated can be seen with the ::reporting::GetContext function. Once
+ // |GetPayload| is called, |context_| will be merged into the payload and
+ // reset.
+ absl::optional<base::Value::Dict> context_;
+
+ UploadCompleteCallback callback_;
+
+ private:
+ // Initializes request payload. If |include_device_info| is false, the
+ // "device" and "browser.machineUser" fields (see comment at the top of the
+ // file) are excluded from the payload.
+ void InitializePayload(CloudPolicyClient* client, bool include_device_info);
+
+ const std::string server_url_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_REPORTING_JOB_CONFIGURATION_BASE_H_
diff --git a/chromium/components/policy/core/common/cloud/resource_cache.cc b/chromium/components/policy/core/common/cloud/resource_cache.cc
new file mode 100644
index 00000000000..8fa8abad785
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/resource_cache.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/resource_cache.h"
+
+#include "base/base64url.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+
+namespace policy {
+
+namespace {
+
+// Decodes all elements of |input| from base64url format and stores the decoded
+// elements in |output|.
+bool Base64UrlEncode(const std::set<std::string>& input,
+ std::set<std::string>* output) {
+ output->clear();
+ for (const auto& plain : input) {
+ if (plain.empty()) {
+ NOTREACHED();
+ output->clear();
+ return false;
+ }
+
+ std::string encoded;
+ base::Base64UrlEncode(plain, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+ &encoded);
+
+ output->insert(encoded);
+ }
+ return true;
+}
+
+} // namespace
+
+ResourceCache::ResourceCache(
+ const base::FilePath& cache_dir,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ absl::optional<int64_t> max_cache_size)
+ : cache_dir_(cache_dir),
+ task_runner_(task_runner),
+ max_cache_size_(max_cache_size) {
+ // Safe to post this without a WeakPtr because this class must be destructed
+ // on the same thread.
+ if (max_cache_size_.has_value()) {
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&ResourceCache::InitCurrentCacheSize,
+ base::Unretained(this)));
+ }
+}
+
+ResourceCache::~ResourceCache() {
+ // No RunsTasksInCurrentSequence() check to avoid unit tests failures.
+ // In unit tests the browser process instance is deleted only after test ends
+ // and test task scheduler is shutted down. Therefore we need to delete some
+ // components of BrowserPolicyConnector (ResourceCache and
+ // CloudExternalDataManagerBase::Backend) manually when task runner doesn't
+ // accept new tasks (DeleteSoon in this case). This leads to the situation
+ // when this destructor is called not on |task_runner|.
+}
+
+base::FilePath ResourceCache::Store(const std::string& key,
+ const std::string& subkey,
+ const std::string& data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePath subkey_path;
+ if (!VerifyKeyPathAndGetSubkeyPath(key, true, subkey, &subkey_path))
+ return base::FilePath();
+ int64_t size = base::checked_cast<int64_t>(data.size());
+ if (max_cache_size_.has_value() &&
+ current_cache_size_ - GetCacheDirectoryOrFileSize(subkey_path) + size >
+ max_cache_size_.value()) {
+ LOG(ERROR) << "Data (" << key << ", " << subkey << ") with size " << size
+ << " bytes doesn't fit in cache, left size: "
+ << max_cache_size_.value() - current_cache_size_ << " bytes";
+ return base::FilePath();
+ }
+ if (!WriteCacheFile(subkey_path, data))
+ return base::FilePath();
+ return subkey_path;
+}
+
+base::FilePath ResourceCache::Load(const std::string& key,
+ const std::string& subkey,
+ std::string* data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePath subkey_path;
+ // Only read from |subkey_path| if it is not a symlink.
+ if (!VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path) ||
+ base::IsLink(subkey_path)) {
+ return base::FilePath();
+ }
+ data->clear();
+ if (!base::ReadFileToString(subkey_path, data))
+ return base::FilePath();
+ return subkey_path;
+}
+
+void ResourceCache::LoadAllSubkeys(
+ const std::string& key,
+ std::map<std::string, std::string>* contents) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ contents->clear();
+ base::FilePath key_path;
+ if (!VerifyKeyPath(key, false, &key_path))
+ return;
+
+ base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ const std::string encoded_subkey = path.BaseName().MaybeAsASCII();
+ std::string subkey;
+ std::string data;
+ // Only read from |subkey_path| if it is not a symlink and its name is
+ // a base64-encoded string.
+ if (!base::IsLink(path) &&
+ base::Base64UrlDecode(encoded_subkey,
+ base::Base64UrlDecodePolicy::REQUIRE_PADDING,
+ &subkey) &&
+ !subkey.empty() && base::ReadFileToString(path, &data)) {
+ (*contents)[subkey].swap(data);
+ }
+ }
+}
+
+void ResourceCache::Delete(const std::string& key, const std::string& subkey) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePath subkey_path;
+ if (VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path))
+ DeleteCacheFile(subkey_path, false);
+ base::FilePath key_path;
+ // DeleteCacheFile() does nothing if the directory given to it is not empty.
+ // Hence, the call below deletes the directory representing |key| if its last
+ // subkey was just removed and does nothing otherwise.
+ if (VerifyKeyPath(key, false, &key_path))
+ DeleteCacheFile(key_path, false);
+}
+
+void ResourceCache::Clear(const std::string& key) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePath key_path;
+ if (VerifyKeyPath(key, false, &key_path))
+ DeleteCacheFile(key_path, true);
+}
+
+void ResourceCache::FilterSubkeys(const std::string& key,
+ const SubkeyFilter& test) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ base::FilePath key_path;
+ if (!VerifyKeyPath(key, false, &key_path))
+ return;
+
+ base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
+ for (base::FilePath subkey_path = enumerator.Next();
+ !subkey_path.empty(); subkey_path = enumerator.Next()) {
+ std::string subkey;
+ // Delete files with invalid names, and files whose subkey doesn't pass the
+ // filter.
+ if (!base::Base64UrlDecode(subkey_path.BaseName().MaybeAsASCII(),
+ base::Base64UrlDecodePolicy::REQUIRE_PADDING,
+ &subkey) ||
+ subkey.empty() || test.Run(subkey)) {
+ DeleteCacheFile(subkey_path, true);
+ }
+ }
+
+ // Delete() does nothing if the directory given to it is not empty. Hence, the
+ // call below deletes the directory representing |key| if all of its subkeys
+ // were just removed and does nothing otherwise.
+ DeleteCacheFile(key_path, false);
+}
+
+void ResourceCache::PurgeOtherKeys(const std::set<std::string>& keys_to_keep) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ std::set<std::string> encoded_keys_to_keep;
+ if (!Base64UrlEncode(keys_to_keep, &encoded_keys_to_keep))
+ return;
+
+ base::FileEnumerator enumerator(
+ cache_dir_, false, base::FileEnumerator::DIRECTORIES);
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ const std::string name(path.BaseName().MaybeAsASCII());
+ if (encoded_keys_to_keep.find(name) == encoded_keys_to_keep.end())
+ DeleteCacheFile(path, true);
+ }
+}
+
+void ResourceCache::PurgeOtherSubkeys(
+ const std::string& key,
+ const std::set<std::string>& subkeys_to_keep) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePath key_path;
+ if (!VerifyKeyPath(key, false, &key_path))
+ return;
+
+ std::set<std::string> encoded_subkeys_to_keep;
+ if (!Base64UrlEncode(subkeys_to_keep, &encoded_subkeys_to_keep))
+ return;
+
+ base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
+ for (base::FilePath path = enumerator.Next(); !path.empty();
+ path = enumerator.Next()) {
+ const std::string name(path.BaseName().MaybeAsASCII());
+ if (encoded_subkeys_to_keep.find(name) == encoded_subkeys_to_keep.end())
+ DeleteCacheFile(path, false);
+ }
+ // Delete() does nothing if the directory given to it is not empty. Hence, the
+ // call below deletes the directory representing |key| if all of its subkeys
+ // were just removed and does nothing otherwise.
+ DeleteCacheFile(key_path, false);
+}
+
+bool ResourceCache::VerifyKeyPath(const std::string& key,
+ bool allow_create,
+ base::FilePath* path) {
+ if (key.empty()) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string encoded;
+ base::Base64UrlEncode(key, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+ &encoded);
+
+ *path = cache_dir_.AppendASCII(encoded);
+ return allow_create ? base::CreateDirectory(*path) :
+ base::DirectoryExists(*path);
+}
+
+bool ResourceCache::VerifyKeyPathAndGetSubkeyPath(const std::string& key,
+ bool allow_create_key,
+ const std::string& subkey,
+ base::FilePath* path) {
+ if (subkey.empty()) {
+ NOTREACHED();
+ return false;
+ }
+
+ base::FilePath key_path;
+ if (!VerifyKeyPath(key, allow_create_key, &key_path))
+ return false;
+
+ std::string encoded;
+ base::Base64UrlEncode(subkey, base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+ &encoded);
+
+ *path = key_path.AppendASCII(encoded);
+ return true;
+}
+
+void ResourceCache::InitCurrentCacheSize() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ current_cache_size_ = GetCacheDirectoryOrFileSize(cache_dir_);
+}
+
+bool ResourceCache::WriteCacheFile(const base::FilePath& path,
+ const std::string& data) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(cache_dir_.IsParent(path));
+ bool success = DeleteCacheFile(path, false);
+ int size = base::checked_cast<int>(data.size());
+ int bytes_written = base::WriteFile(path, data.data(), size);
+ if (max_cache_size_.has_value())
+ current_cache_size_ += bytes_written;
+ return success && bytes_written == size;
+}
+
+bool ResourceCache::DeleteCacheFile(const base::FilePath& path,
+ bool recursive) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(cache_dir_.IsParent(path));
+ int64_t size = GetCacheDirectoryOrFileSize(path);
+ bool success;
+ if (recursive)
+ success = base::DeletePathRecursively(path);
+ else
+ success = base::DeleteFile(path);
+ if (success && max_cache_size_.has_value())
+ current_cache_size_ -= size;
+ return success;
+}
+
+int64_t ResourceCache::GetCacheDirectoryOrFileSize(
+ const base::FilePath& path) const {
+ DCHECK(path == cache_dir_ || cache_dir_.IsParent(path));
+ if (base::IsLink(path)) {
+ DLOG(WARNING) << "Symlink " << path.LossyDisplayName()
+ << " detected in cache directory";
+ return 0;
+ }
+ int64_t path_size = 0;
+ if (base::DirectoryExists(path)) {
+ int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
+ base::FileEnumerator enumerator(path, /* recursive */ false, types);
+ for (base::FilePath child_path = enumerator.Next(); !child_path.empty();
+ child_path = enumerator.Next()) {
+ path_size += GetCacheDirectoryOrFileSize(child_path);
+ }
+ } else if (!base::GetFileSize(path, &path_size)) {
+ path_size = 0;
+ }
+ return path_size;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/resource_cache.h b/chromium/components/policy/core/common/cloud/resource_cache.h
new file mode 100644
index 00000000000..a9842e15298
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/resource_cache.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_RESOURCE_CACHE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_RESOURCE_CACHE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+// Manages storage of data at a given path. The data is keyed by a key and
+// a subkey, and can be queried by (key, subkey) or (key) lookups.
+// The contents of the cache have to be manually cleared using Delete() or
+// Purge*().
+// The class can be instantiated on any thread but from then on, it must be
+// accessed via the |task_runner| only. The |task_runner| must support file I/O.
+// The class needs to have exclusive control on cache directory since it should
+// know about all files changes for correct recalculating cache directory size.
+class POLICY_EXPORT ResourceCache {
+ public:
+ ResourceCache(const base::FilePath& cache_path,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const absl::optional<int64_t> max_cache_size);
+ ResourceCache(const ResourceCache&) = delete;
+ ResourceCache& operator=(const ResourceCache&) = delete;
+ virtual ~ResourceCache();
+
+ // Stores |data| under (key, subkey). Returns file path if the store
+ // succeeded, and empty FilePath otherwise.
+ base::FilePath Store(const std::string& key,
+ const std::string& subkey,
+ const std::string& data);
+
+ // Loads the contents of (key, subkey) into |data| and returns true. Returns
+ // empty FilePath if (key, subkey) isn't found or if there is a problem
+ // reading the data, and file path otherwise.
+ base::FilePath Load(const std::string& key,
+ const std::string& subkey,
+ std::string* data);
+
+ // Loads all the subkeys of |key| into |contents|.
+ void LoadAllSubkeys(const std::string& key,
+ std::map<std::string, std::string>* contents);
+
+ // Deletes (key, subkey).
+ void Delete(const std::string& key, const std::string& subkey);
+
+ // Deletes all the subkeys of |key|.
+ void Clear(const std::string& key);
+
+ // Deletes the subkeys of |key| for which the |filter| returns true.
+ typedef base::RepeatingCallback<bool(const std::string&)> SubkeyFilter;
+ void FilterSubkeys(const std::string& key, const SubkeyFilter& filter);
+
+ // Deletes all keys not in |keys_to_keep|, along with their subkeys.
+ void PurgeOtherKeys(const std::set<std::string>& keys_to_keep);
+
+ // Deletes all the subkeys of |key| not in |subkeys_to_keep|.
+ void PurgeOtherSubkeys(const std::string& key,
+ const std::set<std::string>& subkeys_to_keep);
+
+ private:
+ // Points |path| at the cache directory for |key| and returns whether the
+ // directory exists. If |allow_create| is |true|, the directory is created if
+ // it did not exist yet.
+ bool VerifyKeyPath(const std::string& key,
+ bool allow_create,
+ base::FilePath* path);
+
+ // Points |subkey_path| at the file in which data for (key, subkey) should be
+ // stored and returns whether the parent directory of this file exists. If
+ // |allow_create_key| is |true|, the directory is created if it did not exist
+ // yet. This method does not check whether the file at |subkey_path| exists or
+ // not.
+ bool VerifyKeyPathAndGetSubkeyPath(const std::string& key,
+ bool allow_create_key,
+ const std::string& subkey,
+ base::FilePath* subkey_path);
+
+ // Initializes |current_cache_size_| with the size of cache directory.
+ // It's called once from constructor and is executed in provided
+ // |task_runner_|.
+ void InitCurrentCacheSize();
+
+ // Writes the given data into the file in the cache directory and updates
+ // |current_cache_size_| accordingly.
+ // Deletes the file before writing to it. This ensures that the write does not
+ // follow a symlink planted at |subkey_path|, clobbering a file outside the
+ // cache directory. The mechanism is meant to foil file-system-level attacks
+ // where a symlink is planted in the cache directory before Chrome has
+ // started. An attacker controlling a process running concurrently with Chrome
+ // would be able to race against the protection by re-creating the symlink
+ // between these two calls. There is nothing in file_util that could be used
+ // to protect against such races, especially as the cache is cross-platform
+ // and therefore cannot use any POSIX-only tricks.
+ // Note that |path| must belong to |cache_dir_|.
+ bool WriteCacheFile(const base::FilePath& path, const std::string& data);
+
+ // Deletes the given path in the cache directory and updates
+ // |current_cache_size_| accordingly.
+ // Note that |path| must belong to |cache_dir_|.
+ bool DeleteCacheFile(const base::FilePath& path, bool recursive);
+
+ // Returns the size of given directory or file in the cache directory skipping
+ // symlinks or 0 if any error occurred.
+ // We couldn't use |base::ComputeDirectorySize| here because it doesn't allow
+ // to skip symlinks.
+ // Skipping symlinks is important here to prevent exploit which puts symlink
+ // to e.g. root directory and freezes this thread since traversing the whole
+ // filesystem takes a while.
+ // Note that |path| must belong to |cache_dir_|.
+ int64_t GetCacheDirectoryOrFileSize(const base::FilePath& path) const;
+
+ base::FilePath cache_dir_;
+
+ // Task runner that |this| runs on.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Maximum size of the cache directory.
+ const absl::optional<int64_t> max_cache_size_;
+
+ // Note that this variable could be created on any thread, but is modified
+ // only on the |task_runner_| thread.
+ int64_t current_cache_size_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_RESOURCE_CACHE_H_
diff --git a/chromium/components/policy/core/common/cloud/resource_cache_unittest.cc b/chromium/components/policy/core/common/cloud/resource_cache_unittest.cc
new file mode 100644
index 00000000000..92af374bca0
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/resource_cache_unittest.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/resource_cache.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kKey1[] = "key 1";
+const char kKey2[] = "key 2";
+const char kKey3[] = "key 3";
+const char kSubA[] = "a";
+const char kSubB[] = "bb";
+const char kSubC[] = "ccc";
+const char kSubD[] = "dddd";
+const char kSubE[] = "eeeee";
+
+const char kData0[] = "{ \"key\": \"value\" }";
+const char kData1[] = "{}";
+
+const int kMaxCacheSize = 1024 * 10;
+const std::string kData1Kb = std::string(1024, ' ');
+const std::string kData2Kb = std::string(1024 * 2, ' ');
+const std::string kData9Kb = std::string(1024 * 9, ' ');
+const std::string kData10Kb = std::string(1024 * 10, ' ');
+const std::string kData9KbUpdated = std::string(1024 * 9, '*');
+
+bool Matches(const std::string& expected, const std::string& subkey) {
+ return subkey == expected;
+}
+
+} // namespace
+
+class ResourceCacheTest : public testing::Test {
+ protected:
+ void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ void TearDown() override { task_environment_.RunUntilIdle(); }
+
+ base::test::TaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(ResourceCacheTest, StoreAndLoad) {
+ ResourceCache cache(temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ /* max_cache_size */ absl::nullopt);
+
+ // No data initially.
+ std::string data;
+ EXPECT_TRUE(cache.Load(kKey1, kSubA, &data).empty());
+
+ // Store some data and load it.
+ base::FilePath file_path = cache.Store(kKey1, kSubA, kData0);
+ EXPECT_FALSE(file_path.empty());
+ std::string file_content;
+ EXPECT_TRUE(base::ReadFileToString(file_path, &file_content));
+ EXPECT_EQ(kData0, file_content);
+
+ file_path = cache.Load(kKey1, kSubA, &data);
+ EXPECT_FALSE(file_path.empty());
+ file_content.clear();
+ EXPECT_TRUE(base::ReadFileToString(file_path, &file_content));
+ EXPECT_EQ(kData0, file_content);
+ EXPECT_EQ(kData0, data);
+
+ // Store more data in another subkey.
+ EXPECT_FALSE(cache.Store(kKey1, kSubB, kData1).empty());
+
+ // Write subkeys to two other keys.
+ EXPECT_FALSE(cache.Store(kKey2, kSubA, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey2, kSubB, kData1).empty());
+ EXPECT_FALSE(cache.Store(kKey3, kSubA, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey3, kSubB, kData1).empty());
+
+ // Enumerate all the subkeys.
+ std::map<std::string, std::string> contents;
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData0, contents[kSubA]);
+ EXPECT_EQ(kData1, contents[kSubB]);
+
+ // Store more subkeys.
+ EXPECT_FALSE(cache.Store(kKey1, kSubC, kData1).empty());
+ EXPECT_FALSE(cache.Store(kKey1, kSubD, kData1).empty());
+ EXPECT_FALSE(cache.Store(kKey1, kSubE, kData1).empty());
+
+ // Now purge some of them.
+ std::set<std::string> keep;
+ keep.insert(kSubB);
+ keep.insert(kSubD);
+ cache.PurgeOtherSubkeys(kKey1, keep);
+
+ // Enumerate all the remaining subkeys.
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData1, contents[kSubB]);
+ EXPECT_EQ(kData1, contents[kSubD]);
+
+ // Delete subkeys directly.
+ cache.Delete(kKey1, kSubB);
+ cache.Delete(kKey1, kSubD);
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(0u, contents.size());
+
+ // The other two keys were not affected.
+ cache.LoadAllSubkeys(kKey2, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData0, contents[kSubA]);
+ EXPECT_EQ(kData1, contents[kSubB]);
+ cache.LoadAllSubkeys(kKey3, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData0, contents[kSubA]);
+ EXPECT_EQ(kData1, contents[kSubB]);
+
+ // Now purge all keys except the third.
+ keep.clear();
+ keep.insert(kKey3);
+ cache.PurgeOtherKeys(keep);
+
+ // The first two keys are empty.
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(0u, contents.size());
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(0u, contents.size());
+
+ // The third key is unaffected.
+ cache.LoadAllSubkeys(kKey3, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData0, contents[kSubA]);
+ EXPECT_EQ(kData1, contents[kSubB]);
+}
+
+TEST_F(ResourceCacheTest, FilterSubkeys) {
+ ResourceCache cache(temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ /* max_cache_size */ absl::nullopt);
+
+ // Store some data.
+ EXPECT_FALSE(cache.Store(kKey1, kSubA, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey1, kSubB, kData1).empty());
+ EXPECT_FALSE(cache.Store(kKey1, kSubC, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey2, kSubA, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey2, kSubB, kData1).empty());
+ EXPECT_FALSE(cache.Store(kKey3, kSubA, kData0).empty());
+ EXPECT_FALSE(cache.Store(kKey3, kSubB, kData1).empty());
+
+ // Check the contents of kKey1.
+ std::map<std::string, std::string> contents;
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(3u, contents.size());
+ EXPECT_EQ(kData0, contents[kSubA]);
+ EXPECT_EQ(kData1, contents[kSubB]);
+ EXPECT_EQ(kData0, contents[kSubC]);
+
+ // Filter some subkeys.
+ cache.FilterSubkeys(kKey1, base::BindRepeating(&Matches, kSubA));
+
+ // Check the contents of kKey1 again.
+ cache.LoadAllSubkeys(kKey1, &contents);
+ EXPECT_EQ(2u, contents.size());
+ EXPECT_EQ(kData1, contents[kSubB]);
+ EXPECT_EQ(kData0, contents[kSubC]);
+
+ // Other keys weren't affected.
+ cache.LoadAllSubkeys(kKey2, &contents);
+ EXPECT_EQ(2u, contents.size());
+ cache.LoadAllSubkeys(kKey3, &contents);
+ EXPECT_EQ(2u, contents.size());
+}
+
+TEST_F(ResourceCacheTest, StoreWithEnabledCacheLimit) {
+ ResourceCache cache(temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ kMaxCacheSize);
+ task_environment_.RunUntilIdle();
+
+ // Put first subkey with 9Kb data in cache.
+ EXPECT_FALSE(cache.Store(kKey1, kSubA, kData9Kb).empty());
+ // Try to put second subkey with 2Kb data in cache, expected to fail while
+ // total size exceeds 10Kb.
+ EXPECT_TRUE(cache.Store(kKey2, kSubB, kData2Kb).empty());
+ // Put second subkey with 1Kb data in cache.
+ EXPECT_FALSE(cache.Store(kKey2, kSubC, kData1Kb).empty());
+ // Try to put third subkey with 2 bytes data in cache, expected to fail while
+ // total size exceeds 10Kb.
+ EXPECT_TRUE(cache.Store(kKey1, kSubB, kData1).empty());
+
+ // Remove keys with all subkeys.
+ cache.Clear(kKey1);
+ cache.Clear(kKey2);
+
+ // Put first subkey with 9Kb data in cache.
+ EXPECT_FALSE(cache.Store(kKey3, kSubA, kData9Kb).empty());
+ // Put second subkey with 1Kb data in cache.
+ EXPECT_FALSE(cache.Store(kKey3, kSubB, kData1Kb).empty());
+ // Try to put third subkey with 2 bytes data in cache, expected to fail while
+ // total size exceeds 10Kb.
+ EXPECT_TRUE(cache.Store(kKey1, kSubB, kData1).empty());
+
+ // Replace data in first subkey with another 9Kb data.
+ EXPECT_FALSE(cache.Store(kKey3, kSubA, kData9KbUpdated).empty());
+
+ // Remove this key with 9Kb data.
+ cache.Delete(kKey3, kSubA);
+
+ // Put second subkey with 2 bytes data in cache.
+ EXPECT_FALSE(cache.Store(kKey1, kSubB, kData1).empty());
+}
+
+#if BUILDFLAG(IS_POSIX) // Because of symbolic links.
+
+TEST_F(ResourceCacheTest, StoreInDirectoryWithCycleSymlinks) {
+ base::FilePath inner_dir = temp_dir_.GetPath().AppendASCII("inner");
+ ASSERT_TRUE(base::CreateDirectory(inner_dir));
+ base::FilePath symlink_to_parent = inner_dir.AppendASCII("symlink");
+ ASSERT_TRUE(base::CreateSymbolicLink(temp_dir_.GetPath(), symlink_to_parent));
+
+ ResourceCache cache(temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ kMaxCacheSize);
+ task_environment_.RunUntilIdle();
+
+ // Check if the cache is empty
+ EXPECT_FALSE(cache.Store(kKey1, kSubA, kData10Kb).empty());
+}
+
+TEST_F(ResourceCacheTest, StoreInDirectoryWithSymlinkToRoot) {
+ base::FilePath inner_dir = temp_dir_.GetPath().AppendASCII("inner");
+ ASSERT_TRUE(base::CreateDirectory(inner_dir));
+ base::FilePath root_path(FILE_PATH_LITERAL("/"));
+ base::FilePath symlink_to_root = temp_dir_.GetPath().AppendASCII("symlink");
+ ASSERT_TRUE(base::CreateSymbolicLink(root_path, symlink_to_root));
+
+ ResourceCache cache(temp_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(),
+ kMaxCacheSize);
+ task_environment_.RunUntilIdle();
+
+ // Check if the cache is empty
+ EXPECT_FALSE(cache.Store(kKey1, kSubA, kData10Kb).empty());
+}
+
+#endif // BUILDFLAG(IS_POSIX)
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/signing_service.h b/chromium/components/policy/core/common/cloud/signing_service.h
new file mode 100644
index 00000000000..39435717e05
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/signing_service.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_SIGNING_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_SIGNING_SERVICE_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+// Data signing interface.
+class POLICY_EXPORT SigningService {
+ public:
+ using SigningCallback =
+ base::OnceCallback<void(bool success,
+ enterprise_management::SignedData signed_data)>;
+
+ virtual ~SigningService() = default;
+
+ // Signs |data| and calls |callback| with the signed data.
+ virtual void SignData(const std::string& data, SigningCallback callback) = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_SIGNING_SERVICE_H_
+
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.cc b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.cc
new file mode 100644
index 00000000000..a9ef8c15816
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_service.h"
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace em = enterprise_management;
+
+namespace {
+
+// Directory inside the profile directory where policy-related resources are
+// stored.
+const base::FilePath::CharType kPolicy[] = FILE_PATH_LITERAL("Policy");
+
+// Directory under `kPolicy`, in the user's profile dir, where policy for
+// components is cached.
+const base::FilePath::CharType kComponentsDir[] =
+ FILE_PATH_LITERAL("Components");
+
+} // namespace
+
+namespace policy {
+
+UserCloudPolicyManager::UserCloudPolicyManager(
+ std::unique_ptr<UserCloudPolicyStore> store,
+ const base::FilePath& component_policy_cache_path,
+ std::unique_ptr<CloudExternalDataManager> external_data_manager,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter)
+ : CloudPolicyManager(dm_protocol::kChromeUserPolicyType,
+ std::string(),
+ store.get(),
+ task_runner,
+ network_connection_tracker_getter),
+ store_(std::move(store)),
+ component_policy_cache_path_(component_policy_cache_path),
+ external_data_manager_(std::move(external_data_manager)) {}
+
+UserCloudPolicyManager::~UserCloudPolicyManager() {}
+
+std::unique_ptr<UserCloudPolicyManager> UserCloudPolicyManager::Create(
+ const base::FilePath& profile_path,
+ SchemaRegistry* schema_registry,
+ bool force_immediate_load,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ network::NetworkConnectionTrackerGetter network_connection_tracker_getter) {
+ std::unique_ptr<UserCloudPolicyStore> store =
+ UserCloudPolicyStore::Create(profile_path, background_task_runner);
+ if (force_immediate_load)
+ store->LoadImmediately();
+
+ const base::FilePath component_policy_cache_dir =
+ profile_path.Append(kPolicy).Append(kComponentsDir);
+
+ auto policy_manager = std::make_unique<UserCloudPolicyManager>(
+ std::move(store), component_policy_cache_dir,
+ std::unique_ptr<CloudExternalDataManager>(),
+ base::ThreadTaskRunnerHandle::Get(), network_connection_tracker_getter);
+ policy_manager->Init(schema_registry);
+ return policy_manager;
+}
+
+void UserCloudPolicyManager::Shutdown() {
+ if (external_data_manager_)
+ external_data_manager_->Disconnect();
+ CloudPolicyManager::Shutdown();
+}
+
+void UserCloudPolicyManager::SetSigninAccountId(const AccountId& account_id) {
+ store_->SetSigninAccountId(account_id);
+}
+
+void UserCloudPolicyManager::SetPoliciesRequired(bool required) {
+ policies_required_ = required;
+ RefreshPolicies();
+}
+
+void UserCloudPolicyManager::Connect(
+ PrefService* local_state,
+ std::unique_ptr<CloudPolicyClient> client) {
+ CHECK(!core()->client());
+
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
+ client->GetURLLoaderFactory();
+
+ CreateComponentCloudPolicyService(dm_protocol::kChromeExtensionPolicyType,
+ component_policy_cache_path_, client.get(),
+ schema_registry());
+ core()->Connect(std::move(client));
+ core()->StartRefreshScheduler();
+ core()->TrackRefreshDelayPref(local_state,
+ policy_prefs::kUserPolicyRefreshRate);
+ if (external_data_manager_)
+ external_data_manager_->Connect(std::move(url_loader_factory));
+}
+
+// static
+std::unique_ptr<CloudPolicyClient>
+UserCloudPolicyManager::CreateCloudPolicyClient(
+ DeviceManagementService* device_management_service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
+ return std::make_unique<CloudPolicyClient>(
+ device_management_service, std::move(url_loader_factory),
+ CloudPolicyClient::DeviceDMTokenCallback());
+}
+
+void UserCloudPolicyManager::DisconnectAndRemovePolicy() {
+ if (external_data_manager_)
+ external_data_manager_->Disconnect();
+ core()->Disconnect();
+
+ // store_->Clear() will publish the updated, empty policy. The component
+ // policy service must be cleared before OnStoreLoaded() is issued, so that
+ // component policies are also empty at CheckAndPublishPolicy().
+ ClearAndDestroyComponentCloudPolicyService();
+
+ // When the |store_| is cleared, it informs the |external_data_manager_| that
+ // all external data references have been removed, causing the
+ // |external_data_manager_| to clear its cache as well.
+ store_->Clear();
+ SetPoliciesRequired(false);
+}
+
+void UserCloudPolicyManager::GetChromePolicy(PolicyMap* policy_map) {
+ CloudPolicyManager::GetChromePolicy(policy_map);
+
+ // If the store has a verified policy blob received from the server then apply
+ // the defaults for policies that haven't been configured by the administrator
+ // given that this is an enterprise user.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ if (!store()->has_policy())
+ return;
+
+ // TODO(https://crbug.com/1206315): Don't apply enterprise defaults for Child
+ // user.
+ SetEnterpriseUsersProfileDefaults(policy_map);
+#endif
+#if BUILDFLAG(IS_ANDROID)
+ if (store()->has_policy() &&
+ !policy_map->Get(key::kNTPContentSuggestionsEnabled)) {
+ policy_map->Set(key::kNTPContentSuggestionsEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ base::Value(false), nullptr /* external_data_fetcher */);
+ }
+#endif
+}
+
+bool UserCloudPolicyManager::IsFirstPolicyLoadComplete(
+ PolicyDomain domain) const {
+ return !policies_required_ ||
+ CloudPolicyManager::IsFirstPolicyLoadComplete(domain);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.h b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.h
new file mode 100644
index 00000000000..aacdb5dc890
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager.h
@@ -0,0 +1,112 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_MANAGER_H_
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+#include "components/policy/policy_export.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+
+class AccountId;
+class PrefService;
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+class SchemaRegistry;
+
+namespace policy {
+
+class CloudExternalDataManager;
+class DeviceManagementService;
+class UserCloudPolicyStore;
+
+// UserCloudPolicyManager handles initialization of user policy.
+class POLICY_EXPORT UserCloudPolicyManager : public CloudPolicyManager {
+ public:
+ // |task_runner| is the runner for policy refresh tasks.
+ UserCloudPolicyManager(
+ std::unique_ptr<UserCloudPolicyStore> store,
+ const base::FilePath& component_policy_cache_path,
+ std::unique_ptr<CloudExternalDataManager> external_data_manager,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+ UserCloudPolicyManager(const UserCloudPolicyManager&) = delete;
+ UserCloudPolicyManager& operator=(const UserCloudPolicyManager&) = delete;
+ ~UserCloudPolicyManager() override;
+
+ static std::unique_ptr<UserCloudPolicyManager> Create(
+ const base::FilePath& profile_path,
+ SchemaRegistry* schema_registry,
+ bool force_immediate_load,
+ const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+ network::NetworkConnectionTrackerGetter
+ network_connection_tracker_getter);
+
+ // ConfigurationPolicyProvider overrides:
+ void Shutdown() override;
+
+ void SetSigninAccountId(const AccountId& account_id);
+
+ // Sets whether or not policies are required for this policy manager.
+ // This might be set to false if the user profile is an unmanaged consumer
+ // profile.
+ void SetPoliciesRequired(bool required);
+
+ // Initializes the cloud connection. |local_state| must stay valid until this
+ // object is deleted or DisconnectAndRemovePolicy() gets called. Virtual for
+ // mocking.
+ virtual void Connect(
+ PrefService* local_state,
+ std::unique_ptr<CloudPolicyClient> client);
+
+ // Shuts down the UserCloudPolicyManager (removes and stops refreshing the
+ // cached cloud policy). This is typically called when a profile is being
+ // disassociated from a given user (e.g. during signout). No policy will be
+ // provided by this object until the next time Initialize() is invoked.
+ void DisconnectAndRemovePolicy();
+
+ // Creates a CloudPolicyClient for this client. Used in situations where
+ // callers want to create a DMToken without actually initializing the
+ // profile's policy infrastructure (for example, during signin when we
+ // want to check if the user's domain requires policy).
+ static std::unique_ptr<CloudPolicyClient> CreateCloudPolicyClient(
+ DeviceManagementService* device_management_service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+
+ // ConfigurationPolicyProvider:
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+
+ private:
+ // CloudPolicyManager:
+ void GetChromePolicy(PolicyMap* policy_map) override;
+
+ bool policies_required_ = false;
+
+ // Typed pointer to the store owned by UserCloudPolicyManager. Note that
+ // CloudPolicyManager only keeps a plain CloudPolicyStore pointer.
+ std::unique_ptr<UserCloudPolicyStore> store_;
+
+ // Path where policy for components will be cached.
+ base::FilePath component_policy_cache_path_;
+
+ // Manages external data referenced by policies.
+ std::unique_ptr<CloudExternalDataManager> external_data_manager_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_MANAGER_H_
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc
new file mode 100644
index 00000000000..d616502be60
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_manager_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/mock_user_cloud_policy_store.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::_;
+
+namespace policy {
+namespace {
+
+class UserCloudPolicyManagerTest : public testing::Test {
+ public:
+ UserCloudPolicyManagerTest(const UserCloudPolicyManagerTest&) = delete;
+ UserCloudPolicyManagerTest& operator=(const UserCloudPolicyManagerTest&) =
+ delete;
+
+ protected:
+ UserCloudPolicyManagerTest() : store_(nullptr) {}
+
+ void SetUp() override {
+ // Set up a policy map for testing.
+ policy_map_.Set("key", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+ expected_bundle_.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy_map_.Clone();
+ }
+
+ void TearDown() override {
+ if (manager_) {
+ manager_->RemoveObserver(&observer_);
+ manager_->Shutdown();
+ }
+ }
+
+ void CreateManager() {
+ store_ = new MockUserCloudPolicyStore();
+ EXPECT_CALL(*store_, Load());
+ const auto task_runner = task_environment_.GetMainThreadTaskRunner();
+ manager_ = std::make_unique<UserCloudPolicyManager>(
+ std::unique_ptr<UserCloudPolicyStore>(store_), base::FilePath(),
+ std::unique_ptr<CloudExternalDataManager>(), task_runner,
+ network::TestNetworkConnectionTracker::CreateGetter());
+ manager_->Init(&schema_registry_);
+ manager_->AddObserver(&observer_);
+ Mock::VerifyAndClearExpectations(store_);
+ }
+
+ // Needs to be the first member.
+ base::test::TaskEnvironment task_environment_;
+
+ // Convenience policy objects.
+ PolicyMap policy_map_;
+ PolicyBundle expected_bundle_;
+
+ // Policy infrastructure.
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ raw_ptr<MockUserCloudPolicyStore> store_; // Not owned.
+ std::unique_ptr<UserCloudPolicyManager> manager_;
+};
+
+TEST_F(UserCloudPolicyManagerTest, DisconnectAndRemovePolicy) {
+ // Load policy, make sure it goes away when DisconnectAndRemovePolicy() is
+ // called.
+ CreateManager();
+ store_->policy_map_ = policy_map_.Clone();
+ EXPECT_CALL(observer_, OnUpdatePolicy(manager_.get())).Times(2);
+ store_->NotifyStoreLoaded();
+ EXPECT_TRUE(expected_bundle_.Equals(manager_->policies()));
+ EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_CALL(*store_, Clear());
+ manager_->DisconnectAndRemovePolicy();
+ EXPECT_FALSE(manager_->core()->service());
+}
+
+} // namespace
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_store.cc b/chromium/components/policy/core/common/cloud/user_cloud_policy_store.cc
new file mode 100644
index 00000000000..3c4590092cf
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_store.cc
@@ -0,0 +1,441 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_runner_util.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/proto/policy_signing_key.pb.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+// Subdirectory in the user's profile for storing user policies.
+const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy");
+// File in the above directory for storing user policy data.
+const base::FilePath::CharType kPolicyCacheFile[] =
+ FILE_PATH_LITERAL("User Policy");
+
+// File in the above directory for storing policy signing key data.
+const base::FilePath::CharType kKeyCacheFile[] =
+ FILE_PATH_LITERAL("Signing Key");
+
+// Maximum policy and key size that will be loaded, in bytes.
+const size_t kPolicySizeLimit = 1024 * 1024;
+const size_t kKeySizeLimit = 16 * 1024;
+
+bool WriteStringToFile(const base::FilePath path, const std::string& data) {
+ if (!base::CreateDirectory(path.DirName())) {
+ DLOG(WARNING) << "Failed to create directory " << path.DirName().value();
+ return false;
+ }
+
+ int size = data.size();
+ if (base::WriteFile(path, data.c_str(), size) != size) {
+ DLOG(WARNING) << "Failed to write " << path.value();
+ return false;
+ }
+
+ return true;
+}
+
+// Stores policy to the backing file (must be called via a task on
+// the background thread).
+void StorePolicyToDiskOnBackgroundThread(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ const em::PolicyFetchResponse& policy) {
+ DVLOG(1) << "Storing policy to " << policy_path.value();
+ std::string data;
+ if (!policy.SerializeToString(&data)) {
+ DLOG(WARNING) << "Failed to serialize policy data";
+ return;
+ }
+
+ if (!WriteStringToFile(policy_path, data))
+ return;
+
+ if (policy.has_new_public_key()) {
+ // Write the new public key and its verification signature/key to a file.
+ em::PolicySigningKey key_info;
+ key_info.set_signing_key(policy.new_public_key());
+ key_info.set_signing_key_signature(
+ policy.new_public_key_verification_signature_deprecated());
+ key_info.set_verification_key(GetPolicyVerificationKey());
+ std::string key_data;
+ if (!key_info.SerializeToString(&key_data)) {
+ DLOG(WARNING) << "Failed to serialize policy signing key";
+ return;
+ }
+
+ WriteStringToFile(key_path, key_data);
+ }
+}
+
+} // namespace
+
+DesktopCloudPolicyStore::DesktopCloudPolicyStore(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ PolicyLoadFilter policy_load_filter,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ PolicyScope policy_scope)
+ : UserCloudPolicyStoreBase(background_task_runner, policy_scope),
+ policy_path_(policy_path),
+ key_path_(key_path),
+ policy_load_filter_(std::move(policy_load_filter)) {}
+
+DesktopCloudPolicyStore::~DesktopCloudPolicyStore() {}
+
+void DesktopCloudPolicyStore::LoadImmediately() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DVLOG(1) << "Initiating immediate policy load from disk";
+ // Cancel any pending Load/Store/Validate operations.
+ weak_factory_.InvalidateWeakPtrs();
+ // Load the policy from disk...
+ PolicyLoadResult result =
+ LoadAndFilterPolicyFromDisk(policy_path_, key_path_, policy_load_filter_);
+ // ...and install it, reporting success/failure to any observers.
+ PolicyLoaded(false, result);
+}
+
+void DesktopCloudPolicyStore::Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(base::GetDeleteFileCallback(), policy_path_));
+ background_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(base::GetDeleteFileCallback(), key_path_));
+ ResetPolicy();
+ policy_map_.Clear();
+ policy_signature_public_key_.clear();
+ persisted_policy_key_.clear();
+ NotifyStoreLoaded();
+}
+
+void DesktopCloudPolicyStore::Load() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DVLOG(1) << "Initiating policy load from disk";
+ // Cancel any pending Load/Store/Validate operations.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Start a new Load operation and have us get called back when it is
+ // complete.
+ base::PostTaskAndReplyWithResult(
+ background_task_runner().get(), FROM_HERE,
+ base::BindOnce(&DesktopCloudPolicyStore::LoadAndFilterPolicyFromDisk,
+ policy_path_, key_path_, policy_load_filter_),
+ base::BindOnce(&DesktopCloudPolicyStore::PolicyLoaded,
+ weak_factory_.GetWeakPtr(), true));
+}
+
+// static
+PolicyLoadResult DesktopCloudPolicyStore::LoadAndFilterPolicyFromDisk(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ const PolicyLoadFilter& policy_load_filter) {
+ policy::PolicyLoadResult result =
+ DesktopCloudPolicyStore::LoadPolicyFromDisk(policy_path, key_path);
+ return policy_load_filter ? policy_load_filter.Run(std::move(result))
+ : result;
+}
+
+// static
+PolicyLoadResult DesktopCloudPolicyStore::LoadPolicyFromDisk(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path) {
+ policy::PolicyLoadResult result;
+ // If the backing file does not exist, just return. We don't verify the key
+ // path here, because the key is optional (the validation code will fail if
+ // the key does not exist but the loaded policy is unsigned).
+ if (!base::PathExists(policy_path)) {
+ result.status = policy::LOAD_RESULT_NO_POLICY_FILE;
+ return result;
+ }
+ std::string data;
+
+ if (!base::ReadFileToStringWithMaxSize(policy_path, &data,
+ kPolicySizeLimit) ||
+ !result.policy.ParseFromString(data)) {
+ LOG(WARNING) << "Failed to read or parse policy data from "
+ << policy_path.value();
+ result.status = policy::LOAD_RESULT_LOAD_ERROR;
+ return result;
+ }
+
+ result.status = policy::LOAD_RESULT_SUCCESS;
+
+ if (key_path.empty())
+ return result;
+
+ if (!base::ReadFileToStringWithMaxSize(key_path, &data, kKeySizeLimit) ||
+ !result.key.ParseFromString(data)) {
+ LOG(ERROR) << "Failed to read or parse key data from " << key_path;
+ result.key.clear_signing_key();
+ }
+
+ return result;
+}
+
+void DesktopCloudPolicyStore::PolicyLoaded(bool validate_in_background,
+ PolicyLoadResult result) {
+ switch (result.status) {
+ case LOAD_RESULT_LOAD_ERROR:
+ status_ = STATUS_LOAD_ERROR;
+ NotifyStoreError();
+ break;
+
+ case LOAD_RESULT_NO_POLICY_FILE:
+ DVLOG(1) << "No policy found on disk";
+ NotifyStoreLoaded();
+ break;
+
+ case LOAD_RESULT_SUCCESS: {
+ // Found policy on disk - need to validate it before it can be used.
+ std::unique_ptr<em::PolicyFetchResponse> cloud_policy(
+ new em::PolicyFetchResponse(result.policy));
+ std::unique_ptr<em::PolicySigningKey> key =
+ std::make_unique<em::PolicySigningKey>(result.key);
+
+ bool doing_key_rotation = result.doing_key_rotation;
+ if (key && (!key->has_verification_key() ||
+ key->verification_key() != GetPolicyVerificationKey())) {
+ // The cached key didn't match our current key, so we're doing a key
+ // rotation - make sure we request a new key from the server on our
+ // next fetch.
+ doing_key_rotation = true;
+ DLOG(WARNING) << "Verification key rotation detected";
+ }
+
+ Validate(std::move(cloud_policy), std::move(key), validate_in_background,
+ base::BindRepeating(
+ &DesktopCloudPolicyStore::InstallLoadedPolicyAfterValidation,
+ weak_factory_.GetWeakPtr(), doing_key_rotation,
+ result.key.has_signing_key() ? result.key.signing_key()
+ : std::string()));
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+void DesktopCloudPolicyStore::ValidateKeyAndSignature(
+ UserCloudPolicyValidator* validator,
+ const em::PolicySigningKey* cached_key,
+ const std::string& owning_domain) {
+ // There are 4 cases:
+ //
+ // 1) Validation after loading from cache with no cached key.
+ // Action: Just validate signature with an empty key - this will result in
+ // a failed validation and the cached policy will be rejected.
+ //
+ // 2) Validation after loading from cache with a cached key
+ // Action: Validate signature on policy blob but don't allow key rotation.
+ //
+ // 3) Validation after loading new policy from the server with no cached key
+ // Action: Validate as initial key provisioning (case where we are migrating
+ // from unsigned policy)
+ //
+ // 4) Validation after loading new policy from the server with a cached key
+ // Action: Validate as normal, and allow key rotation.
+ if (cached_key) {
+ // Case #1/#2 - loading from cache. Validate the cached key (if no key,
+ // then the validation will fail), then do normal policy data signature
+ // validation using the cached key.
+
+ // Loading from cache should not change the cached keys.
+ DCHECK(persisted_policy_key_.empty() ||
+ persisted_policy_key_ == cached_key->signing_key());
+ DLOG_IF(WARNING, !cached_key->has_signing_key())
+ << "Unsigned policy blob detected";
+
+ validator->ValidateCachedKey(cached_key->signing_key(),
+ cached_key->signing_key_signature(),
+ owning_domain);
+ // Loading from cache, so don't allow key rotation.
+ validator->ValidateSignature(cached_key->signing_key());
+ } else {
+ // No passed cached_key - this is not validating the initial policy load
+ // from cache, but rather an update from the server.
+ if (persisted_policy_key_.empty()) {
+ // Case #3 - no valid existing policy key (either this is the initial
+ // policy fetch, or we're doing a key rotation), so this new policy fetch
+ // should include an initial key provision.
+ validator->ValidateInitialKey(owning_domain);
+ } else {
+ // Case #4 - verify new policy with existing key. We always allow key
+ // rotation - the verification key will prevent invalid policy from being
+ // injected. |persisted_policy_key_| is already known to be valid, so no
+ // need to verify via ValidateCachedKey().
+ validator->ValidateSignatureAllowingRotation(persisted_policy_key_,
+ owning_domain);
+ }
+ }
+}
+
+void DesktopCloudPolicyStore::InstallLoadedPolicyAfterValidation(
+ bool doing_key_rotation,
+ const std::string& signing_key,
+ UserCloudPolicyValidator* validator) {
+ validation_result_ = validator->GetValidationResult();
+ if (!validator->success()) {
+ DVLOG(1) << "Validation failed: status=" << validator->status();
+ status_ = STATUS_VALIDATION_ERROR;
+ NotifyStoreError();
+ return;
+ }
+
+ DVLOG(1) << "Validation succeeded - installing policy with dm_token: "
+ << validator->policy_data()->request_token();
+ DVLOG(1) << "Device ID: " << validator->policy_data()->device_id();
+
+ // If we're doing a key rotation, clear the public key version so a future
+ // policy fetch will force regeneration of the keys.
+ if (doing_key_rotation) {
+ validator->policy_data()->clear_public_key_version();
+ persisted_policy_key_.clear();
+ } else {
+ // Policy validation succeeded, so we know the signing key is good.
+ persisted_policy_key_ = signing_key;
+ }
+
+ InstallPolicy(std::move(validator->policy()),
+ std::move(validator->policy_data()),
+ std::move(validator->payload()), persisted_policy_key_);
+ status_ = STATUS_OK;
+ NotifyStoreLoaded();
+}
+
+void DesktopCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Cancel all pending requests.
+ weak_factory_.InvalidateWeakPtrs();
+
+ std::unique_ptr<em::PolicyFetchResponse> policy_copy(
+ new em::PolicyFetchResponse(policy));
+ Validate(
+ std::move(policy_copy), std::unique_ptr<em::PolicySigningKey>(), true,
+ base::BindRepeating(&DesktopCloudPolicyStore::OnPolicyToStoreValidated,
+ weak_factory_.GetWeakPtr()));
+}
+
+void DesktopCloudPolicyStore::OnPolicyToStoreValidated(
+ UserCloudPolicyValidator* validator) {
+ validation_result_ = validator->GetValidationResult();
+ DVLOG(1) << "Policy validation complete: status = " << validator->status();
+ if (!validator->success()) {
+ status_ = STATUS_VALIDATION_ERROR;
+ NotifyStoreError();
+ return;
+ }
+
+ // Persist the validated policy (just fire a task - don't bother getting a
+ // reply because we can't do anything if it fails).
+ background_task_runner()->PostTask(
+ FROM_HERE,
+ base::BindRepeating(&StorePolicyToDiskOnBackgroundThread, policy_path_,
+ key_path_, *validator->policy()));
+
+ // If the key was rotated, update our local cache of the key.
+ if (validator->policy()->has_new_public_key())
+ persisted_policy_key_ = validator->policy()->new_public_key();
+
+ InstallPolicy(std::move(validator->policy()),
+ std::move(validator->policy_data()),
+ std::move(validator->payload()), persisted_policy_key_);
+ status_ = STATUS_OK;
+ NotifyStoreLoaded();
+}
+
+UserCloudPolicyStore::UserCloudPolicyStore(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+ : DesktopCloudPolicyStore(policy_path,
+ key_path,
+ PolicyLoadFilter(),
+ background_task_runner,
+ PolicyScope::POLICY_SCOPE_USER) {}
+
+UserCloudPolicyStore::~UserCloudPolicyStore() {}
+
+// static
+std::unique_ptr<UserCloudPolicyStore> UserCloudPolicyStore::Create(
+ const base::FilePath& profile_path,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
+ base::FilePath policy_path =
+ profile_path.Append(kPolicyDir).Append(kPolicyCacheFile);
+ base::FilePath key_path =
+ profile_path.Append(kPolicyDir).Append(kKeyCacheFile);
+ return base::WrapUnique(
+ new UserCloudPolicyStore(policy_path, key_path, background_task_runner));
+}
+
+void UserCloudPolicyStore::SetSigninAccountId(const AccountId& account_id) {
+ account_id_ = account_id;
+}
+
+void UserCloudPolicyStore::Validate(
+ std::unique_ptr<em::PolicyFetchResponse> policy,
+ std::unique_ptr<em::PolicySigningKey> cached_key,
+ bool validate_in_background,
+ UserCloudPolicyValidator::CompletionCallback callback) {
+ // Configure the validator.
+ std::unique_ptr<UserCloudPolicyValidator> validator = CreateValidator(
+ std::move(policy), CloudPolicyValidatorBase::TIMESTAMP_VALIDATED);
+
+ // Extract the owning domain from the signed-in user (if any is set yet).
+ // If there's no owning domain, then the code just ensures that the policy
+ // is self-consistent (that the keys are signed with the same domain that the
+ // username field in the policy contains). UserPolicySigninServerBase will
+ // verify that the username matches the signed in user once profile
+ // initialization is complete (http://crbug.com/342327).
+ std::string owning_domain;
+
+ // Validate the account id if the user is signed in. The account_id_ can
+ // be empty during initial policy load because this happens before the
+ // Prefs subsystem is initialized.
+ if (account_id_.is_valid()) {
+ DVLOG(1) << "Validating account: " << account_id_;
+ validator->ValidateUser(account_id_);
+ owning_domain = gaia::ExtractDomainName(gaia::CanonicalizeEmail(
+ gaia::SanitizeEmail(account_id_.GetUserEmail())));
+ }
+
+ ValidateKeyAndSignature(validator.get(), cached_key.get(), owning_domain);
+
+ if (validate_in_background) {
+ // Start validation in the background.
+ UserCloudPolicyValidator::StartValidation(std::move(validator),
+ std::move(callback));
+ } else {
+ // Run validation immediately and invoke the callback with the results.
+ validator->RunValidation();
+ std::move(callback).Run(validator.get());
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_store.h b/chromium/components/policy/core/common/cloud/user_cloud_policy_store.h
new file mode 100644
index 00000000000..93b3fdf29a5
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_store.h
@@ -0,0 +1,204 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/user_cloud_policy_store_base.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/proto/policy_signing_key.pb.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration.
+//
+// Keep this in sync with EnterprisePolicyLoadStatus in histograms.xml.
+enum PolicyLoadStatusForUma {
+ // Policy blob was successfully loaded and parsed.
+ LOAD_RESULT_SUCCESS,
+
+ // No previously stored policy was found.
+ LOAD_RESULT_NO_POLICY_FILE,
+
+ // Could not load the previously stored policy due to either a parse or
+ // file read error.
+ LOAD_RESULT_LOAD_ERROR,
+
+ // LOAD_RESULT_SIZE is the number of items in this enum and is used when
+ // logging histograms to set the bucket size, so should always be the last
+ // item.
+ LOAD_RESULT_SIZE,
+};
+
+// Struct containing the result of a policy load - if |status| ==
+// LOAD_RESULT_SUCCESS, |policy| is initialized from the policy file on disk.
+// |key| is initialized from the signing key file on disk.
+// |doing_key_rotation| is true if we need to re-download the key again when key
+// loaded from external place is different than the local one.
+struct PolicyLoadResult {
+ PolicyLoadStatusForUma status;
+ enterprise_management::PolicyFetchResponse policy;
+ enterprise_management::PolicySigningKey key;
+ bool doing_key_rotation = false;
+};
+
+// Function that takes in a PolicyLoadResult and returns a PolicyLoadResult with
+// filtered policies.
+using PolicyLoadFilter =
+ base::RepeatingCallback<PolicyLoadResult(PolicyLoadResult)>;
+
+// Implements a cloud policy store that stores policy on desktop. This is used
+// on (non-chromeos) platforms that do not have a secure storage
+// implementation.
+class POLICY_EXPORT DesktopCloudPolicyStore : public UserCloudPolicyStoreBase {
+ public:
+ DesktopCloudPolicyStore(
+ const base::FilePath& policy_file,
+ const base::FilePath& key_file,
+ PolicyLoadFilter policy_load_filter,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ PolicyScope policy_scope);
+ DesktopCloudPolicyStore(const DesktopCloudPolicyStore&) = delete;
+ DesktopCloudPolicyStore& operator=(const DesktopCloudPolicyStore&) = delete;
+ ~DesktopCloudPolicyStore() override;
+
+ // Loads policy immediately on the current thread. Virtual for mocks.
+ virtual void LoadImmediately();
+
+ // Deletes any existing policy blob and notifies observers via OnStoreLoaded()
+ // that the blob has changed. Virtual for mocks.
+ virtual void Clear();
+
+ // CloudPolicyStore implementation.
+ void Load() override;
+ void Store(const enterprise_management::PolicyFetchResponse& policy) override;
+
+ protected:
+ // Loads cloud policies that have been written on the disk at |policy_path|
+ // for caching purposes. Reads the optional |key_path| to load the signing key
+ // for those policies.
+ static PolicyLoadResult LoadPolicyFromDisk(const base::FilePath& policy_path,
+ const base::FilePath& key_path);
+
+ // Callback invoked when a new policy has been loaded from disk. If
+ // |validate_in_background| is true, then policy is validated via a background
+ // thread.
+ void PolicyLoaded(bool validate_in_background,
+ PolicyLoadResult policy_load_result);
+
+ // Starts policy blob validation. |callback| is invoked once validation is
+ // complete. If |validate_in_background| is true, then the validation work
+ // occurs on a background thread (results are sent back to the calling
+ // thread).
+ virtual void Validate(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ std::unique_ptr<enterprise_management::PolicySigningKey> key,
+ bool validate_in_background,
+ UserCloudPolicyValidator::CompletionCallback callback) = 0;
+
+ // Validate the |cached_key| with the |owning_domain|.
+ void ValidateKeyAndSignature(
+ UserCloudPolicyValidator* validator,
+ const enterprise_management::PolicySigningKey* cached_key,
+ const std::string& owning_domain);
+
+ // Callback invoked to install a just-loaded policy after validation has
+ // finished.
+ void InstallLoadedPolicyAfterValidation(bool doing_key_rotation,
+ const std::string& signing_key,
+ UserCloudPolicyValidator* validator);
+
+ // Callback invoked to store the policy after validation has finished.
+ void OnPolicyToStoreValidated(UserCloudPolicyValidator* validator);
+
+ private:
+ // Loads cloud policies that have been written on the disk at |policy_path|
+ // for caching purposes. Reads the optional |key_path| to load the signing key
+ // for those policies. |policy_load_filter| is used to filter the policies
+ // loaded.
+ static PolicyLoadResult LoadAndFilterPolicyFromDisk(
+ const base::FilePath& policy_path,
+ const base::FilePath& key_path,
+ const PolicyLoadFilter& policy_load_filter);
+
+ // The current key used to verify signatures of policy. This value is
+ // eventually consistent with the one persisted in the key cache file. This
+ // is, generally, different from |policy_signature_public_key_| member of
+ // the base class CloudPolicyStore, which always corresponds to the currently
+ // effective policy.
+ std::string persisted_policy_key_;
+
+ // Path to file where we store persisted policy.
+ base::FilePath policy_path_;
+
+ // Path to file where we store the signing key for the policy blob.
+ base::FilePath key_path_;
+
+ // Function to filter load policies.
+ PolicyLoadFilter policy_load_filter_;
+
+ // WeakPtrFactory used to create callbacks for validating and storing policy.
+ base::WeakPtrFactory<DesktopCloudPolicyStore> weak_factory_{this};
+};
+
+// Implements a cloud policy store that is stored in a simple file in the user's
+// profile directory. This is used on (non-chromeos) platforms that do not have
+// a secure storage implementation.
+//
+// The public key, which is used to verify signatures of policy, is also
+// persisted in a file. During the load operation, the key is loaded from the
+// file and is itself verified against the verification public key before using
+// it to verify the policy signature. During the store operation, the key cache
+// file is updated whenever the key rotation happens.
+class POLICY_EXPORT UserCloudPolicyStore : public DesktopCloudPolicyStore {
+ public:
+ // Creates a policy store associated with a signed-in (or in the progress of
+ // it) user.
+ UserCloudPolicyStore(
+ const base::FilePath& policy_file,
+ const base::FilePath& key_file,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+ UserCloudPolicyStore(const UserCloudPolicyStore&) = delete;
+ UserCloudPolicyStore& operator=(const UserCloudPolicyStore&) = delete;
+ ~UserCloudPolicyStore() override;
+
+ // Factory method for creating a UserCloudPolicyStore for a profile with path
+ // |profile_path|.
+ static std::unique_ptr<UserCloudPolicyStore> Create(
+ const base::FilePath& profile_path,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+
+ // The account id from signin for validation of the policy.
+ const AccountId& signin_account_id() const { return account_id_; }
+
+ // Sets the account id from signin for validation of the policy.
+ void SetSigninAccountId(const AccountId& account_id);
+
+ private:
+ void Validate(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ std::unique_ptr<enterprise_management::PolicySigningKey> key,
+ bool validate_in_background,
+ UserCloudPolicyValidator::CompletionCallback callback) override;
+
+ // The account id from signin for validation of the policy.
+ AccountId account_id_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_H_
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.cc b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.cc
new file mode 100644
index 00000000000..16e9c72d899
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.cc
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_cloud_policy_store_base.h"
+
+#include <utility>
+
+#include "base/task/sequenced_task_runner.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_proto_decoders.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+UserCloudPolicyStoreBase::UserCloudPolicyStoreBase(
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ PolicyScope policy_scope)
+ : background_task_runner_(background_task_runner),
+ policy_scope_(policy_scope) {}
+
+UserCloudPolicyStoreBase::~UserCloudPolicyStoreBase() {}
+
+std::unique_ptr<UserCloudPolicyValidator>
+UserCloudPolicyStoreBase::CreateValidator(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ CloudPolicyValidatorBase::ValidateTimestampOption timestamp_option) {
+ // Configure the validator.
+ auto validator = std::make_unique<UserCloudPolicyValidator>(
+ std::move(policy_fetch_response), background_task_runner_);
+ validator->ValidatePolicyType(dm_protocol::kChromeUserPolicyType);
+ validator->ValidateAgainstCurrentPolicy(
+ policy(), timestamp_option, CloudPolicyValidatorBase::DM_TOKEN_REQUIRED,
+ CloudPolicyValidatorBase::DEVICE_ID_REQUIRED);
+ validator->ValidatePayload();
+ return validator;
+}
+
+void UserCloudPolicyStoreBase::InstallPolicy(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ std::unique_ptr<enterprise_management::PolicyData> policy_data,
+ std::unique_ptr<enterprise_management::CloudPolicySettings> payload,
+ const std::string& policy_signature_public_key) {
+ // Decode the payload.
+ policy_map_.Clear();
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ // From the policies that Lacros fetched from the cloud, it should only
+ // respect the ones with per_profile=True. Session-wide policies
+ // (per_profile=False) are be provided by ash and installed by
+ // PolicyLoaderLacros.
+ PolicyPerProfileFilter filter = PolicyPerProfileFilter::kTrue;
+#else
+ PolicyPerProfileFilter filter = PolicyPerProfileFilter::kAny;
+#endif
+ DecodeProtoFields(*payload, external_data_manager(), POLICY_SOURCE_CLOUD,
+ policy_scope_, &policy_map_, filter);
+
+ if (policy_data->user_affiliation_ids_size() > 0) {
+ policy_map_.SetUserAffiliationIds(
+ {policy_data->user_affiliation_ids().begin(),
+ policy_data->user_affiliation_ids().end()});
+ }
+ if (policy_data->device_affiliation_ids_size() > 0) {
+ policy_map_.SetDeviceAffiliationIds(
+ {policy_data->device_affiliation_ids().begin(),
+ policy_data->device_affiliation_ids().end()});
+ }
+ SetPolicy(std::move(policy_fetch_response), std::move(policy_data));
+ policy_signature_public_key_ = policy_signature_public_key;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.h b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.h
new file mode 100644
index 00000000000..fdc3a9a6c1f
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_base.h
@@ -0,0 +1,63 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+// Base class that implements common cross-platform UserCloudPolicyStore
+// functionality.
+class POLICY_EXPORT UserCloudPolicyStoreBase : public CloudPolicyStore {
+ public:
+ UserCloudPolicyStoreBase(
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ PolicyScope policy_scope);
+ UserCloudPolicyStoreBase(const UserCloudPolicyStoreBase&) = delete;
+ UserCloudPolicyStoreBase& operator=(const UserCloudPolicyStoreBase&) = delete;
+ ~UserCloudPolicyStoreBase() override;
+
+ protected:
+ // Creates a validator configured to validate a user policy. The caller owns
+ // the resulting object until StartValidation() is invoked.
+ virtual std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
+ CloudPolicyValidatorBase::ValidateTimestampOption option);
+
+ // Sets |policy_fetch_response|, |policy_data| and |payload| as the active
+ // policy, and sets |policy_signature_public_key| as the active public key.
+ void InstallPolicy(
+ std::unique_ptr<enterprise_management::PolicyFetchResponse>
+ policy_fetch_response,
+ std::unique_ptr<enterprise_management::PolicyData> policy_data,
+ std::unique_ptr<enterprise_management::CloudPolicySettings> payload,
+ const std::string& policy_signature_public_key);
+
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner() const {
+ return background_task_runner_;
+ }
+
+ private:
+ // Task runner for background file operations.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+ PolicyScope policy_scope_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_CLOUD_POLICY_STORE_BASE_H_
diff --git a/chromium/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc
new file mode 100644
index 00000000000..32a7caf16dd
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc
@@ -0,0 +1,518 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
+
+#include <memory>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/policy_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::AllOf;
+using testing::Eq;
+using testing::Mock;
+using testing::Property;
+using testing::Sequence;
+
+namespace policy {
+
+namespace {
+
+void RunUntilIdle() {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+bool WriteStringToFile(const base::FilePath path, const std::string& data) {
+ if (!base::CreateDirectory(path.DirName())) {
+ DLOG(WARNING) << "Failed to create directory " << path.DirName().value();
+ return false;
+ }
+
+ int size = data.size();
+ if (base::WriteFile(path, data.c_str(), size) != size) {
+ DLOG(WARNING) << "Failed to write " << path.value();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+class UserCloudPolicyStoreTest : public testing::Test {
+ public:
+ UserCloudPolicyStoreTest()
+ : task_environment_(
+ base::test::SingleThreadTaskEnvironment::MainThreadType::UI) {}
+ UserCloudPolicyStoreTest(const UserCloudPolicyStoreTest&) = delete;
+ UserCloudPolicyStoreTest& operator=(const UserCloudPolicyStoreTest&) = delete;
+
+ void SetUp() override {
+ ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
+ store_ = std::make_unique<UserCloudPolicyStore>(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get());
+ external_data_manager_ = std::make_unique<MockCloudExternalDataManager>();
+ external_data_manager_->SetPolicyStore(store_.get());
+ store_->SetSigninAccountId(PolicyBuilder::GetFakeAccountIdForTesting());
+ EXPECT_EQ(PolicyBuilder::GetFakeAccountIdForTesting(),
+ store_->signin_account_id());
+ store_->AddObserver(&observer_);
+
+ // Install an initial public key, so that by default the validation of
+ // the stored/loaded policy blob succeeds (it looks like a new key
+ // provision).
+ policy_.SetDefaultInitialSigningKey();
+
+ InitPolicyPayload(&policy_.payload());
+
+ policy_.Build();
+ }
+
+ void TearDown() override {
+ store_->RemoveObserver(&observer_);
+ external_data_manager_.reset();
+ store_.reset();
+ RunUntilIdle();
+ }
+
+ void InitPolicyPayload(enterprise_management::CloudPolicySettings* payload) {
+ payload->mutable_searchsuggestenabled()->set_value(true);
+ payload->mutable_urlblocklist()->mutable_value()->add_entries(
+ "chromium.org");
+ }
+
+ base::FilePath policy_file() {
+ return tmp_dir_.GetPath().AppendASCII("policy");
+ }
+
+ base::FilePath key_file() {
+ return tmp_dir_.GetPath().AppendASCII("policy_key");
+ }
+
+ // Verifies that store_->policy_map() has the appropriate entries.
+ void VerifyPolicyMap(CloudPolicyStore* store) {
+ EXPECT_EQ(2U, store->policy_map().size());
+ const PolicyMap::Entry* entry =
+ store->policy_map().Get(key::kSearchSuggestEnabled);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(
+ base::Value(true).Equals(entry->value(base::Value::Type::BOOLEAN)));
+ ASSERT_TRUE(store->policy_map().Get(key::kURLBlocklist));
+ }
+
+ // Install an expectation on |observer_| for an error code.
+ void ExpectError(CloudPolicyStore* store, CloudPolicyStore::Status error) {
+ EXPECT_CALL(observer_,
+ OnStoreError(AllOf(Eq(store),
+ Property(&CloudPolicyStore::status,
+ Eq(error)))));
+ }
+
+ void StorePolicyAndEnsureLoaded(
+ const enterprise_management::PolicyFetchResponse& policy) {
+ Sequence s;
+ EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s);
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s);
+ store_->Store(policy);
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(external_data_manager_.get());
+ Mock::VerifyAndClearExpectations(&observer_);
+ ASSERT_TRUE(store_->policy());
+ }
+
+ UserPolicyBuilder policy_;
+ MockCloudPolicyStoreObserver observer_;
+ std::unique_ptr<UserCloudPolicyStore> store_;
+ std::unique_ptr<MockCloudExternalDataManager> external_data_manager_;
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+
+ base::ScopedTempDir tmp_dir_;
+};
+
+TEST_F(UserCloudPolicyStoreTest, LoadWithNoFile) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ Sequence s;
+ EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s);
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s);
+ store_->Load();
+ RunUntilIdle();
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+}
+
+TEST_F(UserCloudPolicyStoreTest, LoadWithInvalidFile) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Create a bogus file.
+ ASSERT_TRUE(base::CreateDirectory(policy_file().DirName()));
+ std::string bogus_data = "bogus_data";
+ int size = bogus_data.size();
+ ASSERT_EQ(size, base::WriteFile(policy_file(),
+ bogus_data.c_str(), bogus_data.size()));
+
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_LOAD_ERROR);
+ store_->Load();
+ RunUntilIdle();
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+}
+
+TEST_F(UserCloudPolicyStoreTest, LoadImmediatelyWithNoFile) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ Sequence s;
+ EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s);
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s);
+ store_->LoadImmediately(); // Should load without running the message loop.
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+}
+
+TEST_F(UserCloudPolicyStoreTest, LoadImmediatelyWithInvalidFile) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Create a bogus file.
+ ASSERT_TRUE(base::CreateDirectory(policy_file().DirName()));
+ std::string bogus_data = "bogus_data";
+ int size = bogus_data.size();
+ ASSERT_EQ(size, base::WriteFile(policy_file(),
+ bogus_data.c_str(), bogus_data.size()));
+
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_LOAD_ERROR);
+ store_->LoadImmediately(); // Should load without running the message loop.
+
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+}
+
+// Load file from cache with no key data - should give us a validation error.
+TEST_F(UserCloudPolicyStoreTest, ShouldFailToLoadUnsignedPolicy) {
+ UserPolicyBuilder unsigned_builder;
+ unsigned_builder.UnsetSigningKey();
+ InitPolicyPayload(&unsigned_builder.payload());
+ unsigned_builder.Build();
+ // Policy should be unsigned.
+ EXPECT_FALSE(unsigned_builder.policy().has_policy_data_signature());
+
+ // Write policy to disk.
+ std::string data;
+ ASSERT_TRUE(unsigned_builder.policy().SerializeToString(&data));
+ ASSERT_TRUE(base::CreateDirectory(policy_file().DirName()));
+ int size = data.size();
+ ASSERT_EQ(size, base::WriteFile(policy_file(), data.c_str(), size));
+
+ // Now make sure the data generates a validation error.
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store_->LoadImmediately(); // Should load without running the message loop.
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Now mimic a new policy coming down - this should result in a new key
+ // being installed.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ EXPECT_EQ(policy_.policy().new_public_key(),
+ store_->policy_signature_public_key());
+ EXPECT_TRUE(store_->policy()->has_public_key_version());
+ EXPECT_TRUE(base::PathExists(key_file()));
+}
+
+TEST_F(UserCloudPolicyStoreTest, Store) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Store a simple policy and make sure it ends up as the currently active
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ // Policy should be decoded and stored.
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ store_->policy()->SerializeAsString());
+ VerifyPolicyMap(store_.get());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreThenClear) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Store a simple policy and make sure the file exists.
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ EXPECT_FALSE(store_->policy_map().empty());
+
+ // Policy file should exist.
+ ASSERT_TRUE(base::PathExists(policy_file()));
+
+ Sequence s2;
+ EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s2);
+ EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s2);
+ store_->Clear();
+ RunUntilIdle();
+
+ // Policy file should not exist.
+ ASSERT_TRUE(!base::PathExists(policy_file()));
+
+ // Policy should be gone.
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreRotatedKey) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Store a simple policy and make sure it ends up as the currently active
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ EXPECT_FALSE(policy_.policy().has_new_public_key_signature());
+ std::string original_policy_key = policy_.policy().new_public_key();
+ EXPECT_EQ(original_policy_key, store_->policy_signature_public_key());
+
+ // Now do key rotation.
+ policy_.SetDefaultSigningKey();
+ policy_.SetDefaultNewSigningKey();
+ policy_.Build();
+ EXPECT_TRUE(policy_.policy().has_new_public_key_signature());
+ EXPECT_NE(original_policy_key, policy_.policy().new_public_key());
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ EXPECT_EQ(policy_.policy().new_public_key(),
+ store_->policy_signature_public_key());
+}
+
+TEST_F(UserCloudPolicyStoreTest, ProvisionKeyTwice) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Store a simple policy and make sure it ends up as the currently active
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ // Now try sending down policy signed with a different key (i.e. do key
+ // rotation with a key not signed with the original signing key).
+ policy_.UnsetSigningKey();
+ policy_.SetDefaultNewSigningKey();
+ policy_.Build();
+ EXPECT_FALSE(policy_.policy().has_new_public_key_signature());
+
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store_->Store(policy_.policy());
+ RunUntilIdle();
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreTwoTimes) {
+ EXPECT_FALSE(store_->policy());
+ EXPECT_TRUE(store_->policy_map().empty());
+
+ // Store a simple policy then store a second policy before the first one
+ // finishes validating, and make sure the second policy ends up as the active
+ // policy.
+ UserPolicyBuilder first_policy;
+ first_policy.SetDefaultInitialSigningKey();
+ first_policy.payload().mutable_searchsuggestenabled()->set_value(false);
+ first_policy.Build();
+ StorePolicyAndEnsureLoaded(first_policy.policy());
+
+ // Rebuild policy with the same signing key as |first_policy| (no rotation).
+ policy_.UnsetNewSigningKey();
+ policy_.SetDefaultSigningKey();
+ policy_.Build();
+ ASSERT_FALSE(policy_.policy().has_new_public_key());
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ // Policy should be decoded and stored.
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ store_->policy()->SerializeAsString());
+ VerifyPolicyMap(store_.get());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreThenLoad) {
+ // Store a simple policy and make sure it can be read back in.
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ EXPECT_FALSE(store_->policy_signature_public_key().empty());
+
+ // Now, make sure the policy can be read back in from a second store.
+ std::unique_ptr<UserCloudPolicyStore> store2(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store2->SetSigninAccountId(PolicyBuilder::GetFakeAccountIdForTesting());
+ store2->AddObserver(&observer_);
+ EXPECT_CALL(observer_, OnStoreLoaded(store2.get()));
+ store2->Load();
+ RunUntilIdle();
+
+ ASSERT_TRUE(store2->policy());
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ store2->policy()->SerializeAsString());
+ VerifyPolicyMap(store2.get());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store2->status());
+ store2->RemoveObserver(&observer_);
+ // Make sure that we properly resurrected the keys.
+ EXPECT_EQ(store2->policy_signature_public_key(),
+ store_->policy_signature_public_key());
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreThenLoadImmediately) {
+ // Store a simple policy and make sure it can be read back in.
+ // policy.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ // Now, make sure the policy can be read back in from a second store.
+ std::unique_ptr<UserCloudPolicyStore> store2(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store2->SetSigninAccountId(PolicyBuilder::GetFakeAccountIdForTesting());
+ store2->AddObserver(&observer_);
+ EXPECT_CALL(observer_, OnStoreLoaded(store2.get()));
+ store2->LoadImmediately(); // Should load without running the message loop.
+
+ ASSERT_TRUE(store2->policy());
+ EXPECT_EQ(policy_.policy_data().SerializeAsString(),
+ store2->policy()->SerializeAsString());
+ VerifyPolicyMap(store2.get());
+ EXPECT_EQ(CloudPolicyStore::STATUS_OK, store2->status());
+ store2->RemoveObserver(&observer_);
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreValidationError) {
+ // Create an invalid policy (no policy type).
+ policy_.policy_data().clear_policy_type();
+ policy_.Build();
+
+ // Store policy.
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store_->Store(policy_.policy());
+ RunUntilIdle();
+ ASSERT_FALSE(store_->policy());
+}
+
+TEST_F(UserCloudPolicyStoreTest, StoreUnsigned) {
+ // Create unsigned policy, try to store it, should get a validation error.
+ policy_.policy().mutable_policy_data_signature()->clear();
+
+ // Store policy.
+ ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store_->Store(policy_.policy());
+ RunUntilIdle();
+ ASSERT_FALSE(store_->policy());
+}
+
+TEST_F(UserCloudPolicyStoreTest, LoadValidationError) {
+ AccountId other_account_id =
+ AccountId::FromUserEmailGaiaId("foobar@foobar.com", "another-gaia-id");
+ // Force a validation error by changing the account id after policy is stored.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ // Sign out, and sign back in as a different user, and try to load the profile
+ // data (should fail due to mismatched account id).
+ std::unique_ptr<UserCloudPolicyStore> store2(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store2->SetSigninAccountId(other_account_id);
+ store2->AddObserver(&observer_);
+ ExpectError(store2.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store2->Load();
+ RunUntilIdle();
+
+ ASSERT_FALSE(store2->policy());
+ store2->RemoveObserver(&observer_);
+
+ // Sign out - we should be able to load the policy (don't check users
+ // when signed out).
+ std::unique_ptr<UserCloudPolicyStore> store3(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store3->AddObserver(&observer_);
+ EXPECT_CALL(observer_, OnStoreLoaded(store3.get()));
+ store3->Load();
+ RunUntilIdle();
+
+ ASSERT_TRUE(store3->policy());
+ store3->RemoveObserver(&observer_);
+
+ // Now start a signin as a different user - this should fail validation.
+ std::unique_ptr<UserCloudPolicyStore> store4(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store4->SetSigninAccountId(other_account_id);
+ store4->AddObserver(&observer_);
+ ExpectError(store4.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store4->Load();
+ RunUntilIdle();
+
+ ASSERT_FALSE(store4->policy());
+ store4->RemoveObserver(&observer_);
+}
+
+TEST_F(UserCloudPolicyStoreTest, KeyRotation) {
+ // Make sure when we load data from disk with a different key, that we trigger
+ // a server-side key rotation.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+ ASSERT_TRUE(store_->policy()->has_public_key_version());
+
+ std::string key_data;
+ enterprise_management::PolicySigningKey key;
+ ASSERT_TRUE(base::ReadFileToString(key_file(), &key_data));
+ ASSERT_TRUE(key.ParseFromString(key_data));
+ key.set_verification_key("different_key");
+ key.SerializeToString(&key_data);
+ WriteStringToFile(key_file(), key_data);
+
+ // Now load this in a new store - this should trigger key rotation. The keys
+ // will still verify using the existing verification key.
+ std::unique_ptr<UserCloudPolicyStore> store2(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store2->SetSigninAccountId(PolicyBuilder::GetFakeAccountIdForTesting());
+ store2->AddObserver(&observer_);
+ EXPECT_CALL(observer_, OnStoreLoaded(store2.get()));
+ store2->Load();
+ RunUntilIdle();
+ ASSERT_TRUE(store2->policy());
+ ASSERT_FALSE(store2->policy()->has_public_key_version());
+ store2->RemoveObserver(&observer_);
+}
+
+TEST_F(UserCloudPolicyStoreTest, InvalidCachedVerificationSignature) {
+ // Make sure that we reject code with an invalid key.
+ StorePolicyAndEnsureLoaded(policy_.policy());
+
+ std::string key_data;
+ enterprise_management::PolicySigningKey key;
+ ASSERT_TRUE(base::ReadFileToString(key_file(), &key_data));
+ ASSERT_TRUE(key.ParseFromString(key_data));
+ key.set_signing_key_signature("different_key");
+ key.SerializeToString(&key_data);
+ WriteStringToFile(key_file(), key_data);
+
+ // Now load this in a new store - this should cause a validation error because
+ // the key won't verify.
+ std::unique_ptr<UserCloudPolicyStore> store2(new UserCloudPolicyStore(
+ policy_file(), key_file(), base::ThreadTaskRunnerHandle::Get()));
+ store2->SetSigninAccountId(PolicyBuilder::GetFakeAccountIdForTesting());
+ store2->AddObserver(&observer_);
+ ExpectError(store2.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR);
+ store2->Load();
+ RunUntilIdle();
+ store2->RemoveObserver(&observer_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_info_fetcher.cc b/chromium/components/policy/core/common/cloud/user_info_fetcher.cc
new file mode 100644
index 00000000000..749456c6800
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_info_fetcher.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_info_fetcher.h"
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/gurl.h"
+
+namespace {
+
+static const char kAuthorizationHeaderFormat[] = "Bearer %s";
+
+static std::string MakeAuthorizationHeader(const std::string& auth_token) {
+ return base::StringPrintf(kAuthorizationHeaderFormat, auth_token.c_str());
+}
+
+} // namespace
+
+namespace policy {
+
+UserInfoFetcher::UserInfoFetcher(
+ Delegate* delegate,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+ : delegate_(delegate), url_loader_factory_(std::move(url_loader_factory)) {
+ DCHECK(delegate_);
+}
+
+UserInfoFetcher::~UserInfoFetcher() {
+}
+
+void UserInfoFetcher::Start(const std::string& access_token) {
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("user_info_fetcher", R"(
+ semantics {
+ sender: "Cloud Policy"
+ description:
+ "Calls to the Google Account service to check if the signed-in "
+ "user is managed."
+ trigger: "User signing in to Chrome."
+ data: "OAuth2 token."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature cannot be controlled by Chrome settings, but users "
+ "can sign out of Chrome to disable it."
+ chrome_policy {
+ SigninAllowed {
+ policy_options {mode: MANDATORY}
+ SigninAllowed: false
+ }
+ }
+ })");
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GaiaUrls::GetInstance()->oauth_user_info_url();
+ resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
+ MakeAuthorizationHeader(access_token));
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ url_loader_->DownloadToString(
+ url_loader_factory_.get(),
+ base::BindOnce(&UserInfoFetcher::OnFetchComplete, base::Unretained(this)),
+ 1024 * 1024 /* 1 MiB */);
+}
+
+void UserInfoFetcher::OnFetchComplete(
+ std::unique_ptr<std::string> unparsed_data) {
+ std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(url_loader_);
+
+ GoogleServiceAuthError error = GoogleServiceAuthError::AuthErrorNone();
+ if (url_loader->NetError() != net::OK) {
+ if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) {
+ int response_code = url_loader->ResponseInfo()->headers->response_code();
+ DLOG(WARNING) << "UserInfo request failed with HTTP code: "
+ << response_code;
+ error = GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED);
+ } else {
+ error =
+ GoogleServiceAuthError::FromConnectionError(url_loader->NetError());
+ }
+ }
+ if (error.state() != GoogleServiceAuthError::NONE) {
+ delegate_->OnGetUserInfoFailure(error);
+ return;
+ }
+
+ // Successfully fetched userinfo from the server - parse it and hand it off
+ // to the delegate.
+ DCHECK(unparsed_data);
+ DVLOG(1) << "Received UserInfo response: " << *unparsed_data;
+ std::unique_ptr<base::Value> parsed_value =
+ base::JSONReader::ReadDeprecated(*unparsed_data);
+ base::DictionaryValue* dict;
+ if (parsed_value.get() && parsed_value->GetAsDictionary(&dict)) {
+ delegate_->OnGetUserInfoSuccess(dict);
+ } else {
+ NOTREACHED() << "Could not parse userinfo response from server";
+ delegate_->OnGetUserInfoFailure(GoogleServiceAuthError(
+ GoogleServiceAuthError::CONNECTION_FAILED));
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/cloud/user_info_fetcher.h b/chromium/components/policy/core/common/cloud/user_info_fetcher.h
new file mode 100644
index 00000000000..fe2e9f30656
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_info_fetcher.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_INFO_FETCHER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_INFO_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/policy_export.h"
+
+class GoogleServiceAuthError;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+}
+
+namespace policy {
+
+// Class that makes a UserInfo request, parses the response, and notifies
+// a provided Delegate when the request is complete.
+class POLICY_EXPORT UserInfoFetcher {
+ public:
+ class POLICY_EXPORT Delegate {
+ public:
+ // Invoked when the UserInfo request has succeeded, passing the parsed
+ // response in |response|. Delegate may free the UserInfoFetcher in this
+ // callback.
+ virtual void OnGetUserInfoSuccess(
+ const base::DictionaryValue* response) = 0;
+
+ // Invoked when the UserInfo request has failed, passing the associated
+ // error in |error|. Delegate may free the UserInfoFetcher in this
+ // callback.
+ virtual void OnGetUserInfoFailure(const GoogleServiceAuthError& error) = 0;
+ };
+
+ // Create a new UserInfoFetcher. |url_loader_factory| can be nullptr for unit
+ // tests.
+ UserInfoFetcher(
+ Delegate* delegate,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+ UserInfoFetcher(const UserInfoFetcher&) = delete;
+ UserInfoFetcher& operator=(const UserInfoFetcher&) = delete;
+ ~UserInfoFetcher();
+
+ // Starts the UserInfo request, using the passed OAuth2 |access_token|.
+ void Start(const std::string& access_token);
+
+ // Called by |url_loader_| on completion.
+ void OnFetchComplete(std::unique_ptr<std::string> body);
+
+ private:
+ raw_ptr<Delegate> delegate_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_USER_INFO_FETCHER_H_
diff --git a/chromium/components/policy/core/common/cloud/user_info_fetcher_unittest.cc b/chromium/components/policy/core/common/cloud/user_info_fetcher_unittest.cc
new file mode 100644
index 00000000000..82daf13dd70
--- /dev/null
+++ b/chromium/components/policy/core/common/cloud/user_info_fetcher_unittest.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/cloud/user_info_fetcher.h"
+
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace policy {
+
+namespace {
+
+static const char kUserInfoUrl[] =
+ "https://www.googleapis.com/oauth2/v1/userinfo";
+static const char kUserInfoResponse[] =
+ "{"
+ " \"email\": \"test_user@test.com\","
+ " \"verified_email\": true,"
+ " \"hd\": \"test.com\""
+ "}";
+
+class MockUserInfoFetcherDelegate : public UserInfoFetcher::Delegate {
+ public:
+ MockUserInfoFetcherDelegate() {}
+ ~MockUserInfoFetcherDelegate() {}
+ MOCK_METHOD1(OnGetUserInfoFailure,
+ void(const GoogleServiceAuthError& error));
+ MOCK_METHOD1(OnGetUserInfoSuccess, void(const base::DictionaryValue* result));
+};
+
+MATCHER_P(MatchDict, expected, "matches DictionaryValue") {
+ return *arg == *expected;
+}
+
+class UserInfoFetcherTest : public testing::Test {
+ public:
+ UserInfoFetcherTest() = default;
+ UserInfoFetcherTest(const UserInfoFetcherTest&) = delete;
+ UserInfoFetcherTest& operator=(const UserInfoFetcherTest&) = delete;
+ ~UserInfoFetcherTest() override = default;
+
+ protected:
+ base::test::TaskEnvironment task_env_;
+ network::TestURLLoaderFactory loader_factory_;
+};
+
+TEST_F(UserInfoFetcherTest, FailedFetch) {
+ MockUserInfoFetcherDelegate delegate;
+ UserInfoFetcher fetcher(
+ &delegate,
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &loader_factory_));
+ fetcher.Start("access_token");
+
+ // Fake a failed fetch - should result in the failure callback being invoked.
+ EXPECT_CALL(delegate, OnGetUserInfoFailure(_));
+ EXPECT_TRUE(loader_factory_.SimulateResponseForPendingRequest(
+ kUserInfoUrl, std::string(), net::HTTP_INTERNAL_SERVER_ERROR));
+ task_env_.RunUntilIdle();
+}
+
+TEST_F(UserInfoFetcherTest, SuccessfulFetch) {
+ MockUserInfoFetcherDelegate delegate;
+ UserInfoFetcher fetcher(
+ &delegate,
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &loader_factory_));
+ fetcher.Start("access_token");
+
+ // Generate what we expect our result will look like (should match
+ // parsed kUserInfoResponse).
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetKey("email", base::Value("test_user@test.com"));
+ dict.SetKey("verified_email", base::Value(true));
+ dict.SetKey("hd", base::Value("test.com"));
+
+ // Fake a successful fetch - should result in the data being parsed and
+ // the values passed off to the success callback.
+ EXPECT_CALL(delegate, OnGetUserInfoSuccess(MatchDict(&dict)));
+ EXPECT_TRUE(loader_factory_.SimulateResponseForPendingRequest(
+ kUserInfoUrl, kUserInfoResponse));
+}
+
+} // namespace
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/command_line_policy_provider.cc b/chromium/components/policy/core/common/command_line_policy_provider.cc
new file mode 100644
index 00000000000..9592b8c1950
--- /dev/null
+++ b/chromium/components/policy/core/common/command_line_policy_provider.cc
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/command_line_policy_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/build_info.h"
+#endif // BUILDFLAG(IS_ANDROID)
+
+namespace policy {
+
+// static
+std::unique_ptr<CommandLinePolicyProvider>
+CommandLinePolicyProvider::CreateIfAllowed(
+ const base::CommandLine& command_line,
+ version_info::Channel channel) {
+#if BUILDFLAG(IS_ANDROID)
+ if (channel == version_info::Channel::STABLE ||
+ channel == version_info::Channel::BETA) {
+ return nullptr;
+ }
+
+ if (!base::android::BuildInfo::GetInstance()->is_debug_android())
+ return nullptr;
+
+ return base::WrapUnique(new CommandLinePolicyProvider(command_line));
+#else
+ return nullptr;
+#endif // BUILDFLAG(IS_ANDROID)
+}
+
+// static
+std::unique_ptr<CommandLinePolicyProvider>
+CommandLinePolicyProvider::CreateForTesting(
+ const base::CommandLine& command_line) {
+ return base::WrapUnique(new CommandLinePolicyProvider(command_line));
+}
+
+CommandLinePolicyProvider::~CommandLinePolicyProvider() = default;
+
+void CommandLinePolicyProvider::RefreshPolicies() {
+ std::unique_ptr<PolicyBundle> bundle = loader_.Load();
+ first_policies_loaded_ = true;
+ UpdatePolicy(std::move(bundle));
+}
+
+bool CommandLinePolicyProvider::IsFirstPolicyLoadComplete(
+ PolicyDomain domain) const {
+ return first_policies_loaded_;
+}
+
+CommandLinePolicyProvider::CommandLinePolicyProvider(
+ const base::CommandLine& command_line)
+ : loader_(command_line) {
+ RefreshPolicies();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/command_line_policy_provider.h b/chromium/components/policy/core/common/command_line_policy_provider.h
new file mode 100644
index 00000000000..fb6cade6294
--- /dev/null
+++ b/chromium/components/policy/core/common/command_line_policy_provider.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_COMMAND_LINE_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_COMMAND_LINE_POLICY_PROVIDER_H_
+
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_loader_command_line.h"
+#include "components/policy/policy_export.h"
+#include "components/version_info/channel.h"
+
+namespace policy {
+
+// The policy provider for the Command Line Policy which is used for development
+// and testing purposes.
+class POLICY_EXPORT CommandLinePolicyProvider
+ : public ConfigurationPolicyProvider {
+ public:
+ // The |CommandLinePolicyProvider| provides an extremely easy way to set up
+ // policies which means it can be used for malicious purposes. So it should
+ // be created if and only if the browser is under development environment.
+ static std::unique_ptr<CommandLinePolicyProvider> CreateIfAllowed(
+ const base::CommandLine& command_line,
+ version_info::Channel channel);
+
+ static std::unique_ptr<CommandLinePolicyProvider> CreateForTesting(
+ const base::CommandLine& command_line);
+
+ CommandLinePolicyProvider(const CommandLinePolicyProvider&) = delete;
+ CommandLinePolicyProvider& operator=(const CommandLinePolicyProvider&) =
+ delete;
+
+ ~CommandLinePolicyProvider() override;
+
+ // ConfigurationPolicyProvider implementation.
+ void RefreshPolicies() override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+
+ private:
+ explicit CommandLinePolicyProvider(const base::CommandLine& command_line);
+
+ bool first_policies_loaded_;
+ PolicyLoaderCommandLine loader_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_COMMAND_LINE_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/command_line_policy_provider_unittest.cc b/chromium/components/policy/core/common/command_line_policy_provider_unittest.cc
new file mode 100644
index 00000000000..23219c4e418
--- /dev/null
+++ b/chromium/components/policy/core/common/command_line_policy_provider_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/command_line_policy_provider.h"
+
+#include <memory>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/build_info.h"
+#endif // BUILDFLAG(IS_ANDROID)
+
+namespace policy {
+
+namespace {
+
+void VerifyPolicyProvider(ConfigurationPolicyProvider* provider) {
+ const base::Value* policy_value =
+ provider->policies()
+ .Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .GetValue("policy", base::Value::Type::INTEGER);
+ ASSERT_TRUE(policy_value);
+ ASSERT_TRUE(policy_value->is_int());
+ EXPECT_EQ(10, policy_value->GetInt());
+}
+
+} // namespace
+
+class CommandLinePolicyProviderTest : public ::testing::Test {
+ public:
+ CommandLinePolicyProviderTest() {
+ command_line_.AppendSwitchASCII(switches::kChromePolicy,
+ R"({"policy":10})");
+ }
+
+ std::unique_ptr<CommandLinePolicyProvider> CreatePolicyProvider() {
+ return CommandLinePolicyProvider::CreateForTesting(command_line_);
+ }
+
+ std::unique_ptr<CommandLinePolicyProvider> CreatePolicyProviderWithCheck(
+ version_info::Channel channel) {
+ return CommandLinePolicyProvider::CreateIfAllowed(command_line_, channel);
+ }
+
+ base::CommandLine* command_line() { return &command_line_; }
+
+ private:
+ base::CommandLine command_line_{base::CommandLine::NO_PROGRAM};
+};
+
+TEST_F(CommandLinePolicyProviderTest, LoadAndRefresh) {
+ std::unique_ptr<CommandLinePolicyProvider> policy_provider =
+ CreatePolicyProvider();
+ VerifyPolicyProvider(policy_provider.get());
+
+ policy_provider->RefreshPolicies();
+ VerifyPolicyProvider(policy_provider.get());
+}
+
+TEST_F(CommandLinePolicyProviderTest, Creator) {
+ version_info::Channel channels[] = {
+ version_info::Channel::UNKNOWN, version_info::Channel::CANARY,
+ version_info::Channel::DEV, version_info::Channel::BETA,
+ version_info::Channel::STABLE};
+ for (auto channel : channels) {
+ bool is_created = false;
+#if BUILDFLAG(IS_ANDROID)
+ is_created = channel != version_info::Channel::BETA &&
+ channel != version_info::Channel::STABLE &&
+ base::android::BuildInfo::GetInstance()->is_debug_android();
+#endif // BUILDFLAG(IS_ANDROID)
+ auto policy_provider = CreatePolicyProviderWithCheck(channel);
+ if (is_created)
+ EXPECT_TRUE(policy_provider);
+ else
+ EXPECT_FALSE(policy_provider);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/config_dir_policy_loader.cc b/chromium/components/policy/core/common/config_dir_policy_loader.cc
new file mode 100644
index 00000000000..fb70e431afc
--- /dev/null
+++ b/chromium/components/policy/core/common/config_dir_policy_loader.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/config_dir_policy_loader.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/containers/adapters.h"
+#include "base/containers/contains.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/syslog_logging.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectories that contain the mandatory and recommended policies.
+constexpr base::FilePath::CharType kMandatoryConfigDir[] =
+ FILE_PATH_LITERAL("managed");
+constexpr base::FilePath::CharType kRecommendedConfigDir[] =
+ FILE_PATH_LITERAL("recommended");
+
+PolicyLoadStatus JsonErrorToPolicyLoadStatus(int status) {
+ switch (status) {
+ case JSONFileValueDeserializer::JSON_ACCESS_DENIED:
+ case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE:
+ case JSONFileValueDeserializer::JSON_FILE_LOCKED:
+ return POLICY_LOAD_STATUS_READ_ERROR;
+ case JSONFileValueDeserializer::JSON_NO_SUCH_FILE:
+ return POLICY_LOAD_STATUS_MISSING;
+ case base::ValueDeserializer::kErrorCodeNoError:
+ NOTREACHED();
+ return POLICY_LOAD_STATUS_STARTED;
+ }
+ if (!base::ValueDeserializer::ErrorCodeIsDataError(status)) {
+ NOTREACHED() << "Invalid status " << status;
+ }
+ return POLICY_LOAD_STATUS_PARSE_ERROR;
+}
+
+} // namespace
+
+ConfigDirPolicyLoader::ConfigDirPolicyLoader(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/true),
+ task_runner_(task_runner),
+ config_dir_(config_dir),
+ scope_(scope) {}
+
+ConfigDirPolicyLoader::~ConfigDirPolicyLoader() {}
+
+void ConfigDirPolicyLoader::InitOnBackgroundThread() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::FilePathWatcher::Callback callback = base::BindRepeating(
+ &ConfigDirPolicyLoader::OnFileUpdated, base::Unretained(this));
+ mandatory_watcher_.Watch(config_dir_.Append(kMandatoryConfigDir),
+ base::FilePathWatcher::Type::kNonRecursive,
+ callback);
+ recommended_watcher_.Watch(config_dir_.Append(kRecommendedConfigDir),
+ base::FilePathWatcher::Type::kNonRecursive,
+ callback);
+}
+
+std::unique_ptr<PolicyBundle> ConfigDirPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ LoadFromPath(config_dir_.Append(kMandatoryConfigDir),
+ POLICY_LEVEL_MANDATORY,
+ bundle.get());
+ LoadFromPath(config_dir_.Append(kRecommendedConfigDir),
+ POLICY_LEVEL_RECOMMENDED,
+ bundle.get());
+ return bundle;
+}
+
+base::Time ConfigDirPolicyLoader::LastModificationTime() {
+ static constexpr const base::FilePath::CharType* kConfigDirSuffixes[] = {
+ kMandatoryConfigDir, kRecommendedConfigDir,
+ };
+
+ base::Time last_modification = base::Time();
+ base::File::Info info;
+
+ for (size_t i = 0; i < std::size(kConfigDirSuffixes); ++i) {
+ base::FilePath path(config_dir_.Append(kConfigDirSuffixes[i]));
+
+ // Skip if the file doesn't exist, or it isn't a directory.
+ if (!base::GetFileInfo(path, &info) || !info.is_directory)
+ continue;
+
+ // Enumerate the files and find the most recent modification timestamp.
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file = file_enumerator.Next();
+ !config_file.empty();
+ config_file = file_enumerator.Next()) {
+ if (base::GetFileInfo(config_file, &info) && !info.is_directory)
+ last_modification = std::max(last_modification, info.last_modified);
+ }
+ }
+
+ return last_modification;
+}
+
+void ConfigDirPolicyLoader::LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle) {
+ // Enumerate the files and sort them lexicographically.
+ std::set<base::FilePath> files;
+ base::FileEnumerator file_enumerator(path, false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath config_file_path = file_enumerator.Next();
+ !config_file_path.empty(); config_file_path = file_enumerator.Next())
+ files.insert(config_file_path);
+
+ PolicyLoadStatusUmaReporter status;
+ if (files.empty()) {
+ status.Add(POLICY_LOAD_STATUS_NO_POLICY);
+ return;
+ }
+
+ // Start with an empty dictionary and merge the files' contents.
+ // The files are processed in reverse order because |MergeFrom| gives priority
+ // to existing keys, but the ConfigDirPolicyProvider gives priority to the
+ // last file in lexicographic order.
+ for (const base::FilePath& config_file : base::Reversed(files)) {
+ JSONFileValueDeserializer deserializer(
+ config_file, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+ base::JSON_ALLOW_TRAILING_COMMAS);
+ int error_code = 0;
+ std::string error_msg;
+ std::unique_ptr<base::Value> value =
+ deserializer.Deserialize(&error_code, &error_msg);
+ if (!value) {
+ SYSLOG(WARNING) << "Failed to read configuration file "
+ << config_file.value() << ": " << error_msg;
+ status.Add(JsonErrorToPolicyLoadStatus(error_code));
+ continue;
+ }
+ base::DictionaryValue* dictionary_value = nullptr;
+ if (!value->GetAsDictionary(&dictionary_value)) {
+ SYSLOG(WARNING) << "Expected JSON dictionary in configuration file "
+ << config_file.value();
+ status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ continue;
+ }
+
+ // Detach the "3rdparty" node.
+ absl::optional<base::Value> third_party =
+ dictionary_value->ExtractKey("3rdparty");
+ if (third_party.has_value()) {
+ Merge3rdPartyPolicy(&*third_party, level, bundle,
+ /*signin_profile=*/true);
+ Merge3rdPartyPolicy(&*third_party, level, bundle,
+ /*signin_profile=*/false);
+ }
+
+ // Add chrome policy.
+ PolicyMap policy_map;
+ policy_map.LoadFrom(dictionary_value, level, scope_,
+ POLICY_SOURCE_PLATFORM);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .MergeFrom(policy_map);
+ }
+}
+
+void ConfigDirPolicyLoader::Merge3rdPartyPolicy(const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle,
+ bool signin_profile) {
+ // The first-level entries in |policies| are PolicyDomains. The second-level
+ // entries are component IDs, and the third-level entries are the policies
+ // for that domain/component namespace.
+
+ const base::DictionaryValue* domains_dictionary;
+ if (!policies->GetAsDictionary(&domains_dictionary)) {
+ SYSLOG(WARNING) << "3rdparty value is not a dictionary!";
+ return;
+ }
+
+ // Helper to lookup a domain given its string name.
+ std::map<std::string, PolicyDomain> supported_domains;
+ supported_domains["extensions"] = signin_profile
+ ? POLICY_DOMAIN_SIGNIN_EXTENSIONS
+ : POLICY_DOMAIN_EXTENSIONS;
+
+ for (base::DictionaryValue::Iterator domains_it(*domains_dictionary);
+ !domains_it.IsAtEnd(); domains_it.Advance()) {
+ if (!base::Contains(supported_domains, domains_it.key())) {
+ SYSLOG(WARNING) << "Unsupported 3rd party policy domain: "
+ << domains_it.key();
+ continue;
+ }
+
+ const base::DictionaryValue* components_dictionary;
+ if (!domains_it.value().GetAsDictionary(&components_dictionary)) {
+ SYSLOG(WARNING) << "3rdparty/" << domains_it.key()
+ << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyDomain domain = supported_domains[domains_it.key()];
+ for (base::DictionaryValue::Iterator components_it(*components_dictionary);
+ !components_it.IsAtEnd(); components_it.Advance()) {
+ const base::DictionaryValue* policy_dictionary;
+ if (!components_it.value().GetAsDictionary(&policy_dictionary)) {
+ SYSLOG(WARNING) << "3rdparty/" << domains_it.key() << "/"
+ << components_it.key() << " value is not a dictionary!";
+ continue;
+ }
+
+ PolicyMap policy;
+ policy.LoadFrom(policy_dictionary, level, scope_, POLICY_SOURCE_PLATFORM);
+ bundle->Get(PolicyNamespace(domain, components_it.key()))
+ .MergeFrom(policy);
+ }
+ }
+}
+
+void ConfigDirPolicyLoader::OnFileUpdated(const base::FilePath& path,
+ bool error) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (!error)
+ Reload(false);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/config_dir_policy_loader.h b/chromium/components/policy/core/common/config_dir_policy_loader.h
new file mode 100644
index 00000000000..48b23541c42
--- /dev/null
+++ b/chromium/components/policy/core/common/config_dir_policy_loader.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+// A policy loader implementation backed by a set of files in a given
+// directory. The files should contain JSON-formatted policy settings. They are
+// merged together and the result is returned in a PolicyBundle.
+// The files are consulted in lexicographic file name order, so the
+// last value read takes precedence in case of policy key collisions.
+class POLICY_EXPORT ConfigDirPolicyLoader : public AsyncPolicyLoader {
+ public:
+ ConfigDirPolicyLoader(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& config_dir,
+ PolicyScope scope);
+ ConfigDirPolicyLoader(const ConfigDirPolicyLoader&) = delete;
+ ConfigDirPolicyLoader& operator=(const ConfigDirPolicyLoader&) = delete;
+ ~ConfigDirPolicyLoader() override;
+
+ // AsyncPolicyLoader implementation.
+ void InitOnBackgroundThread() override;
+ std::unique_ptr<PolicyBundle> Load() override;
+ base::Time LastModificationTime() override;
+
+ private:
+ // Loads the policy files at |path| into the |bundle|, with the given |level|.
+ void LoadFromPath(const base::FilePath& path,
+ PolicyLevel level,
+ PolicyBundle* bundle);
+
+ // Merges the 3rd party |policies| (extension policies) into the |bundle|,
+ // with the given |level|.
+ void Merge3rdPartyPolicy(const base::Value* policies,
+ PolicyLevel level,
+ PolicyBundle* bundle,
+ bool signin_profile = false);
+
+ // Callback for the FilePathWatchers.
+ void OnFileUpdated(const base::FilePath& path, bool error);
+
+ // Task runner for running background jobs.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // The directory containing the policy files.
+ const base::FilePath config_dir_;
+
+ // Policies loaded by this provider will have this scope.
+ const PolicyScope scope_;
+
+ // Watchers for events on the mandatory and recommended subdirectories of
+ // |config_dir_|.
+ base::FilePathWatcher mandatory_watcher_;
+ base::FilePathWatcher recommended_watcher_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIG_DIR_POLICY_LOADER_H_
diff --git a/chromium/components/policy/core/common/config_dir_policy_loader_unittest.cc b/chromium/components/policy/core/common/config_dir_policy_loader_unittest.cc
new file mode 100644
index 00000000000..e20ab8b3a6c
--- /dev/null
+++ b/chromium/components/policy/core/common/config_dir_policy_loader_unittest.cc
@@ -0,0 +1,284 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/config_dir_policy_loader.h"
+#include <memory>
+
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace {
+
+// Subdirectory of the config dir that contains mandatory policies.
+const base::FilePath::CharType kMandatoryPath[] = FILE_PATH_LITERAL("managed");
+// The policy input supports trailing comma and c++ styled comments.
+const char PolicyWithQuirks[] = R"({
+ // Some comments here.
+ "HomepageIsNewTabPage": true,
+ /* Some more comments here */
+})";
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ TestHarness();
+ TestHarness(const TestHarness&) = delete;
+ TestHarness& operator=(const TestHarness&) = delete;
+ ~TestHarness() override;
+
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) override;
+ void Install3rdPartyPolicy(const base::DictionaryValue* policies) override;
+
+ const base::FilePath& test_dir() { return test_dir_.GetPath(); }
+
+ // JSON-encode a dictionary and write it to a file.
+ void WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name);
+ void WriteConfigFile(const std::string& data, const std::string& file_name);
+
+ // Returns a unique name for a policy file. Each subsequent call returns a
+ // new name that comes lexicographically after the previous one.
+ std::string NextConfigFileName();
+
+ static PolicyProviderTestHarness* Create();
+
+ private:
+ base::ScopedTempDir test_dir_;
+ int next_policy_file_index_;
+};
+
+TestHarness::TestHarness()
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM),
+ next_policy_file_index_(100) {}
+
+TestHarness::~TestHarness() = default;
+
+void TestHarness::SetUp() {
+ ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
+}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ std::unique_ptr<AsyncPolicyLoader> loader(
+ new ConfigDirPolicyLoader(task_runner, test_dir(), POLICY_SCOPE_MACHINE));
+ return new AsyncPolicyProvider(registry, std::move(loader));
+}
+
+void TestHarness::InstallEmptyPolicy() {
+ base::DictionaryValue dict;
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ base::DictionaryValue dict;
+ dict.SetStringKey(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ base::DictionaryValue dict;
+ dict.SetIntKey(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ base::DictionaryValue dict;
+ dict.SetBoolKey(policy_name, policy_value);
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ base::DictionaryValue dict;
+ dict.SetKey(policy_name, policy_value->Clone());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) {
+ base::DictionaryValue dict;
+ dict.SetKey(policy_name, policy_value->Clone());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) {
+ base::DictionaryValue dict;
+ dict.SetKey("3rdparty", policies->Clone());
+ WriteConfigFile(dict, NextConfigFileName());
+}
+
+void TestHarness::WriteConfigFile(const base::DictionaryValue& dict,
+ const std::string& file_name) {
+ std::string data;
+ JSONStringValueSerializer serializer(&data);
+ serializer.Serialize(dict);
+ WriteConfigFile(data, file_name);
+}
+
+void TestHarness::WriteConfigFile(const std::string& data,
+ const std::string& file_name) {
+ const base::FilePath mandatory_dir(test_dir().Append(kMandatoryPath));
+ ASSERT_TRUE(base::CreateDirectory(mandatory_dir));
+ const base::FilePath file_path(mandatory_dir.AppendASCII(file_name));
+ ASSERT_EQ((int)data.size(),
+ base::WriteFile(file_path, data.c_str(), data.size()));
+}
+
+std::string TestHarness::NextConfigFileName() {
+ EXPECT_LE(next_policy_file_index_, 999);
+ return std::string("policy") +
+ base::NumberToString(next_policy_file_index_++);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+ return new TestHarness();
+}
+
+} // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_SUITE_P(ConfigDirPolicyLoaderTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Instantiate abstract test case for 3rd party policy reading tests.
+INSTANTIATE_TEST_SUITE_P(ConfigDir3rdPartyPolicyLoaderTest,
+ Configuration3rdPartyPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// Some tests that exercise special functionality in ConfigDirPolicyLoader.
+class ConfigDirPolicyLoaderTest : public PolicyTestBase {
+ protected:
+ void SetUp() override {
+ PolicyTestBase::SetUp();
+ harness_.SetUp();
+ }
+
+ TestHarness harness_;
+};
+
+// The preferences dictionary is expected to be empty when there are no files to
+// load.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsEmpty) {
+ ConfigDirPolicyLoader loader(task_environment_.GetMainThreadTaskRunner(),
+ harness_.test_dir(), POLICY_SCOPE_MACHINE);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+// Reading from a non-existent directory should result in an empty preferences
+// dictionary.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsNonExistentDirectory) {
+ base::FilePath non_existent_dir(
+ harness_.test_dir().Append(FILE_PATH_LITERAL("not_there")));
+ ConfigDirPolicyLoader loader(task_environment_.GetMainThreadTaskRunner(),
+ non_existent_dir, POLICY_SCOPE_MACHINE);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(bundle->Equals(kEmptyBundle));
+}
+
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsWithComments) {
+ harness_.WriteConfigFile(PolicyWithQuirks, "policies.json");
+ ConfigDirPolicyLoader loader(task_environment_.GetMainThreadTaskRunner(),
+ harness_.test_dir(), POLICY_SCOPE_MACHINE);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(key::kHomepageIsNewTabPage, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM, base::Value(true),
+ /*external_data_fetcher=*/nullptr);
+
+ EXPECT_TRUE(bundle->Equals(expected_bundle));
+}
+
+// Test merging values from different files.
+TEST_F(ConfigDirPolicyLoaderTest, ReadPrefsMergePrefs) {
+ // Write a bunch of data files in order to increase the chance to detect the
+ // provider not respecting lexicographic ordering when reading them. Since the
+ // filesystem may return files in arbitrary order, there is no way to be sure,
+ // but this is better than nothing.
+ base::DictionaryValue test_dict_bar;
+ const char kHomepageLocation[] = "HomepageLocation";
+ test_dict_bar.SetStringKey(kHomepageLocation, "http://bar.com");
+ for (unsigned int i = 1; i <= 4; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::NumberToString(i));
+ base::DictionaryValue test_dict_foo;
+ test_dict_foo.SetStringKey(kHomepageLocation, "http://foo.com");
+ harness_.WriteConfigFile(test_dict_foo, "9");
+ for (unsigned int i = 5; i <= 8; ++i)
+ harness_.WriteConfigFile(test_dict_bar, base::NumberToString(i));
+
+ ConfigDirPolicyLoader loader(task_environment_.GetMainThreadTaskRunner(),
+ harness_.test_dir(), POLICY_SCOPE_USER);
+ std::unique_ptr<PolicyBundle> bundle(loader.Load());
+ ASSERT_TRUE(bundle.get());
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .LoadFrom(&test_dict_foo, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+ for (unsigned int i = 1; i <= 8; ++i) {
+ auto conflict_policy =
+ expected_bundle
+ .Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Get(kHomepageLocation)
+ ->DeepCopy();
+ conflict_policy.conflicts.clear();
+ conflict_policy.set_value(base::Value("http://bar.com"));
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .GetMutable(kHomepageLocation)
+ ->AddConflictingPolicy(std::move(conflict_policy));
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .GetMutable(kHomepageLocation)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ }
+ EXPECT_TRUE(bundle->Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/configuration_policy_provider.cc b/chromium/components/policy/core/common/configuration_policy_provider.cc
new file mode 100644
index 00000000000..61a20890813
--- /dev/null
+++ b/chromium/components/policy/core/common/configuration_policy_provider.cc
@@ -0,0 +1,91 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/configuration_policy_provider.h"
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/observer_list.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+ConfigurationPolicyProvider::Observer::~Observer() = default;
+
+ConfigurationPolicyProvider::ConfigurationPolicyProvider()
+ : initialized_(false), schema_registry_(nullptr) {}
+
+ConfigurationPolicyProvider::~ConfigurationPolicyProvider() {
+ DCHECK(!initialized_);
+}
+
+void ConfigurationPolicyProvider::Init(SchemaRegistry* registry) {
+ schema_registry_ = registry;
+ schema_registry_->AddObserver(this);
+ initialized_ = true;
+}
+
+void ConfigurationPolicyProvider::Shutdown() {
+ initialized_ = false;
+ if (schema_registry_) {
+ // Unit tests don't initialize the BrowserPolicyConnector but call
+ // shutdown; handle that.
+ schema_registry_->RemoveObserver(this);
+ schema_registry_ = nullptr;
+ }
+}
+
+bool ConfigurationPolicyProvider::IsInitializationComplete(
+ PolicyDomain domain) const {
+ return true;
+}
+
+bool ConfigurationPolicyProvider::IsFirstPolicyLoadComplete(
+ PolicyDomain domain) const {
+ return true;
+}
+
+void ConfigurationPolicyProvider::UpdatePolicy(
+ std::unique_ptr<PolicyBundle> bundle) {
+ if (bundle) {
+ policy_bundle_.Swap(bundle.get());
+ } else {
+ policy_bundle_.Clear();
+ }
+ for (auto& observer : observer_list_)
+ observer.OnUpdatePolicy(this);
+}
+
+SchemaRegistry* ConfigurationPolicyProvider::schema_registry() const {
+ return schema_registry_;
+}
+
+const scoped_refptr<SchemaMap>&
+ConfigurationPolicyProvider::schema_map() const {
+ return schema_registry_->schema_map();
+}
+
+void ConfigurationPolicyProvider::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void ConfigurationPolicyProvider::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void ConfigurationPolicyProvider::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {}
+
+void ConfigurationPolicyProvider::OnSchemaRegistryReady() {}
+
+#if BUILDFLAG(IS_ANDROID)
+void ConfigurationPolicyProvider::ShutdownForTesting() {
+ observer_list_.Clear();
+ Shutdown();
+}
+#endif // BUILDFLAG(IS_ANDROID)
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/configuration_policy_provider.h b/chromium/components/policy/core/common/configuration_policy_provider.h
new file mode 100644
index 00000000000..f66f20df652
--- /dev/null
+++ b/chromium/components/policy/core/common/configuration_policy_provider.h
@@ -0,0 +1,117 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A mostly-abstract super class for platform-specific policy providers.
+// Platform-specific policy providers (Windows Group Policy, gconf,
+// etc.) should implement a subclass of this class.
+class POLICY_EXPORT ConfigurationPolicyProvider
+ : public SchemaRegistry::Observer {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ virtual ~Observer();
+ virtual void OnUpdatePolicy(ConfigurationPolicyProvider* provider) = 0;
+ };
+
+ ConfigurationPolicyProvider();
+ ConfigurationPolicyProvider(const ConfigurationPolicyProvider&) = delete;
+ ConfigurationPolicyProvider& operator=(const ConfigurationPolicyProvider&) =
+ delete;
+
+ // Policy providers can be deleted quite late during shutdown of the browser,
+ // and it's not guaranteed that the message loops will still be running when
+ // this is invoked. Override Shutdown() instead for cleanup code that needs
+ // to post to the FILE thread, for example.
+ ~ConfigurationPolicyProvider() override;
+
+ // Invoked as soon as the main message loops are spinning. Policy providers
+ // are created early during startup to provide the initial policies; the
+ // Init() call allows them to perform initialization tasks that require
+ // running message loops.
+ // The policy provider will load policy for the components registered in
+ // the |schema_registry| whose domain is supported by this provider.
+ virtual void Init(SchemaRegistry* registry);
+
+ // Must be invoked before deleting the provider. Implementations can override
+ // this method to do appropriate cleanup while threads are still running, and
+ // must also invoke ConfigurationPolicyProvider::Shutdown().
+ // The provider should keep providing the current policies after Shutdown()
+ // is invoked, it only has to stop updating.
+ virtual void Shutdown();
+
+ // Returns the current PolicyBundle.
+ const PolicyBundle& policies() const { return policy_bundle_; }
+
+ // Check whether this provider has completed initialization for the given
+ // policy |domain|. This is used to detect whether initialization is done in
+ // case implementations need to do asynchronous operations for initialization.
+ virtual bool IsInitializationComplete(PolicyDomain domain) const;
+
+ // Check whether this provider has loaded its first policies for the given
+ // policy |domain|. This is used to detect whether policies have been loaded
+ // is done in case implementations need to do asynchronous operations to get
+ // the policies.
+ virtual bool IsFirstPolicyLoadComplete(PolicyDomain domain) const;
+
+ // Asks the provider to refresh its policies. All the updates caused by this
+ // call will be visible on the next call of OnUpdatePolicy on the observers,
+ // which are guaranteed to happen even if the refresh fails.
+ // It is possible that Shutdown() is called first though, and
+ // OnUpdatePolicy won't be called if that happens.
+ virtual void RefreshPolicies() = 0;
+
+ // Observers must detach themselves before the provider is deleted.
+ virtual void AddObserver(Observer* observer);
+ virtual void RemoveObserver(Observer* observer);
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+ void OnSchemaRegistryReady() override;
+
+#if BUILDFLAG(IS_ANDROID)
+ void ShutdownForTesting();
+#endif // BUILDFLAG(IS_ANDROID)
+
+ protected:
+ // Subclasses must invoke this to update the policies currently served by
+ // this provider. UpdatePolicy() takes ownership of |policies|.
+ // The observers are notified after the policies are updated.
+ void UpdatePolicy(std::unique_ptr<PolicyBundle> bundle);
+
+ SchemaRegistry* schema_registry() const;
+
+ const scoped_refptr<SchemaMap>& schema_map() const;
+
+ private:
+ // The policies currently configured at this provider.
+ PolicyBundle policy_bundle_;
+
+ // Used to validate proper Init() and Shutdown() nesting. This flag is set by
+ // Init() and cleared by Shutdown() and needs to be false in the destructor.
+ bool initialized_;
+
+ raw_ptr<SchemaRegistry> schema_registry_;
+
+ base::ObserverList<Observer, true>::Unchecked observer_list_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/configuration_policy_provider_test.cc b/chromium/components/policy/core/common/configuration_policy_provider_test.cc
new file mode 100644
index 00000000000..85b79f1cf8e
--- /dev/null
+++ b/chromium/components/policy/core/common/configuration_policy_provider_test.cc
@@ -0,0 +1,423 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_migrator.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::Mock;
+using ::testing::_;
+
+namespace policy {
+
+const char kTestChromeSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"StringPolicy\": { \"type\": \"string\" },"
+ " \"BooleanPolicy\": { \"type\": \"boolean\" },"
+ " \"IntegerPolicy\": { \"type\": \"integer\" },"
+ " \"StringListPolicy\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"DictionaryPolicy\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" },"
+ " \"array\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"dictionary\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"sub\": { \"type\": \"string\" },"
+ " \"sublist\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"aaa\": { \"type\": \"integer\" },"
+ " \"bbb\": { \"type\": \"integer\" },"
+ " \"ccc\": { \"type\": \"string\" },"
+ " \"ddd\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"subdictindex\": { \"type\": \"integer\" },"
+ " \"subdict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " },"
+ " \"dict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" },"
+ " \"list\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"subdictindex\": { \"type\": \"integer\" },"
+ " \"subdict\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"double\": { \"type\": \"number\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"string\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ " }"
+ "}";
+
+namespace test_keys {
+
+const char kKeyString[] = "StringPolicy";
+const char kKeyBoolean[] = "BooleanPolicy";
+const char kKeyInteger[] = "IntegerPolicy";
+const char kKeyStringList[] = "StringListPolicy";
+const char kKeyDictionary[] = "DictionaryPolicy";
+
+} // namespace test_keys
+
+PolicyTestBase::PolicyTestBase() {}
+
+PolicyTestBase::~PolicyTestBase() {}
+
+void PolicyTestBase::SetUp() {
+ const PolicyNamespace ns(POLICY_DOMAIN_CHROME, "");
+ ASSERT_TRUE(RegisterSchema(ns, kTestChromeSchema));
+}
+
+void PolicyTestBase::TearDown() {
+ task_environment_.RunUntilIdle();
+}
+
+bool PolicyTestBase::RegisterSchema(const PolicyNamespace& ns,
+ const std::string& schema_string) {
+ std::string error;
+ Schema schema = Schema::Parse(schema_string, &error);
+ if (schema.valid()) {
+ schema_registry_.RegisterComponent(ns, schema);
+ return true;
+ }
+ ADD_FAILURE() << error;
+ return false;
+}
+
+PolicyProviderTestHarness::PolicyProviderTestHarness(PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source)
+ : level_(level), scope_(scope), source_(source) {}
+
+PolicyProviderTestHarness::~PolicyProviderTestHarness() {}
+
+PolicyLevel PolicyProviderTestHarness::policy_level() const {
+ return level_;
+}
+
+PolicyScope PolicyProviderTestHarness::policy_scope() const {
+ return scope_;
+}
+
+PolicySource PolicyProviderTestHarness::policy_source() const {
+ return source_;
+}
+
+void PolicyProviderTestHarness::Install3rdPartyPolicy(
+ const base::DictionaryValue* policies) {
+ FAIL();
+}
+
+ConfigurationPolicyProviderTest::ConfigurationPolicyProviderTest() {}
+
+ConfigurationPolicyProviderTest::~ConfigurationPolicyProviderTest() {}
+
+void ConfigurationPolicyProviderTest::SetUp() {
+ PolicyTestBase::SetUp();
+
+ test_harness_.reset((*GetParam())());
+ ASSERT_NO_FATAL_FAILURE(test_harness_->SetUp());
+
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ Schema chrome_schema = *schema_registry_.schema_map()->GetSchema(chrome_ns);
+ Schema extension_schema =
+ chrome_schema.GetKnownProperty(test_keys::kKeyDictionary);
+ ASSERT_TRUE(extension_schema.valid());
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ extension_schema);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
+ extension_schema);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "cccccccccccccccccccccccccccccccc"),
+ extension_schema);
+
+ provider_.reset(test_harness_->CreateProvider(
+ &schema_registry_,
+ base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})));
+ provider_->Init(&schema_registry_);
+ // Some providers do a reload on init. Make sure any notifications generated
+ // are fired now.
+ task_environment_.RunUntilIdle();
+
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
+}
+
+void ConfigurationPolicyProviderTest::TearDown() {
+ // Give providers the chance to clean up after themselves on the file thread.
+ provider_->Shutdown();
+ provider_.reset();
+
+ PolicyTestBase::TearDown();
+}
+
+void ConfigurationPolicyProviderTest::CheckValue(
+ const char* policy_name,
+ const base::Value& expected_value,
+ base::OnceClosure install_value) {
+ // Install the value, reload policy and check the provider for the value.
+ std::move(install_value).Run();
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(policy_name, test_harness_->policy_level(),
+ test_harness_->policy_scope(), test_harness_->policy_source(),
+ expected_value.Clone(), nullptr);
+ EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, Empty) {
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, StringValue) {
+ const char kTestString[] = "string_value";
+ base::Value expected_value(kTestString);
+ CheckValue(test_keys::kKeyString, expected_value,
+ base::BindOnce(&PolicyProviderTestHarness::InstallStringPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyString, kTestString));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, BooleanValue) {
+ base::Value expected_value(true);
+ CheckValue(test_keys::kKeyBoolean, expected_value,
+ base::BindOnce(&PolicyProviderTestHarness::InstallBooleanPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyBoolean, true));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, IntegerValue) {
+ base::Value expected_value(42);
+ CheckValue(test_keys::kKeyInteger, expected_value,
+ base::BindOnce(&PolicyProviderTestHarness::InstallIntegerPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyInteger, 42));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, StringListValue) {
+ base::ListValue expected_value;
+ expected_value.Append("first");
+ expected_value.Append("second");
+ CheckValue(test_keys::kKeyStringList, expected_value,
+ base::BindOnce(&PolicyProviderTestHarness::InstallStringListPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyStringList, &expected_value));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, DictionaryValue) {
+ base::Value expected_value(base::Value::Type::DICTIONARY);
+ expected_value.SetBoolKey("bool", true);
+ expected_value.SetDoubleKey("double", 123.456);
+ expected_value.SetIntKey("int", 123);
+ expected_value.SetStringKey("string", "omg");
+
+ {
+ base::Value list(base::Value::Type::LIST);
+ list.Append("first");
+ list.Append("second");
+ expected_value.SetKey("array", std::move(list));
+ }
+
+ base::Value sublist(base::Value::Type::LIST);
+ {
+ base::Value sub(base::Value::Type::DICTIONARY);
+ sub.SetIntKey("aaa", 111);
+ sub.SetIntKey("bbb", 222);
+ sublist.Append(std::move(sub));
+ }
+
+ {
+ base::Value sub(base::Value::Type::DICTIONARY);
+ sub.SetStringKey("ccc", "333");
+ sub.SetStringKey("ddd", "444");
+ sublist.Append(std::move(sub));
+ }
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetStringKey("sub", "value");
+ dict.SetKey("sublist", std::move(sublist));
+ expected_value.SetKey("dictionary", std::move(dict));
+
+ CheckValue(test_keys::kKeyDictionary, expected_value,
+ base::BindOnce(&PolicyProviderTestHarness::InstallDictionaryPolicy,
+ base::Unretained(test_harness_.get()),
+ test_keys::kKeyDictionary, &expected_value));
+}
+
+TEST_P(ConfigurationPolicyProviderTest, RefreshPolicies) {
+ PolicyBundle bundle;
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+
+ // OnUpdatePolicy is called even when there are no changes.
+ MockConfigurationPolicyObserver observer;
+ provider_->AddObserver(&observer);
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+
+ // OnUpdatePolicy is called when there are changes.
+ test_harness_->InstallStringPolicy(test_keys::kKeyString, "value");
+ EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(test_keys::kKeyString, test_harness_->policy_level(),
+ test_harness_->policy_scope(), test_harness_->policy_source(),
+ base::Value("value"), nullptr);
+ EXPECT_TRUE(provider_->policies().Equals(bundle));
+ provider_->RemoveObserver(&observer);
+}
+
+Configuration3rdPartyPolicyProviderTest::
+ Configuration3rdPartyPolicyProviderTest() {}
+
+Configuration3rdPartyPolicyProviderTest::
+ ~Configuration3rdPartyPolicyProviderTest() {}
+
+TEST_P(Configuration3rdPartyPolicyProviderTest, Load3rdParty) {
+ base::DictionaryValue policy_dict;
+ policy_dict.SetBoolKey("bool", true);
+ policy_dict.SetDoubleKey("double", 123.456);
+ policy_dict.SetIntKey("int", 789);
+ policy_dict.SetStringKey("string", "string value");
+
+ base::ListValue list;
+ for (int i = 0; i < 2; ++i) {
+ auto dict = std::make_unique<base::DictionaryValue>();
+ dict->SetIntKey("subdictindex", i);
+ dict->SetKey("subdict", policy_dict.Clone());
+ list.Append(std::move(dict));
+ }
+ policy_dict.SetKey("list", std::move(list));
+ policy_dict.SetKey("dict", policy_dict.Clone());
+
+ // Install these policies as a Chrome policy.
+ test_harness_->InstallDictionaryPolicy(test_keys::kKeyDictionary,
+ &policy_dict);
+ // Install them as 3rd party policies too.
+ base::DictionaryValue policy_3rdparty;
+ policy_3rdparty.SetPath({"extensions", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
+ policy_dict.Clone());
+ policy_3rdparty.SetPath({"extensions", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
+ policy_dict.Clone());
+ // Install invalid 3rd party policies that shouldn't be loaded. These also
+ // help detecting memory leaks in the code paths that detect invalid input.
+ policy_3rdparty.SetPath({"invalid-domain", "component"}, policy_dict.Clone());
+ policy_3rdparty.SetStringPath("extensions.cccccccccccccccccccccccccccccccc",
+ "invalid-value");
+ test_harness_->Install3rdPartyPolicy(&policy_3rdparty);
+
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+
+ PolicyMap expected_policy;
+ expected_policy.Set(test_keys::kKeyDictionary, test_harness_->policy_level(),
+ test_harness_->policy_scope(),
+ test_harness_->policy_source(), policy_dict.Clone(),
+ nullptr);
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ expected_policy.Clone();
+ expected_policy.Clear();
+ expected_policy.LoadFrom(&policy_dict,
+ test_harness_->policy_level(),
+ test_harness_->policy_scope(),
+ test_harness_->policy_source());
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) =
+ expected_policy.Clone();
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) =
+ expected_policy.Clone();
+ EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
+}
+
+// These are abstract policy provider tests, which are instantiated for each
+// policy provider implementation.
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ConfigurationPolicyProviderTest);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
+ Configuration3rdPartyPolicyProviderTest);
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/configuration_policy_provider_test.h b/chromium/components/policy/core/common/configuration_policy_provider_test.h
new file mode 100644
index 00000000000..210055da579
--- /dev/null
+++ b/chromium/components/policy/core/common/configuration_policy_provider_test.h
@@ -0,0 +1,162 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class SequencedTaskRunner;
+class Value;
+}
+
+namespace policy {
+
+class ConfigurationPolicyProvider;
+
+namespace test_keys {
+
+extern const char kKeyString[];
+extern const char kKeyBoolean[];
+extern const char kKeyInteger[];
+extern const char kKeyStringList[];
+extern const char kKeyDictionary[];
+
+} // namespace test_keys
+
+class PolicyTestBase : public testing::Test {
+ public:
+ PolicyTestBase();
+ PolicyTestBase(const PolicyTestBase&) = delete;
+ PolicyTestBase& operator=(const PolicyTestBase&) = delete;
+ ~PolicyTestBase() override;
+
+ // testing::Test:
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ bool RegisterSchema(const PolicyNamespace& ns,
+ const std::string& schema);
+
+ // Needs to be the first member
+ base::test::TaskEnvironment task_environment_;
+ SchemaRegistry schema_registry_;
+};
+
+// An interface for creating a test policy provider and creating a policy
+// provider instance for testing. Used as the parameter to the abstract
+// ConfigurationPolicyProviderTest below.
+class PolicyProviderTestHarness {
+ public:
+ // |level|, |scope| and |source| are the level, scope and source of the
+ // policies returned by the providers from CreateProvider().
+ PolicyProviderTestHarness(PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source);
+ PolicyProviderTestHarness(const PolicyProviderTestHarness&) = delete;
+ PolicyProviderTestHarness& operator=(const PolicyProviderTestHarness&) =
+ delete;
+ virtual ~PolicyProviderTestHarness();
+
+ // Actions to run at gtest SetUp() time.
+ virtual void SetUp() = 0;
+
+ // Create a new policy provider.
+ virtual ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) = 0;
+
+ // Returns the policy level, scope and source set by the policy provider.
+ PolicyLevel policy_level() const;
+ PolicyScope policy_scope() const;
+ PolicySource policy_source() const;
+
+ // Helpers to configure the environment the policy provider reads from.
+ virtual void InstallEmptyPolicy() = 0;
+ virtual void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) = 0;
+ virtual void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) = 0;
+ virtual void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) = 0;
+ virtual void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) = 0;
+ virtual void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) = 0;
+
+ // Not every provider supports installing 3rd party policy. Those who do
+ // should override this method; the default just makes the test fail.
+ virtual void Install3rdPartyPolicy(const base::DictionaryValue* policies);
+
+ private:
+ PolicyLevel level_;
+ PolicyScope scope_;
+ PolicySource source_;
+};
+
+// A factory method for creating a test harness.
+typedef PolicyProviderTestHarness* (*CreatePolicyProviderTestHarness)();
+
+// Abstract policy provider test. This is meant to be instantiated for each
+// policy provider implementation, passing in a suitable harness factory
+// function as the test parameter.
+class ConfigurationPolicyProviderTest
+ : public PolicyTestBase,
+ public testing::WithParamInterface<CreatePolicyProviderTestHarness> {
+ public:
+ ConfigurationPolicyProviderTest(const ConfigurationPolicyProviderTest&) =
+ delete;
+ ConfigurationPolicyProviderTest& operator=(
+ const ConfigurationPolicyProviderTest&) = delete;
+
+ protected:
+ ConfigurationPolicyProviderTest();
+ ~ConfigurationPolicyProviderTest() override;
+
+ void SetUp() override;
+ void TearDown() override;
+
+ // Installs a valid policy and checks whether the provider returns the
+ // |expected_value|.
+ void CheckValue(const char* policy_name,
+ const base::Value& expected_value,
+ base::OnceClosure install_value);
+
+ std::unique_ptr<PolicyProviderTestHarness> test_harness_;
+ std::unique_ptr<ConfigurationPolicyProvider> provider_;
+};
+
+// An extension of ConfigurationPolicyProviderTest that also tests loading of
+// 3rd party policy. Policy provider implementations that support loading of
+// 3rd party policy should also instantiate these tests.
+class Configuration3rdPartyPolicyProviderTest
+ : public ConfigurationPolicyProviderTest {
+ public:
+ Configuration3rdPartyPolicyProviderTest(
+ const Configuration3rdPartyPolicyProviderTest&) = delete;
+ Configuration3rdPartyPolicyProviderTest& operator=(
+ const Configuration3rdPartyPolicyProviderTest&) = delete;
+
+ protected:
+ Configuration3rdPartyPolicyProviderTest();
+ virtual ~Configuration3rdPartyPolicyProviderTest();
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_CONFIGURATION_POLICY_PROVIDER_TEST_H_
diff --git a/chromium/components/policy/core/common/default_chrome_apps_migrator.cc b/chromium/components/policy/core/common/default_chrome_apps_migrator.cc
new file mode 100644
index 00000000000..ecee5085eca
--- /dev/null
+++ b/chromium/components/policy/core/common/default_chrome_apps_migrator.cc
@@ -0,0 +1,133 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/default_chrome_apps_migrator.h"
+
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace {
+
+std::map<std::string, std::string> GetChromeAppToWebAppMapping() {
+ return std::map<std::string, std::string>();
+}
+
+} // namespace
+
+DefaultChromeAppsMigrator::DefaultChromeAppsMigrator()
+ : DefaultChromeAppsMigrator(GetChromeAppToWebAppMapping()) {}
+
+DefaultChromeAppsMigrator::DefaultChromeAppsMigrator(
+ std::map<std::string, std::string> chrome_app_to_web_app)
+ : chrome_app_to_web_app_(std::move(chrome_app_to_web_app)) {}
+
+DefaultChromeAppsMigrator::DefaultChromeAppsMigrator(
+ DefaultChromeAppsMigrator&&) noexcept = default;
+DefaultChromeAppsMigrator& DefaultChromeAppsMigrator::operator=(
+ DefaultChromeAppsMigrator&&) noexcept = default;
+
+DefaultChromeAppsMigrator::~DefaultChromeAppsMigrator() = default;
+
+void DefaultChromeAppsMigrator::Migrate(PolicyMap* policies) const {
+ std::vector<std::string> chrome_app_ids =
+ RemoveChromeAppsFromExtensionForcelist(policies);
+
+ // If no chrome apps need to be replaced, we have nothing to do.
+ if (chrome_app_ids.empty())
+ return;
+
+ EnsurePolicyValueIsList(policies, key::kExtensionInstallBlocklist);
+ base::Value* blocklist_value = policies->GetMutableValue(
+ key::kExtensionInstallBlocklist, base::Value::Type::LIST);
+ for (const std::string& chrome_app_id : chrome_app_ids) {
+ blocklist_value->Append(chrome_app_id);
+ }
+
+ EnsurePolicyValueIsList(policies, key::kWebAppInstallForceList);
+ base::Value* web_app_policy_value = policies->GetMutableValue(
+ key::kWebAppInstallForceList, base::Value::Type::LIST);
+ for (const std::string& chrome_app_id : chrome_app_ids) {
+ base::Value web_app(base::Value::Type::DICTIONARY);
+ web_app.SetStringKey("url", chrome_app_to_web_app_.at(chrome_app_id));
+ web_app_policy_value->Append(std::move(web_app));
+ }
+
+ MigratePinningPolicy(policies);
+}
+
+std::vector<std::string>
+DefaultChromeAppsMigrator::RemoveChromeAppsFromExtensionForcelist(
+ PolicyMap* policies) const {
+ PolicyMap::Entry* forcelist_entry =
+ policies->GetMutable(key::kExtensionInstallForcelist);
+ if (!forcelist_entry)
+ return std::vector<std::string>();
+
+ const base::Value* forcelist_value =
+ forcelist_entry->value(base::Value::Type::LIST);
+ if (!forcelist_value)
+ return std::vector<std::string>();
+
+ std::vector<std::string> chrome_app_ids;
+ base::Value new_forcelist_value(base::Value::Type::LIST);
+ for (const auto& list_entry : forcelist_value->GetListDeprecated()) {
+ if (!list_entry.is_string()) {
+ new_forcelist_value.Append(list_entry.Clone());
+ continue;
+ }
+
+ const std::string entry = list_entry.GetString();
+ const size_t pos = entry.find(';');
+ const std::string extension_id = entry.substr(0, pos);
+
+ if (chrome_app_to_web_app_.count(extension_id))
+ chrome_app_ids.push_back(extension_id);
+ else
+ new_forcelist_value.Append(entry);
+ }
+
+ forcelist_entry->set_value(std::move(new_forcelist_value));
+ return chrome_app_ids;
+}
+
+void DefaultChromeAppsMigrator::EnsurePolicyValueIsList(
+ PolicyMap* policies,
+ const std::string& policy_name) const {
+ // It is safe to use `GetValueUnsafe()` because type checking is performed
+ // before the value is used.
+ const base::Value* policy_value = policies->GetValueUnsafe(policy_name);
+ if (!policy_value || !policy_value->is_list()) {
+ const PolicyMap::Entry* forcelist_entry =
+ policies->Get(key::kExtensionInstallForcelist);
+ PolicyMap::Entry policy_entry(
+ forcelist_entry->level, forcelist_entry->scope, forcelist_entry->source,
+ base::Value(base::Value::Type::LIST), nullptr);
+ // If `policy_value` has wrong type, add message before overriding value.
+ if (policy_value) {
+ policy_entry.AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_TYPE_ERROR);
+ }
+ policies->Set(policy_name, std::move(policy_entry));
+ }
+}
+
+void DefaultChromeAppsMigrator::MigratePinningPolicy(
+ PolicyMap* policies) const {
+ base::Value* pinned_apps_value = policies->GetMutableValue(
+ key::kPinnedLauncherApps, base::Value::Type::LIST);
+ if (!pinned_apps_value)
+ return;
+ for (auto& list_entry : pinned_apps_value->GetListDeprecated()) {
+ if (!list_entry.is_string())
+ continue;
+ const std::string pinned_app = list_entry.GetString();
+ auto it = chrome_app_to_web_app_.find(pinned_app);
+ if (it != chrome_app_to_web_app_.end())
+ list_entry = base::Value(it->second);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/default_chrome_apps_migrator.h b/chromium/components/policy/core/common/default_chrome_apps_migrator.h
new file mode 100644
index 00000000000..6220db402fa
--- /dev/null
+++ b/chromium/components/policy/core/common/default_chrome_apps_migrator.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_DEFAULT_CHROME_APPS_MIGRATOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_DEFAULT_CHROME_APPS_MIGRATOR_H_
+
+#include <map>
+#include <string>
+
+#include "base/values.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+// This class is used as a temporary solution to handle force install policies
+// for deprecated Chrome apps. It replaces ExtensionInstallForcelist policy
+// for Chrome app with ExtensionInstallBlocklist for Chrome app and
+// WebAppInstallForceList policy for the corresponding Web App. To preserve the
+// pinning state, PinnedLauncherApps policy for Chrome app is replaced with the
+// one for Web App.
+// This code will be removed when the following steps are done:
+// 1. Build discoverability for default apps in Admin panel (Dpanel).
+// 2. Build new control logic for blocking installation (but not blocking use
+// of) PWAs.
+// 3. Migrate policy from Chrome Apps to PWAs in Admin surface (DMServer).
+class POLICY_EXPORT DefaultChromeAppsMigrator {
+ public:
+ DefaultChromeAppsMigrator();
+ explicit DefaultChromeAppsMigrator(
+ std::map<std::string, std::string> chrome_app_to_web_app);
+
+ DefaultChromeAppsMigrator(const DefaultChromeAppsMigrator&) = delete;
+ DefaultChromeAppsMigrator& operator=(const DefaultChromeAppsMigrator&) =
+ delete;
+
+ DefaultChromeAppsMigrator(DefaultChromeAppsMigrator&&) noexcept;
+ DefaultChromeAppsMigrator& operator=(DefaultChromeAppsMigrator&&) noexcept;
+
+ ~DefaultChromeAppsMigrator();
+
+ // Replaces ExtensionInstallForcelist policy for Chrome apps listed in
+ // `chrome_app_to_web_app_`.
+ void Migrate(PolicyMap* policies) const;
+
+ private:
+ // Removes chrome apps listed in `chrome_app_to_web_app_` from
+ // ExtensionInstallForcelist policy. Returns ids of removed apps.
+ std::vector<std::string> RemoveChromeAppsFromExtensionForcelist(
+ PolicyMap* policies) const;
+
+ // Checks that policy value type is list. If not, adds error message to the
+ // policy entry and overrides policy value with an empty list.
+ void EnsurePolicyValueIsList(PolicyMap* policies,
+ const std::string& policy_name) const;
+
+ // Replaces policy to pin Chrome app from `chrome_app_to_web_app_` with policy
+ // to pin corresponding Web App. It only changes PinnedLauncherApps policy,
+ // which specifies pinned apps on Chrome OS.
+ void MigratePinningPolicy(PolicyMap* policies) const;
+
+ // Maps from ids of Chrome apps that need to be replaced to Web App urls.
+ std::map<std::string, std::string> chrome_app_to_web_app_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_DEFAULT_CHROME_APPS_MIGRATOR_H_
diff --git a/chromium/components/policy/core/common/default_chrome_apps_migrator_unittest.cc b/chromium/components/policy/core/common/default_chrome_apps_migrator_unittest.cc
new file mode 100644
index 00000000000..d62ca8c4a49
--- /dev/null
+++ b/chromium/components/policy/core/common/default_chrome_apps_migrator_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/default_chrome_apps_migrator.h"
+
+#include <map>
+#include <string>
+
+#include "base/logging.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+constexpr char kAppId1[] = "aaaa";
+constexpr char kAppId2[] = "bbbb";
+constexpr char kWebAppUrl1[] = "https://gmail.com";
+constexpr char kWebAppUrl2[] = "https://google.com";
+} // namespace
+
+class DefaultChromeAppsMigratorTest : public testing::Test {
+ void SetUp() override {
+ migrator_ = DefaultChromeAppsMigrator(std::map<std::string, std::string>{
+ {kAppId1, kWebAppUrl1}, {kAppId2, kWebAppUrl2}});
+
+ // Set initial values for ExtensionInstallForcelist,
+ // ExtensionInstallBlocklist and WebAppInstallForceList policies.
+ policy_map_.Set(key::kExtensionInstallForcelist, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(base::Value::Type::LIST), nullptr);
+
+ base::Value blocklist_value(base::Value::Type::LIST);
+ blocklist_value.Append("eeee");
+ policy_map_.Set(key::kExtensionInstallBlocklist, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(blocklist_value), nullptr);
+
+ base::Value web_app_value(base::Value::Type::LIST);
+ base::Value maps_web_app(base::Value::Type::DICTIONARY);
+ maps_web_app.SetStringKey("url", "https://google.com/maps");
+ web_app_value.Append(std::move(maps_web_app));
+ policy_map_.Set(key::kWebAppInstallForceList, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(web_app_value), nullptr);
+
+ base::Value pinned_apps_value(base::Value::Type::LIST);
+ pinned_apps_value.Append("ffff");
+ policy_map_.Set(key::kPinnedLauncherApps, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(pinned_apps_value), nullptr);
+ }
+
+ protected:
+ DefaultChromeAppsMigrator migrator_;
+ PolicyMap policy_map_;
+};
+
+TEST_F(DefaultChromeAppsMigratorTest, NoChromeApps) {
+ PolicyMap expected_map(policy_map_.Clone());
+ migrator_.Migrate(&policy_map_);
+
+ // No chrome apps in ExtensionInstallForcelist policy, policy map should not
+ // change.
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+TEST_F(DefaultChromeAppsMigratorTest, ChromeAppWithUpdateUrl) {
+ PolicyMap expected_map(policy_map_.Clone());
+
+ // Add force installed chrome app that should be migrated.
+ base::Value* forcelist_value = policy_map_.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ forcelist_value->Append(std::string(kAppId1) + ";https://example.com");
+
+ // Chrome app should be blocked after migration.
+ base::Value* blocklist_value = expected_map.GetMutableValue(
+ key::kExtensionInstallBlocklist, base::Value::Type::LIST);
+ blocklist_value->Append(std::string(kAppId1));
+
+ // Corresponding web app should be force installed after migration.
+ base::Value first_app(base::Value::Type::DICTIONARY);
+ first_app.SetStringKey("url", kWebAppUrl1);
+ base::Value* web_app_value = expected_map.GetMutableValue(
+ key::kWebAppInstallForceList, base::Value::Type::LIST);
+ web_app_value->Append(std::move(first_app));
+
+ migrator_.Migrate(&policy_map_);
+
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+TEST_F(DefaultChromeAppsMigratorTest, ChromeAppsAndExtensions) {
+ PolicyMap expected_map(policy_map_.Clone());
+
+ // Add two force installed chrome apps and two extensions.
+ base::Value* forcelist_value = policy_map_.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ forcelist_value->Append("extension1");
+ forcelist_value->Append(kAppId1);
+ forcelist_value->Append("extension2");
+ forcelist_value->Append(kAppId2);
+
+ // Only extensions should be left now.
+ base::Value* expected_forcelist = expected_map.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ expected_forcelist->Append("extension1");
+ expected_forcelist->Append("extension2");
+
+ // Chrome apps should be blocked after migration.
+ base::Value* blocklist_value = expected_map.GetMutableValue(
+ key::kExtensionInstallBlocklist, base::Value::Type::LIST);
+ blocklist_value->Append(kAppId1);
+ blocklist_value->Append(kAppId2);
+
+ // Corresponding web apps should be force installed after migration.
+ base::Value first_app(base::Value::Type::DICTIONARY);
+ first_app.SetStringKey("url", kWebAppUrl1);
+ base::Value second_app(base::Value::Type::DICTIONARY);
+ second_app.SetStringKey("url", kWebAppUrl2);
+ base::Value* web_app_value = expected_map.GetMutableValue(
+ key::kWebAppInstallForceList, base::Value::Type::LIST);
+ web_app_value->Append(std::move(first_app));
+ web_app_value->Append(std::move(second_app));
+
+ migrator_.Migrate(&policy_map_);
+
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+// Tests the case when ExtensionInstallBlocklist is initially set to wrong type
+// and we have to append chrome app id to it. The value should be overridden and
+// error message should be added.
+TEST_F(DefaultChromeAppsMigratorTest, ExtensionBlocklistPolicyWrongType) {
+ PolicyMap expected_map(policy_map_.Clone());
+
+ // Add force installed chrome app.
+ base::Value* forcelist_value = policy_map_.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ forcelist_value->Append(kAppId1);
+
+ // Set ExtensionInstallBlocklist to non-list type.
+ base::Value blocklist_value(base::Value::Type::DICTIONARY);
+ policy_map_.GetMutable(key::kExtensionInstallBlocklist)
+ ->set_value(std::move(blocklist_value));
+
+ base::Value blocklist_expected_value(base::Value::Type::LIST);
+ blocklist_expected_value.Append(kAppId1);
+ PolicyMap::Entry* blocklist_expected_entry =
+ expected_map.GetMutable(key::kExtensionInstallBlocklist);
+ blocklist_expected_entry->set_value(std::move(blocklist_expected_value));
+ blocklist_expected_entry->AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_TYPE_ERROR);
+
+ // Corresponding web app should be force installed after migration.
+ base::Value first_app(base::Value::Type::DICTIONARY);
+ first_app.SetStringKey("url", kWebAppUrl1);
+ base::Value* web_app_value = expected_map.GetMutableValue(
+ key::kWebAppInstallForceList, base::Value::Type::LIST);
+ web_app_value->Append(std::move(first_app));
+
+ migrator_.Migrate(&policy_map_);
+
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+// Tests the case when WebAppInstallForceList is initially set to wrong type and
+// we have to append a web app to it. The value should be overridden and error
+// message should be added.
+TEST_F(DefaultChromeAppsMigratorTest, WebAppPolicyWrongType) {
+ PolicyMap expected_map(policy_map_.Clone());
+
+ // Add force installed chrome app.
+ base::Value* forcelist_value = policy_map_.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ forcelist_value->Append(kAppId1);
+
+ // Set WebAppInstallForceList to non-list type.
+ base::Value web_app_value(base::Value::Type::DICTIONARY);
+ policy_map_.GetMutable(key::kWebAppInstallForceList)
+ ->set_value(std::move(web_app_value));
+
+ // Chrome app should be blocked after migration.
+ base::Value* blocklist_value = expected_map.GetMutableValue(
+ key::kExtensionInstallBlocklist, base::Value::Type::LIST);
+ blocklist_value->Append(kAppId1);
+
+ base::Value web_app_expected_value(base::Value::Type::LIST);
+ base::Value first_app(base::Value::Type::DICTIONARY);
+ first_app.SetStringKey("url", kWebAppUrl1);
+ web_app_expected_value.Append(std::move(first_app));
+ PolicyMap::Entry* web_app_expected_entry =
+ expected_map.GetMutable(key::kWebAppInstallForceList);
+ web_app_expected_entry->set_value(std::move(web_app_expected_value));
+ web_app_expected_entry->AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_TYPE_ERROR);
+
+ migrator_.Migrate(&policy_map_);
+
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+TEST_F(DefaultChromeAppsMigratorTest, PinnedApp) {
+ PolicyMap expected_map(policy_map_.Clone());
+
+ // Add force installed chrome app that should be migrated.
+ base::Value* forcelist_value = policy_map_.GetMutableValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ forcelist_value->Append(std::string(kAppId1));
+
+ // Make the chrome app pinned.
+ base::Value* pinned_apps_value = policy_map_.GetMutableValue(
+ key::kPinnedLauncherApps, base::Value::Type::LIST);
+ pinned_apps_value->Append(std::string(kAppId1));
+
+ // Chrome app should be blocked after migration.
+ base::Value* blocklist_value = expected_map.GetMutableValue(
+ key::kExtensionInstallBlocklist, base::Value::Type::LIST);
+ blocklist_value->Append(std::string(kAppId1));
+
+ // Corresponding web app should be force installed after migration.
+ base::Value first_app(base::Value::Type::DICTIONARY);
+ first_app.SetStringKey("url", kWebAppUrl1);
+ base::Value* web_app_value = expected_map.GetMutableValue(
+ key::kWebAppInstallForceList, base::Value::Type::LIST);
+ web_app_value->Append(std::move(first_app));
+
+ // The corresponding Web App should be pinned.
+ base::Value* pinned_expected_value = expected_map.GetMutableValue(
+ key::kPinnedLauncherApps, base::Value::Type::LIST);
+ pinned_expected_value->Append(std::string(kWebAppUrl1));
+
+ migrator_.Migrate(&policy_map_);
+
+ EXPECT_TRUE(policy_map_.Equals(expected_map));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/external_data_fetcher.cc b/chromium/components/policy/core/common/external_data_fetcher.cc
new file mode 100644
index 00000000000..9d58e0060b2
--- /dev/null
+++ b/chromium/components/policy/core/common/external_data_fetcher.cc
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/external_data_fetcher.h"
+
+#include "base/callback.h"
+#include "components/policy/core/common/external_data_manager.h"
+
+namespace policy {
+
+ExternalDataFetcher::ExternalDataFetcher(
+ base::WeakPtr<ExternalDataManager> manager,
+ const std::string& policy)
+ : manager_(manager),
+ policy_(policy) {
+}
+
+ExternalDataFetcher::ExternalDataFetcher(const ExternalDataFetcher& other)
+ : manager_(other.manager_),
+ policy_(other.policy_) {
+}
+
+ExternalDataFetcher::~ExternalDataFetcher() {
+}
+
+// static
+bool ExternalDataFetcher::Equals(const ExternalDataFetcher* first,
+ const ExternalDataFetcher* second) {
+ if (!first && !second)
+ return true;
+ if (!first || !second)
+ return false;
+ return first->manager_.get() == second->manager_.get() &&
+ first->policy_ == second->policy_;
+}
+
+void ExternalDataFetcher::Fetch(FetchCallback callback) const {
+ if (manager_)
+ manager_->Fetch(policy_, std::string(), std::move(callback));
+ else
+ std::move(callback).Run(nullptr, base::FilePath());
+}
+
+void ExternalDataFetcher::Fetch(const std::string& field_name,
+ FetchCallback callback) const {
+ if (manager_)
+ manager_->Fetch(policy_, field_name, std::move(callback));
+ else
+ std::move(callback).Run(nullptr, base::FilePath());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/external_data_fetcher.h b/chromium/components/policy/core/common/external_data_fetcher.h
new file mode 100644
index 00000000000..7daf9b33d59
--- /dev/null
+++ b/chromium/components/policy/core/common/external_data_fetcher.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class ExternalDataManager;
+
+// A helper that encapsulates the parameters required to retrieve the external
+// data for a policy.
+class POLICY_EXPORT ExternalDataFetcher {
+ public:
+ using FetchCallback = base::OnceCallback<void(std::unique_ptr<std::string>,
+ const base::FilePath&)>;
+
+ // This instance's Fetch() method will instruct the |manager| to retrieve the
+ // external data referenced by the given |policy|.
+ ExternalDataFetcher(base::WeakPtr<ExternalDataManager> manager,
+ const std::string& policy);
+ ExternalDataFetcher(const ExternalDataFetcher& other);
+
+ ~ExternalDataFetcher();
+
+ static bool Equals(const ExternalDataFetcher* first,
+ const ExternalDataFetcher* second);
+
+ // Retrieves the external data referenced by |policy_| and invokes |callback|
+ // with the result. If |policy_| does not reference any external data, the
+ // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is
+ // invoked with the referenced data once it has been successfully retrieved.
+ // If retrieval is temporarily impossible (e.g. no network connectivity), the
+ // |callback| will be invoked when the temporary hindrance is resolved. If
+ // retrieval is permanently impossible (e.g. |policy_| references data that
+ // does not exist on the server), the |callback| will never be invoked.
+ void Fetch(FetchCallback callback) const;
+
+ // Same as above, except there might be multiple pieces of data associated
+ // with the |policy_|. |field_name| specifies which specific data should be
+ // fetched. The relation between |field_name| and the policy value is
+ // policy-specific.
+ void Fetch(const std::string& field_name, FetchCallback callback) const;
+
+ private:
+ base::WeakPtr<ExternalDataManager> manager_;
+ const std::string policy_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_FETCHER_H_
diff --git a/chromium/components/policy/core/common/external_data_manager.h b/chromium/components/policy/core/common/external_data_manager.h
new file mode 100644
index 00000000000..5ad4c638a92
--- /dev/null
+++ b/chromium/components/policy/core/common/external_data_manager.h
@@ -0,0 +1,40 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
+
+#include <string>
+
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Downloads, verifies, caches and retrieves external data referenced by
+// policies.
+// An implementation of this abstract interface should be provided for each
+// policy source (cloud policy, platform policy) that supports external data
+// references.
+class POLICY_EXPORT ExternalDataManager {
+ public:
+ // Retrieves the external data referenced by the (|policy|, |field_name|) pair
+ // and invokes |callback|
+ // with the result. Most external policies only reference a single piece of
+ // data, in which case |field_name| should be empty. If (|policy|,
+ // |field_name|) does not reference any external data, the
+ // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is
+ // invoked with the referenced data once it has been successfully retrieved.
+ // If retrieval is temporarily impossible (e.g. no network connectivity), the
+ // |callback| will be invoked when the temporary hindrance is resolved. If
+ // retrieval is permanently impossible (e.g. |policy| references data that
+ // does not exist on the server), the |callback| will never be invoked.
+ virtual void Fetch(const std::string& policy,
+ const std::string& field_name,
+ ExternalDataFetcher::FetchCallback callback) = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_EXTERNAL_DATA_MANAGER_H_
diff --git a/chromium/components/policy/core/common/fake_async_policy_loader.cc b/chromium/components/policy/core/common/fake_async_policy_loader.cc
new file mode 100644
index 00000000000..7850344f00b
--- /dev/null
+++ b/chromium/components/policy/core/common/fake_async_policy_loader.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/fake_async_policy_loader.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/task/sequenced_task_runner.h"
+
+namespace policy {
+
+FakeAsyncPolicyLoader::FakeAsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/true) {}
+
+std::unique_ptr<PolicyBundle> FakeAsyncPolicyLoader::Load() {
+ std::unique_ptr<PolicyBundle> result(new PolicyBundle());
+ result->CopyFrom(policy_bundle_);
+ return result;
+}
+
+void FakeAsyncPolicyLoader::InitOnBackgroundThread() {
+ // Nothing to do.
+}
+
+void FakeAsyncPolicyLoader::SetPolicies(const PolicyBundle& policy_bundle) {
+ policy_bundle_.CopyFrom(policy_bundle);
+}
+
+void FakeAsyncPolicyLoader::PostReloadOnBackgroundThread(bool force) {
+ task_runner()->PostTask(FROM_HERE,
+ base::BindOnce(&AsyncPolicyLoader::Reload,
+ base::Unretained(this), force));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/fake_async_policy_loader.h b/chromium/components/policy/core/common/fake_async_policy_loader.h
new file mode 100644
index 00000000000..77210498a02
--- /dev/null
+++ b/chromium/components/policy/core/common/fake_async_policy_loader.h
@@ -0,0 +1,53 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
+
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace policy {
+
+// Fake AsyncPolicyLoader for testing with test-controlled policies.
+//
+// Typical test code would populate the policy contents via calls to
+// ClearPolicies and AddPolicies and then notify the rest of the policy
+// subsystem of the changes by calling PostReloadOnBackgroundThread.
+class FakeAsyncPolicyLoader : public AsyncPolicyLoader {
+ public:
+ explicit FakeAsyncPolicyLoader(
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner);
+ FakeAsyncPolicyLoader(const FakeAsyncPolicyLoader&) = delete;
+ FakeAsyncPolicyLoader& operator=(const FakeAsyncPolicyLoader&) = delete;
+
+ // Implementation of virtual methods from AsyncPolicyLoader base class.
+ std::unique_ptr<PolicyBundle> Load() override;
+ void InitOnBackgroundThread() override;
+
+ // Provides content for the simulated / faked policies.
+ void SetPolicies(const PolicyBundle& policy_bundle);
+
+ // Notifies the rest of the policy subsystem that policy contents have
+ // changed. This simulates / fakes a notification that normally would be
+ // triggered by a FilePathWatcher or (registry)ObjectWatcher in a real loader.
+ //
+ // See AsyncPolicyLoader::Reload method for description of the |force|
+ // parameter.
+ void PostReloadOnBackgroundThread(bool force);
+
+ private:
+ PolicyBundle policy_bundle_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_FAKE_ASYNC_POLICY_LOADER_H_
diff --git a/chromium/components/policy/core/common/features.cc b/chromium/components/policy/core/common/features.cc
new file mode 100644
index 00000000000..b1b69e0b35d
--- /dev/null
+++ b/chromium/components/policy/core/common/features.cc
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/features.h"
+
+#include "google_apis/gaia/gaia_constants.h"
+
+namespace policy {
+
+namespace features {
+
+const base::Feature kDefaultChromeAppsMigration{
+ "EnableDefaultAppsMigration", base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kUploadBrowserDeviceIdentifier{
+ "UploadBrowserDeviceIdentifier", base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kLoginEventReporting{"LoginEventReporting",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kPasswordBreachEventReporting{
+ "PasswordBreachEventReporting", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kChromeManagementPageAndroid{
+ "ChromeManagementPageAndroid", base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kEnableUserCloudSigninRestrictionPolicyFetcher{
+ "UserCloudSigninRestrictionPolicyFetcher",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kActivateMetricsReportingEnabledPolicyAndroid{
+ "ActivateMetricsReportingEnabledPolicyAndroid",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kEnableCachedManagementStatus{
+ "EnableCachedManagementStatus", base::FEATURE_ENABLED_BY_DEFAULT};
+} // namespace features
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/features.h b/chromium/components/policy/core/common/features.h
new file mode 100644
index 00000000000..6ade4f6b922
--- /dev/null
+++ b/chromium/components/policy/core/common/features.h
@@ -0,0 +1,50 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_FEATURES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_FEATURES_H_
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace features {
+
+// Enable chrome://management page on Android.
+POLICY_EXPORT extern const base::Feature kChromeManagementPageAndroid;
+
+// Enable force installed Chrome apps policy migration.
+POLICY_EXPORT extern const base::Feature kDefaultChromeAppsMigration;
+
+// Update browser device identifier during enrollment and fetching policies.
+POLICY_EXPORT extern const base::Feature kUploadBrowserDeviceIdentifier;
+
+// Enable reporting Login events to the reporting connector when the Password
+// Manager detects that the user logged in to a web page.
+POLICY_EXPORT extern const base::Feature kLoginEventReporting;
+
+// Enable reporting password leaks to the reporting connector when the Password
+// Manager's Leak Detector has found some compromised credentials.
+POLICY_EXPORT extern const base::Feature kPasswordBreachEventReporting;
+
+// Enable the UserCloudSigninRestrictionPolicyFetcher to get the
+// ManagedAccountsSigninRestriction policy for a dasher account.
+POLICY_EXPORT extern const base::Feature
+ kEnableUserCloudSigninRestrictionPolicyFetcher;
+
+// Enable MetricsReportingEnabled policy to alter MetricsReportingState on
+// Android.
+POLICY_EXPORT extern const base::Feature
+ kActivateMetricsReportingEnabledPolicyAndroid;
+
+// Enable caching the value of the ManagementStatus.
+POLICY_EXPORT extern const base::Feature kEnableCachedManagementStatus;
+
+} // namespace features
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_FEATURES_H_
diff --git a/chromium/components/policy/core/common/generate_policy_source_unittest.cc b/chromium/components/policy/core/common/generate_policy_source_unittest.cc
new file mode 100644
index 00000000000..58be40b4873
--- /dev/null
+++ b/chromium/components/policy/core/common/generate_policy_source_unittest.cc
@@ -0,0 +1,305 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstring>
+#include <memory>
+#include <string>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/proxy_settings_constants.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This unittest tests the code generated by
+// chrome/tools/build/generate_policy_source.py.
+
+namespace policy {
+
+namespace {
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Checks if two schemas are the same or not. Note that this function doesn't
+// consider restrictions on integers and strings nor pattern properties.
+bool IsSameSchema(Schema a, Schema b) {
+ if (a.valid() != b.valid())
+ return false;
+ if (!a.valid())
+ return true;
+ if (a.type() != b.type())
+ return false;
+ if (a.type() == base::Value::Type::LIST)
+ return IsSameSchema(a.GetItems(), b.GetItems());
+ if (a.type() != base::Value::Type::DICTIONARY)
+ return true;
+ Schema::Iterator a_it = a.GetPropertiesIterator();
+ Schema::Iterator b_it = b.GetPropertiesIterator();
+ while (!a_it.IsAtEnd()) {
+ if (b_it.IsAtEnd())
+ return false;
+ if (strcmp(a_it.key(), b_it.key()) != 0)
+ return false;
+ if (!IsSameSchema(a_it.schema(), b_it.schema()))
+ return false;
+ a_it.Advance();
+ b_it.Advance();
+ }
+ if (!b_it.IsAtEnd())
+ return false;
+ return IsSameSchema(a.GetAdditionalProperties(), b.GetAdditionalProperties());
+}
+#endif
+
+} // namespace
+
+TEST(GeneratePolicySource, ChromeSchemaData) {
+ Schema schema = Schema::Wrap(GetChromeSchemaData());
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema subschema = schema.GetAdditionalProperties();
+ EXPECT_FALSE(subschema.valid());
+
+ subschema = schema.GetProperty("no such policy exists");
+ EXPECT_FALSE(subschema.valid());
+
+ subschema = schema.GetProperty(key::kSearchSuggestEnabled);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, subschema.type());
+
+ subschema = schema.GetProperty(key::kURLBlocklist);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::LIST, subschema.type());
+ ASSERT_TRUE(subschema.GetItems().valid());
+ EXPECT_EQ(base::Value::Type::STRING, subschema.GetItems().type());
+
+ // Verify that all the Chrome policies are there.
+ for (Schema::Iterator it = schema.GetPropertiesIterator(); !it.IsAtEnd();
+ it.Advance()) {
+ EXPECT_TRUE(it.key());
+ EXPECT_FALSE(std::string(it.key()).empty());
+ EXPECT_TRUE(GetChromePolicyDetails(it.key()));
+ }
+
+#if !BUILDFLAG(IS_IOS)
+ subschema = schema.GetProperty(key::kDefaultCookiesSetting);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subschema.type());
+
+ subschema = schema.GetProperty(key::kProxyMode);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subschema.type());
+
+ subschema = schema.GetProperty(key::kProxySettings);
+ ASSERT_TRUE(subschema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ EXPECT_FALSE(subschema.GetAdditionalProperties().valid());
+ EXPECT_FALSE(subschema.GetProperty("no such proxy key exists").valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyMode).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyServer).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyServerMode).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyPacUrl).valid());
+ ASSERT_TRUE(subschema.GetProperty(kProxyPacMandatory).valid());
+ ASSERT_TRUE(subschema.GetProperty(key::kProxyBypassList).valid());
+
+ // The properties are iterated in order.
+ const char* kExpectedProperties[] = {
+ key::kProxyBypassList,
+ key::kProxyMode,
+ kProxyPacMandatory,
+ key::kProxyPacUrl,
+ key::kProxyServer,
+ key::kProxyServerMode,
+ nullptr,
+ };
+ const char** next = kExpectedProperties;
+ for (Schema::Iterator it(subschema.GetPropertiesIterator());
+ !it.IsAtEnd(); it.Advance(), ++next) {
+ ASSERT_TRUE(*next != nullptr);
+ EXPECT_STREQ(*next, it.key());
+ ASSERT_TRUE(it.schema().valid());
+ if (it.key() == key::kProxyServerMode)
+ EXPECT_EQ(base::Value::Type::INTEGER, it.schema().type());
+ else if (strcmp(it.key(), kProxyPacMandatory) == 0)
+ EXPECT_EQ(base::Value::Type::BOOLEAN, it.schema().type());
+ else
+ EXPECT_EQ(base::Value::Type::STRING, it.schema().type());
+ }
+ EXPECT_TRUE(*next == nullptr);
+#endif // !BUILDFLAG(IS_IOS)
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
+ subschema = schema.GetProperty(key::kExtensionSettings);
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ EXPECT_FALSE(subschema.GetAdditionalProperties().valid());
+ EXPECT_FALSE(subschema.GetProperty("no such extension id exists").valid());
+ EXPECT_TRUE(subschema.GetPatternProperties("*").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties("no such extension id").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties("^[a-p]{32}$").empty());
+ EXPECT_TRUE(subschema.GetPatternProperties("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+ .empty());
+ EXPECT_TRUE(
+ subschema.GetPatternProperties("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+ .empty());
+ SchemaList schema_list =
+ subschema.GetPatternProperties("abcdefghijklmnopabcdefghijklmnop");
+ ASSERT_EQ(1u, schema_list.size());
+ subschema = schema_list[0];
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ subschema = subschema.GetProperty("installation_mode");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subschema.type());
+
+ subschema = schema.GetProperty(key::kExtensionSettings).GetProperty("*");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, subschema.type());
+ subschema = subschema.GetProperty("installation_mode");
+ ASSERT_TRUE(subschema.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subschema.type());
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ subschema = schema.GetKnownProperty(key::kPowerManagementIdleSettings);
+ ASSERT_TRUE(subschema.valid());
+
+ EXPECT_TRUE(IsSameSchema(subschema.GetKnownProperty("AC"),
+ subschema.GetKnownProperty("Battery")));
+
+ subschema = schema.GetKnownProperty(key::kDeviceLoginScreenPowerManagement);
+ ASSERT_TRUE(subschema.valid());
+
+ EXPECT_TRUE(IsSameSchema(subschema.GetKnownProperty("AC"),
+ subschema.GetKnownProperty("Battery")));
+#endif
+}
+
+TEST(GeneratePolicySource, PolicyDetails) {
+ EXPECT_FALSE(GetChromePolicyDetails(""));
+ EXPECT_FALSE(GetChromePolicyDetails("no such policy"));
+ EXPECT_FALSE(GetChromePolicyDetails("SearchSuggestEnable"));
+ EXPECT_FALSE(GetChromePolicyDetails("searchSuggestEnabled"));
+ EXPECT_FALSE(GetChromePolicyDetails("SSearchSuggestEnabled"));
+
+ const PolicyDetails* details =
+ GetChromePolicyDetails(key::kSearchSuggestEnabled);
+ ASSERT_TRUE(details);
+ EXPECT_FALSE(details->is_deprecated);
+ EXPECT_FALSE(details->is_device_policy);
+ EXPECT_EQ(6, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+
+#if !BUILDFLAG(IS_IOS)
+ details = GetChromePolicyDetails(key::kJavascriptEnabled);
+ ASSERT_TRUE(details);
+ EXPECT_TRUE(details->is_deprecated);
+ EXPECT_FALSE(details->is_device_policy);
+ EXPECT_EQ(9, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ details = GetChromePolicyDetails(key::kDevicePolicyRefreshRate);
+ ASSERT_TRUE(details);
+ EXPECT_FALSE(details->is_deprecated);
+ EXPECT_TRUE(details->is_device_policy);
+ EXPECT_EQ(90, details->id);
+ EXPECT_EQ(0u, details->max_external_data_size);
+
+ // Policies of type 'external' have a greater-than-zero value for
+ // |max_external_data_size|.
+ details = GetChromePolicyDetails(key::kWallpaperImage);
+ ASSERT_TRUE(details);
+ EXPECT_FALSE(details->is_deprecated);
+ EXPECT_FALSE(details->is_device_policy);
+ EXPECT_EQ(262, details->id);
+ EXPECT_GT(details->max_external_data_size, 0u);
+#endif
+}
+
+#if BUILDFLAG(IS_CHROMEOS)
+TEST(GeneratePolicySource, SetEnterpriseDefaults) {
+ PolicyMap policy_map;
+
+ // If policy not configured yet, set the enterprise default.
+ SetEnterpriseUsersDefaults(&policy_map);
+
+ const base::Value* multiprof_behavior = policy_map.GetValue(
+ key::kChromeOsMultiProfileUserBehavior, base::Value::Type::STRING);
+ base::Value expected("primary-only");
+ EXPECT_EQ(expected, *multiprof_behavior);
+
+ // If policy already configured, it's not changed to enterprise defaults.
+ policy_map.Set(key::kChromeOsMultiProfileUserBehavior, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("test_value"), nullptr);
+ SetEnterpriseUsersDefaults(&policy_map);
+ multiprof_behavior = policy_map.GetValue(
+ key::kChromeOsMultiProfileUserBehavior, base::Value::Type::STRING);
+ expected = base::Value("test_value");
+ EXPECT_EQ(expected, *multiprof_behavior);
+}
+
+TEST(GeneratePolicySource, SetEnterpriseSystemWideDefaults) {
+ PolicyMap policy_map;
+
+ // If policy not configured yet, set the enterprise system-wide default.
+ SetEnterpriseUsersSystemWideDefaults(&policy_map);
+
+ const base::Value* pin_unlock_autosubmit_enabled = policy_map.GetValue(
+ key::kPinUnlockAutosubmitEnabled, base::Value::Type::BOOLEAN);
+ ASSERT_TRUE(pin_unlock_autosubmit_enabled);
+ EXPECT_FALSE(pin_unlock_autosubmit_enabled->GetBool());
+ const base::Value* allow_dinosaur_easter_egg = policy_map.GetValue(
+ key::kAllowDinosaurEasterEgg, base::Value::Type::BOOLEAN);
+ EXPECT_EQ(nullptr, allow_dinosaur_easter_egg);
+
+ // If policy already configured, it's not changed to enterprise defaults.
+ policy_map.Set(key::kPinUnlockAutosubmitEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
+ nullptr);
+ SetEnterpriseUsersSystemWideDefaults(&policy_map);
+ pin_unlock_autosubmit_enabled = policy_map.GetValue(
+ key::kPinUnlockAutosubmitEnabled, base::Value::Type::BOOLEAN);
+ ASSERT_TRUE(pin_unlock_autosubmit_enabled);
+ EXPECT_TRUE(pin_unlock_autosubmit_enabled->GetBool());
+ allow_dinosaur_easter_egg = policy_map.GetValue(key::kAllowDinosaurEasterEgg,
+ base::Value::Type::BOOLEAN);
+ EXPECT_EQ(nullptr, allow_dinosaur_easter_egg);
+}
+
+TEST(GeneratePolicySource, SetEnterpriseProfileDefaults) {
+ PolicyMap policy_map;
+
+ // If policy not configured yet, set the enterprise profile default.
+ SetEnterpriseUsersProfileDefaults(&policy_map);
+
+ const base::Value* allow_dinosaur_easter_egg = policy_map.GetValue(
+ key::kAllowDinosaurEasterEgg, base::Value::Type::BOOLEAN);
+ ASSERT_TRUE(allow_dinosaur_easter_egg);
+ EXPECT_FALSE(allow_dinosaur_easter_egg->GetBool());
+ const base::Value* pin_unlock_autosubmit_enabled = policy_map.GetValue(
+ key::kPinUnlockAutosubmitEnabled, base::Value::Type::BOOLEAN);
+ EXPECT_EQ(nullptr, pin_unlock_autosubmit_enabled);
+
+ // If policy already configured, it's not changed to enterprise defaults.
+ policy_map.Set(key::kAllowDinosaurEasterEgg, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
+ nullptr);
+ SetEnterpriseUsersProfileDefaults(&policy_map);
+ allow_dinosaur_easter_egg = policy_map.GetValue(key::kAllowDinosaurEasterEgg,
+ base::Value::Type::BOOLEAN);
+ ASSERT_TRUE(allow_dinosaur_easter_egg);
+ EXPECT_TRUE(allow_dinosaur_easter_egg->GetBool());
+ pin_unlock_autosubmit_enabled = policy_map.GetValue(
+ key::kPinUnlockAutosubmitEnabled, base::Value::Type::BOOLEAN);
+ EXPECT_EQ(nullptr, pin_unlock_autosubmit_enabled);
+}
+#endif
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/json_schema_constants.cc b/chromium/components/policy/core/common/json_schema_constants.cc
new file mode 100644
index 00000000000..fe7c94b6b9b
--- /dev/null
+++ b/chromium/components/policy/core/common/json_schema_constants.cc
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/json_schema_constants.h"
+
+namespace json_schema_constants {
+
+const char kAdditionalProperties[] = "additionalProperties";
+const char kArray[] = "array";
+const char kBoolean[] = "boolean";
+const char kDescription[] = "description";
+const char kEnum[] = "enum";
+const char kId[] = "id";
+const char kInteger[] = "integer";
+const char kItems[] = "items";
+const char kMaximum[] = "maximum";
+const char kMinimum[] = "minimum";
+const char kNumber[] = "number";
+const char kObject[] = "object";
+const char kPattern[] = "pattern";
+const char kPatternProperties[] = "patternProperties";
+const char kProperties[] = "properties";
+const char kRef[] = "$ref";
+const char kRequired[] = "required";
+const char kSensitiveValue[] = "sensitiveValue";
+const char kString[] = "string";
+const char kTitle[] = "title";
+const char kType[] = "type";
+
+} // namespace json_schema_constants
diff --git a/chromium/components/policy/core/common/json_schema_constants.h b/chromium/components/policy/core/common/json_schema_constants.h
new file mode 100644
index 00000000000..f8ba6c49744
--- /dev/null
+++ b/chromium/components/policy/core/common/json_schema_constants.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
+
+// These constants are shared by code that uses JSON schemas.
+namespace json_schema_constants {
+
+extern const char kAdditionalProperties[];
+extern const char kArray[];
+extern const char kBoolean[];
+extern const char kDescription[];
+extern const char kEnum[];
+extern const char kId[];
+extern const char kInteger[];
+extern const char kItems[];
+extern const char kMaximum[];
+extern const char kMinimum[];
+extern const char kNumber[];
+extern const char kObject[];
+extern const char kPattern[];
+extern const char kPatternProperties[];
+extern const char kProperties[];
+extern const char kRef[];
+extern const char kRequired[];
+extern const char kSensitiveValue[];
+extern const char kString[];
+extern const char kTitle[];
+extern const char kType[];
+
+} // namespace json_schema_constants
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
diff --git a/chromium/components/policy/core/common/legacy_chrome_policy_migrator.cc b/chromium/components/policy/core/common/legacy_chrome_policy_migrator.cc
new file mode 100644
index 00000000000..bf3a1f05d09
--- /dev/null
+++ b/chromium/components/policy/core/common/legacy_chrome_policy_migrator.cc
@@ -0,0 +1,34 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/legacy_chrome_policy_migrator.h"
+
+#include <string>
+
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+
+namespace policy {
+
+LegacyChromePolicyMigrator::LegacyChromePolicyMigrator(const char* old_name,
+ const char* new_name)
+ : migration_(old_name, new_name) {}
+
+LegacyChromePolicyMigrator::LegacyChromePolicyMigrator(
+ const char* old_name,
+ const char* new_name,
+ Migration::ValueTransform transform)
+ : migration_(old_name, new_name, transform) {}
+
+LegacyChromePolicyMigrator::~LegacyChromePolicyMigrator() = default;
+
+void LegacyChromePolicyMigrator::Migrate(policy::PolicyBundle* bundle) {
+ policy::PolicyMap& chrome_map =
+ bundle->Get(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, ""));
+
+ CopyPolicyIfUnset(chrome_map, &chrome_map, migration_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/legacy_chrome_policy_migrator.h b/chromium/components/policy/core/common/legacy_chrome_policy_migrator.h
new file mode 100644
index 00000000000..0012b6463ec
--- /dev/null
+++ b/chromium/components/policy/core/common/legacy_chrome_policy_migrator.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_LEGACY_CHROME_POLICY_MIGRATOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_LEGACY_CHROME_POLICY_MIGRATOR_H_
+
+#include "components/policy/core/common/policy_migrator.h"
+
+namespace policy {
+
+// LegacyChromePolicyMigrator migrates a deprecated Chrome domain policy to a
+// new name, setting up the new policy based on the old one.
+//
+// This is intended to be used for policies that do not have a corresponding
+// pref. If the policy has a pref, please use
+// |LegacyPoliciesDeprecatingPolicyHandler| instead.
+class POLICY_EXPORT LegacyChromePolicyMigrator : public PolicyMigrator {
+ public:
+ using Migration = PolicyMigrator::Migration;
+
+ LegacyChromePolicyMigrator(const char* old_name, const char* new_name);
+ LegacyChromePolicyMigrator(const char* old_name,
+ const char* new_name,
+ Migration::ValueTransform transform);
+ ~LegacyChromePolicyMigrator() override;
+
+ LegacyChromePolicyMigrator(const LegacyChromePolicyMigrator&) = delete;
+ LegacyChromePolicyMigrator& operator=(const LegacyChromePolicyMigrator&) =
+ delete;
+
+ void Migrate(policy::PolicyBundle* bundle) override;
+
+ private:
+ Migration migration_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_LEGACY_CHROME_POLICY_MIGRATOR_H_
diff --git a/chromium/components/policy/core/common/legacy_chrome_policy_migrator_unittest.cc b/chromium/components/policy/core/common/legacy_chrome_policy_migrator_unittest.cc
new file mode 100644
index 00000000000..3259f5013d8
--- /dev/null
+++ b/chromium/components/policy/core/common/legacy_chrome_policy_migrator_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/legacy_chrome_policy_migrator.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+const char kOldPolicy[] = "OldPolicy";
+const char kNewPolicy[] = "NewPolicy";
+const char kOtherPolicy[] = "OtherPolicy";
+
+const int kOldValue = 111;
+const int kNewValue = 222;
+const int kTransformedValue = 333;
+const int kOtherValue = 999;
+
+void MultiplyByThree(base::Value* val) {
+ *val = base::Value(val->GetInt() * 3);
+}
+
+void SetPolicy(PolicyMap* policy, const char* policy_name, base::Value value) {
+ policy->Set(policy_name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, std::move(value), nullptr);
+}
+
+} // namespace
+
+TEST(LegacyChromePolicyMigratorTest, CopyPolicyIfUnset) {
+ PolicyBundle bundle;
+
+ PolicyMap& chrome_map = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+
+ SetPolicy(&chrome_map, kOldPolicy, base::Value(kOldValue));
+ SetPolicy(&chrome_map, kOtherPolicy, base::Value(kOtherValue));
+
+ LegacyChromePolicyMigrator migrator(kOldPolicy, kNewPolicy);
+
+ migrator.Migrate(&bundle);
+
+ // kOldPolicy should have been copied to kNewPolicy, kOtherPolicy remains
+ EXPECT_EQ(3u, chrome_map.size());
+ ASSERT_TRUE(chrome_map.GetValue(kNewPolicy, base::Value::Type::INTEGER));
+ // Old Value should be copied over.
+ EXPECT_EQ(base::Value(kOldValue),
+ *chrome_map.GetValue(kNewPolicy, base::Value::Type::INTEGER));
+ // Other Value should be unchanged.
+ EXPECT_EQ(base::Value(kOtherValue),
+ *chrome_map.GetValue(kOtherPolicy, base::Value::Type::INTEGER));
+ base::RepeatingCallback<std::u16string(int)> l10nlookup =
+ base::BindRepeating(&l10n_util::GetStringUTF16);
+ // Old policy should always be marked deprecated
+ EXPECT_FALSE(
+ chrome_map.Get(kOldPolicy)
+ ->GetLocalizedMessages(PolicyMap::MessageType::kError, l10nlookup)
+ .empty());
+ EXPECT_FALSE(
+ chrome_map.Get(kNewPolicy)
+ ->GetLocalizedMessages(PolicyMap::MessageType::kWarning, l10nlookup)
+ .empty());
+}
+
+TEST(LegacyChromePolicyMigratorTest, TransformPolicy) {
+ PolicyBundle bundle;
+
+ PolicyMap& chrome_map = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+
+ SetPolicy(&chrome_map, kOldPolicy, base::Value(kOldValue));
+
+ LegacyChromePolicyMigrator migrator(kOldPolicy, kNewPolicy,
+ base::BindRepeating(&MultiplyByThree));
+
+ migrator.Migrate(&bundle);
+
+ ASSERT_TRUE(chrome_map.GetValue(kNewPolicy, base::Value::Type::INTEGER));
+ // Old Value should be transformed
+ EXPECT_EQ(base::Value(kTransformedValue),
+ *chrome_map.GetValue(kNewPolicy, base::Value::Type::INTEGER));
+}
+
+TEST(LegacyChromePolicyMigratorTest, IgnoreOldIfNewIsSet) {
+ PolicyBundle bundle;
+
+ PolicyMap& chrome_map = bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+
+ SetPolicy(&chrome_map, kOldPolicy, base::Value(kOldValue));
+ SetPolicy(&chrome_map, kNewPolicy, base::Value(kNewValue));
+
+ LegacyChromePolicyMigrator migrator(kOldPolicy, kNewPolicy);
+
+ migrator.Migrate(&bundle);
+ // New Value is unchanged
+ EXPECT_EQ(base::Value(kNewValue),
+ *chrome_map.GetValue(kNewPolicy, base::Value::Type::INTEGER));
+ // Should be no warning on new policy
+ base::RepeatingCallback<std::u16string(int)> l10nlookup =
+ base::BindRepeating(&l10n_util::GetStringUTF16);
+ // Old policy should always be marked deprecated
+ EXPECT_FALSE(
+ chrome_map.Get(kOldPolicy)
+ ->GetLocalizedMessages(PolicyMap::MessageType::kError, l10nlookup)
+ .empty());
+ // No warnings on new policy because it was unchanged.
+ EXPECT_TRUE(
+ chrome_map.Get(kNewPolicy)
+ ->GetLocalizedMessages(PolicyMap::MessageType::kWarning, l10nlookup)
+ .empty());
+ EXPECT_TRUE(
+ chrome_map.Get(kNewPolicy)
+ ->GetLocalizedMessages(PolicyMap::MessageType::kError, l10nlookup)
+ .empty());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/mac_util.cc b/chromium/components/policy/core/common/mac_util.cc
new file mode 100644
index 00000000000..c94255aaa7d
--- /dev/null
+++ b/chromium/components/policy/core/common/mac_util.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mac_util.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/values.h"
+
+using base::mac::CFCast;
+
+namespace policy {
+
+namespace {
+
+// Callback function for CFDictionaryApplyFunction. |key| and |value| are an
+// entry of the CFDictionary that should be converted into an equivalent entry
+// in the DictionaryValue in |context|.
+void DictionaryEntryToValue(const void* key, const void* value, void* context) {
+ if (CFStringRef cf_key = CFCast<CFStringRef>(key)) {
+ std::unique_ptr<base::Value> converted =
+ PropertyToValue(static_cast<CFPropertyListRef>(value));
+ if (converted) {
+ const std::string string = base::SysCFStringRefToUTF8(cf_key);
+ static_cast<base::DictionaryValue*>(context)->Set(string,
+ std::move(converted));
+ }
+ }
+}
+
+// Callback function for CFArrayApplyFunction. |value| is an entry of the
+// CFArray that should be converted into an equivalent entry in the ListValue
+// in |context|.
+void ArrayEntryToValue(const void* value, void* context) {
+ std::unique_ptr<base::Value> converted =
+ PropertyToValue(static_cast<CFPropertyListRef>(value));
+ if (converted)
+ static_cast<base::ListValue*>(context)->Append(std::move(converted));
+}
+
+} // namespace
+
+std::unique_ptr<base::Value> PropertyToValue(CFPropertyListRef property) {
+ if (CFCast<CFNullRef>(property))
+ return std::make_unique<base::Value>();
+
+ if (CFBooleanRef boolean = CFCast<CFBooleanRef>(property)) {
+ return std::make_unique<base::Value>(
+ static_cast<bool>(CFBooleanGetValue(boolean)));
+ }
+
+ if (CFNumberRef number = CFCast<CFNumberRef>(property)) {
+ // CFNumberGetValue() converts values implicitly when the conversion is not
+ // lossy. Check the type before trying to convert.
+ if (CFNumberIsFloatType(number)) {
+ double double_value = 0.0;
+ if (CFNumberGetValue(number, kCFNumberDoubleType, &double_value)) {
+ return std::make_unique<base::Value>(double_value);
+ }
+ } else {
+ int int_value = 0;
+ if (CFNumberGetValue(number, kCFNumberIntType, &int_value)) {
+ return std::make_unique<base::Value>(int_value);
+ }
+ }
+ }
+
+ if (CFStringRef string = CFCast<CFStringRef>(property)) {
+ return std::make_unique<base::Value>(base::SysCFStringRefToUTF8(string));
+ }
+
+ if (CFDictionaryRef dict = CFCast<CFDictionaryRef>(property)) {
+ std::unique_ptr<base::DictionaryValue> dict_value(
+ new base::DictionaryValue());
+ CFDictionaryApplyFunction(dict, DictionaryEntryToValue, dict_value.get());
+ return std::move(dict_value);
+ }
+
+ if (CFArrayRef array = CFCast<CFArrayRef>(property)) {
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue());
+ CFArrayApplyFunction(array,
+ CFRangeMake(0, CFArrayGetCount(array)),
+ ArrayEntryToValue,
+ list_value.get());
+ return std::move(list_value);
+ }
+
+ return nullptr;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/mac_util.h b/chromium/components/policy/core/common/mac_util.h
new file mode 100644
index 00000000000..f5b5fb7f7aa
--- /dev/null
+++ b/chromium/components/policy/core/common/mac_util.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MAC_UTIL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MAC_UTIL_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <memory>
+
+#include "components/policy/policy_export.h"
+
+// This file contains utilities shared by both Mac OS X and iOS.
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+// Converts a CFPropertyListRef to the equivalent base::Value. CFDictionary
+// entries whose key is not a CFStringRef are ignored.
+// Returns NULL if an invalid CFType was found, such as CFDate or CFData.
+// NSDictionary is toll-free bridged to CFDictionaryRef, which is a
+// CFPropertyListRef, so it can also be passed directly here. Same for the
+// other NS* classes that map to CF* properties.
+POLICY_EXPORT std::unique_ptr<base::Value> PropertyToValue(
+ CFPropertyListRef property);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MAC_UTIL_H_
diff --git a/chromium/components/policy/core/common/mac_util_unittest.cc b/chromium/components/policy/core/common/mac_util_unittest.cc
new file mode 100644
index 00000000000..b3e7826b871
--- /dev/null
+++ b/chromium/components/policy/core/common/mac_util_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mac_util.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <memory>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+TEST(PolicyMacUtilTest, PropertyToValue) {
+ base::DictionaryValue root;
+
+ // base::Value::Type::NONE
+ root.Set("null", std::make_unique<base::Value>());
+
+ // base::Value::Type::BOOLEAN
+ root.SetBoolKey("false", false);
+ root.SetBoolKey("true", true);
+
+ // base::Value::Type::INTEGER
+ root.SetIntKey("int", 123);
+ root.SetIntKey("zero", 0);
+
+ // base::Value::Type::DOUBLE
+ root.SetDoubleKey("double", 123.456);
+ root.SetDoubleKey("zerod", 0.0);
+
+ // base::Value::Type::STRING
+ root.SetStringKey("string", "the fox jumps over something");
+ root.SetStringKey("empty", "");
+
+ // base::Value::Type::LIST
+ root.Set("emptyl", std::make_unique<base::Value>(base::Value::Type::LIST));
+ base::ListValue list;
+ for (base::DictionaryValue::Iterator it(root); !it.IsAtEnd(); it.Advance())
+ list.Append(std::make_unique<base::Value>(it.value().Clone()));
+ EXPECT_EQ(root.DictSize(), list.GetListDeprecated().size());
+ list.Append(std::make_unique<base::Value>(root.Clone()));
+ root.SetKey("list", list.Clone());
+
+ // base::Value::Type::DICTIONARY
+ root.Set("emptyd",
+ std::make_unique<base::Value>(base::Value::Type::DICTIONARY));
+ // Very meta.
+ root.SetKey("dict", root.Clone());
+
+ base::ScopedCFTypeRef<CFPropertyListRef> property(ValueToProperty(root));
+ ASSERT_TRUE(property);
+ std::unique_ptr<base::Value> value = PropertyToValue(property);
+ ASSERT_TRUE(value);
+ EXPECT_TRUE(root.Equals(value.get()));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/management_service.cc b/chromium/components/policy/core/common/management/management_service.cc
new file mode 100644
index 00000000000..8dd64594630
--- /dev/null
+++ b/chromium/components/policy/core/common/management/management_service.cc
@@ -0,0 +1,203 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/management_service.h"
+
+#include <tuple>
+
+#include "base/barrier_closure.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace policy {
+
+ManagementStatusProvider::ManagementStatusProvider() = default;
+ManagementStatusProvider::ManagementStatusProvider(
+ const std::string& cache_pref_name)
+ : cache_pref_name_(cache_pref_name) {}
+ManagementStatusProvider::~ManagementStatusProvider() = default;
+
+EnterpriseManagementAuthority ManagementStatusProvider::GetAuthority() {
+ if (!RequiresCache())
+ return FetchAuthority();
+ if (!base::FeatureList::IsEnabled(features::kEnableCachedManagementStatus))
+ return EnterpriseManagementAuthority::NONE;
+
+ if (absl::holds_alternative<PrefService*>(cache_) &&
+ absl::get<PrefService*>(cache_) &&
+ absl::get<PrefService*>(cache_)->HasPrefPath(cache_pref_name_))
+ return static_cast<EnterpriseManagementAuthority>(
+ absl::get<PrefService*>(cache_)->GetInteger(cache_pref_name_));
+
+ if (absl::holds_alternative<scoped_refptr<PersistentPrefStore>>(cache_) &&
+ absl::holds_alternative<scoped_refptr<PersistentPrefStore>>(cache_)) {
+ const base::Value* value = nullptr;
+ if (absl::get<scoped_refptr<PersistentPrefStore>>(cache_)->GetValue(
+ cache_pref_name_, &value) &&
+ value->is_int())
+ return static_cast<EnterpriseManagementAuthority>(value->GetInt());
+ }
+ return EnterpriseManagementAuthority::NONE;
+}
+
+bool ManagementStatusProvider::RequiresCache() const {
+ return !cache_pref_name_.empty();
+}
+
+void ManagementStatusProvider::UpdateCache(
+ EnterpriseManagementAuthority authority) {
+ DCHECK(absl::holds_alternative<PrefService*>(cache_))
+ << "A PrefService is required to refresh the management "
+ "status provider cache.";
+ absl::get<PrefService*>(cache_)->SetInteger(cache_pref_name_, authority);
+}
+
+void ManagementStatusProvider::UsePrefStoreAsCache(
+ scoped_refptr<PersistentPrefStore> pref_store) {
+ DCHECK(!cache_pref_name_.empty())
+ << "This management status provider does not support caching";
+ cache_ = pref_store;
+}
+
+void ManagementStatusProvider::UsePrefServiceAsCache(PrefService* prefs) {
+ DCHECK(!cache_pref_name_.empty())
+ << "This management status provider does not support caching";
+ cache_ = prefs;
+}
+
+ManagementService::ManagementService(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers)
+ : management_status_providers_(std::move(providers)) {}
+
+ManagementService::~ManagementService() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void ManagementService::UsePrefServiceAsCache(PrefService* prefs) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (const auto& provider : management_status_providers_) {
+ if (provider->RequiresCache())
+ provider->UsePrefServiceAsCache(prefs);
+ }
+}
+
+void ManagementService::UsePrefStoreAsCache(
+ scoped_refptr<PersistentPrefStore> pref_store) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (const auto& provider : management_status_providers_) {
+ if (provider->RequiresCache())
+ provider->UsePrefStoreAsCache(pref_store);
+ }
+}
+
+void ManagementService::RefreshCache(CacheRefreshCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!base::FeatureList::IsEnabled(features::kEnableCachedManagementStatus))
+ return;
+
+ ManagementAuthorityTrustworthiness previous =
+ GetManagementAuthorityTrustworthiness();
+ for (const auto& provider : management_status_providers_) {
+ if (provider->RequiresCache()) {
+ provider->UpdateCache(provider->FetchAuthority());
+ }
+
+ ManagementAuthorityTrustworthiness next =
+ GetManagementAuthorityTrustworthiness();
+ base::UmaHistogramBoolean(
+ "Enterprise.ManagementAuthorityTrustworthiness.Cache.ValueChange",
+ previous != next);
+ if (callback)
+ std::move(callback).Run(previous, next);
+ }
+}
+
+bool ManagementService::HasManagementAuthority(
+ EnterpriseManagementAuthority authority) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return GetManagementAuthorities() & authority;
+}
+
+ManagementAuthorityTrustworthiness
+ManagementService::GetManagementAuthorityTrustworthiness() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (HasManagementAuthority(EnterpriseManagementAuthority::CLOUD_DOMAIN))
+ return ManagementAuthorityTrustworthiness::FULLY_TRUSTED;
+ if (HasManagementAuthority(EnterpriseManagementAuthority::CLOUD))
+ return ManagementAuthorityTrustworthiness::TRUSTED;
+ if (HasManagementAuthority(EnterpriseManagementAuthority::DOMAIN_LOCAL))
+ return ManagementAuthorityTrustworthiness::TRUSTED;
+ if (HasManagementAuthority(EnterpriseManagementAuthority::COMPUTER_LOCAL))
+ return ManagementAuthorityTrustworthiness::LOW;
+ return ManagementAuthorityTrustworthiness::NONE;
+}
+
+int ManagementService::GetManagementAuthorities() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (management_authorities_for_testing_)
+ return management_authorities_for_testing_.value();
+
+ int result = 0;
+ for (const auto& provider : management_status_providers_)
+ result |= provider->GetAuthority();
+ return result;
+}
+
+void ManagementService::SetManagementStatusProviderForTesting(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ SetManagementStatusProvider(std::move(providers));
+}
+
+void ManagementService::SetManagementAuthoritiesForTesting(
+ int management_authorities) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ management_authorities_for_testing_ = management_authorities;
+}
+
+void ManagementService::ClearManagementAuthoritiesForTesting() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ management_authorities_for_testing_.reset();
+}
+
+// static
+void ManagementService::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
+#if BUILDFLAG(IS_WIN)
+ registry->RegisterIntegerPref(policy_prefs::kAzureActiveDirectoryManagement,
+ NONE);
+ registry->RegisterIntegerPref(policy_prefs::kEnterpriseMDMManagementWindows,
+ NONE);
+#elif BUILDFLAG(IS_MAC)
+ registry->RegisterIntegerPref(policy_prefs::kEnterpriseMDMManagementMac,
+ NONE);
+#endif
+}
+
+bool ManagementService::IsManaged() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return GetManagementAuthorityTrustworthiness() >
+ ManagementAuthorityTrustworthiness::NONE;
+}
+
+void ManagementService::SetManagementStatusProvider(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ management_status_providers_ = std::move(providers);
+}
+
+void ManagementService::AddManagementStatusProvider(
+ std::unique_ptr<ManagementStatusProvider> provider) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ management_status_providers_.push_back(std::move(provider));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/management_service.h b/chromium/components/policy/core/common/management/management_service.h
new file mode 100644
index 00000000000..6836d27f19a
--- /dev/null
+++ b/chromium/components/policy/core/common/management/management_service.h
@@ -0,0 +1,157 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_MANAGEMENT_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_MANAGEMENT_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/sequence_checker.h"
+#include "components/policy/policy_export.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
+// For more imformation about this file please read
+// //components/policy/core/common/management/management_service.md
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace policy {
+
+class ManagementService;
+
+enum class ManagementAuthorityTrustworthiness {
+ NONE = 0, // No management authority found
+ LOW = 1, // Local device management authority
+ TRUSTED = 2, // Non-local management authority
+ FULLY_TRUSTED = 3, // Cryptographically verifiable policy source e.g. CBCM,
+ // ChromeOS
+ kMaxValue = FULLY_TRUSTED
+};
+
+enum EnterpriseManagementAuthority : int {
+ NONE = 0,
+ COMPUTER_LOCAL =
+ 1 << 0, // local GPO or registry, /etc files, local root profile
+ DOMAIN_LOCAL = 1 << 1, // AD joined, puppet
+ CLOUD = 1 << 2, // MDM, GSuite user
+ CLOUD_DOMAIN = 1 << 3 // Azure AD, CBCM, CrosEnrolled
+};
+
+using CacheRefreshCallback =
+ base::OnceCallback<void(ManagementAuthorityTrustworthiness,
+ ManagementAuthorityTrustworthiness)>;
+
+// Interface to provide management information from a single source on an entity
+// to a ManagementService. All implmementations of this interface must be used
+// by a ManagementService.
+class POLICY_EXPORT ManagementStatusProvider {
+ public:
+ ManagementStatusProvider();
+
+ // `cache_pref_name` is an optional that is the name of the pref used to store
+ // the management authority from this provider. If this is empty, the provider
+ // always returns the up-to-date management authority, otherwise returns the
+ // value from the prefs.
+ explicit ManagementStatusProvider(const std::string& cache_pref_name);
+ virtual ~ManagementStatusProvider();
+
+ // Returns a valid authority if the service or component is managed.
+ // The returned value may be a cached value.
+ EnterpriseManagementAuthority GetAuthority();
+
+ // Returns a valid authority if the service or component is managed.
+ // This value is never ached and may required blocking I/O to get.
+ virtual EnterpriseManagementAuthority FetchAuthority() = 0;
+
+ bool RequiresCache() const;
+ void UpdateCache(EnterpriseManagementAuthority authority);
+
+ void UsePrefStoreAsCache(scoped_refptr<PersistentPrefStore> pref_store);
+ virtual void UsePrefServiceAsCache(PrefService* prefs);
+
+ protected:
+ const std::string& cache_pref_name() const { return cache_pref_name_; }
+
+ private:
+ absl::variant<PrefService*, scoped_refptr<PersistentPrefStore>> cache_ =
+ nullptr;
+ const std::string cache_pref_name_;
+};
+
+// Interface to gives information related to an entity's management state.
+// This class must be used on the main thread at all times.
+class POLICY_EXPORT ManagementService {
+ public:
+ explicit ManagementService(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers);
+ virtual ~ManagementService();
+
+ // Sets `prefs` as a read-write cache.
+ void UsePrefServiceAsCache(PrefService* prefs);
+
+ // Sets `pref_store` as a readonly cache.
+ // Use only if a PrefService is not yet available.
+ void UsePrefStoreAsCache(scoped_refptr<PersistentPrefStore> pref_store);
+
+ // Refreshes the cached values and call `callback` with the previous and new
+ // management authority trustworthiness. This function must only be called on
+ // on an instance of ManagementService that is certain to not be destroyed
+ // until `callback` is called.
+ virtual void RefreshCache(CacheRefreshCallback callback);
+
+ // Returns true if `authority` is are actively managed.
+ bool HasManagementAuthority(EnterpriseManagementAuthority authority);
+
+ // Returns the highest trustworthiness of the active management authorities.
+ ManagementAuthorityTrustworthiness GetManagementAuthorityTrustworthiness();
+
+ // Returns whether there is any management authority at all.
+ bool IsManaged();
+
+ const absl::optional<int>& management_authorities_for_testing() {
+ return management_authorities_for_testing_;
+ }
+
+ void SetManagementAuthoritiesForTesting(int management_authorities);
+ void ClearManagementAuthoritiesForTesting();
+ void SetManagementStatusProviderForTesting(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers);
+
+ static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
+
+ protected:
+ // Sets the management status providers to be used by the service.
+ void SetManagementStatusProvider(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers);
+
+ void AddManagementStatusProvider(
+ std::unique_ptr<ManagementStatusProvider> provider);
+
+ const std::vector<std::unique_ptr<ManagementStatusProvider>>&
+ management_status_providers() {
+ return management_status_providers_;
+ }
+
+ private:
+ // Returns a bitset of with the active `EnterpriseManagementAuthority` on the
+ // managed entity.
+ int GetManagementAuthorities();
+
+ absl::optional<int> management_authorities_for_testing_;
+ std::vector<std::unique_ptr<ManagementStatusProvider>>
+ management_status_providers_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_MANAGEMENT_SERVICE_H_
diff --git a/chromium/components/policy/core/common/management/management_service.md b/chromium/components/policy/core/common/management/management_service.md
new file mode 100644
index 00000000000..df5722d1669
--- /dev/null
+++ b/chromium/components/policy/core/common/management/management_service.md
@@ -0,0 +1,103 @@
+# ManagementService
+
+ManagementService is an abstract class that exposes an interface to get an
+entity's management state.
+
+This class allows the consumer to find out if and EnterpriseManagementAuthority
+is actively exercising management.
+
+This class also allows the consumer to get the highest level of
+trustworthiness of all the active management authorities.
+
+## ManagementStatusProvider
+This is an abstract class used to get the management status of a single entity.
+This should return the appropriate EnterpriseManagementAuthority.
+
+### Caching
+
+Some management status result is cached due to system API performance. It means we will return stale data during Chrome launch process.
+
+## BrowserManagementService
+
+A specialization of ManagementService that returns management information on the
+browser itself. This returns info solely on the browser's management regardless
+of the OS or device management since the OS or device could be managed without
+the browser being managed.
+- The browser is managed if there are policies being applied on the browser
+- The browser is managed is the primary signed in account comes from a managed
+domain.
+- The browser is not managed if the device is managed but no policy is applied
+ on the browser.
+- The browser is managed if the device is enrolled in CBCM.
+
+**Usage** This class' lifetime is bound to a Profile as a KeyedService and must
+be called from the UI thread at all times.
+Use `policy::ManagementServiceFactory::GetForProfile()` to access this class.
+
+## PlatformManagementService
+
+A specialization of ManagementService that returns management information on the
+OS or device. This returns info solely on the OS or device management regardless
+of the browser.
+- The OS is managed if it is domain joined or an enterprise version.
+- The device is managed if it is registered as an enterprise device.
+- The OS nor the device are necessarily managed if CBCM is active.
+
+**Usage** This class is a singleton and must be called from the
+UI thread at all times. It is recommended to use
+`policy::ManagementServiceFactory::GetForPlatform()` to access this class.
+`policy::PlatformManagementService::GetInstance()` should only be used outside of
+ //chrome/*.
+
+## EnterpriseManagementAuthority
+
+An enterprise management authority is an enum used to classify an entity that
+can exercise management.
+
+**NONE** No active entity is exercising management.
+
+**COMPUTER_LOCAL** An entity exercising management from the computer itself.
+This type of entity has a low level of trust. This means that this type of
+management has a high chance of not coming from an enterprise.
+i.e. : Policies set locally by an admin (GPO, linux JSON policies)
+
+**DOMAIN_LOCAL** An entity exercising management from the computer and tied to
+a local domain. This type of entity has a high level of trust. This means that
+this type of management has a low chance of not coming from an enterprise.
+i.e. : Computer is Active Directory Joined, Puppet
+
+**CLOUD** An entity exercising management from a cloud source.
+This type of entity has a high level of trust. This means that this type of
+management has a low chance of not coming from an enterprise.
+i.e. : MDM management, GSuite User
+
+**CLOUD_DOMAIN** An entity exercising management from a cryptographically
+verifiable cloud source. This type of entity has the highest level of trust.
+This means that this type of management has a very low chance of not coming from
+an enterprise.
+i.e. : Azure Active directory, CBCM
+
+The presence of `CLOUD` and/or `CLOUD_DOMAIN` in `BrowserManagementService` may
+be used to determine that the browser is managed by a Google product such as
+GSuite or CBCM.
+
+The presence of `CLOUD` and/or `CLOUD_DOMAIN` in `PlatformManagementService` may
+be used to determine that the platform is managed by a Google product only on
+ChromeOS.
+
+## ManagementAuthorityTrustworthiness
+
+This enum is used to rank the level of trustworthiness of the
+management authorities.
+
+**NONE** No management authority was found.
+
+**LOW** Local device management authority.
+
+**TRUSTED** Non-local management authority.
+
+**FULLY_TRUSTED** Cryptographically verifiable policy source.
+
+# ManagementStatusProvider
+This is an abstract class used to get the management status of a single entity.
+This should return the appropriate EnterpriseManagementAuthority. \ No newline at end of file
diff --git a/chromium/components/policy/core/common/management/management_service_unittest.cc b/chromium/components/policy/core/common/management/management_service_unittest.cc
new file mode 100644
index 00000000000..67cc51edc0d
--- /dev/null
+++ b/chromium/components/policy/core/common/management/management_service_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/management_service.h"
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+constexpr char kPrefName[] = "pref_name";
+
+class TestManagementStatusProvider : public ManagementStatusProvider {
+ public:
+ explicit TestManagementStatusProvider(const std::string& cache_pref_name,
+ EnterpriseManagementAuthority authority)
+ : ManagementStatusProvider(cache_pref_name), authority_(authority) {}
+ ~TestManagementStatusProvider() override = default;
+
+ protected:
+ // Returns the authority responsible for the management.
+ EnterpriseManagementAuthority FetchAuthority() override { return authority_; }
+
+ private:
+ EnterpriseManagementAuthority authority_;
+};
+
+class TestManagementService : public ManagementService {
+ public:
+ TestManagementService() : ManagementService({}) {}
+ explicit TestManagementService(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers)
+ : ManagementService(std::move(providers)) {}
+ void SetManagementStatusProviderForTesting(
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers) {
+ SetManagementStatusProvider(std::move(providers));
+ }
+};
+
+class ManagementServiceTests : public testing::Test {
+ public:
+ ManagementServiceTests(const ManagementServiceTests&) = delete;
+ ManagementServiceTests& operator=(const ManagementServiceTests&) = delete;
+
+ void SetUp() override {
+ scoped_feature_list_.InitAndEnableFeature(
+ features::kEnableCachedManagementStatus);
+ prefs_.registry()->RegisterIntegerPref(kPrefName, 0);
+ }
+
+ PrefService* prefs() { return &prefs_; }
+ scoped_refptr<TestingPrefStore> user_prefs_store() {
+ return prefs_.user_prefs_store();
+ }
+
+ protected:
+ ManagementServiceTests() = default;
+ ~ManagementServiceTests() override = default;
+
+ private:
+ TestingPrefServiceSimple prefs_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(ManagementServiceTests, ScopedManagementServiceOverrideForTesting) {
+ TestManagementService management_service;
+ EXPECT_FALSE(management_service.IsManaged());
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::COMPUTER_LOCAL));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::DOMAIN_LOCAL));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD));
+ EXPECT_EQ(ManagementAuthorityTrustworthiness::NONE,
+ management_service.GetManagementAuthorityTrustworthiness());
+
+ {
+ ScopedManagementServiceOverrideForTesting override_1(
+ &management_service, EnterpriseManagementAuthority::CLOUD_DOMAIN);
+ EXPECT_TRUE(management_service.IsManaged());
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD_DOMAIN));
+ EXPECT_EQ(ManagementAuthorityTrustworthiness::FULLY_TRUSTED,
+ management_service.GetManagementAuthorityTrustworthiness());
+ {
+ ScopedManagementServiceOverrideForTesting override_2(
+ &management_service, EnterpriseManagementAuthority::CLOUD);
+ EXPECT_TRUE(management_service.IsManaged());
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD));
+ EXPECT_EQ(ManagementAuthorityTrustworthiness::TRUSTED,
+ management_service.GetManagementAuthorityTrustworthiness());
+ }
+ EXPECT_TRUE(management_service.IsManaged());
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD_DOMAIN));
+ EXPECT_EQ(ManagementAuthorityTrustworthiness::FULLY_TRUSTED,
+ management_service.GetManagementAuthorityTrustworthiness());
+ }
+ EXPECT_FALSE(management_service.IsManaged());
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::COMPUTER_LOCAL));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::DOMAIN_LOCAL));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD));
+ EXPECT_EQ(ManagementAuthorityTrustworthiness::NONE,
+ management_service.GetManagementAuthorityTrustworthiness());
+}
+
+TEST_F(ManagementServiceTests, LoadCachedValues) {
+ base::test::TaskEnvironment task_environment;
+ prefs()->SetInteger(kPrefName, EnterpriseManagementAuthority::CLOUD);
+
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers;
+ providers.emplace_back(std::make_unique<TestManagementStatusProvider>(
+ kPrefName, EnterpriseManagementAuthority::CLOUD_DOMAIN));
+ providers.emplace_back(std::make_unique<TestManagementStatusProvider>(
+ std::string(), EnterpriseManagementAuthority::COMPUTER_LOCAL));
+
+ TestManagementService management_service(std::move(providers));
+ management_service.UsePrefStoreAsCache(user_prefs_store());
+
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD));
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::COMPUTER_LOCAL));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD_DOMAIN));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::DOMAIN_LOCAL));
+ EXPECT_EQ(management_service.GetManagementAuthorityTrustworthiness(),
+ ManagementAuthorityTrustworthiness::TRUSTED);
+
+ management_service.UsePrefServiceAsCache(prefs());
+
+ base::RunLoop run_loop;
+ management_service.RefreshCache(base::BindLambdaForTesting(
+ [&](ManagementAuthorityTrustworthiness previous,
+ ManagementAuthorityTrustworthiness next) {
+ EXPECT_EQ(previous, ManagementAuthorityTrustworthiness::TRUSTED);
+ EXPECT_EQ(next, ManagementAuthorityTrustworthiness::FULLY_TRUSTED);
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD));
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::COMPUTER_LOCAL));
+ EXPECT_TRUE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::CLOUD_DOMAIN));
+ EXPECT_FALSE(management_service.HasManagementAuthority(
+ EnterpriseManagementAuthority::DOMAIN_LOCAL));
+ EXPECT_EQ(management_service.GetManagementAuthorityTrustworthiness(),
+ ManagementAuthorityTrustworthiness::FULLY_TRUSTED);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/platform_management_service.cc b/chromium/components/policy/core/common/management/platform_management_service.cc
new file mode 100644
index 00000000000..a68d37873fa
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_service.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/platform_management_service.h"
+
+#include "base/feature_list.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/no_destructor.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/features.h"
+#if BUILDFLAG(IS_MAC)
+#include "components/policy/core/common/management/platform_management_status_provider_mac.h"
+#elif BUILDFLAG(IS_WIN)
+#include "components/policy/core/common/management/platform_management_status_provider_win.h"
+#endif
+
+namespace policy {
+
+namespace {
+std::vector<std::unique_ptr<ManagementStatusProvider>>
+GetPlatformManagementSatusProviders() {
+ std::vector<std::unique_ptr<ManagementStatusProvider>> providers;
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+ providers.emplace_back(std::make_unique<DomainEnrollmentStatusProvider>());
+ providers.emplace_back(
+ std::make_unique<EnterpriseMDMManagementStatusProvider>());
+#endif
+#if BUILDFLAG(IS_WIN)
+ providers.emplace_back(
+ std::make_unique<AzureActiveDirectoryStatusProvider>());
+#endif
+ return providers;
+}
+
+} // namespace
+
+// static
+PlatformManagementService* PlatformManagementService::GetInstance() {
+ static base::NoDestructor<PlatformManagementService> instance;
+ return instance.get();
+}
+
+PlatformManagementService::PlatformManagementService()
+ : ManagementService(GetPlatformManagementSatusProviders()) {}
+
+PlatformManagementService::~PlatformManagementService() = default;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+void PlatformManagementService::AddChromeOsStatusProvider(
+ std::unique_ptr<ManagementStatusProvider> provider) {
+ AddManagementStatusProvider(std::move(provider));
+ has_cros_status_provider_ = true;
+}
+#endif
+
+void PlatformManagementService::RefreshCache(CacheRefreshCallback callback) {
+ if (!base::FeatureList::IsEnabled(features::kEnableCachedManagementStatus))
+ return;
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ // Unretained here since this class should never be destroyed.
+ base::BindOnce(&PlatformManagementService::GetCacheUpdate,
+ base::Unretained(this)),
+ base::BindOnce(&PlatformManagementService::UpdateCache,
+ base::Unretained(this), std::move(callback)));
+}
+
+base::flat_map<ManagementStatusProvider*, EnterpriseManagementAuthority>
+PlatformManagementService::GetCacheUpdate() {
+ base::flat_map<ManagementStatusProvider*, EnterpriseManagementAuthority>
+ result;
+ for (const auto& provider : management_status_providers()) {
+ if (provider->RequiresCache())
+ result.insert({provider.get(), provider->FetchAuthority()});
+ }
+ return result;
+}
+
+void PlatformManagementService::UpdateCache(
+ CacheRefreshCallback callback,
+ base::flat_map<ManagementStatusProvider*, EnterpriseManagementAuthority>
+ cache_update) {
+ ManagementAuthorityTrustworthiness previous =
+ GetManagementAuthorityTrustworthiness();
+ for (const auto& it : cache_update) {
+ it.first->UpdateCache(it.second);
+ }
+ ManagementAuthorityTrustworthiness next =
+ GetManagementAuthorityTrustworthiness();
+ base::UmaHistogramBoolean(
+ "Enterprise.ManagementAuthorityTrustworthiness.Cache.ValueChange",
+ previous != next);
+ if (callback)
+ std::move(callback).Run(previous, next);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/platform_management_service.h b/chromium/components/policy/core/common/management/platform_management_service.h
new file mode 100644
index 00000000000..ea542f71752
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_service.h
@@ -0,0 +1,60 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_SERVICE_H_
+
+#include "base/containers/flat_map.h"
+#include "base/no_destructor.h"
+#include "build/chromeos_buildflags.h"
+
+#include "components/policy/core/common/management/management_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// This class gives information related to the OS and device's management
+// state.
+// For more imformation please read
+// //components/policy/core/common/management/management_service.md
+class POLICY_EXPORT PlatformManagementService : public ManagementService {
+ public:
+ // Returns the singleton instance of PlatformManagementService.
+ static PlatformManagementService* GetInstance();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ void AddChromeOsStatusProvider(
+ std::unique_ptr<ManagementStatusProvider> provider);
+ bool has_cros_status_provider() const { return has_cros_status_provider_; }
+#endif
+
+ void RefreshCache(CacheRefreshCallback callback) override;
+
+ private:
+ friend class base::NoDestructor<PlatformManagementService>;
+
+ // Returns a map of the status providers to their non-cached management
+ // authority. This is used to update their cache. This may have some I/O
+ // calls, therefore must never be called on the main thread.
+ base::flat_map<ManagementStatusProvider*, EnterpriseManagementAuthority>
+ GetCacheUpdate();
+
+ // Updates the cached values of the status providers with the appropriate
+ // value and call `callback` with the previous and new management authority
+ // trustworthiness.
+ void UpdateCache(CacheRefreshCallback callback,
+ base::flat_map<ManagementStatusProvider*,
+ EnterpriseManagementAuthority> cache_update);
+
+ PlatformManagementService();
+ ~PlatformManagementService() override;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ bool has_cros_status_provider_;
+#endif
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_SERVICE_H_
diff --git a/chromium/components/policy/core/common/management/platform_management_status_provider_mac.cc b/chromium/components/policy/core/common/management/platform_management_status_provider_mac.cc
new file mode 100644
index 00000000000..9a2e8206550
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_status_provider_mac.cc
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/platform_management_status_provider_mac.h"
+
+#include "components/policy/core/common/policy_pref_names.h"
+
+namespace policy {
+
+DomainEnrollmentStatusProvider::DomainEnrollmentStatusProvider() {
+ domain_join_state_ = base::AreDeviceAndUserJoinedToDomain();
+}
+
+DomainEnrollmentStatusProvider::~DomainEnrollmentStatusProvider() = default;
+
+EnterpriseManagementAuthority DomainEnrollmentStatusProvider::FetchAuthority() {
+ return domain_join_state_.device_joined || domain_join_state_.user_joined
+ ? EnterpriseManagementAuthority::DOMAIN_LOCAL
+ : EnterpriseManagementAuthority::NONE;
+}
+
+EnterpriseMDMManagementStatusProvider::EnterpriseMDMManagementStatusProvider()
+ : ManagementStatusProvider(policy_prefs::kEnterpriseMDMManagementMac) {}
+
+EnterpriseMDMManagementStatusProvider::
+ ~EnterpriseMDMManagementStatusProvider() = default;
+
+EnterpriseManagementAuthority
+EnterpriseMDMManagementStatusProvider::FetchAuthority() {
+ base::MacDeviceManagementStateNew mdm_state_new =
+ base::IsDeviceRegisteredWithManagementNew();
+
+ bool managed = false;
+ switch (mdm_state_new) {
+ case base::MacDeviceManagementStateNew::kLimitedMDMEnrollment:
+ case base::MacDeviceManagementStateNew::kFullMDMEnrollment:
+ case base::MacDeviceManagementStateNew::kDEPMDMEnrollment:
+ managed = true;
+ break;
+ case base::MacDeviceManagementStateNew::kFailureAPIUnavailable:
+ managed = base::MacDeviceManagementStateOld::kMDMEnrollment ==
+ base::IsDeviceRegisteredWithManagementOld();
+ break;
+ default:
+ break;
+ }
+
+ return managed ? EnterpriseManagementAuthority::CLOUD
+ : EnterpriseManagementAuthority::NONE;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/platform_management_status_provider_mac.h b/chromium/components/policy/core/common/management/platform_management_status_provider_mac.h
new file mode 100644
index 00000000000..7b34d7aed45
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_status_provider_mac.h
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_MAC_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_MAC_H_
+
+#include "base/enterprise_util.h"
+#include "components/policy/core/common/management/management_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class POLICY_EXPORT DomainEnrollmentStatusProvider final
+ : public ManagementStatusProvider {
+ public:
+ DomainEnrollmentStatusProvider();
+ ~DomainEnrollmentStatusProvider() final;
+
+ protected:
+ // ManagementStatusProvider impl
+ EnterpriseManagementAuthority FetchAuthority() final;
+
+ private:
+ base::DeviceUserDomainJoinState domain_join_state_;
+};
+
+class POLICY_EXPORT EnterpriseMDMManagementStatusProvider final
+ : public ManagementStatusProvider {
+ public:
+ EnterpriseMDMManagementStatusProvider();
+ ~EnterpriseMDMManagementStatusProvider() final;
+
+ protected:
+ // ManagementStatusProvider impl
+ EnterpriseManagementAuthority FetchAuthority() final;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_MAC_H_
diff --git a/chromium/components/policy/core/common/management/platform_management_status_provider_win.cc b/chromium/components/policy/core/common/management/platform_management_status_provider_win.cc
new file mode 100644
index 00000000000..c1236936254
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_status_provider_win.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/platform_management_status_provider_win.h"
+
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/policy_pref_names.h"
+
+namespace policy {
+DomainEnrollmentStatusProvider::DomainEnrollmentStatusProvider() = default;
+
+EnterpriseManagementAuthority DomainEnrollmentStatusProvider::FetchAuthority() {
+ return DomainEnrollmentStatusProvider::IsEnrolledToDomain() ? DOMAIN_LOCAL
+ : NONE;
+}
+
+bool DomainEnrollmentStatusProvider::IsEnrolledToDomain() {
+ return base::win::IsEnrolledToDomain();
+}
+
+EnterpriseMDMManagementStatusProvider::EnterpriseMDMManagementStatusProvider()
+ : ManagementStatusProvider(policy_prefs::kEnterpriseMDMManagementWindows) {}
+
+EnterpriseManagementAuthority
+EnterpriseMDMManagementStatusProvider::FetchAuthority() {
+ return base::win::OSInfo::GetInstance()->version_type() !=
+ base::win::SUITE_HOME &&
+ base::win::IsDeviceRegisteredWithManagement()
+ ? CLOUD
+ : NONE;
+}
+
+AzureActiveDirectoryStatusProvider::AzureActiveDirectoryStatusProvider()
+ : ManagementStatusProvider(policy_prefs::kAzureActiveDirectoryManagement) {}
+
+EnterpriseManagementAuthority
+AzureActiveDirectoryStatusProvider::FetchAuthority() {
+ return base::win::IsJoinedToAzureAD() ? CLOUD_DOMAIN : NONE;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/platform_management_status_provider_win.h b/chromium/components/policy/core/common/management/platform_management_status_provider_win.h
new file mode 100644
index 00000000000..5a0084d81f1
--- /dev/null
+++ b/chromium/components/policy/core/common/management/platform_management_status_provider_win.h
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_WIN_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_WIN_H_
+
+#include "components/policy/core/common/management/management_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class POLICY_EXPORT DomainEnrollmentStatusProvider final
+ : public ManagementStatusProvider {
+ public:
+ DomainEnrollmentStatusProvider();
+
+ DomainEnrollmentStatusProvider(const DomainEnrollmentStatusProvider&) =
+ delete;
+ DomainEnrollmentStatusProvider& operator=(
+ const DomainEnrollmentStatusProvider&) = delete;
+
+ static bool IsEnrolledToDomain();
+
+ protected:
+ // ManagementStatusProvider impl
+ EnterpriseManagementAuthority FetchAuthority() final;
+};
+
+class POLICY_EXPORT EnterpriseMDMManagementStatusProvider final
+ : public ManagementStatusProvider {
+ public:
+ EnterpriseMDMManagementStatusProvider();
+
+ EnterpriseMDMManagementStatusProvider(
+ const EnterpriseMDMManagementStatusProvider&) = delete;
+ EnterpriseMDMManagementStatusProvider& operator=(
+ const EnterpriseMDMManagementStatusProvider&) = delete;
+
+ static bool IsEnrolledToDomain();
+
+ protected:
+ // ManagementStatusProvider impl
+ EnterpriseManagementAuthority FetchAuthority() final;
+};
+
+// TODO (crbug/1300217): Handle management state changing while the browser is
+// running.
+class POLICY_EXPORT AzureActiveDirectoryStatusProvider final
+ : public ManagementStatusProvider {
+ public:
+ AzureActiveDirectoryStatusProvider();
+
+ AzureActiveDirectoryStatusProvider(
+ const AzureActiveDirectoryStatusProvider&) = delete;
+ AzureActiveDirectoryStatusProvider& operator=(
+ const AzureActiveDirectoryStatusProvider&) = delete;
+
+ protected:
+ // ManagementStatusProvider impl
+ EnterpriseManagementAuthority FetchAuthority() override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_PLATFORM_MANAGEMENT_STATUS_PROVIDER_WIN_H_
diff --git a/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.cc b/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.cc
new file mode 100644
index 00000000000..f552bc595ce
--- /dev/null
+++ b/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
+
+namespace policy {
+
+ScopedManagementServiceOverrideForTesting::
+ ScopedManagementServiceOverrideForTesting(ManagementService* service,
+ uint64_t authorities)
+ : service_(service) {
+ if (service_->management_authorities_for_testing().has_value())
+ previous_authorities_ =
+ service_->management_authorities_for_testing().value();
+ service_->SetManagementAuthoritiesForTesting(authorities);
+}
+
+ScopedManagementServiceOverrideForTesting::
+ ~ScopedManagementServiceOverrideForTesting() {
+ if (previous_authorities_.has_value()) {
+ service_->SetManagementAuthoritiesForTesting(previous_authorities_.value());
+ } else {
+ service_->ClearManagementAuthoritiesForTesting();
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.h b/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.h
new file mode 100644
index 00000000000..c4c2fb5e562
--- /dev/null
+++ b/chromium/components/policy/core/common/management/scoped_management_service_override_for_testing.h
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_SCOPED_MANAGEMENT_SERVICE_OVERRIDE_FOR_TESTING_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_SCOPED_MANAGEMENT_SERVICE_OVERRIDE_FOR_TESTING_H_
+
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/core/common/management/management_service.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+// Sets the management authorities override for |target| on construction, and
+// removes it when the object goes out of scope. This class is intended to be
+// used by tests that need to override management authorities to ensure their
+// overrides are properly handled and reverted when the scope of the test is
+// left.
+// |authorities| here are the management authorities we want to fake as active
+// for the testing purposes.
+// Use case example:
+// ScopedManagementServiceOverrideForTesting
+// scoped_management_service_override(
+// service, EnterpriseManagementAuthority::DOMAIN_LOCAL);
+
+class ScopedManagementServiceOverrideForTesting {
+ public:
+ ScopedManagementServiceOverrideForTesting(ManagementService* service,
+ uint64_t authorities);
+ ~ScopedManagementServiceOverrideForTesting();
+
+ private:
+ raw_ptr<ManagementService> service_;
+ absl::optional<uint64_t> previous_authorities_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MANAGEMENT_SCOPED_MANAGEMENT_SERVICE_OVERRIDE_FOR_TESTING_H_
diff --git a/chromium/components/policy/core/common/mock_configuration_policy_provider.cc b/chromium/components/policy/core/common/mock_configuration_policy_provider.cc
new file mode 100644
index 00000000000..97f5bd5b1b1
--- /dev/null
+++ b/chromium/components/policy/core/common/mock_configuration_policy_provider.cc
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/run_loop.h"
+#include "base/task/current_thread.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+using testing::Invoke;
+
+namespace policy {
+
+MockConfigurationPolicyProvider::MockConfigurationPolicyProvider() {}
+
+MockConfigurationPolicyProvider::~MockConfigurationPolicyProvider() {
+#if BUILDFLAG(IS_ANDROID)
+ ShutdownForTesting();
+#endif // BUILDFLAG(IS_ANDROID)
+}
+
+void MockConfigurationPolicyProvider::UpdateChromePolicy(
+ const PolicyMap& policy) {
+ std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy.Clone();
+ UpdatePolicy(std::move(bundle));
+ bool spin_run_loop = base::CurrentThread::IsSet();
+#if BUILDFLAG(IS_IOS)
+ // On iOS, the UI message loop does not support RunUntilIdle().
+ spin_run_loop &= !base::CurrentUIThread::IsSet();
+#endif // BUILDFLAG(IS_IOS)
+ if (spin_run_loop)
+ base::RunLoop().RunUntilIdle();
+}
+
+void MockConfigurationPolicyProvider::UpdateExtensionPolicy(
+ const PolicyMap& policy,
+ const std::string& extension_id) {
+ std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, extension_id)) =
+ policy.Clone();
+ UpdatePolicy(std::move(bundle));
+ if (base::CurrentThread::IsSet())
+ base::RunLoop().RunUntilIdle();
+}
+
+void MockConfigurationPolicyProvider::SetAutoRefresh() {
+ EXPECT_CALL(*this, RefreshPolicies()).WillRepeatedly(
+ Invoke(this, &MockConfigurationPolicyProvider::RefreshWithSamePolicies));
+}
+
+void MockConfigurationPolicyProvider::RefreshWithSamePolicies() {
+ std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+ bundle->CopyFrom(policies());
+ UpdatePolicy(std::move(bundle));
+}
+
+MockConfigurationPolicyObserver::MockConfigurationPolicyObserver() {}
+
+MockConfigurationPolicyObserver::~MockConfigurationPolicyObserver() {}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/mock_configuration_policy_provider.h b/chromium/components/policy/core/common/mock_configuration_policy_provider.h
new file mode 100644
index 00000000000..1be2c298e9b
--- /dev/null
+++ b/chromium/components/policy/core/common/mock_configuration_policy_provider.h
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
+
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+// Mock ConfigurationPolicyProvider implementation that supplies canned
+// values for polices.
+// TODO(joaodasilva, mnissler): introduce an implementation that non-policy
+// code can use that doesn't require the usual boilerplate.
+// http://crbug.com/242087
+class MockConfigurationPolicyProvider : public ConfigurationPolicyProvider {
+ public:
+ MockConfigurationPolicyProvider();
+ MockConfigurationPolicyProvider(const MockConfigurationPolicyProvider&) =
+ delete;
+ MockConfigurationPolicyProvider& operator=(
+ const MockConfigurationPolicyProvider&) = delete;
+ ~MockConfigurationPolicyProvider() override;
+
+ MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+ MOCK_CONST_METHOD1(IsFirstPolicyLoadComplete, bool(PolicyDomain domain));
+ MOCK_METHOD0(RefreshPolicies, void());
+
+ // Make public for tests.
+ using ConfigurationPolicyProvider::UpdatePolicy;
+
+ // Utility method that invokes UpdatePolicy() with a PolicyBundle that maps
+ // the Chrome namespace to a copy of |policy|.
+ // Note: Replaces the PolicyBundle, so any policy that has been set previously
+ // will be lost when calling this utility method.
+ void UpdateChromePolicy(const PolicyMap& policy);
+
+ // Utility method that invokes UpdatePolicy() with a PolicyBundle that maps
+ // the extension's |extension_id| namespace to a copy of |policy|.
+ // Note: Replaces the PolicyBundle, so any policy that has been set previously
+ // will be lost when calling this utility method.
+ void UpdateExtensionPolicy(const PolicyMap& policy,
+ const std::string& extension_id);
+
+ // Convenience method so that tests don't need to create a registry to create
+ // this mock.
+ using ConfigurationPolicyProvider::Init;
+ void Init() {
+ ConfigurationPolicyProvider::Init(&registry_);
+ }
+
+ // Utility testing method used to set up boilerplate |ON_CALL| defaults.
+ void SetDefaultReturns(bool is_initialization_complete_return,
+ bool is_first_policy_load_complete_return) {
+ ON_CALL(*this, IsInitializationComplete(testing::_))
+ .WillByDefault(testing::Return(is_initialization_complete_return));
+ ON_CALL(*this, IsFirstPolicyLoadComplete(testing::_))
+ .WillByDefault(testing::Return(is_first_policy_load_complete_return));
+ }
+
+ // Convenience method that installs an expectation on RefreshPolicies that
+ // just notifies the observers and serves the same policies.
+ void SetAutoRefresh();
+
+ private:
+ void RefreshWithSamePolicies();
+
+ SchemaRegistry registry_;
+};
+
+class MockConfigurationPolicyObserver
+ : public ConfigurationPolicyProvider::Observer {
+ public:
+ MockConfigurationPolicyObserver();
+ ~MockConfigurationPolicyObserver() override;
+
+ MOCK_METHOD1(OnUpdatePolicy, void(ConfigurationPolicyProvider*));
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MOCK_CONFIGURATION_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/mock_policy_service.cc b/chromium/components/policy/core/common/mock_policy_service.cc
new file mode 100644
index 00000000000..84a4583dd33
--- /dev/null
+++ b/chromium/components/policy/core/common/mock_policy_service.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/mock_policy_service.h"
+
+namespace policy {
+
+MockPolicyServiceObserver::MockPolicyServiceObserver() = default;
+
+MockPolicyServiceObserver::~MockPolicyServiceObserver() = default;
+
+MockPolicyServiceProviderUpdateObserver::
+ MockPolicyServiceProviderUpdateObserver() = default;
+
+MockPolicyServiceProviderUpdateObserver::
+ ~MockPolicyServiceProviderUpdateObserver() = default;
+
+MockPolicyService::MockPolicyService() = default;
+
+MockPolicyService::~MockPolicyService() = default;
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/mock_policy_service.h b/chromium/components/policy/core/common/mock_policy_service.h
new file mode 100644
index 00000000000..f2f9f8cdc61
--- /dev/null
+++ b/chromium/components/policy/core/common/mock_policy_service.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
+
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockPolicyServiceObserver : public PolicyService::Observer {
+ public:
+ MockPolicyServiceObserver();
+ ~MockPolicyServiceObserver() override;
+
+ MOCK_METHOD3(OnPolicyUpdated, void(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current));
+ MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
+ MOCK_METHOD1(OnFirstPoliciesLoaded, void(PolicyDomain));
+};
+
+class MockPolicyServiceProviderUpdateObserver
+ : public PolicyService::ProviderUpdateObserver {
+ public:
+ MockPolicyServiceProviderUpdateObserver();
+ ~MockPolicyServiceProviderUpdateObserver() override;
+
+ MOCK_METHOD1(OnProviderUpdatePropagated,
+ void(ConfigurationPolicyProvider* provider));
+};
+
+class MockPolicyService : public PolicyService {
+ public:
+ MockPolicyService();
+ ~MockPolicyService() override;
+
+ MOCK_METHOD2(AddObserver, void(PolicyDomain, Observer*));
+ MOCK_METHOD2(RemoveObserver, void(PolicyDomain, Observer*));
+ MOCK_METHOD1(AddProviderUpdateObserver, void(ProviderUpdateObserver*));
+ MOCK_METHOD1(RemoveProviderUpdateObserver, void(ProviderUpdateObserver*));
+ MOCK_CONST_METHOD1(HasProvider, bool(ConfigurationPolicyProvider*));
+
+ MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
+ MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
+ MOCK_CONST_METHOD1(IsFirstPolicyLoadComplete, bool(PolicyDomain domain));
+ MOCK_METHOD1(RefreshPolicies, void(base::OnceClosure));
+
+#if BUILDFLAG(IS_ANDROID)
+ MOCK_METHOD0(GetPolicyServiceAndroid, android::PolicyServiceAndroid*());
+#endif
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_MOCK_POLICY_SERVICE_H_
diff --git a/chromium/components/policy/core/common/policy_bundle.cc b/chromium/components/policy/core/common/policy_bundle.cc
new file mode 100644
index 00000000000..6cb05de8d4d
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_bundle.cc
@@ -0,0 +1,106 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_bundle.h"
+
+#include "base/check.h"
+#include "base/notreached.h"
+
+namespace policy {
+
+PolicyBundle::PolicyBundle() {}
+
+PolicyBundle::~PolicyBundle() {
+ Clear();
+}
+
+PolicyMap& PolicyBundle::Get(const PolicyNamespace& ns) {
+ DCHECK(ns.domain != POLICY_DOMAIN_CHROME || ns.component_id.empty());
+ return policy_bundle_[ns];
+}
+
+const PolicyMap& PolicyBundle::Get(const PolicyNamespace& ns) const {
+ DCHECK(ns.domain != POLICY_DOMAIN_CHROME || ns.component_id.empty());
+ const auto it = policy_bundle_.find(ns);
+ return it == end() ? kEmpty_ : it->second;
+}
+
+void PolicyBundle::Swap(PolicyBundle* other) {
+ policy_bundle_.swap(other->policy_bundle_);
+}
+
+void PolicyBundle::CopyFrom(const PolicyBundle& other) {
+ DCHECK_NE(this, &other);
+
+ Clear();
+ for (const auto& entry_other : other) {
+ policy_bundle_[entry_other.first] = entry_other.second.Clone();
+ }
+}
+
+void PolicyBundle::MergeFrom(const PolicyBundle& other) {
+ DCHECK_NE(this, &other);
+
+ // Iterate over both |this| and |other| in order; skip what's extra in |this|,
+ // add what's missing, and merge the namespaces in common.
+ auto it_this = policy_bundle_.begin();
+ auto end_this = policy_bundle_.end();
+ auto it_other = other.begin();
+ auto end_other = other.end();
+
+ while (it_this != end_this && it_other != end_other) {
+ if (it_this->first == it_other->first) {
+ // Same namespace: merge existing PolicyMaps.
+ it_this->second.MergeFrom(it_other->second);
+ ++it_this;
+ ++it_other;
+ } else if (it_this->first < it_other->first) {
+ // |this| has a PolicyMap that |other| doesn't; skip it.
+ ++it_this;
+ } else if (it_other->first < it_this->first) {
+ // |other| has a PolicyMap that |this| doesn't; copy it.
+ policy_bundle_[it_other->first] = it_other->second.Clone();
+ ++it_other;
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ // Add extra PolicyMaps at the end.
+ while (it_other != end_other) {
+ policy_bundle_[it_other->first] = it_other->second.Clone();
+ ++it_other;
+ }
+}
+
+bool PolicyBundle::Equals(const PolicyBundle& other) const {
+ // Equals() has the peculiarity that an entry with an empty PolicyMap equals
+ // an non-existent entry. This handles usage of non-const Get() that doesn't
+ // insert any policies.
+ auto it_this = begin();
+ auto it_other = other.begin();
+
+ while (true) {
+ // Skip empty PolicyMaps.
+ while (it_this != end() && it_this->second.empty())
+ ++it_this;
+ while (it_other != other.end() && it_other->second.empty())
+ ++it_other;
+ if (it_this == end() || it_other == other.end())
+ break;
+ if (it_this->first != it_other->first ||
+ !it_this->second.Equals(it_other->second)) {
+ return false;
+ }
+ ++it_this;
+ ++it_other;
+ }
+ return it_this == end() && it_other == other.end();
+}
+
+void PolicyBundle::Clear() {
+ policy_bundle_.clear();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_bundle.h b/chromium/components/policy/core/common/policy_bundle.h
new file mode 100644
index 00000000000..2fe4fe43f71
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_bundle.h
@@ -0,0 +1,71 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
+
+#include <map>
+
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Maps policy namespaces to PolicyMaps.
+class POLICY_EXPORT PolicyBundle {
+ public:
+ using MapType = std::map<PolicyNamespace, PolicyMap>;
+ using iterator = MapType::iterator;
+ using const_iterator = MapType::const_iterator;
+
+ PolicyBundle();
+ PolicyBundle(const PolicyBundle&) = delete;
+ PolicyBundle& operator=(const PolicyBundle&) = delete;
+ virtual ~PolicyBundle();
+
+ // Returns the PolicyMap for namespace |ns|. Creates a new map if necessary.
+ PolicyMap& Get(const PolicyNamespace& ns);
+ const PolicyMap& Get(const PolicyNamespace& ns) const;
+
+ // Swaps the internal representation of |this| with |other|.
+ void Swap(PolicyBundle* other);
+
+ // |this| becomes a copy of |other|. Any existing PolicyMaps are dropped.
+ void CopyFrom(const PolicyBundle& other);
+
+ // Merges the PolicyMaps of |this| with those of |other| for each namespace
+ // in common. Also adds copies of the (namespace, PolicyMap) pairs in |other|
+ // that don't have an entry in |this|.
+ // Each policy in each PolicyMap is replaced only if the policy from |other|
+ // has a higher priority.
+ // See PolicyMap::MergeFrom for details on merging individual PolicyMaps.
+ void MergeFrom(const PolicyBundle& other);
+
+ // Returns true if |other| has the same keys and value as |this|.
+ bool Equals(const PolicyBundle& other) const;
+
+ // Returns iterators to the beginning and end of the underlying container.
+ iterator begin() { return policy_bundle_.begin(); }
+ iterator end() { return policy_bundle_.end(); }
+
+ // These can be used to iterate over and read the PolicyMaps, but not to
+ // modify them.
+ const_iterator begin() const { return policy_bundle_.begin(); }
+ const_iterator end() const { return policy_bundle_.end(); }
+
+ // Erases all the existing pairs.
+ void Clear();
+
+ private:
+ MapType policy_bundle_;
+
+ // An empty PolicyMap that is returned by const Get() for namespaces that
+ // do not exist in |policy_bundle_|.
+ const PolicyMap kEmpty_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_BUNDLE_H_
diff --git a/chromium/components/policy/core/common/policy_bundle_unittest.cc b/chromium/components/policy/core/common/policy_bundle_unittest.cc
new file mode 100644
index 00000000000..4b5171334c1
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_bundle_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_bundle.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+const char kPolicyClashing0[] = "policy-clashing-0";
+const char kPolicyClashing1[] = "policy-clashing-1";
+const char kPolicy0[] = "policy-0";
+const char kPolicy1[] = "policy-1";
+const char kPolicy2[] = "policy-2";
+const char kExtension0[] = "extension-0";
+const char kExtension1[] = "extension-1";
+const char kExtension2[] = "extension-2";
+const char kExtension3[] = "extension-3";
+
+// Adds test policies to |policy|.
+void AddTestPolicies(PolicyMap* policy) {
+ policy->Set("mandatory-user", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ policy->Set("mandatory-machine", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value("omg"), nullptr);
+ policy->Set("recommended-user", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetBoolKey("false", false);
+ dict.SetIntKey("int", 456);
+ dict.SetStringKey("str", "bbq");
+ policy->Set("recommended-machine", POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD, std::move(dict),
+ nullptr);
+}
+
+// Adds test policies to |policy| based on the parameters:
+// - kPolicyClashing0 mapped to |value|, user mandatory
+// - kPolicyClashing1 mapped to |value|, with |level| and |scope|
+// - |name| mapped to |value|, user mandatory
+void AddTestPoliciesWithParams(PolicyMap *policy,
+ const char* name,
+ int value,
+ PolicyLevel level,
+ PolicyScope scope) {
+ policy->Set(kPolicyClashing0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(value), nullptr);
+ policy->Set(kPolicyClashing1, level, scope, POLICY_SOURCE_CLOUD,
+ base::Value(value), nullptr);
+ policy->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(value), nullptr);
+}
+
+// Returns true if |bundle| is empty.
+bool IsEmpty(const PolicyBundle& bundle) {
+ return bundle.begin() == bundle.end();
+}
+
+} // namespace
+
+TEST(PolicyBundleTest, Get) {
+ PolicyBundle bundle;
+ EXPECT_TRUE(IsEmpty(bundle));
+
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ EXPECT_FALSE(IsEmpty(bundle));
+
+ PolicyMap policy;
+ AddTestPolicies(&policy);
+ EXPECT_TRUE(bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+
+ PolicyBundle::const_iterator it = bundle.begin();
+ ASSERT_TRUE(it != bundle.end());
+ EXPECT_EQ(POLICY_DOMAIN_CHROME, it->first.domain);
+ EXPECT_EQ("", it->first.component_id);
+ EXPECT_TRUE(it->second.Equals(policy));
+ ++it;
+ EXPECT_TRUE(it == bundle.end());
+ EXPECT_TRUE(bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).empty());
+
+ EXPECT_FALSE(IsEmpty(bundle));
+ bundle.Clear();
+ EXPECT_TRUE(IsEmpty(bundle));
+}
+
+TEST(PolicyBundleTest, SwapAndCopy) {
+ PolicyBundle bundle0;
+ PolicyBundle bundle1;
+
+ AddTestPolicies(&bundle0.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ AddTestPolicies(&bundle0.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)));
+ EXPECT_FALSE(IsEmpty(bundle0));
+ EXPECT_TRUE(IsEmpty(bundle1));
+
+ PolicyMap policy;
+ AddTestPolicies(&policy);
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+
+ bundle0.Swap(&bundle1);
+ EXPECT_TRUE(IsEmpty(bundle0));
+ EXPECT_FALSE(IsEmpty(bundle1));
+
+ EXPECT_TRUE(bundle1.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+
+ bundle0.CopyFrom(bundle1);
+ EXPECT_FALSE(IsEmpty(bundle0));
+ EXPECT_FALSE(IsEmpty(bundle1));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(policy));
+ EXPECT_TRUE(bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy));
+}
+
+TEST(PolicyBundleTest, MergeFrom) {
+ // Each bundleN has kExtensionN. Each bundle also has policy for
+ // chrome and kExtension3.
+ // |bundle0| has the highest priority, |bundle2| the lowest.
+ PolicyBundle bundle0;
+ PolicyBundle bundle1;
+ PolicyBundle bundle2;
+
+ PolicyMap policy0;
+ AddTestPoliciesWithParams(
+ &policy0, kPolicy0, 0u, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy0.Clone();
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)) =
+ policy0.Clone();
+ bundle0.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3)) =
+ policy0.Clone();
+
+ PolicyMap policy1;
+ AddTestPoliciesWithParams(
+ &policy1, kPolicy1, 1u, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy1.Clone();
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1)) =
+ policy1.Clone();
+ bundle1.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3)) =
+ policy1.Clone();
+
+ PolicyMap policy2;
+ AddTestPoliciesWithParams(
+ &policy2, kPolicy2, 2u, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy2.Clone();
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2)) =
+ policy2.Clone();
+ bundle2.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension3)) =
+ policy2.Clone();
+
+ // Merge in order of decreasing priority.
+ PolicyBundle merged;
+ merged.MergeFrom(bundle0);
+ merged.MergeFrom(bundle1);
+ merged.MergeFrom(bundle2);
+ PolicyBundle empty_bundle;
+ merged.MergeFrom(empty_bundle);
+
+ // chrome and kExtension3 policies are merged:
+ // - kPolicyClashing0 comes from bundle0, which has the highest priority;
+ // - kPolicyClashing1 comes from bundle1, which has the highest level/scope
+ // combination;
+ // - kPolicyN are merged from each bundle.
+ PolicyMap expected;
+ expected.Set(kPolicyClashing0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ expected.GetMutable(kPolicyClashing0)
+ ->AddConflictingPolicy(policy1.Get(kPolicyClashing0)->DeepCopy());
+ expected.GetMutable(kPolicyClashing0)
+ ->AddConflictingPolicy(policy2.Get(kPolicyClashing0)->DeepCopy());
+ expected.GetMutable(kPolicyClashing0)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable(kPolicyClashing0)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.Set(kPolicyClashing1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ expected.GetMutable(kPolicyClashing1)
+ ->AddConflictingPolicy(policy0.Get(kPolicyClashing1)->DeepCopy());
+ expected.GetMutable(kPolicyClashing1)
+ ->AddConflictingPolicy(policy2.Get(kPolicyClashing1)->DeepCopy());
+ expected.GetMutable(kPolicyClashing1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable(kPolicyClashing1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ expected.Set(kPolicy1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ expected.Set(kPolicy2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(2), nullptr);
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())).Equals(expected));
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension3)).Equals(expected));
+ // extension0 comes only from bundle0.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension0)).Equals(policy0));
+ // extension1 comes only from bundle1.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension1)).Equals(policy1));
+ // extension2 comes only from bundle2.
+ EXPECT_TRUE(merged.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+ kExtension2)).Equals(policy2));
+}
+
+TEST(PolicyBundleTest, Equals) {
+ PolicyBundle bundle;
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)));
+
+ PolicyBundle other;
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+
+ AddTestPolicies(&bundle.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1)));
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+ AddTestPolicies(&other.Get(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2)));
+ EXPECT_FALSE(bundle.Equals(other));
+
+ other.CopyFrom(bundle);
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+ other.CopyFrom(bundle);
+ EXPECT_TRUE(bundle.Equals(other));
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+
+ // Test non-const Get().
+ bundle.Clear();
+ other.Clear();
+ PolicyMap& policy_map =
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_TRUE(bundle.Equals(other));
+ policy_map.Set(kPolicy0, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ EXPECT_FALSE(bundle.Equals(other));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_details.h b/chromium/components/policy/core/common/policy_details.h
new file mode 100644
index 00000000000..2972561ebb2
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_details.h
@@ -0,0 +1,51 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/risk_tag.h"
+
+namespace policy {
+
+// Contains read-only metadata about a Chrome policy.
+struct POLICY_EXPORT PolicyDetails {
+ // True if this policy has been deprecated.
+ bool is_deprecated : 1;
+
+ // True if the policy hasn't been released yet.
+ bool is_future : 1;
+
+ // True if this policy is a Chrome OS device policy.
+ bool is_device_policy : 1;
+
+ // The id of the protobuf field that contains this policy,
+ // in the cloud policy protobuf.
+ short id;
+
+ // If this policy references external data then this is the maximum size
+ // allowed for that data.
+ // Otherwise this field is 0 and doesn't have any meaning.
+ uint32_t max_external_data_size;
+
+ // Contains tags that describe impact on a user's privacy or security.
+ RiskTag risk_tags[kMaxRiskTagCount];
+};
+
+// A typedef for functions that match the signature of
+// GetChromePolicyDetails(). This can be used to inject that
+// function into objects, so that it can be easily mocked for
+// tests.
+using GetChromePolicyDetailsCallback =
+ base::RepeatingCallback<const PolicyDetails*(const std::string&)>;
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_DETAILS_H_
diff --git a/chromium/components/policy/core/common/policy_load_status.cc b/chromium/components/policy/core/common/policy_load_status.cc
new file mode 100644
index 00000000000..43087f96acf
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_load_status.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_load_status.h"
+
+#include "base/bind.h"
+#include "base/metrics/histogram.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+namespace {
+
+const char kHistogramName[] = "Enterprise.PolicyLoadStatus";
+
+} // namespace
+
+PolicyLoadStatusSampler::PolicyLoadStatusSampler() {
+ Add(POLICY_LOAD_STATUS_STARTED);
+}
+
+PolicyLoadStatusSampler::~PolicyLoadStatusSampler() {}
+
+void PolicyLoadStatusSampler::Add(PolicyLoadStatus status) {
+ status_bits_[status] = true;
+}
+
+PolicyLoadStatusUmaReporter::PolicyLoadStatusUmaReporter() {}
+
+PolicyLoadStatusUmaReporter::~PolicyLoadStatusUmaReporter() {
+ base::HistogramBase* histogram(base::LinearHistogram::FactoryGet(
+ kHistogramName, 1, POLICY_LOAD_STATUS_SIZE, POLICY_LOAD_STATUS_SIZE + 1,
+ base::Histogram::kUmaTargetedHistogramFlag));
+
+ for (int i = 0; i < POLICY_LOAD_STATUS_SIZE; ++i) {
+ if (GetStatusSet()[i])
+ histogram->Add(i);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_load_status.h b/chromium/components/policy/core/common/policy_load_status.h
new file mode 100644
index 00000000000..2cf8abc47b6
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_load_status.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
+
+#include <bitset>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// UMA histogram enum for policy load status. Don't change existing constants,
+// append additional constants to the end if needed.
+enum PolicyLoadStatus {
+ // Policy load attempt started. This gets logged for each policy load attempt
+ // to get a baseline on the number of requests, and an arbitrary number of
+ // the below status codes may get added in addition.
+ POLICY_LOAD_STATUS_STARTED = 0,
+ // System failed to determine whether there's policy.
+ POLICY_LOAD_STATUS_QUERY_FAILED = 1,
+ // No policy present.
+ POLICY_LOAD_STATUS_NO_POLICY = 2,
+ // Data inaccessible, such as non-local policy file.
+ POLICY_LOAD_STATUS_INACCCESSIBLE = 3,
+ // Data missing, such as policy file not present.
+ POLICY_LOAD_STATUS_MISSING = 4,
+ // Trying with Wow64 redirection disabled.
+ POLICY_LOAD_STATUS_WOW64_REDIRECTION_DISABLED = 5,
+ // Data read error, for example file reading errors.
+ POLICY_LOAD_STATUS_READ_ERROR = 6,
+ // Data too large to process.
+ POLICY_LOAD_STATUS_TOO_BIG = 7,
+ // Parse error.
+ POLICY_LOAD_STATUS_PARSE_ERROR = 8,
+
+ // This must stay last.
+ POLICY_LOAD_STATUS_SIZE
+};
+
+// A helper for collecting statuses for a policy load operation.
+class POLICY_EXPORT PolicyLoadStatusSampler {
+ public:
+ using StatusSet = std::bitset<POLICY_LOAD_STATUS_SIZE>;
+
+ PolicyLoadStatusSampler();
+ PolicyLoadStatusSampler(const PolicyLoadStatusSampler&) = delete;
+ PolicyLoadStatusSampler& operator=(const PolicyLoadStatusSampler&) = delete;
+ virtual ~PolicyLoadStatusSampler();
+
+ // Adds a status code.
+ void Add(PolicyLoadStatus status);
+
+ // Returns a set with all statuses.
+ const StatusSet& GetStatusSet() const { return status_bits_; }
+
+ private:
+ StatusSet status_bits_;
+};
+
+// A helper for generating policy load status UMA statistics. On destruction,
+// records histogram samples for the collected status codes.
+class POLICY_EXPORT PolicyLoadStatusUmaReporter
+ : public PolicyLoadStatusSampler {
+ public:
+ PolicyLoadStatusUmaReporter();
+ PolicyLoadStatusUmaReporter(const PolicyLoadStatusUmaReporter&) = delete;
+ PolicyLoadStatusUmaReporter& operator=(const PolicyLoadStatusUmaReporter&) =
+ delete;
+ ~PolicyLoadStatusUmaReporter() override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOAD_STATUS_H_
diff --git a/chromium/components/policy/core/common/policy_loader_command_line.cc b/chromium/components/policy/core/common/policy_loader_command_line.cc
new file mode 100644
index 00000000000..b0912cf86a0
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_command_line.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_command_line.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/core/common/policy_types.h"
+
+namespace policy {
+
+PolicyLoaderCommandLine::PolicyLoaderCommandLine(
+ const base::CommandLine& command_line)
+ : command_line_(command_line) {}
+PolicyLoaderCommandLine::~PolicyLoaderCommandLine() = default;
+
+std::unique_ptr<PolicyBundle> PolicyLoaderCommandLine::Load() {
+ std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+ if (!command_line_.HasSwitch(switches::kChromePolicy))
+ return bundle;
+
+ base::JSONReader::ValueWithError policies =
+ base::JSONReader::ReadAndReturnValueWithError(
+ command_line_.GetSwitchValueASCII(switches::kChromePolicy),
+ base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+
+ if (!policies.value) {
+ VLOG(1) << "Command line policy error: " << policies.error_message;
+ return bundle;
+ }
+ if (!policies.value->is_dict()) {
+ VLOG(1) << "Command line policy is not a dictionary";
+ return bundle;
+ }
+
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .LoadFrom(&base::Value::AsDictionaryValue(*policies.value),
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_COMMAND_LINE);
+ return bundle;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_command_line.h b/chromium/components/policy/core/common/policy_loader_command_line.h
new file mode 100644
index 00000000000..14b84615aa0
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_command_line.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMAND_LINE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMAND_LINE_H_
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyBundle;
+
+// Loads policy value from command line switch for development and testing
+// purposes. It can be only used with strict limitation. For example, on
+// Android, the device must be rooted.
+class POLICY_EXPORT PolicyLoaderCommandLine {
+ public:
+ explicit PolicyLoaderCommandLine(const base::CommandLine& command_line);
+ PolicyLoaderCommandLine(const PolicyLoaderCommandLine&) = delete;
+ PolicyLoaderCommandLine& operator=(const PolicyLoaderCommandLine&) = delete;
+
+ ~PolicyLoaderCommandLine();
+
+ std::unique_ptr<PolicyBundle> Load();
+
+ private:
+ const base::CommandLine& command_line_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMAND_LINE_H_
diff --git a/chromium/components/policy/core/common/policy_loader_command_line_unittest.cc b/chromium/components/policy/core/common/policy_loader_command_line_unittest.cc
new file mode 100644
index 00000000000..281fac85595
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_command_line_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_command_line.h"
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_switches.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class PolicyLoaderCommandLineTest : public ::testing::Test {
+ public:
+ void SetCommandLinePolicy(const std::string& policies) {
+ command_line_.AppendSwitchASCII(switches::kChromePolicy, policies);
+ }
+
+ void LoadAndVerifyPolicies(PolicyLoaderCommandLine* loader,
+ const base::Value& expected_policies) {
+ DCHECK(expected_policies.is_dict());
+ std::unique_ptr<PolicyBundle> bundle = loader->Load();
+ PolicyMap& map =
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_EQ(expected_policies.DictSize(), map.size());
+ for (auto expected_policy : expected_policies.DictItems()) {
+ const PolicyMap::Entry* actual_policy = map.Get(expected_policy.first);
+ ASSERT_TRUE(actual_policy);
+ EXPECT_EQ(POLICY_LEVEL_MANDATORY, actual_policy->level);
+ EXPECT_EQ(POLICY_SCOPE_MACHINE, actual_policy->scope);
+ EXPECT_EQ(POLICY_SOURCE_COMMAND_LINE, actual_policy->source);
+
+ ASSERT_TRUE(actual_policy->value_unsafe());
+ EXPECT_EQ(expected_policy.second, *(actual_policy->value_unsafe()));
+ }
+ }
+
+ std::unique_ptr<PolicyLoaderCommandLine> CreatePolicyLoader() {
+ return std::make_unique<PolicyLoaderCommandLine>(command_line_);
+ }
+
+ private:
+ base::CommandLine command_line_{base::CommandLine::NO_PROGRAM};
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(PolicyLoaderCommandLineTest, NoSwitch) {
+ LoadAndVerifyPolicies(CreatePolicyLoader().get(),
+ base::Value(base::Value::Type::DICTIONARY));
+}
+
+TEST_F(PolicyLoaderCommandLineTest, InvalidSwitch) {
+ SetCommandLinePolicy("a");
+ LoadAndVerifyPolicies(CreatePolicyLoader().get(),
+ base::Value(base::Value::Type::DICTIONARY));
+
+ SetCommandLinePolicy("a:b");
+ LoadAndVerifyPolicies(CreatePolicyLoader().get(),
+ base::Value(base::Value::Type::DICTIONARY));
+
+ SetCommandLinePolicy("[a]");
+ LoadAndVerifyPolicies(CreatePolicyLoader().get(),
+ base::Value(base::Value::Type::DICTIONARY));
+}
+
+TEST_F(PolicyLoaderCommandLineTest, ParseSwitchValue) {
+ SetCommandLinePolicy(R"({
+ "int_policy": 42,
+ "string_policy": "string",
+ "bool_policy": true,
+ "list_policy": [1,2],
+ "dict_policy": {"k1":1, "k2": {"k3":true}}
+ })");
+ base::Value policies(base::Value::Type::DICTIONARY);
+ policies.SetIntKey("int_policy", 42);
+ policies.SetStringKey("string_policy", "string");
+ policies.SetBoolKey("bool_policy", true);
+
+ // list policy
+ base::Value::ListStorage list_storage;
+ list_storage.emplace_back(1);
+ list_storage.emplace_back(2);
+ policies.SetKey("list_policy", base::Value(list_storage));
+
+ // dict policy
+ policies.SetIntPath({"dict_policy.k1"}, 1);
+ policies.SetBoolPath({"dict_policy.k2.k3"}, true);
+
+ LoadAndVerifyPolicies(CreatePolicyLoader().get(), policies);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_common.cc b/chromium/components/policy/core/common/policy_loader_common.cc
new file mode 100644
index 00000000000..5d64293bfd6
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_common.cc
@@ -0,0 +1,184 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_common.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace {
+
+// Duplicate the extension constants in order to avoid extension dependency.
+// However, those values below must be synced with files in extension folders.
+// In long term, we can refactor the code and create an interface for sensitive
+// policy filtering so that each policy component users can have their own
+// implementation. And the Chrome one can be moved to c/b/policy.
+// From extensions/common/extension_urls.cc
+const char kChromeWebstoreUpdateURL[] =
+ "https://clients2.google.com/service/update2/crx";
+const char16_t kChromeWebstoreUpdateURL16[] =
+ u"https://clients2.google.com/service/update2/crx";
+
+// From chrome/browser/extensions/extension_management_constants.cc
+const char kWildcard[] = "*";
+const char kInstallationMode[] = "installation_mode";
+const char kForceInstalled[] = "force_installed";
+const char kNormalInstalled[] = "normal_installed";
+const char kUpdateUrl[] = "update_url";
+
+// String to be prepended to each blocked entry.
+const char kBlockedExtensionPrefix[] = "[BLOCKED]";
+
+// List of policies that are considered only if the user is part of a AD domain
+// on Windows or managed on the Mac. Please document any new additions in
+// policy_templates.json!
+// Please keep the list in alphabetical order!
+const char* kSensitivePolicies[] = {
+ key::kAutoOpenFileTypes,
+ key::kChromeCleanupEnabled,
+ key::kChromeCleanupReportingEnabled,
+ key::kCommandLineFlagSecurityWarningsEnabled,
+ key::kDefaultSearchProviderEnabled,
+ key::kHomepageIsNewTabPage,
+ key::kHomepageLocation,
+ key::kMetricsReportingEnabled,
+ key::kNewTabPageLocation,
+ key::kPasswordProtectionChangePasswordURL,
+ key::kPasswordProtectionLoginURLs,
+ key::kRestoreOnStartup,
+ key::kRestoreOnStartupURLs,
+ key::kSafeBrowsingForTrustedSourcesEnabled,
+ key::kSafeBrowsingEnabled,
+ key::kSafeBrowsingAllowlistDomains,
+};
+
+void RecordInvalidPolicies(const std::string& policy_name) {
+ const PolicyDetails* details = GetChromePolicyDetails(policy_name);
+ base::UmaHistogramSparse("EnterpriseCheck.InvalidPolicies", details->id);
+}
+
+// Marks the sensitive ExtensionInstallForceList policy entries, returns true if
+// there is any sensitive entries in the policy.
+bool FilterSensitiveExtensionsInstallForcelist(PolicyMap::Entry* map_entry) {
+ bool has_invalid_policies = false;
+ if (!map_entry)
+ return false;
+
+ base::Value* policy_list_value = map_entry->value(base::Value::Type::LIST);
+ if (!policy_list_value)
+ return false;
+
+ // Using index for loop to update the list in place.
+ for (size_t i = 0; i < policy_list_value->GetListDeprecated().size(); i++) {
+ const auto& list_entry = policy_list_value->GetListDeprecated()[i];
+ if (!list_entry.is_string())
+ continue;
+
+ const std::string& entry = list_entry.GetString();
+ size_t pos = entry.find(';');
+ if (pos == std::string::npos)
+ continue;
+
+ // Only allow custom update urls in enterprise environments.
+ if (!base::LowerCaseEqualsASCII(entry.substr(pos + 1),
+ kChromeWebstoreUpdateURL)) {
+ policy_list_value->GetListDeprecated()[i] =
+ base::Value(kBlockedExtensionPrefix + entry);
+ has_invalid_policies = true;
+ }
+ }
+
+ if (has_invalid_policies) {
+ map_entry->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_OFF_CWS_URL_ERROR,
+ {kChromeWebstoreUpdateURL16});
+ RecordInvalidPolicies(key::kExtensionInstallForcelist);
+ }
+
+ return has_invalid_policies;
+}
+
+// Marks the sensitive ExtensionSettings policy entries, returns the number of
+// sensitive entries in the policy.
+bool FilterSensitiveExtensionSettings(PolicyMap::Entry* map_entry) {
+ if (!map_entry)
+ return false;
+ base::Value* policy_dict_value = map_entry->value(base::Value::Type::DICT);
+ if (!policy_dict_value) {
+ return false;
+ }
+
+ // Note that we only search for sensitive entries, all other validations will
+ // be handled by ExtensionSettingsPolicyHandler.
+ std::vector<std::string> filtered_extensions;
+ for (auto entry : policy_dict_value->DictItems()) {
+ if (entry.first == kWildcard)
+ continue;
+ if (!entry.second.is_dict())
+ continue;
+ std::string* installation_mode =
+ entry.second.FindStringKey(kInstallationMode);
+ if (!installation_mode || (*installation_mode != kForceInstalled &&
+ *installation_mode != kNormalInstalled)) {
+ continue;
+ }
+ std::string* update_url = entry.second.FindStringKey(kUpdateUrl);
+ if (!update_url ||
+ base::LowerCaseEqualsASCII(*update_url, kChromeWebstoreUpdateURL)) {
+ continue;
+ }
+
+ filtered_extensions.push_back(entry.first);
+ }
+
+ // Marking the blocked extension by adding the "[BLOCKED]" prefix. This is an
+ // invalid extension id and will be removed by PolicyHandler later.
+ if (!filtered_extensions.empty()) {
+ for (const auto& extension : filtered_extensions) {
+ auto setting = policy_dict_value->ExtractKey(extension);
+ if (!setting)
+ continue;
+ policy_dict_value->SetKey(kBlockedExtensionPrefix + extension,
+ std::move(setting.value()));
+ }
+ map_entry->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_OFF_CWS_URL_ERROR,
+ {kChromeWebstoreUpdateURL16});
+ RecordInvalidPolicies(key::kExtensionSettings);
+ }
+
+ return !filtered_extensions.empty();
+}
+
+} // namespace
+
+void FilterSensitivePolicies(PolicyMap* policy) {
+ int invalid_policies = 0;
+ if (FilterSensitiveExtensionsInstallForcelist(
+ policy->GetMutable(key::kExtensionInstallForcelist))) {
+ invalid_policies++;
+ }
+ if (FilterSensitiveExtensionSettings(
+ policy->GetMutable(key::kExtensionSettings))) {
+ invalid_policies++;
+ }
+ for (const char* sensitive_policy : kSensitivePolicies) {
+ if (policy->Get(sensitive_policy)) {
+ policy->GetMutable(sensitive_policy)->SetBlocked();
+ invalid_policies++;
+ RecordInvalidPolicies(sensitive_policy);
+ }
+ }
+
+ UMA_HISTOGRAM_COUNTS_1M("EnterpriseCheck.InvalidPoliciesDetected",
+ invalid_policies);
+} // namespace policy
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_common.h b/chromium/components/policy/core/common/policy_loader_common.h
new file mode 100644
index 00000000000..8e84bf89d99
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_common.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMON_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMON_H_
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyMap;
+
+// Blocks sensitive policies from having an effect in the specified source.
+// Modifies the |policy| in place. Only call this if the source is untrusted.
+POLICY_EXPORT void FilterSensitivePolicies(PolicyMap* policy);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_COMMON_H_
diff --git a/chromium/components/policy/core/common/policy_loader_common_unittest.cc b/chromium/components/policy/core/common/policy_loader_common_unittest.cc
new file mode 100644
index 00000000000..92c3ecd7bb6
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_common_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_common.h"
+#include <algorithm>
+
+#include "base/debug/activity_tracker.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace {
+base::Value ToListValue(const std::vector<std::string>& values) {
+ base::Value::ListStorage storage(values.size());
+ std::transform(values.begin(), values.end(), storage.begin(),
+ [](const auto& value) { return base::Value(value); });
+
+ return base::Value(storage);
+}
+
+base::Value ToDictValue(const std::string& json) {
+ absl::optional<base::Value> value = base::JSONReader::Read(json);
+ return std::move(value.value());
+}
+
+} // namespace
+
+class SensitivePolicyFilterTest : public ::testing::Test {
+ public:
+ void AddNewPolicy(const std::string& name, const base::Value& value) {
+ policies_.Set(name, PolicyLevel::POLICY_LEVEL_MANDATORY,
+ PolicyScope::POLICY_SCOPE_MACHINE,
+ PolicySource::POLICY_SOURCE_PLATFORM, value.Clone(), nullptr);
+ }
+
+ PolicyMap* policies() { return &policies_; }
+
+ private:
+ PolicyMap policies_;
+};
+
+TEST_F(SensitivePolicyFilterTest, TestSimplePolicyFilter) {
+ AddNewPolicy(key::kHomepageLocation, base::Value("https://example.com"));
+ AddNewPolicy(key::kBrowserSignin, base::Value(2));
+
+ EXPECT_TRUE(policies()->Get(key::kHomepageLocation));
+ EXPECT_TRUE(policies()->Get(key::kBrowserSignin));
+
+ FilterSensitivePolicies(policies());
+
+ EXPECT_FALSE(policies()->Get(key::kHomepageLocation));
+ EXPECT_TRUE(policies()->Get(key::kBrowserSignin));
+}
+
+TEST_F(SensitivePolicyFilterTest, TestExtensionInstallForceListFilter) {
+ base::Value policy = ToListValue(
+ {"extension0", "extension1;example.com", "extension2;",
+ "extension3;https://clients2.google.com/service/update2/crx"});
+ AddNewPolicy(key::kExtensionInstallForcelist, policy);
+
+ EXPECT_TRUE(policies()->Get(key::kExtensionInstallForcelist));
+
+ FilterSensitivePolicies(policies());
+
+ const auto* actual_filtered_policy = policies()->GetValue(
+ key::kExtensionInstallForcelist, base::Value::Type::LIST);
+ ASSERT_TRUE(actual_filtered_policy);
+ base::Value expected_filtered_policy = ToListValue(
+ {"extension0", "[BLOCKED]extension1;example.com", "[BLOCKED]extension2;",
+ "extension3;https://clients2.google.com/service/update2/crx"});
+ EXPECT_EQ(expected_filtered_policy, *actual_filtered_policy);
+}
+
+TEST_F(SensitivePolicyFilterTest, TestExtensionSettingsFilter) {
+ base::Value policy = ToDictValue(R"({
+ "*": {
+ "installation_mode": "force_installed",
+ "update_url": "https://example.com"
+ },
+ "extension0": {
+ "installation_mode": "blocked",
+ "update_url": "https://example.com"
+ },
+ "extension1": {
+ "update_url": "https://example.com"
+ },
+ "extension2": {
+ "installation_mode": "force_installed"
+ },
+ "extension3": {
+ "installation_mode": "force_installed",
+ "update_url": "https://clients2.google.com/service/update2/crx"
+ },
+ "extension4": {
+ "installation_mode": "force_installed",
+ "update_url": "https://example.com"
+ },
+ "extension5": {
+ "installation_mode": "normal_installed",
+ "update_url": "https://example.com"
+ },
+ "invalid": "settings"
+ })");
+ AddNewPolicy(key::kExtensionSettings, policy);
+
+ EXPECT_TRUE(policies()->Get(key::kExtensionSettings));
+
+ FilterSensitivePolicies(policies());
+
+ const auto* filtered_policy =
+ policies()->GetValue(key::kExtensionSettings, base::Value::Type::DICT);
+ ASSERT_TRUE(filtered_policy);
+ EXPECT_EQ(policy.DictSize(), filtered_policy->DictSize());
+ for (const auto entry : policy.DictItems()) {
+ std::string extension = entry.first;
+ if (extension == "extension4" || extension == "extension5")
+ extension = "[BLOCKED]" + extension;
+
+ const base::Value* filtered_setting = filtered_policy->FindKey(extension);
+ ASSERT_TRUE(filtered_setting);
+ EXPECT_EQ(entry.second, *filtered_setting)
+ << "Mismatch for extension: " + extension;
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_ios.h b/chromium/components/policy/core/common/policy_loader_ios.h
new file mode 100644
index 00000000000..6ecbe92dd52
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_ios.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class SchemaRegistry;
+
+// A policy loader that loads policy from the managed app configuration
+// introduced in iOS 7.
+class POLICY_EXPORT PolicyLoaderIOS : public AsyncPolicyLoader {
+ public:
+ explicit PolicyLoaderIOS(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+ PolicyLoaderIOS(const PolicyLoaderIOS&) = delete;
+ PolicyLoaderIOS& operator=(const PolicyLoaderIOS&) = delete;
+ ~PolicyLoaderIOS() override;
+
+ // AsyncPolicyLoader implementation.
+ void InitOnBackgroundThread() override;
+ std::unique_ptr<PolicyBundle> Load() override;
+ base::Time LastModificationTime() override;
+
+ private:
+ void UserDefaultsChanged();
+
+ // Loads the Chrome policies in |dictionary| into the given |bundle|.
+ void LoadNSDictionaryToPolicyBundle(NSDictionary* dictionary,
+ PolicyBundle* bundle);
+
+ // Validates the given policy data against the stored |schema_|, converting
+ // data to the expected type if necessary. The returned value is suitable for
+ // adding to a PolicyMap.
+ base::Value ConvertPolicyDataIfNecessary(const std::string& key,
+ const base::Value& value);
+
+ // The schema used by |ValidatePolicyData()|.
+ const Schema* policy_schema_;
+
+ // Used to manage the registration for NSNotificationCenter notifications.
+ __strong id notification_observer_;
+
+ // Timestamp of the last notification.
+ // Used to coalesce repeated notifications into a single Load() call.
+ base::Time last_notification_time_;
+
+ // Used to Bind() a WeakPtr to |this| for the callback passed to the
+ // |notification_observer_|.
+ base::WeakPtrFactory<PolicyLoaderIOS> weak_factory_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_H_
diff --git a/chromium/components/policy/core/common/policy_loader_ios.mm b/chromium/components/policy/core/common/policy_loader_ios.mm
new file mode 100644
index 00000000000..df43149d399
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_ios.mm
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_ios.h"
+
+#import <Foundation/Foundation.h>
+#include <stddef.h>
+#import <UIKit/UIKit.h>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/location.h"
+#import "base/mac/foundation_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/policy/core/common/mac_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#import "components/policy/core/common/policy_loader_ios_constants.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "components/policy/policy_constants.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// This policy loader loads a managed app configuration from the NSUserDefaults.
+// For example code from Apple see:
+// https://developer.apple.com/library/ios/samplecode/sc2279/Introduction/Intro.html
+// For an introduction to the API see session 301 from WWDC 2013,
+// "Extending Your Apps for Enterprise and Education Use":
+// https://developer.apple.com/videos/wwdc/2013/?id=301
+
+// Helper that observes notifications for NSUserDefaults and triggers an update
+// at the loader on the right thread.
+@interface PolicyNotificationObserver : NSObject {
+ base::RepeatingClosure _callback;
+ scoped_refptr<base::SequencedTaskRunner> _taskRunner;
+}
+
+// Designated initializer. |callback| will be posted to |taskRunner| whenever
+// the NSUserDefaults change.
+- (id)initWithCallback:(const base::RepeatingClosure&)callback
+ taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner;
+
+// Invoked when the NSUserDefaults change.
+- (void)userDefaultsChanged:(NSNotification*)notification;
+
+- (void)dealloc;
+
+@end
+
+@implementation PolicyNotificationObserver
+
+- (id)initWithCallback:(const base::RepeatingClosure&)callback
+ taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner {
+ if ((self = [super init])) {
+ _callback = callback;
+ _taskRunner = taskRunner;
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(userDefaultsChanged:)
+ name:NSUserDefaultsDidChangeNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)userDefaultsChanged:(NSNotification*)notification {
+ // This may be invoked on any thread. Post the |callback_| to the loader's
+ // |taskRunner_| to make sure it Reloads() on the right thread.
+ _taskRunner->PostTask(FROM_HERE, _callback);
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+@end
+
+namespace policy {
+
+PolicyLoaderIOS::PolicyLoaderIOS(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/true),
+ weak_factory_(this) {
+ PolicyNamespace ns(POLICY_DOMAIN_CHROME, std::string());
+ policy_schema_ = registry->schema_map()->GetSchema(ns);
+}
+
+PolicyLoaderIOS::~PolicyLoaderIOS() {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+}
+
+void PolicyLoaderIOS::InitOnBackgroundThread() {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+ base::RepeatingClosure callback = base::BindRepeating(
+ &PolicyLoaderIOS::UserDefaultsChanged, weak_factory_.GetWeakPtr());
+ notification_observer_ =
+ [[PolicyNotificationObserver alloc] initWithCallback:callback
+ taskRunner:task_runner()];
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderIOS::Load() {
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ NSDictionary* configuration = [[NSUserDefaults standardUserDefaults]
+ dictionaryForKey:kPolicyLoaderIOSConfigurationKey];
+ LoadNSDictionaryToPolicyBundle(configuration, bundle.get());
+
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, std::string());
+ size_t count = bundle->Get(chrome_ns).size();
+ UMA_HISTOGRAM_COUNTS_100("Enterprise.IOSPolicies", count);
+
+ return bundle;
+}
+
+base::Time PolicyLoaderIOS::LastModificationTime() {
+ return last_notification_time_;
+}
+
+void PolicyLoaderIOS::UserDefaultsChanged() {
+ // The base class coalesces multiple Reload() calls into a single Load() if
+ // the LastModificationTime() has a small delta between Reload() calls.
+ // This coalesces the multiple notifications sent during startup into a single
+ // Load() call.
+ last_notification_time_ = base::Time::Now();
+ Reload(false);
+}
+
+void PolicyLoaderIOS::LoadNSDictionaryToPolicyBundle(NSDictionary* dictionary,
+ PolicyBundle* bundle) {
+ // NSDictionary is toll-free bridged to CFDictionaryRef, which is a
+ // CFPropertyListRef.
+ std::unique_ptr<base::Value> value =
+ PropertyToValue((__bridge CFPropertyListRef)(dictionary));
+ base::DictionaryValue* dict = NULL;
+ if (value && value->GetAsDictionary(&dict)) {
+ PolicyMap& map = bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+ for (const auto it : dict->DictItems()) {
+ map.Set(it.first, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ ConvertPolicyDataIfNecessary(it.first, it.second), nullptr);
+ }
+ }
+}
+
+base::Value PolicyLoaderIOS::ConvertPolicyDataIfNecessary(
+ const std::string& key,
+ const base::Value& value) {
+ const Schema schema = policy_schema_->GetKnownProperty(key);
+
+ if (!schema.valid()) {
+ return value.Clone();
+ }
+
+ // Handle the case of a JSON-encoded string for a dict policy.
+ if (schema.type() == base::Value::Type::DICTIONARY && value.is_string()) {
+ absl::optional<base::Value> decoded_value = base::JSONReader::Read(
+ value.GetString(), base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ if (decoded_value.has_value()) {
+ return std::move(decoded_value.value());
+ }
+ }
+
+ // Otherwise return an unchanged value.
+ return value.Clone();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_ios_constants.h b/chromium/components/policy/core/common/policy_loader_ios_constants.h
new file mode 100644
index 00000000000..d044e6a9d0e
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_ios_constants.h
@@ -0,0 +1,13 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_CONSTANTS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_CONSTANTS_H_
+
+#import <Foundation/Foundation.h>
+
+// Key in NSUserDefaults that contains the managed app configuration.
+extern NSString* const kPolicyLoaderIOSConfigurationKey;
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_IOS_CONSTANTS_H_
diff --git a/chromium/components/policy/core/common/policy_loader_ios_constants.mm b/chromium/components/policy/core/common/policy_loader_ios_constants.mm
new file mode 100644
index 00000000000..85d72fccc61
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_ios_constants.mm
@@ -0,0 +1,8 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "components/policy/core/common/policy_loader_ios_constants.h"
+
+NSString* const kPolicyLoaderIOSConfigurationKey =
+ @"com.apple.configuration.managed";
diff --git a/chromium/components/policy/core/common/policy_loader_ios_unittest.mm b/chromium/components/policy/core/common/policy_loader_ios_unittest.mm
new file mode 100644
index 00000000000..d5add46cda4
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_ios_unittest.mm
@@ -0,0 +1,192 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_ios.h"
+
+#import <UIKit/UIKit.h>
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#import "components/policy/core/common/policy_loader_ios_constants.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace policy {
+
+namespace {
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ TestHarness(bool encode_complex_data_as_json);
+ TestHarness(const TestHarness&) = delete;
+ TestHarness& operator=(const TestHarness&) = delete;
+ ~TestHarness() override;
+
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) override;
+
+ static PolicyProviderTestHarness* Create();
+ static PolicyProviderTestHarness* CreateWithJSONEncoding();
+
+ private:
+ // Merges the policies in |policy| into the current policy dictionary
+ // in NSUserDefaults, after making sure that the policy dictionary
+ // exists.
+ void AddPolicies(NSDictionary* policy);
+
+ // If true, the test harness will encode complex data (dicts and lists) as
+ // JSON strings.
+ bool encode_complex_data_as_json_;
+};
+
+TestHarness::TestHarness(bool encode_complex_data_as_json)
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM),
+ encode_complex_data_as_json_(encode_complex_data_as_json) {}
+
+TestHarness::~TestHarness() {
+ // Cleanup any policies left from the test.
+ [[NSUserDefaults standardUserDefaults]
+ removeObjectForKey:kPolicyLoaderIOSConfigurationKey];
+}
+
+void TestHarness::SetUp() {
+ // Make sure there is no pre-existing policy present.
+ [[NSUserDefaults standardUserDefaults]
+ removeObjectForKey:kPolicyLoaderIOSConfigurationKey];
+}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ return new AsyncPolicyProvider(
+ registry, std::make_unique<PolicyLoaderIOS>(registry, task_runner));
+}
+
+void TestHarness::InstallEmptyPolicy() {
+ AddPolicies(@{});
+}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ NSString* key = base::SysUTF8ToNSString(policy_name);
+ NSString* value = base::SysUTF8ToNSString(policy_value);
+ AddPolicies(@{
+ key: value
+ });
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ NSString* key = base::SysUTF8ToNSString(policy_name);
+ AddPolicies(@{
+ key: [NSNumber numberWithInt:policy_value]
+ });
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ NSString* key = base::SysUTF8ToNSString(policy_name);
+ AddPolicies(@{
+ key: [NSNumber numberWithBool:policy_value]
+ });
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ NSString* key = base::SysUTF8ToNSString(policy_name);
+ base::ScopedCFTypeRef<CFPropertyListRef> value(
+ ValueToProperty(*policy_value));
+ AddPolicies(@{key : (__bridge NSArray*)(value.get())});
+}
+
+void TestHarness::InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) {
+ NSString* key = base::SysUTF8ToNSString(policy_name);
+
+ if (encode_complex_data_as_json_) {
+ // Convert |policy_value| to a JSON-encoded string.
+ std::string json_string;
+ JSONStringValueSerializer serializer(&json_string);
+ ASSERT_TRUE(serializer.Serialize(*policy_value));
+
+ AddPolicies(@{key : base::SysUTF8ToNSString(json_string)});
+ } else {
+ base::ScopedCFTypeRef<CFPropertyListRef> value(
+ ValueToProperty(*policy_value));
+ AddPolicies(@{key : (__bridge NSDictionary*)(value.get())});
+ }
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+ return new TestHarness(false);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::CreateWithJSONEncoding() {
+ return new TestHarness(true);
+}
+
+void TestHarness::AddPolicies(NSDictionary* policy) {
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+ NSMutableDictionary* chromePolicy = [[NSMutableDictionary alloc] init];
+
+ NSDictionary* previous =
+ [defaults dictionaryForKey:kPolicyLoaderIOSConfigurationKey];
+ if (previous) {
+ [chromePolicy addEntriesFromDictionary:previous];
+ }
+
+ [chromePolicy addEntriesFromDictionary:policy];
+ [[NSUserDefaults standardUserDefaults]
+ setObject:chromePolicy
+ forKey:kPolicyLoaderIOSConfigurationKey];
+}
+
+} // namespace
+
+INSTANTIATE_TEST_SUITE_P(PolicyProviderIOSChromePolicyTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+INSTANTIATE_TEST_SUITE_P(PolicyProviderIOSChromePolicyJSONTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::CreateWithJSONEncoding));
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_lacros.cc b/chromium/components/policy/core/common/policy_loader_lacros.cc
new file mode 100644
index 00000000000..65a40e90dc6
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_lacros.cc
@@ -0,0 +1,221 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_lacros.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "components/policy/core/common/cloud/affiliation.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_proto_decoders.h"
+#include "components/policy/policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace {
+
+// Remembers if the main user is managed or not.
+// Note: This is a pessimistic default (no policies read - false) and
+// once the profile is loaded, the value is set and will never change in
+// production. The value changes in tests whenever policy data gets overridden.
+bool g_is_main_user_managed_ = false;
+
+enterprise_management::PolicyData* MainUserPolicyDataStorage() {
+ static enterprise_management::PolicyData policy_data;
+ return &policy_data;
+}
+
+bool IsManaged(const enterprise_management::PolicyData& policy_data) {
+ return policy_data.state() == enterprise_management::PolicyData::ACTIVE;
+}
+
+// Returns whether a primary device account for this session is child.
+bool IsChildSession() {
+ const crosapi::mojom::BrowserInitParams* init_params =
+ chromeos::LacrosService::Get()->init_params();
+ if (!init_params) {
+ return false;
+ }
+ return init_params->session_type ==
+ crosapi::mojom::SessionType::kChildSession;
+}
+
+} // namespace
+
+namespace policy {
+
+PolicyLoaderLacros::PolicyLoaderLacros(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ PolicyPerProfileFilter per_profile)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/false),
+ per_profile_(per_profile) {
+ auto* lacros_service = chromeos::LacrosService::Get();
+ const crosapi::mojom::BrowserInitParams* init_params =
+ lacros_service->init_params();
+ if (!init_params) {
+ LOG(ERROR) << "No init params";
+ return;
+ }
+ if (!init_params->device_account_policy) {
+ LOG(ERROR) << "No policy data";
+ return;
+ }
+ policy_fetch_response_ = init_params->device_account_policy.value();
+}
+
+PolicyLoaderLacros::~PolicyLoaderLacros() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* lacros_service = chromeos::LacrosService::Get();
+ if (lacros_service) {
+ lacros_service->RemoveObserver(this);
+ }
+}
+
+void PolicyLoaderLacros::InitOnBackgroundThread() {
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ // We add this as observer on background thread to avoid a situation when
+ // notification comes after the object is destroyed, but not removed from the
+ // list yet.
+ // TODO(crbug.com/1114069): Set up LacrosService in tests.
+ auto* lacros_service = chromeos::LacrosService::Get();
+ if (lacros_service) {
+ lacros_service->AddObserver(this);
+ }
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderLacros::Load() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+
+ if (!policy_fetch_response_ || policy_fetch_response_->empty()) {
+ return bundle;
+ }
+
+ auto policy = std::make_unique<enterprise_management::PolicyFetchResponse>();
+ if (!policy->ParseFromArray(policy_fetch_response_.value().data(),
+ policy_fetch_response_->size())) {
+ LOG(ERROR) << "Failed to parse policy data";
+ return bundle;
+ }
+ UserCloudPolicyValidator validator(std::move(policy),
+ /*background_task_runner=*/nullptr);
+ validator.ValidatePayload();
+ validator.RunValidation();
+
+ PolicyMap policy_map;
+ base::WeakPtr<CloudExternalDataManager> external_data_manager;
+ DecodeProtoFields(*(validator.payload()), external_data_manager,
+ PolicySource::POLICY_SOURCE_CLOUD_FROM_ASH,
+ PolicyScope::POLICY_SCOPE_USER, &policy_map, per_profile_);
+
+ // We do not set enterprise defaults for child accounts, because they are
+ // consumer users. The same rule is applied to policy in Ash. See
+ // UserCloudPolicyManagerAsh.
+ if (!IsChildSession()) {
+ switch (per_profile_) {
+ case PolicyPerProfileFilter::kTrue:
+ SetEnterpriseUsersProfileDefaults(&policy_map);
+ break;
+ case PolicyPerProfileFilter::kFalse:
+ SetEnterpriseUsersSystemWideDefaults(&policy_map);
+ break;
+ case PolicyPerProfileFilter::kAny:
+ NOTREACHED();
+ }
+ }
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .MergeFrom(policy_map);
+
+ // Remember if the policy is managed or not.
+ g_is_main_user_managed_ = IsManaged(*validator.policy_data());
+ if (g_is_main_user_managed_ &&
+ per_profile_ == PolicyPerProfileFilter::kFalse) {
+ *MainUserPolicyDataStorage() = *validator.policy_data();
+ }
+ policy_data_ = std::move(validator.policy_data());
+
+ return bundle;
+}
+
+void PolicyLoaderLacros::OnPolicyUpdated(
+ const std::vector<uint8_t>& policy_fetch_response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ policy_fetch_response_ = policy_fetch_response;
+ Reload(true);
+}
+
+enterprise_management::PolicyData* PolicyLoaderLacros::GetPolicyData() {
+ if (!policy_fetch_response_ || !policy_data_)
+ return nullptr;
+
+ return policy_data_.get();
+}
+
+// static
+bool PolicyLoaderLacros::IsDeviceLocalAccountUser() {
+ const crosapi::mojom::BrowserInitParams* init_params =
+ chromeos::LacrosService::Get()->init_params();
+ if (!init_params) {
+ return false;
+ }
+ crosapi::mojom::SessionType session_type = init_params->session_type;
+ return session_type == crosapi::mojom::SessionType::kPublicSession ||
+ session_type == crosapi::mojom::SessionType::kWebKioskSession ||
+ session_type == crosapi::mojom::SessionType::kAppKioskSession;
+}
+
+// static
+bool PolicyLoaderLacros::IsMainUserManaged() {
+ return g_is_main_user_managed_;
+}
+
+// static
+bool PolicyLoaderLacros::IsMainUserAffiliated() {
+ const enterprise_management::PolicyData* policy =
+ policy::PolicyLoaderLacros::main_user_policy_data();
+ const crosapi::mojom::BrowserInitParams* init_params =
+ chromeos::LacrosService::Get()->init_params();
+
+ // To align with `DeviceLocalAccountUserBase::IsAffiliated()`, a device local
+ // account user is always treated as affiliated.
+ if (IsDeviceLocalAccountUser()) {
+ return true;
+ }
+
+ if (policy && !policy->user_affiliation_ids().empty() && init_params &&
+ init_params->device_properties &&
+ init_params->device_properties->device_affiliation_ids.has_value()) {
+ const auto& user_ids = policy->user_affiliation_ids();
+ const auto& device_ids =
+ init_params->device_properties->device_affiliation_ids.value();
+ return policy::IsAffiliated({user_ids.begin(), user_ids.end()},
+ {device_ids.begin(), device_ids.end()});
+ }
+ return false;
+}
+
+// static
+const enterprise_management::PolicyData*
+PolicyLoaderLacros::main_user_policy_data() {
+ return MainUserPolicyDataStorage();
+}
+
+// static
+void PolicyLoaderLacros::set_main_user_policy_data_for_testing(
+ const enterprise_management::PolicyData& policy_data) {
+ *MainUserPolicyDataStorage() = policy_data;
+ g_is_main_user_managed_ = IsManaged(policy_data);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_lacros.h b/chromium/components/policy/core/common/policy_loader_lacros.h
new file mode 100644
index 00000000000..dcbc7e31c8e
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_lacros.h
@@ -0,0 +1,92 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_proto_decoders.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+// A policy loader for Lacros. The data is taken from Ash and the validitity of
+// data is trusted, since they have been validated by Ash.
+class POLICY_EXPORT PolicyLoaderLacros
+ : public AsyncPolicyLoader,
+ public chromeos::LacrosService::Observer {
+ public:
+ // Creates the policy loader, saving the task_runner internally. Later
+ // task_runner is used to have in sequence the process of policy parsing and
+ // validation. The |per_profile| parameter specifies which policy should be
+ // installed.
+ explicit PolicyLoaderLacros(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ PolicyPerProfileFilter per_profile);
+ // Not copyable or movable
+ PolicyLoaderLacros(const PolicyLoaderLacros&) = delete;
+ PolicyLoaderLacros& operator=(const PolicyLoaderLacros&) = delete;
+ ~PolicyLoaderLacros() override;
+
+ // AsyncPolicyLoader implementation.
+ // Verifies that it runs on correct thread. Detaches from the sequence checker
+ // which allows all other methods to check that they are executed on the same
+ // sequence. |sequence_checker_| is used for that.
+ void InitOnBackgroundThread() override;
+ // Loads the policy data from LacrosInitParams and populates it in the bundle
+ // that is returned.
+ std::unique_ptr<PolicyBundle> Load() override;
+
+ // Return the policy data object as received from Ash. Returns nullptr if
+ // initial load was not done yet.
+ enterprise_management::PolicyData* GetPolicyData();
+
+ // LacrosChromeServiceDelegateImpl::Observer implementation.
+ // Update and reload the policy with new data.
+ void OnPolicyUpdated(
+ const std::vector<uint8_t>& policy_fetch_response) override;
+
+ // Return if the main user is a device local account (i.e. Kiosk, MGS) user.
+ static bool IsDeviceLocalAccountUser();
+
+ // Returns if the main user is managed or not.
+ // TODO(crbug/1245077): Remove once Lacros handles all profiles the same way.
+ static bool IsMainUserManaged();
+
+ // Return if the main user is affiliated or not.
+ static bool IsMainUserAffiliated();
+
+ // Returns the policy data corresponding to the main user to be used by
+ // Enterprise Connector policies.
+ // TODO(crbug/1245077): Remove once Lacros handles all profiles the same way.
+ static const enterprise_management::PolicyData* main_user_policy_data();
+ static void set_main_user_policy_data_for_testing(
+ const enterprise_management::PolicyData& policy_data);
+
+ private:
+ // The filter for policy data to install.
+ const PolicyPerProfileFilter per_profile_;
+
+ // Serialized blob of PolicyFetchResponse object received from the server.
+ absl::optional<std::vector<uint8_t>> policy_fetch_response_;
+
+ // The parsed policy objects received from Ash.
+ std::unique_ptr<enterprise_management::PolicyData> policy_data_;
+
+ // Checks that the method is called on the right sequence.
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
diff --git a/chromium/components/policy/core/common/policy_loader_lacros_unittest.cc b/chromium/components/policy/core/common/policy_loader_lacros_unittest.cc
new file mode 100644
index 00000000000..5143e4ef980
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_lacros_unittest.cc
@@ -0,0 +1,286 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_lacros.h"
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/values.h"
+#include "chromeos/crosapi/mojom/crosapi.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "chromeos/lacros/lacros_test_helper.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+std::vector<uint8_t> GetValidPolicyFetchResponse(
+ const em::CloudPolicySettings& policy_proto) {
+ em::PolicyData policy_data;
+ policy_proto.SerializeToString(policy_data.mutable_policy_value());
+ policy_data.set_policy_type(dm_protocol::kChromeUserPolicyType);
+ em::PolicyFetchResponse policy_response;
+ policy_data.SerializeToString(policy_response.mutable_policy_data());
+ std::vector<uint8_t> data;
+ size_t size = policy_response.ByteSizeLong();
+ data.resize(size);
+ policy_response.SerializeToArray(data.data(), size);
+ return data;
+}
+
+const PolicyMap& GetChromePolicyMap(PolicyBundle* bundle) {
+ PolicyNamespace ns = PolicyNamespace(POLICY_DOMAIN_CHROME, std::string());
+ return bundle->Get(ns);
+}
+
+std::vector<uint8_t> GetValidPolicyFetchResponseWithAllPolicy() {
+ em::CloudPolicySettings policy_proto;
+ // TaskManagerEndProcessEnabled is a per_profile:True policy. See
+ // policy_templates.json for details.
+ policy_proto.mutable_taskmanagerendprocessenabled()->set_value(false);
+ // HomepageLocation is a per_profile:True policy. See policy_templates.json
+ // for details.
+ policy_proto.mutable_homepagelocation()->set_value("http://chromium.org");
+ return GetValidPolicyFetchResponse(policy_proto);
+}
+
+} // namespace
+
+// Test cases for lacros policy provider specific functionality.
+class PolicyLoaderLacrosTest : public PolicyTestBase {
+ protected:
+ PolicyLoaderLacrosTest() = default;
+ ~PolicyLoaderLacrosTest() override {}
+
+ void SetPolicy() {
+ std::vector<uint8_t> data = GetValidPolicyFetchResponseWithAllPolicy();
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+ init_params->device_account_policy = data;
+ chromeos::LacrosService::Get()->SetInitParamsForTests(
+ std::move(init_params));
+ }
+
+ void CheckProfilePolicies(const PolicyMap& policy_map) const {
+ if (per_profile_ == PolicyPerProfileFilter::kFalse) {
+ EXPECT_EQ(nullptr, policy_map.GetValue(key::kHomepageLocation,
+ base::Value::Type::STRING));
+ EXPECT_EQ(nullptr, policy_map.GetValue(key::kAllowDinosaurEasterEgg,
+ base::Value::Type::BOOLEAN));
+ } else {
+ EXPECT_EQ("http://chromium.org",
+ policy_map
+ .GetValue(key::kHomepageLocation, base::Value::Type::STRING)
+ ->GetString());
+ // Enterprise default.
+ EXPECT_EQ(false, policy_map
+ .GetValue(key::kAllowDinosaurEasterEgg,
+ base::Value::Type::BOOLEAN)
+ ->GetBool());
+ }
+ }
+
+ void CheckSystemWidePolicies(const PolicyMap& policy_map) const {
+ if (per_profile_ == PolicyPerProfileFilter::kTrue) {
+ EXPECT_EQ(nullptr, policy_map.GetValue(key::kTaskManagerEndProcessEnabled,
+ base::Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, policy_map.GetValue(key::kPinUnlockAutosubmitEnabled,
+ base::Value::Type::BOOLEAN));
+ } else {
+ EXPECT_FALSE(policy_map
+ .GetValue(key::kTaskManagerEndProcessEnabled,
+ base::Value::Type::BOOLEAN)
+ ->GetBool());
+ // Enterprise default.
+ EXPECT_FALSE(policy_map
+ .GetValue(key::kPinUnlockAutosubmitEnabled,
+ base::Value::Type::BOOLEAN)
+ ->GetBool());
+ }
+ }
+
+ void CheckCorrectPoliciesAreSet(PolicyBundle* bundle) const {
+ const PolicyMap& policy_map = GetChromePolicyMap(bundle);
+ CheckProfilePolicies(policy_map);
+ CheckSystemWidePolicies(policy_map);
+ }
+
+ void SwitchAndCheckLocalDeviceAccountUser(
+ crosapi::mojom::SessionType session_type) {
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+ init_params->session_type = session_type;
+ chromeos::LacrosService::Get()->SetInitParamsForTests(
+ std::move(init_params));
+ EXPECT_TRUE(PolicyLoaderLacros::IsDeviceLocalAccountUser());
+ EXPECT_TRUE(PolicyLoaderLacros::IsMainUserAffiliated());
+ }
+
+ SchemaRegistry schema_registry_;
+ PolicyPerProfileFilter per_profile_ = PolicyPerProfileFilter::kFalse;
+ chromeos::ScopedLacrosServiceTestHelper test_helper_;
+};
+
+TEST_F(PolicyLoaderLacrosTest, BasicTestSystemWidePolicies) {
+ per_profile_ = PolicyPerProfileFilter::kFalse;
+ SetPolicy();
+
+ PolicyLoaderLacros loader(task_environment_.GetMainThreadTaskRunner(),
+ per_profile_);
+ base::RunLoop().RunUntilIdle();
+ CheckCorrectPoliciesAreSet(loader.Load().get());
+}
+
+TEST_F(PolicyLoaderLacrosTest, BasicTestProfilePolicies) {
+ per_profile_ = PolicyPerProfileFilter::kTrue;
+ SetPolicy();
+
+ PolicyLoaderLacros loader(task_environment_.GetMainThreadTaskRunner(),
+ per_profile_);
+ base::RunLoop().RunUntilIdle();
+ CheckCorrectPoliciesAreSet(loader.Load().get());
+}
+
+TEST_F(PolicyLoaderLacrosTest, UpdateTestProfilePolicies) {
+ per_profile_ = PolicyPerProfileFilter::kTrue;
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+
+ chromeos::LacrosService::Get()->SetInitParamsForTests(std::move(init_params));
+
+ PolicyLoaderLacros* loader = new PolicyLoaderLacros(
+ task_environment_.GetMainThreadTaskRunner(), per_profile_);
+ AsyncPolicyProvider provider(&schema_registry_,
+ std::unique_ptr<AsyncPolicyLoader>(loader));
+ provider.Init(&schema_registry_);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(GetChromePolicyMap(loader->Load().get()).size(), (unsigned int)0);
+
+ std::vector<uint8_t> data = GetValidPolicyFetchResponseWithAllPolicy();
+ loader->OnPolicyUpdated(data);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_GT(GetChromePolicyMap(loader->Load().get()).size(),
+ static_cast<unsigned int>(0));
+ provider.Shutdown();
+}
+
+TEST_F(PolicyLoaderLacrosTest, UpdateTestSystemWidePolicies) {
+ per_profile_ = PolicyPerProfileFilter::kFalse;
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+
+ chromeos::LacrosService::Get()->SetInitParamsForTests(std::move(init_params));
+
+ PolicyLoaderLacros* loader = new PolicyLoaderLacros(
+ task_environment_.GetMainThreadTaskRunner(), per_profile_);
+ AsyncPolicyProvider provider(&schema_registry_,
+ std::unique_ptr<AsyncPolicyLoader>(loader));
+ provider.Init(&schema_registry_);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(GetChromePolicyMap(loader->Load().get()).size(), (unsigned int)0);
+
+ std::vector<uint8_t> data = GetValidPolicyFetchResponseWithAllPolicy();
+ loader->OnPolicyUpdated(data);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_GT(GetChromePolicyMap(loader->Load().get()).size(),
+ static_cast<unsigned int>(0));
+ provider.Shutdown();
+}
+
+TEST_F(PolicyLoaderLacrosTest, TwoLoaders) {
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+
+ chromeos::LacrosService::Get()->SetInitParamsForTests(std::move(init_params));
+
+ PolicyLoaderLacros* system_wide_loader =
+ new PolicyLoaderLacros(task_environment_.GetMainThreadTaskRunner(),
+ PolicyPerProfileFilter::kFalse);
+ AsyncPolicyProvider system_wide_provider(
+ &schema_registry_,
+ std::unique_ptr<AsyncPolicyLoader>(system_wide_loader));
+ system_wide_provider.Init(&schema_registry_);
+
+ PolicyLoaderLacros* per_profile_loader =
+ new PolicyLoaderLacros(task_environment_.GetMainThreadTaskRunner(),
+ PolicyPerProfileFilter::kTrue);
+ AsyncPolicyProvider per_profile_provider(
+ &schema_registry_,
+ std::unique_ptr<AsyncPolicyLoader>(per_profile_loader));
+ per_profile_provider.Init(&schema_registry_);
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(GetChromePolicyMap(system_wide_loader->Load().get()).size(),
+ (unsigned int)0);
+ EXPECT_EQ(GetChromePolicyMap(per_profile_loader->Load().get()).size(),
+ (unsigned int)0);
+
+ std::vector<uint8_t> data = GetValidPolicyFetchResponseWithAllPolicy();
+ system_wide_loader->OnPolicyUpdated(data);
+ per_profile_loader->OnPolicyUpdated(data);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_GT(GetChromePolicyMap(system_wide_loader->Load().get()).size(),
+ static_cast<unsigned int>(0));
+ EXPECT_GT(GetChromePolicyMap(per_profile_loader->Load().get()).size(),
+ static_cast<unsigned int>(0));
+ system_wide_provider.Shutdown();
+ per_profile_provider.Shutdown();
+}
+
+TEST_F(PolicyLoaderLacrosTest, ChildUsersNoEnterpriseDefaults) {
+ // Prepare child policy (per_profile:False).
+ per_profile_ = PolicyPerProfileFilter::kFalse;
+
+ em::CloudPolicySettings policy_proto;
+ policy_proto.mutable_lacrossecondaryprofilesallowed()->set_value(false);
+ const std::vector<uint8_t> data = GetValidPolicyFetchResponse(policy_proto);
+
+ // Setup child user session with the policy.
+ auto init_params = crosapi::mojom::BrowserInitParams::New();
+ init_params->session_type = crosapi::mojom::SessionType::kChildSession;
+ init_params->device_account_policy = std::move(data);
+ chromeos::LacrosService::Get()->SetInitParamsForTests(std::move(init_params));
+
+ // Load the policy.
+ PolicyLoaderLacros loader(task_environment_.GetMainThreadTaskRunner(),
+ per_profile_);
+ base::RunLoop().RunUntilIdle();
+ std::unique_ptr<PolicyBundle> bundle = loader.Load();
+
+ EXPECT_TRUE(PolicyLoaderLacros::IsMainUserManaged());
+ EXPECT_FALSE(PolicyLoaderLacros::IsDeviceLocalAccountUser());
+ EXPECT_FALSE(PolicyLoaderLacros::IsMainUserAffiliated());
+
+ // Check that desired policy is set and enterprise defaults are not applied.
+ const PolicyMap& policy_map = GetChromePolicyMap(bundle.get());
+ EXPECT_EQ(1u, policy_map.size());
+
+ const PolicyMap::Entry* entry =
+ policy_map.Get(key::kLacrosSecondaryProfilesAllowed);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->value(base::Value::Type::BOOLEAN)->GetBool());
+ EXPECT_EQ(policy::POLICY_SOURCE_CLOUD_FROM_ASH, entry->source);
+}
+
+TEST_F(PolicyLoaderLacrosTest, DeviceLocalAccountUsers) {
+ SwitchAndCheckLocalDeviceAccountUser(
+ crosapi::mojom::SessionType::kPublicSession);
+ SwitchAndCheckLocalDeviceAccountUser(
+ crosapi::mojom::SessionType::kWebKioskSession);
+ SwitchAndCheckLocalDeviceAccountUser(
+ crosapi::mojom::SessionType::kAppKioskSession);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_mac.h b/chromium/components/policy/core/common/policy_loader_mac.h
new file mode 100644
index 00000000000..20654359d76
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_mac.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_MAC_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_MAC_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+class MacPreferences;
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace policy {
+
+class PolicyBundle;
+class PolicyMap;
+class Schema;
+
+// A policy loader that loads policies from the Mac preferences system, and
+// watches the managed preferences files for updates.
+class POLICY_EXPORT PolicyLoaderMac : public AsyncPolicyLoader {
+ public:
+ PolicyLoaderMac(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& managed_policy_path,
+ MacPreferences* preferences);
+
+ // |application_id| will be passed into Mac's Preference Utilities API
+ // instead of the default value of kCFPreferencesCurrentApplication.
+ PolicyLoaderMac(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& managed_policy_path,
+ MacPreferences* preferences,
+ CFStringRef application_id);
+ PolicyLoaderMac(const PolicyLoaderMac&) = delete;
+ PolicyLoaderMac& operator=(const PolicyLoaderMac&) = delete;
+
+ ~PolicyLoaderMac() override;
+
+ // AsyncPolicyLoader implementation.
+ void InitOnBackgroundThread() override;
+ std::unique_ptr<PolicyBundle> Load() override;
+ base::Time LastModificationTime() override;
+
+#if BUILDFLAG(IS_MAC)
+ // Gets the path to the preferences (.plist) file associated with the given
+ // |bundle_id|. The file at the returned path might not exist (yet).
+ // Returns an empty path upon failure.
+ static base::FilePath GetManagedPolicyPath(CFStringRef bundle_id);
+#endif
+
+ private:
+ // Callback for the FilePathWatcher.
+ void OnFileUpdated(const base::FilePath& path, bool error);
+
+ // Loads policies for the components described in the current schema_map()
+ // which belong to the domain |domain_name|, and stores them in the |bundle|.
+ void LoadPolicyForDomain(
+ PolicyDomain domain,
+ const std::string& domain_name,
+ PolicyBundle* bundle);
+
+ // Loads the policies described in |schema| from the bundle identified by
+ // |bundle_id_string|, and stores them in |policy|.
+ void LoadPolicyForComponent(const std::string& bundle_id_string,
+ const Schema& schema,
+ PolicyMap* policy);
+
+ std::unique_ptr<MacPreferences> preferences_;
+
+ // Path to the managed preferences file for the current user, if it could
+ // be found. Updates of this file trigger a policy reload.
+ base::FilePath managed_policy_path_;
+
+ // Watches for events on the |managed_policy_path_|.
+ base::FilePathWatcher watcher_;
+
+ // Application ID to pass into Mac's Preference Utilities API.
+ base::ScopedCFTypeRef<CFStringRef> application_id_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_MAC_H_
diff --git a/chromium/components/policy/core/common/policy_loader_mac.mm b/chromium/components/policy/core/common/policy_loader_mac.mm
new file mode 100644
index 00000000000..b717d39fbf1
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_mac.mm
@@ -0,0 +1,254 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_mac.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/enterprise_util.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/core/common/mac_util.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/policy_loader_common.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/preferences_mac.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+
+using base::ScopedCFTypeRef;
+
+namespace policy {
+
+namespace {
+
+// Encapsulates logic to determine if enterprise policies should be honored.
+bool ShouldHonorPolicies() {
+ // Only honor sensitive policies if the Mac is managed externally.
+ base::DeviceUserDomainJoinState join_state =
+ base::AreDeviceAndUserJoinedToDomain();
+ if (join_state.device_joined)
+ return true;
+
+ // IsDeviceRegisteredWithManagementNew is only available after 10.13.4.
+ // Eventually switch to it when that is the minimum OS required by Chromium.
+ if (@available(macOS 10.13.4, *)) {
+ base::MacDeviceManagementStateNew mdm_state =
+ base::IsDeviceRegisteredWithManagementNew();
+ return mdm_state ==
+ base::MacDeviceManagementStateNew::kLimitedMDMEnrollment ||
+ mdm_state == base::MacDeviceManagementStateNew::kFullMDMEnrollment ||
+ mdm_state == base::MacDeviceManagementStateNew::kDEPMDMEnrollment;
+ }
+ base::MacDeviceManagementStateOld mdm_state =
+ base::IsDeviceRegisteredWithManagementOld();
+ return mdm_state == base::MacDeviceManagementStateOld::kMDMEnrollment;
+}
+
+} // namespace
+
+PolicyLoaderMac::PolicyLoaderMac(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& managed_policy_path,
+ MacPreferences* preferences)
+ : PolicyLoaderMac(task_runner,
+ managed_policy_path,
+ preferences,
+ kCFPreferencesCurrentApplication) {}
+
+PolicyLoaderMac::PolicyLoaderMac(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const base::FilePath& managed_policy_path,
+ MacPreferences* preferences,
+ CFStringRef application_id)
+ : AsyncPolicyLoader(task_runner, /*periodic_updates=*/true),
+ preferences_(preferences),
+ managed_policy_path_(managed_policy_path),
+ application_id_(CFStringCreateCopy(kCFAllocatorDefault, application_id)) {
+}
+
+PolicyLoaderMac::~PolicyLoaderMac() {
+}
+
+void PolicyLoaderMac::InitOnBackgroundThread() {
+ if (!managed_policy_path_.empty()) {
+ watcher_.Watch(managed_policy_path_,
+ base::FilePathWatcher::Type::kNonRecursive,
+ base::BindRepeating(&PolicyLoaderMac::OnFileUpdated,
+ base::Unretained(this)));
+ }
+
+ base::File::Info file_info;
+ bool managed_policy_file_exists = false;
+ if (base::GetFileInfo(managed_policy_path_, &file_info) &&
+ !file_info.is_directory) {
+ managed_policy_file_exists = true;
+ }
+
+ base::UmaHistogramBoolean("EnterpriseCheck.IsManaged2",
+ managed_policy_file_exists);
+ base::UmaHistogramBoolean("EnterpriseCheck.IsEnterpriseUser",
+ base::IsMachineExternallyManaged());
+
+ base::UmaHistogramEnumeration("EnterpriseCheck.Mac.IsDeviceMDMEnrolledOld",
+ base::IsDeviceRegisteredWithManagementOld());
+ base::UmaHistogramEnumeration("EnterpriseCheck.Mac.IsDeviceMDMEnrolledNew",
+ base::IsDeviceRegisteredWithManagementNew());
+ base::DeviceUserDomainJoinState state =
+ base::AreDeviceAndUserJoinedToDomain();
+ base::UmaHistogramBoolean("EnterpriseCheck.Mac.IsDeviceDomainJoined",
+ state.device_joined);
+ base::UmaHistogramBoolean("EnterpriseCheck.Mac.IsCurrentUserDomainUser",
+ state.user_joined);
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderMac::Load() {
+ preferences_->AppSynchronize(application_id_);
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+
+ // Load Chrome's policy.
+ PolicyMap& chrome_policy =
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+
+ PolicyLoadStatusUmaReporter status;
+ bool policy_present = false;
+ const Schema* schema =
+ schema_map()->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+ for (Schema::Iterator it = schema->GetPropertiesIterator(); !it.IsAtEnd();
+ it.Advance()) {
+ base::ScopedCFTypeRef<CFStringRef> name(
+ base::SysUTF8ToCFStringRef(it.key()));
+ base::ScopedCFTypeRef<CFPropertyListRef> value(
+ preferences_->CopyAppValue(name, application_id_));
+ if (!value)
+ continue;
+ policy_present = true;
+ bool forced = preferences_->AppValueIsForced(name, application_id_);
+ PolicyLevel level =
+ forced ? POLICY_LEVEL_MANDATORY : POLICY_LEVEL_RECOMMENDED;
+ // TODO(joaodasilva): figure the policy scope.
+ std::unique_ptr<base::Value> policy = PropertyToValue(value);
+ if (policy) {
+ chrome_policy.Set(it.key(), level, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, std::move(*policy), nullptr);
+ } else {
+ status.Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ }
+ }
+
+ if (!policy_present)
+ status.Add(POLICY_LOAD_STATUS_NO_POLICY);
+
+ // Load policy for the registered components.
+ LoadPolicyForDomain(POLICY_DOMAIN_EXTENSIONS, "extensions", bundle.get());
+
+ if (!ShouldHonorPolicies())
+ FilterSensitivePolicies(&chrome_policy);
+
+ return bundle;
+}
+
+base::Time PolicyLoaderMac::LastModificationTime() {
+ base::File::Info file_info;
+ if (!base::GetFileInfo(managed_policy_path_, &file_info) ||
+ file_info.is_directory) {
+ return base::Time();
+ }
+
+ return file_info.last_modified;
+}
+
+#if BUILDFLAG(IS_MAC)
+
+base::FilePath PolicyLoaderMac::GetManagedPolicyPath(CFStringRef bundle_id) {
+ // This constructs the path to the plist file in which Mac OS X stores the
+ // managed preference for the application. This is undocumented and therefore
+ // fragile, but if it doesn't work out, AsyncPolicyLoader has a task that
+ // polls periodically in order to reload managed preferences later even if we
+ // missed the change.
+
+ base::FilePath path;
+ if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &path))
+ return base::FilePath();
+ path = path.Append(FILE_PATH_LITERAL("Managed Preferences"));
+ char* login = getlogin();
+ if (!login)
+ return base::FilePath();
+ path = path.AppendASCII(login);
+ return path.Append(base::SysCFStringRefToUTF8(bundle_id) + ".plist");
+}
+
+#endif
+
+void PolicyLoaderMac::LoadPolicyForDomain(PolicyDomain domain,
+ const std::string& domain_name,
+ PolicyBundle* bundle) {
+ std::string id_prefix(base::SysCFStringRefToUTF8(application_id_));
+ id_prefix.append(".").append(domain_name).append(".");
+
+ const ComponentMap* components = schema_map()->GetComponents(domain);
+ if (!components)
+ return;
+
+ for (ComponentMap::const_iterator it = components->begin();
+ it != components->end(); ++it) {
+ PolicyMap policy;
+ LoadPolicyForComponent(id_prefix + it->first, it->second, &policy);
+ if (!policy.empty())
+ bundle->Get(PolicyNamespace(domain, it->first)).Swap(&policy);
+ }
+}
+
+void PolicyLoaderMac::LoadPolicyForComponent(
+ const std::string& bundle_id_string,
+ const Schema& schema,
+ PolicyMap* policy) {
+ // TODO(joaodasilva): Extensions may be registered in a ComponentMap
+ // without a schema, to allow a graceful update of the Legacy Browser Support
+ // extension on Windows. Remove this check once that support is removed.
+ if (!schema.valid())
+ return;
+
+ base::ScopedCFTypeRef<CFStringRef> bundle_id(
+ base::SysUTF8ToCFStringRef(bundle_id_string));
+ preferences_->AppSynchronize(bundle_id);
+
+ for (Schema::Iterator it = schema.GetPropertiesIterator(); !it.IsAtEnd();
+ it.Advance()) {
+ base::ScopedCFTypeRef<CFStringRef> pref_name(
+ base::SysUTF8ToCFStringRef(it.key()));
+ base::ScopedCFTypeRef<CFPropertyListRef> value(
+ preferences_->CopyAppValue(pref_name, bundle_id));
+ if (!value)
+ continue;
+ bool forced = preferences_->AppValueIsForced(pref_name, bundle_id);
+ PolicyLevel level =
+ forced ? POLICY_LEVEL_MANDATORY : POLICY_LEVEL_RECOMMENDED;
+ std::unique_ptr<base::Value> policy_value = PropertyToValue(value);
+ if (policy_value) {
+ policy->Set(it.key(), level, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(*policy_value), nullptr);
+ }
+ }
+}
+
+void PolicyLoaderMac::OnFileUpdated(const base::FilePath& path, bool error) {
+ if (!error)
+ Reload(false);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_mac_unittest.cc b/chromium/components/policy/core/common/policy_loader_mac_unittest.cc
new file mode 100644
index 00000000000..c8f0ca85231
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_mac_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/run_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/preferences_mock_mac.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ScopedCFTypeRef;
+
+namespace policy {
+
+namespace {
+
+class TestHarness : public PolicyProviderTestHarness {
+ public:
+ TestHarness();
+ TestHarness(const TestHarness&) = delete;
+ TestHarness& operator=(const TestHarness&) = delete;
+ ~TestHarness() override;
+
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) override;
+
+ static PolicyProviderTestHarness* Create();
+
+ private:
+ MockPreferences* prefs_;
+};
+
+TestHarness::TestHarness()
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM) {}
+
+TestHarness::~TestHarness() {}
+
+void TestHarness::SetUp() {}
+
+ConfigurationPolicyProvider* TestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ prefs_ = new MockPreferences();
+ std::unique_ptr<AsyncPolicyLoader> loader(
+ new PolicyLoaderMac(task_runner, base::FilePath(), prefs_));
+ return new AsyncPolicyProvider(registry, std::move(loader));
+}
+
+void TestHarness::InstallEmptyPolicy() {}
+
+void TestHarness::InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) {
+ ScopedCFTypeRef<CFStringRef> name(base::SysUTF8ToCFStringRef(policy_name));
+ ScopedCFTypeRef<CFStringRef> value(base::SysUTF8ToCFStringRef(policy_value));
+ prefs_->AddTestItem(name, value, true);
+}
+
+void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) {
+ ScopedCFTypeRef<CFStringRef> name(base::SysUTF8ToCFStringRef(policy_name));
+ ScopedCFTypeRef<CFNumberRef> value(
+ CFNumberCreate(NULL, kCFNumberIntType, &policy_value));
+ prefs_->AddTestItem(name, value, true);
+}
+
+void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) {
+ ScopedCFTypeRef<CFStringRef> name(base::SysUTF8ToCFStringRef(policy_name));
+ prefs_->AddTestItem(name,
+ policy_value ? kCFBooleanTrue : kCFBooleanFalse,
+ true);
+}
+
+void TestHarness::InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ ScopedCFTypeRef<CFStringRef> name(base::SysUTF8ToCFStringRef(policy_name));
+ ScopedCFTypeRef<CFPropertyListRef> array(ValueToProperty(*policy_value));
+ ASSERT_TRUE(array);
+ prefs_->AddTestItem(name, array, true);
+}
+
+void TestHarness::InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) {
+ ScopedCFTypeRef<CFStringRef> name(base::SysUTF8ToCFStringRef(policy_name));
+ ScopedCFTypeRef<CFPropertyListRef> dict(ValueToProperty(*policy_value));
+ ASSERT_TRUE(dict);
+ prefs_->AddTestItem(name, dict, true);
+}
+
+// static
+PolicyProviderTestHarness* TestHarness::Create() {
+ return new TestHarness();
+}
+
+} // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_SUITE_P(PolicyProviderMacTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(TestHarness::Create));
+
+// TODO(joaodasilva): instantiate Configuration3rdPartyPolicyProviderTest too
+// once the mac loader supports 3rd party policy. http://crbug.com/108995
+
+// Special test cases for some mac preferences details.
+class PolicyLoaderMacTest : public PolicyTestBase {
+ protected:
+ PolicyLoaderMacTest()
+ : prefs_(new MockPreferences()) {}
+
+ void SetUp() override {
+ PolicyTestBase::SetUp();
+ std::unique_ptr<AsyncPolicyLoader> loader(new PolicyLoaderMac(
+ task_environment_.GetMainThreadTaskRunner(), base::FilePath(), prefs_));
+ provider_ = std::make_unique<AsyncPolicyProvider>(&schema_registry_,
+ std::move(loader));
+ provider_->Init(&schema_registry_);
+ }
+
+ void TearDown() override {
+ provider_->Shutdown();
+ PolicyTestBase::TearDown();
+ }
+
+ MockPreferences* prefs_;
+ std::unique_ptr<AsyncPolicyProvider> provider_;
+};
+
+TEST_F(PolicyLoaderMacTest, Invalid) {
+ ScopedCFTypeRef<CFStringRef> name(
+ base::SysUTF8ToCFStringRef(test_keys::kKeyString));
+ const char buffer[] = "binary \xde\xad\xbe\xef data";
+ ScopedCFTypeRef<CFDataRef> invalid_data(
+ CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(buffer),
+ std::size(buffer)));
+ ASSERT_TRUE(invalid_data);
+ prefs_->AddTestItem(name, invalid_data.get(), true);
+ prefs_->AddTestItem(name, invalid_data.get(), false);
+
+ // Make the provider read the updated |prefs_|.
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ const PolicyBundle kEmptyBundle;
+ EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
+}
+
+TEST_F(PolicyLoaderMacTest, TestNonForcedValue) {
+ ScopedCFTypeRef<CFStringRef> name(
+ base::SysUTF8ToCFStringRef(test_keys::kKeyString));
+ ScopedCFTypeRef<CFPropertyListRef> test_value(
+ base::SysUTF8ToCFStringRef("string value"));
+ ASSERT_TRUE(test_value.get());
+ prefs_->AddTestItem(name, test_value.get(), false);
+
+ // Make the provider read the updated |prefs_|.
+ provider_->RefreshPolicies();
+ task_environment_.RunUntilIdle();
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(test_keys::kKeyString, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("string value"), nullptr);
+ EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_win.cc b/chromium/components/policy/core/common/policy_loader_win.cc
new file mode 100644
index 00000000000..d11e2aa15b2
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_win.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_win.h"
+
+// Must be included before lm.h
+#include <windows.h>
+
+#include <lm.h> // For NetGetJoinInformation
+// <security.h> needs this.
+#define SECURITY_WIN32 1
+#include <security.h> // For GetUserNameEx()
+#include <stddef.h>
+#include <userenv.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/cxx17_backports.h"
+#include "base/enterprise_util.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/scoped_native_library.h"
+#include "base/strings/string_util.h"
+#include "base/syslog_logging.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/scoped_thread_priority.h"
+#include "base/values.h"
+#include "base/win/shlwapi.h" // For PathIsUNC()
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/policy_loader_common.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/registry_dict.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+
+namespace policy {
+
+namespace {
+
+const char kKeyMandatory[] = "policy";
+const char kKeyRecommended[] = "recommended";
+const char kKeyThirdParty[] = "3rdparty";
+
+// The list of possible errors that can occur while collecting information about
+// the current enterprise environment.
+// This enum is used to define the buckets for an enumerated UMA histogram.
+// Hence,
+// (a) existing enumerated constants should never be deleted or reordered, and
+// (b) new constants should only be appended at the end of the enumeration.
+enum DomainCheckErrors {
+ // The check error below is no longer possible.
+ DEPRECATED_DOMAIN_CHECK_ERROR_GET_JOIN_INFO = 0,
+ DOMAIN_CHECK_ERROR_DS_BIND = 1,
+ DOMAIN_CHECK_ERROR_SIZE, // Not a DomainCheckError. Must be last.
+};
+
+// Parses |gpo_dict| according to |schema| and writes the resulting policy
+// settings to |policy| for the given |scope| and |level|.
+void ParsePolicy(const RegistryDict* gpo_dict,
+ PolicyLevel level,
+ PolicyScope scope,
+ const Schema& schema,
+ PolicyMap* policy) {
+ if (!gpo_dict)
+ return;
+
+ std::unique_ptr<base::Value> policy_value(gpo_dict->ConvertToJSON(schema));
+ const base::DictionaryValue* policy_dict = nullptr;
+ if (!policy_value->GetAsDictionary(&policy_dict) || !policy_dict) {
+ SYSLOG(WARNING) << "Root policy object is not a dictionary!";
+ return;
+ }
+
+ policy->LoadFrom(policy_dict, level, scope, POLICY_SOURCE_PLATFORM);
+}
+
+// Returns a name, using the |get_name| callback, which may refuse the call if
+// the name is longer than _MAX_PATH. So this helper function takes care of the
+// retry with the required size.
+bool GetName(const base::RepeatingCallback<BOOL(LPWSTR, LPDWORD)>& get_name,
+ std::wstring* name) {
+ DCHECK(name);
+ DWORD size = _MAX_PATH;
+ if (!get_name.Run(base::WriteInto(name, size), &size)) {
+ if (::GetLastError() != ERROR_MORE_DATA)
+ return false;
+ // Try again with the required size. This time it must work, the size should
+ // not have changed in between the two calls.
+ if (!get_name.Run(base::WriteInto(name, size), &size))
+ return false;
+ }
+ return true;
+}
+
+// To convert the weird BOOLEAN return value type of ::GetUserNameEx().
+BOOL GetUserNameExBool(EXTENDED_NAME_FORMAT format, LPWSTR name, PULONG size) {
+ // ::GetUserNameEx is documented to return a nonzero value on success.
+ return ::GetUserNameEx(format, name, size) != 0;
+}
+
+// Make sure to use the real NetGetJoinInformation, otherwise fallback to the
+// linked one.
+bool IsDomainJoined() {
+ base::ScopedClosureRunner free_library;
+ decltype(&::NetGetJoinInformation) net_get_join_information_function =
+ &::NetGetJoinInformation;
+ decltype(&::NetApiBufferFree) net_api_buffer_free_function =
+ &::NetApiBufferFree;
+ bool got_function_addresses = false;
+ // Use an absolute path to load the DLL to avoid DLL preloading attacks.
+ base::FilePath path;
+ if (base::PathService::Get(base::DIR_SYSTEM, &path)) {
+ // Mitigate the issues caused by loading DLLs on a background thread
+ // (http://crbug/973868).
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
+
+ HINSTANCE net_api_library = ::LoadLibraryEx(
+ path.Append(FILE_PATH_LITERAL("netapi32.dll")).value().c_str(), nullptr,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (net_api_library) {
+ free_library.ReplaceClosure(
+ base::BindOnce(base::IgnoreResult(&::FreeLibrary), net_api_library));
+ net_get_join_information_function =
+ reinterpret_cast<decltype(&::NetGetJoinInformation)>(
+ ::GetProcAddress(net_api_library, "NetGetJoinInformation"));
+ net_api_buffer_free_function =
+ reinterpret_cast<decltype(&::NetApiBufferFree)>(
+ ::GetProcAddress(net_api_library, "NetApiBufferFree"));
+
+ if (net_get_join_information_function && net_api_buffer_free_function) {
+ got_function_addresses = true;
+ } else {
+ net_get_join_information_function = &::NetGetJoinInformation;
+ net_api_buffer_free_function = &::NetApiBufferFree;
+ }
+ }
+ }
+ base::UmaHistogramBoolean("EnterpriseCheck.NetGetJoinInformationAddress",
+ got_function_addresses);
+
+ LPWSTR buffer = nullptr;
+ NETSETUP_JOIN_STATUS buffer_type = NetSetupUnknownStatus;
+ bool is_joined = net_get_join_information_function(
+ nullptr, &buffer, &buffer_type) == NERR_Success &&
+ buffer_type == NetSetupDomainName;
+ if (buffer)
+ net_api_buffer_free_function(buffer);
+
+ return is_joined;
+}
+
+// Collects stats about the enterprise environment that can be used to decide
+// how to parse the existing policy information.
+void CollectEnterpriseUMAs() {
+ // Collect statistics about the windows suite.
+ UMA_HISTOGRAM_ENUMERATION("EnterpriseCheck.OSType",
+ base::win::OSInfo::GetInstance()->version_type(),
+ base::win::SUITE_LAST);
+
+ base::UmaHistogramBoolean("EnterpriseCheck.IsDomainJoined", IsDomainJoined());
+ base::UmaHistogramBoolean("EnterpriseCheck.InDomain",
+ base::win::IsEnrolledToDomain());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsManaged2",
+ base::win::IsDeviceRegisteredWithManagement());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsEnterpriseUser",
+ base::IsMachineExternallyManaged());
+ base::UmaHistogramBoolean("EnterpriseCheck.IsJoinedToAzureAD",
+ base::win::IsJoinedToAzureAD());
+
+ std::wstring machine_name;
+ if (GetName(
+ base::BindRepeating(&::GetComputerNameEx, ::ComputerNameDnsHostname),
+ &machine_name)) {
+ std::wstring user_name;
+ if (GetName(base::BindRepeating(&GetUserNameExBool, ::NameSamCompatible),
+ &user_name)) {
+ // A local user has the machine name in its sam compatible name, e.g.,
+ // 'MACHINE_NAME\username', otherwise it is perfixed with the domain name
+ // as opposed to the machine, e.g., 'COMPANY\username'.
+ base::UmaHistogramBoolean(
+ "EnterpriseCheck.IsLocalUser",
+ base::StartsWith(user_name, machine_name,
+ base::CompareCase::INSENSITIVE_ASCII) &&
+ user_name[machine_name.size()] == L'\\');
+ }
+
+ std::wstring full_machine_name;
+ if (GetName(base::BindRepeating(&::GetComputerNameEx,
+ ::ComputerNameDnsFullyQualified),
+ &full_machine_name)) {
+ // ComputerNameDnsFullyQualified is the same as the
+ // ComputerNameDnsHostname when not domain joined, otherwise it has a
+ // suffix.
+ base::UmaHistogramBoolean(
+ "EnterpriseCheck.IsLocalMachine",
+ base::EqualsCaseInsensitiveASCII(machine_name, full_machine_name));
+ }
+ }
+}
+
+} // namespace
+
+PolicyLoaderWin::PolicyLoaderWin(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key)
+ : AsyncPolicyLoader(task_runner,
+ management_service,
+ /*periodic_updates=*/true),
+ is_initialized_(false),
+ chrome_policy_key_(chrome_policy_key),
+ user_policy_changed_event_(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ machine_policy_changed_event_(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ user_policy_watcher_failed_(false),
+ machine_policy_watcher_failed_(false) {
+ if (!::RegisterGPNotification(user_policy_changed_event_.handle(), false)) {
+ DPLOG(WARNING) << "Failed to register user group policy notification";
+ user_policy_watcher_failed_ = true;
+ }
+ if (!::RegisterGPNotification(machine_policy_changed_event_.handle(), true)) {
+ DPLOG(WARNING) << "Failed to register machine group policy notification.";
+ machine_policy_watcher_failed_ = true;
+ }
+}
+
+PolicyLoaderWin::~PolicyLoaderWin() {
+ // Mitigate the issues caused by loading DLLs or lazily resolving symbols on a
+ // background thread (http://crbug/973868) which can hold the process wide
+ // LoaderLock and cause contention on Foreground threads. This issue is solved
+ // on Windows version after Win7. This code can be removed when Win7 is no
+ // longer supported.
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+
+ if (!user_policy_watcher_failed_) {
+ ::UnregisterGPNotification(user_policy_changed_event_.handle());
+ user_policy_watcher_.StopWatching();
+ }
+ if (!machine_policy_watcher_failed_) {
+ ::UnregisterGPNotification(machine_policy_changed_event_.handle());
+ machine_policy_watcher_.StopWatching();
+ }
+}
+
+// static
+std::unique_ptr<PolicyLoaderWin> PolicyLoaderWin::Create(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key) {
+ return std::make_unique<PolicyLoaderWin>(task_runner, management_service,
+ chrome_policy_key);
+}
+
+void PolicyLoaderWin::InitOnBackgroundThread() {
+ is_initialized_ = true;
+ SetupWatches();
+ CollectEnterpriseUMAs();
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderWin::Load() {
+ // Reset the watches BEFORE reading the individual policies to avoid
+ // missing a change notification.
+ if (is_initialized_)
+ SetupWatches();
+
+ // Policy scope and corresponding hive.
+ static const struct {
+ PolicyScope scope;
+ HKEY hive;
+ } kScopes[] = {
+ {POLICY_SCOPE_MACHINE, HKEY_LOCAL_MACHINE},
+ {POLICY_SCOPE_USER, HKEY_CURRENT_USER},
+ };
+
+ // Load policy data for the different scopes/levels and merge them.
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ PolicyMap* chrome_policy =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ for (size_t i = 0; i < std::size(kScopes); ++i) {
+ PolicyScope scope = kScopes[i].scope;
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict gpo_dict;
+
+ gpo_dict.ReadRegistry(kScopes[i].hive, chrome_policy_key_);
+
+ // Remove special-cased entries from the GPO dictionary.
+ std::unique_ptr<RegistryDict> recommended_dict(
+ gpo_dict.RemoveKey(kKeyRecommended));
+ std::unique_ptr<RegistryDict> third_party_dict(
+ gpo_dict.RemoveKey(kKeyThirdParty));
+
+ // Load Chrome policy.
+ LoadChromePolicy(&gpo_dict, POLICY_LEVEL_MANDATORY, scope, chrome_policy);
+ LoadChromePolicy(recommended_dict.get(), POLICY_LEVEL_RECOMMENDED, scope,
+ chrome_policy);
+
+ // Load 3rd-party policy.
+ if (third_party_dict)
+ Load3rdPartyPolicy(third_party_dict.get(), scope, bundle.get());
+ }
+
+ return bundle;
+}
+
+void PolicyLoaderWin::LoadChromePolicy(const RegistryDict* gpo_dict,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicyMap* chrome_policy_map) {
+ PolicyMap policy;
+ const Schema* chrome_schema =
+ schema_map()->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
+ ParsePolicy(gpo_dict, level, scope, *chrome_schema, &policy);
+ if (ShouldFilterSensitivePolicies())
+ FilterSensitivePolicies(&policy);
+ chrome_policy_map->MergeFrom(policy);
+}
+
+void PolicyLoaderWin::Load3rdPartyPolicy(const RegistryDict* gpo_dict,
+ PolicyScope scope,
+ PolicyBundle* bundle) {
+ // Map of known 3rd party policy domain name to their enum values.
+ static const struct {
+ const char* name;
+ PolicyDomain domain;
+ } k3rdPartyDomains[] = {
+ {"extensions", POLICY_DOMAIN_EXTENSIONS},
+ };
+
+ // Policy level and corresponding path.
+ static const struct {
+ PolicyLevel level;
+ const char* path;
+ } kLevels[] = {
+ {POLICY_LEVEL_MANDATORY, kKeyMandatory},
+ {POLICY_LEVEL_RECOMMENDED, kKeyRecommended},
+ };
+
+ for (size_t i = 0; i < std::size(k3rdPartyDomains); i++) {
+ const char* name = k3rdPartyDomains[i].name;
+ const PolicyDomain domain = k3rdPartyDomains[i].domain;
+ const RegistryDict* domain_dict = gpo_dict->GetKey(name);
+ if (!domain_dict)
+ continue;
+
+ for (RegistryDict::KeyMap::const_iterator component(
+ domain_dict->keys().begin());
+ component != domain_dict->keys().end(); ++component) {
+ const PolicyNamespace policy_namespace(domain, component->first);
+
+ const Schema* schema_from_map = schema_map()->GetSchema(policy_namespace);
+ if (!schema_from_map) {
+ // This extension isn't installed or doesn't support policies.
+ continue;
+ }
+ Schema schema = *schema_from_map;
+
+ // Parse policy.
+ for (size_t j = 0; j < std::size(kLevels); j++) {
+ const RegistryDict* policy_dict =
+ component->second->GetKey(kLevels[j].path);
+ if (!policy_dict)
+ continue;
+
+ PolicyMap policy;
+ ParsePolicy(policy_dict, kLevels[j].level, scope, schema, &policy);
+ bundle->Get(policy_namespace).MergeFrom(policy);
+ }
+ }
+ }
+}
+
+void PolicyLoaderWin::SetupWatches() {
+ DCHECK(is_initialized_);
+ if (!user_policy_watcher_failed_ &&
+ !user_policy_watcher_.GetWatchedObject() &&
+ !user_policy_watcher_.StartWatchingOnce(
+ user_policy_changed_event_.handle(), this)) {
+ DLOG(WARNING) << "Failed to start watch for user policy change event";
+ user_policy_watcher_failed_ = true;
+ }
+ if (!machine_policy_watcher_failed_ &&
+ !machine_policy_watcher_.GetWatchedObject() &&
+ !machine_policy_watcher_.StartWatchingOnce(
+ machine_policy_changed_event_.handle(), this)) {
+ DLOG(WARNING) << "Failed to start watch for machine policy change event";
+ machine_policy_watcher_failed_ = true;
+ }
+}
+
+void PolicyLoaderWin::OnObjectSignaled(HANDLE object) {
+ DCHECK(object == user_policy_changed_event_.handle() ||
+ object == machine_policy_changed_event_.handle())
+ << "unexpected object signaled policy reload, obj = " << std::showbase
+ << std::hex << object;
+ Reload(false);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_loader_win.h b/chromium/components/policy/core/common/policy_loader_win.h
new file mode 100644
index 00000000000..d48c6c2cc29
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_win.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_WIN_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_WIN_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/win/object_watcher.h"
+#include "components/policy/core/common/async_policy_loader.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace policy {
+
+class PolicyMap;
+class RegistryDict;
+
+// Loads policies from the Windows registry, and watches for Group Policy
+// notifications to trigger reloads.
+class POLICY_EXPORT PolicyLoaderWin
+ : public AsyncPolicyLoader,
+ public base::win::ObjectWatcher::Delegate {
+ public:
+ PolicyLoaderWin(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key);
+ PolicyLoaderWin(const PolicyLoaderWin&) = delete;
+ PolicyLoaderWin& operator=(const PolicyLoaderWin&) = delete;
+ ~PolicyLoaderWin() override;
+
+ // Creates a policy loader that uses the Registry to access GPO.
+ static std::unique_ptr<PolicyLoaderWin> Create(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ ManagementService* management_service,
+ const std::wstring& chrome_policy_key);
+
+ // AsyncPolicyLoader implementation.
+ void InitOnBackgroundThread() override;
+ std::unique_ptr<PolicyBundle> Load() override;
+
+ private:
+ // Parses Chrome policy from |gpo_dict| for the given |scope| and |level| and
+ // merges it into |chrome_policy_map|.
+ void LoadChromePolicy(const RegistryDict* gpo_dict,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicyMap* chrome_policy_map);
+
+ // Loads 3rd-party policy from |gpo_dict| and merges it into |bundle|.
+ void Load3rdPartyPolicy(const RegistryDict* gpo_dict,
+ PolicyScope scope,
+ PolicyBundle* bundle);
+
+ // Installs the watchers for the Group Policy update events.
+ void SetupWatches();
+
+ // ObjectWatcher::Delegate overrides:
+ void OnObjectSignaled(HANDLE object) override;
+
+ bool is_initialized_;
+ const std::wstring chrome_policy_key_;
+
+ base::WaitableEvent user_policy_changed_event_;
+ base::WaitableEvent machine_policy_changed_event_;
+ base::win::ObjectWatcher user_policy_watcher_;
+ base::win::ObjectWatcher machine_policy_watcher_;
+ bool user_policy_watcher_failed_;
+ bool machine_policy_watcher_failed_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_WIN_H_
diff --git a/chromium/components/policy/core/common/policy_loader_win_unittest.cc b/chromium/components/policy/core/common/policy_loader_win_unittest.cc
new file mode 100644
index 00000000000..71472eeb856
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_loader_win_unittest.cc
@@ -0,0 +1,744 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_win.h"
+
+#include <windows.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <userenv.h>
+
+#include <algorithm>
+#include <cstring>
+#include <functional>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "base/win/registry.h"
+#include "base/win/win_util.h"
+#include "components/policy/core/common/async_policy_provider.h"
+#include "components/policy/core/common/configuration_policy_provider_test.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/management/platform_management_service.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::win::RegKey;
+
+namespace policy {
+
+namespace {
+
+// Constants for registry key names.
+const wchar_t kPathSep[] = L"\\";
+const wchar_t kThirdParty[] = L"3rdparty";
+const wchar_t kMandatory[] = L"policy";
+const wchar_t kRecommended[] = L"recommended";
+const wchar_t kTestPolicyKey[] = L"chrome.policy.key";
+
+// Installs |value| in the given registry |path| and |hive|, under the key
+// |name|. Returns false on errors.
+// Some of the possible Value types are stored after a conversion (e.g. doubles
+// are stored as strings), and can only be retrieved if a corresponding schema
+// is written.
+bool InstallValue(const base::Value& value,
+ HKEY hive,
+ const std::wstring& path,
+ const std::wstring& name) {
+ // KEY_ALL_ACCESS causes the ctor to create the key if it does not exist yet.
+ RegKey key(hive, path.c_str(), KEY_ALL_ACCESS);
+ EXPECT_TRUE(key.Valid());
+ switch (value.type()) {
+ case base::Value::Type::NONE:
+ return key.WriteValue(name.c_str(), L"") == ERROR_SUCCESS;
+
+ case base::Value::Type::BOOLEAN: {
+ if (!value.is_bool())
+ return false;
+ return key.WriteValue(name.c_str(), value.GetBool() ? 1 : 0) ==
+ ERROR_SUCCESS;
+ }
+
+ case base::Value::Type::INTEGER: {
+ if (!value.is_int())
+ return false;
+ return key.WriteValue(name.c_str(), value.GetInt()) == ERROR_SUCCESS;
+ }
+
+ case base::Value::Type::DOUBLE: {
+ std::wstring str_value = base::NumberToWString(value.GetDouble());
+ return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
+ }
+
+ case base::Value::Type::STRING: {
+ if (!value.is_string())
+ return false;
+ return key.WriteValue(
+ name.c_str(),
+ base::as_wcstr(base::UTF8ToUTF16(value.GetString()))) ==
+ ERROR_SUCCESS;
+ }
+
+ case base::Value::Type::DICTIONARY: {
+ if (!value.is_dict())
+ return false;
+ for (auto key_value : value.DictItems()) {
+ if (!InstallValue(key_value.second, hive, path + kPathSep + name,
+ base::UTF8ToWide(key_value.first))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ case base::Value::Type::LIST: {
+ if (!value.is_list())
+ return false;
+ const base::Value::ConstListView& list_view = value.GetListDeprecated();
+ for (size_t i = 0; i < list_view.size(); ++i) {
+ if (!InstallValue(list_view[i], hive, path + kPathSep + name,
+ base::NumberToWString(i + 1))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ case base::Value::Type::BINARY:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+// This class provides sandboxing and mocking for the parts of the Windows
+// Registry implementing Group Policy. It prepares two temporary sandbox keys,
+// one for HKLM and one for HKCU. A test's calls to the registry are redirected
+// by Windows to these sandboxes, allowing the tests to manipulate and access
+// policy as if it were active, but without actually changing the parts of the
+// Registry that are managed by Group Policy.
+class ScopedGroupPolicyRegistrySandbox {
+ public:
+ ScopedGroupPolicyRegistrySandbox();
+ ScopedGroupPolicyRegistrySandbox(const ScopedGroupPolicyRegistrySandbox&) =
+ delete;
+ ScopedGroupPolicyRegistrySandbox& operator=(
+ const ScopedGroupPolicyRegistrySandbox&) = delete;
+ ~ScopedGroupPolicyRegistrySandbox();
+
+ // Activates the registry keys overrides. This must be called before doing any
+ // writes to registry and the call should be wrapped in
+ // ASSERT_NO_FATAL_FAILURE.
+ void ActivateOverrides();
+
+ private:
+ void RemoveOverrides();
+
+ // Deletes the sandbox keys.
+ void DeleteKeys();
+
+ std::wstring key_name_;
+
+ // Keys are created for the lifetime of a test to contain
+ // the sandboxed HKCU and HKLM hives, respectively.
+ RegKey temp_hkcu_hive_key_;
+ RegKey temp_hklm_hive_key_;
+};
+
+// A test harness that feeds policy via the Chrome GPO registry subtree.
+class RegistryTestHarness : public PolicyProviderTestHarness {
+ public:
+ RegistryTestHarness(HKEY hive, PolicyScope scope);
+ RegistryTestHarness(const RegistryTestHarness&) = delete;
+ RegistryTestHarness& operator=(const RegistryTestHarness&) = delete;
+ ~RegistryTestHarness() override;
+
+ // PolicyProviderTestHarness:
+ void SetUp() override;
+
+ ConfigurationPolicyProvider* CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) override;
+
+ void InstallEmptyPolicy() override;
+ void InstallStringPolicy(const std::string& policy_name,
+ const std::string& policy_value) override;
+ void InstallIntegerPolicy(const std::string& policy_name,
+ int policy_value) override;
+ void InstallBooleanPolicy(const std::string& policy_name,
+ bool policy_value) override;
+ void InstallStringListPolicy(const std::string& policy_name,
+ const base::ListValue* policy_value) override;
+ void InstallDictionaryPolicy(const std::string& policy_name,
+ const base::Value* policy_value) override;
+ void Install3rdPartyPolicy(const base::DictionaryValue* policies) override;
+
+ // Creates a harness instance that will install policy in HKCU or HKLM,
+ // respectively.
+ static PolicyProviderTestHarness* CreateHKCU();
+ static PolicyProviderTestHarness* CreateHKLM();
+
+ private:
+ HKEY hive_;
+
+ ScopedGroupPolicyRegistrySandbox registry_sandbox_;
+};
+
+ScopedGroupPolicyRegistrySandbox::ScopedGroupPolicyRegistrySandbox() {}
+
+ScopedGroupPolicyRegistrySandbox::~ScopedGroupPolicyRegistrySandbox() {
+ RemoveOverrides();
+ DeleteKeys();
+}
+
+void ScopedGroupPolicyRegistrySandbox::ActivateOverrides() {
+ // Generate a unique registry key for the override for each test. This
+ // makes sure that tests executing in parallel won't delete each other's
+ // key, at DeleteKeys().
+ key_name_ = base::ASCIIToWide(base::StringPrintf(
+ "SOFTWARE\\chromium unittest %" CrPRIdPid, base::GetCurrentProcId()));
+ std::wstring hklm_key_name = key_name_ + L"\\HKLM";
+ std::wstring hkcu_key_name = key_name_ + L"\\HKCU";
+
+ // Delete the registry test keys if they already exist (this could happen if
+ // the process id got recycled and the last test running under the same
+ // process id crashed ).
+ DeleteKeys();
+
+ // Create the subkeys to hold the overridden HKLM and HKCU
+ // policy settings.
+ temp_hklm_hive_key_.Create(HKEY_CURRENT_USER,
+ hklm_key_name.c_str(),
+ KEY_ALL_ACCESS);
+ temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER,
+ hkcu_key_name.c_str(),
+ KEY_ALL_ACCESS);
+
+ auto result_override_hklm =
+ RegOverridePredefKey(HKEY_LOCAL_MACHINE, temp_hklm_hive_key_.Handle());
+ auto result_override_hkcu =
+ RegOverridePredefKey(HKEY_CURRENT_USER, temp_hkcu_hive_key_.Handle());
+
+ if (result_override_hklm != ERROR_SUCCESS ||
+ result_override_hkcu != ERROR_SUCCESS) {
+ // We need to remove the overrides first in case one succeeded and one
+ // failed, otherwise deleting the keys fails.
+ RemoveOverrides();
+ DeleteKeys();
+
+ // Assert on the actual results to print the error code in failure case.
+ ASSERT_HRESULT_SUCCEEDED(result_override_hklm);
+ ASSERT_HRESULT_SUCCEEDED(result_override_hkcu);
+ }
+}
+
+void ScopedGroupPolicyRegistrySandbox::RemoveOverrides() {
+ ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE, nullptr));
+ ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER, nullptr));
+}
+
+void ScopedGroupPolicyRegistrySandbox::DeleteKeys() {
+ RegKey key(HKEY_CURRENT_USER, key_name_.c_str(), KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ key.DeleteKey(L"");
+}
+
+RegistryTestHarness::RegistryTestHarness(HKEY hive, PolicyScope scope)
+ : PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, scope,
+ POLICY_SOURCE_PLATFORM),
+ hive_(hive) {
+}
+
+RegistryTestHarness::~RegistryTestHarness() {}
+
+void RegistryTestHarness::SetUp() {
+ // SetUp is called at gtest SetUp time, and gtest documentation guarantees
+ // that the test will not be executed if SetUp has a fatal failure. This is
+ // important, see crbug.com/721691.
+ ASSERT_NO_FATAL_FAILURE(registry_sandbox_.ActivateOverrides());
+}
+
+ConfigurationPolicyProvider* RegistryTestHarness::CreateProvider(
+ SchemaRegistry* registry,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ base::win::ScopedDomainStateForTesting scoped_domain(true);
+ std::unique_ptr<AsyncPolicyLoader> loader(new PolicyLoaderWin(
+ task_runner, PlatformManagementService::GetInstance(), kTestPolicyKey));
+ return new AsyncPolicyProvider(registry, std::move(loader));
+}
+
+void RegistryTestHarness::InstallEmptyPolicy() {}
+
+void RegistryTestHarness::InstallStringPolicy(
+ const std::string& policy_name,
+ const std::string& policy_value) {
+ RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ ASSERT_HRESULT_SUCCEEDED(
+ key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
+ base::UTF8ToWide(policy_value).c_str()));
+}
+
+void RegistryTestHarness::InstallIntegerPolicy(
+ const std::string& policy_name,
+ int policy_value) {
+ RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
+ static_cast<DWORD>(policy_value));
+}
+
+void RegistryTestHarness::InstallBooleanPolicy(
+ const std::string& policy_name,
+ bool policy_value) {
+ RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
+ static_cast<DWORD>(policy_value));
+}
+
+void RegistryTestHarness::InstallStringListPolicy(
+ const std::string& policy_name,
+ const base::ListValue* policy_value) {
+ RegKey key(
+ hive_,
+ (std::wstring(kTestPolicyKey) + L"\\" + base::UTF8ToWide(policy_name))
+ .c_str(),
+ KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ int index = 1;
+ for (const auto& element : policy_value->GetListDeprecated()) {
+ if (!element.is_string())
+ continue;
+
+ std::string name(base::NumberToString(index++));
+ key.WriteValue(base::UTF8ToWide(name).c_str(),
+ base::UTF8ToWide(element.GetString()).c_str());
+ }
+}
+
+void RegistryTestHarness::InstallDictionaryPolicy(
+ const std::string& policy_name,
+ const base::Value* policy_value) {
+ std::string json;
+ base::JSONWriter::Write(*policy_value, &json);
+ RegKey key(hive_, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(key.Valid());
+ key.WriteValue(base::UTF8ToWide(policy_name).c_str(),
+ base::UTF8ToWide(json).c_str());
+}
+
+void RegistryTestHarness::Install3rdPartyPolicy(
+ const base::DictionaryValue* policies) {
+ // The first level entries are domains, and the second level entries map
+ // components to their policy.
+ const std::wstring kPathPrefix =
+ std::wstring(kTestPolicyKey) + kPathSep + kThirdParty + kPathSep;
+ for (auto domain : policies->DictItems()) {
+ const base::Value& components = domain.second;
+ if (!components.is_dict()) {
+ ADD_FAILURE();
+ continue;
+ }
+ for (auto component : components.DictItems()) {
+ const std::wstring path = kPathPrefix + base::UTF8ToWide(domain.first) +
+ kPathSep + base::UTF8ToWide(component.first);
+ InstallValue(component.second, hive_, path, kMandatory);
+ }
+ }
+}
+
+// static
+PolicyProviderTestHarness* RegistryTestHarness::CreateHKCU() {
+ return new RegistryTestHarness(HKEY_CURRENT_USER, POLICY_SCOPE_USER);
+}
+
+// static
+PolicyProviderTestHarness* RegistryTestHarness::CreateHKLM() {
+ return new RegistryTestHarness(HKEY_LOCAL_MACHINE, POLICY_SCOPE_MACHINE);
+}
+
+} // namespace
+
+// Instantiate abstract test case for basic policy reading tests.
+INSTANTIATE_TEST_SUITE_P(PolicyProviderWinTest,
+ ConfigurationPolicyProviderTest,
+ testing::Values(RegistryTestHarness::CreateHKCU,
+ RegistryTestHarness::CreateHKLM));
+
+// Instantiate abstract test case for 3rd party policy reading tests.
+INSTANTIATE_TEST_SUITE_P(ThirdPartyPolicyProviderWinTest,
+ Configuration3rdPartyPolicyProviderTest,
+ testing::Values(RegistryTestHarness::CreateHKCU,
+ RegistryTestHarness::CreateHKLM));
+
+// Test cases for windows policy provider specific functionality.
+class PolicyLoaderWinTest : public PolicyTestBase {
+ protected:
+ // The policy key this tests places data under. This must match the data
+ // files in chrome/test/data/policy/gpo.
+ static const wchar_t kTestPolicyKey[];
+
+ PolicyLoaderWinTest() : scoped_domain_(false) {}
+ ~PolicyLoaderWinTest() override {}
+
+ void SetUp() override {
+ PolicyTestBase::SetUp();
+
+ // Activate overrides of registry keys. gtest documentation guarantees
+ // that the test will not be executed if SetUp has a fatal failure. This is
+ // important, see crbug.com/721691.
+ ASSERT_NO_FATAL_FAILURE(registry_sandbox_.ActivateOverrides());
+ }
+
+ bool Matches(const PolicyBundle& expected) {
+ PolicyLoaderWin loader(task_environment_.GetMainThreadTaskRunner(),
+ PlatformManagementService::GetInstance(),
+ kTestPolicyKey);
+ std::unique_ptr<PolicyBundle> loaded(
+ loader.InitialLoad(schema_registry_.schema_map()));
+ return loaded->Equals(expected);
+ }
+
+ ScopedGroupPolicyRegistrySandbox registry_sandbox_;
+ base::win::ScopedDomainStateForTesting scoped_domain_;
+};
+
+const wchar_t PolicyLoaderWinTest::kTestPolicyKey[] =
+ L"SOFTWARE\\Policies\\Chromium";
+
+TEST_F(PolicyLoaderWinTest, HKLMOverHKCU) {
+ RegKey hklm_key(HKEY_LOCAL_MACHINE, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(hklm_key.Valid());
+ hklm_key.WriteValue(base::UTF8ToWide(test_keys::kKeyString).c_str(),
+ base::UTF8ToWide("hklm").c_str());
+ RegKey hkcu_key(HKEY_CURRENT_USER, kTestPolicyKey, KEY_ALL_ACCESS);
+ ASSERT_TRUE(hkcu_key.Valid());
+ hkcu_key.WriteValue(base::UTF8ToWide(test_keys::kKeyString).c_str(),
+ base::UTF8ToWide("hkcu").c_str());
+
+ PolicyBundle expected;
+ expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set(test_keys::kKeyString, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value("hklm"), nullptr);
+ expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .GetMutable(test_keys::kKeyString)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+
+ PolicyMap::Entry conflict(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value("hkcu"),
+ nullptr);
+ expected.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .GetMutable(test_keys::kKeyString)
+ ->AddConflictingPolicy(std::move(conflict));
+ EXPECT_TRUE(Matches(expected));
+}
+
+TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) {
+ // Policy for the same extension will be provided at the 4 level/scope
+ // combinations, to verify that they overlap as expected.
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "merge");
+ ASSERT_TRUE(RegisterSchema(
+ ns,
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"a\": { \"type\": \"string\" },"
+ " \"b\": { \"type\": \"string\" },"
+ " \"c\": { \"type\": \"string\" },"
+ " \"d\": { \"type\": \"string\" }"
+ " }"
+ "}"));
+
+ const std::wstring kPathSuffix =
+ kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\merge");
+
+ const char kUserMandatory[] = "user-mandatory";
+ const char kUserRecommended[] = "user-recommended";
+ const char kMachineMandatory[] = "machine-mandatory";
+ const char kMachineRecommended[] = "machine-recommended";
+
+ base::DictionaryValue policy;
+ policy.SetStringKey("a", kMachineMandatory);
+ EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
+ kPathSuffix, kMandatory));
+ policy.SetStringKey("a", kUserMandatory);
+ policy.SetStringKey("b", kUserMandatory);
+ EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
+ kPathSuffix, kMandatory));
+ policy.SetStringKey("a", kMachineRecommended);
+ policy.SetStringKey("b", kMachineRecommended);
+ policy.SetStringKey("c", kMachineRecommended);
+ EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
+ kPathSuffix, kRecommended));
+ policy.SetStringKey("a", kUserRecommended);
+ policy.SetStringKey("b", kUserRecommended);
+ policy.SetStringKey("c", kUserRecommended);
+ policy.SetStringKey("d", kUserRecommended);
+ EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
+ kPathSuffix, kRecommended));
+
+ PolicyBundle expected;
+ PolicyMap& expected_policy = expected.Get(ns);
+ expected_policy.Set("a", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(kMachineMandatory),
+ nullptr);
+ expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected_policy.GetMutable("a")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+
+ PolicyMap::Entry a_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kMachineRecommended), nullptr);
+ PolicyMap::Entry a_conflict_2(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kUserMandatory), nullptr);
+ PolicyMap::Entry a_conflict_3(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kUserRecommended), nullptr);
+ expected_policy.GetMutable("a")->AddConflictingPolicy(
+ std::move(a_conflict_1));
+ expected_policy.GetMutable("a")->AddConflictingPolicy(
+ std::move(a_conflict_2));
+ expected_policy.GetMutable("a")->AddConflictingPolicy(
+ std::move(a_conflict_3));
+
+ expected_policy.Set("b", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(kUserMandatory),
+ nullptr);
+ expected_policy.GetMutable("b")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected_policy.GetMutable("b")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+
+ PolicyMap::Entry b_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kMachineRecommended), nullptr);
+ PolicyMap::Entry b_conflict_2(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kUserRecommended), nullptr);
+ expected_policy.GetMutable("b")->AddConflictingPolicy(
+ std::move(b_conflict_1));
+ expected_policy.GetMutable("b")->AddConflictingPolicy(
+ std::move(b_conflict_2));
+
+ expected_policy.Set("c", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(kMachineRecommended),
+ nullptr);
+ expected_policy.GetMutable("c")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+
+ PolicyMap::Entry c_conflict_1(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(kUserRecommended), nullptr);
+ expected_policy.GetMutable("c")->AddConflictingPolicy(
+ std::move(c_conflict_1));
+
+ expected_policy.Set("d", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(kUserRecommended),
+ nullptr);
+ EXPECT_TRUE(Matches(expected));
+}
+
+TEST_F(PolicyLoaderWinTest, LoadStringEncodedValues) {
+ // Create a dictionary with all the types that can be stored encoded in a
+ // string.
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "string");
+ ASSERT_TRUE(RegisterSchema(ns,
+ R"({
+ "type": "object",
+ "id": "MainType",
+ "properties": {
+ "bool": { "type": "boolean" },
+ "int": { "type": "integer" },
+ "double": { "type": "number" },
+ "list": {
+ "type": "array",
+ "items": { "$ref": "MainType" }
+ },
+ "dict": { "$ref": "MainType" }
+ }
+ })"));
+
+ base::DictionaryValue policy;
+ policy.SetBoolKey("bool", true);
+ policy.SetIntKey("int", -123);
+ policy.SetDoubleKey("double", 456.78e9);
+ base::ListValue list;
+ list.Append(std::make_unique<base::Value>(policy.Clone()));
+ list.Append(std::make_unique<base::Value>(policy.Clone()));
+ policy.SetKey("list", list.Clone());
+ // Encode |policy| before adding the "dict" entry.
+ std::string encoded_dict;
+ base::JSONWriter::Write(policy, &encoded_dict);
+ ASSERT_FALSE(encoded_dict.empty());
+ policy.SetKey("dict", policy.Clone());
+ std::string encoded_list;
+ base::JSONWriter::Write(list, &encoded_list);
+ ASSERT_FALSE(encoded_list.empty());
+ base::DictionaryValue encoded_policy;
+ encoded_policy.SetStringKey("bool", "1");
+ encoded_policy.SetStringKey("int", "-123");
+ encoded_policy.SetStringKey("double", "456.78e9");
+ encoded_policy.SetStringKey("list", encoded_list);
+ encoded_policy.SetStringKey("dict", encoded_dict);
+
+ const std::wstring kPathSuffix =
+ kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\string");
+ EXPECT_TRUE(
+ InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
+
+ PolicyBundle expected;
+ expected.Get(ns).LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+ EXPECT_TRUE(Matches(expected));
+}
+
+TEST_F(PolicyLoaderWinTest, LoadIntegerEncodedValues) {
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "int");
+ ASSERT_TRUE(RegisterSchema(
+ ns,
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"bool\": { \"type\": \"boolean\" },"
+ " \"int\": { \"type\": \"integer\" },"
+ " \"double\": { \"type\": \"number\" }"
+ " }"
+ "}"));
+
+ base::DictionaryValue encoded_policy;
+ encoded_policy.SetIntKey("bool", 1);
+ encoded_policy.SetIntKey("int", 123);
+ encoded_policy.SetIntKey("double", 456);
+
+ const std::wstring kPathSuffix =
+ kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\int");
+ EXPECT_TRUE(
+ InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
+
+ base::DictionaryValue policy;
+ policy.SetBoolKey("bool", true);
+ policy.SetIntKey("int", 123);
+ policy.SetDoubleKey("double", 456.0);
+ PolicyBundle expected;
+ expected.Get(ns).LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+ EXPECT_TRUE(Matches(expected));
+}
+
+TEST_F(PolicyLoaderWinTest, DefaultPropertySchemaType) {
+ // Build a schema for an "object" with a default schema for its properties.
+ // Note that the top-level object can't have "additionalProperties", so
+ // a "policy" property is used instead.
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "test");
+ ASSERT_TRUE(RegisterSchema(
+ ns,
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"policy\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"special-int1\": { \"type\": \"integer\" },"
+ " \"special-int2\": { \"type\": \"integer\" }"
+ " },"
+ " \"additionalProperties\": { \"type\": \"number\" }"
+ " }"
+ " }"
+ "}"));
+
+ // Write some test values.
+ base::DictionaryValue policy;
+ // These special values have a specific schema for them.
+ policy.SetIntKey("special-int1", 123);
+ policy.SetStringKey("special-int2", "-456");
+ // Other values default to be loaded as doubles.
+ policy.SetIntKey("double1", 789.0);
+ policy.SetStringKey("double2", "123.456e7");
+ policy.SetStringKey("invalid", "omg");
+ base::DictionaryValue all_policies;
+ all_policies.SetKey("policy", policy.Clone());
+
+ const std::wstring kPathSuffix =
+ kTestPolicyKey + std::wstring(L"\\3rdparty\\extensions\\test");
+ EXPECT_TRUE(
+ InstallValue(all_policies, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
+
+ base::DictionaryValue expected_policy;
+ expected_policy.SetIntKey("special-int1", 123);
+ expected_policy.SetIntKey("special-int2", -456);
+ expected_policy.SetDoubleKey("double1", 789.0);
+ expected_policy.SetDoubleKey("double2", 123.456e7);
+ base::DictionaryValue expected_policies;
+ expected_policies.SetKey("policy", expected_policy.Clone());
+ PolicyBundle expected;
+ expected.Get(ns).LoadFrom(&expected_policies, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM);
+ EXPECT_TRUE(Matches(expected));
+}
+
+TEST_F(PolicyLoaderWinTest, AlternativePropertySchemaType) {
+ const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"policy 1\": { \"type\": \"integer\" },"
+ " \"policy 2\": { \"type\": \"integer\" }"
+ " }"
+ "}";
+ // Register two namespaces. One will be completely populated with all defined
+ // properties and the second will be only partially populated.
+ const PolicyNamespace ns_a(
+ POLICY_DOMAIN_EXTENSIONS, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ const PolicyNamespace ns_b(
+ POLICY_DOMAIN_EXTENSIONS, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+ ASSERT_TRUE(RegisterSchema(ns_a, kTestSchema));
+ ASSERT_TRUE(RegisterSchema(ns_b, kTestSchema));
+
+ PolicyBundle expected;
+ base::DictionaryValue expected_a;
+ expected_a.SetIntKey("policy 1", 3);
+ expected_a.SetIntKey("policy 2", 3);
+ expected.Get(ns_a).LoadFrom(&expected_a, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM);
+ base::DictionaryValue expected_b;
+ expected_b.SetIntKey("policy 1", 2);
+ expected.Get(ns_b).LoadFrom(&expected_b, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM);
+
+ const std::wstring kPathSuffix =
+ kTestPolicyKey +
+ std::wstring(L"\\3rdparty\\extensions\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ EXPECT_TRUE(
+ InstallValue(expected_a, HKEY_LOCAL_MACHINE, kPathSuffix, kMandatory));
+ const std::wstring kPathSuffix2 =
+ kTestPolicyKey +
+ std::wstring(L"\\3rdparty\\extensions\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+ EXPECT_TRUE(
+ InstallValue(expected_b, HKEY_LOCAL_MACHINE, kPathSuffix2, kMandatory));
+
+ EXPECT_TRUE(Matches(expected));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_map.cc b/chromium/components/policy/core/common/policy_map.cc
new file mode 100644
index 00000000000..df79bf0ff34
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_map.cc
@@ -0,0 +1,662 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_map.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/stl_util.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/cloud/affiliation.h"
+#include "components/policy/core/common/policy_merger.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+const std::u16string GetLocalizedString(
+ PolicyMap::Entry::L10nLookupFunction lookup,
+ const std::map<int, absl::optional<std::vector<std::u16string>>>&
+ localized_string_ids) {
+ std::u16string result = std::u16string();
+ std::u16string line_feed = u"\n";
+ for (const auto& string_pairs : localized_string_ids) {
+ if (string_pairs.second)
+ result += l10n_util::GetStringFUTF16(
+ string_pairs.first, string_pairs.second.value(), nullptr);
+ else
+ result += lookup.Run(string_pairs.first);
+ result += line_feed;
+ }
+ // Remove the trailing newline.
+ if (!result.empty() && result[result.length() - 1] == line_feed[0])
+ result.pop_back();
+ return result;
+}
+
+// Inserts additional user affiliation IDs to the existing set.
+base::flat_set<std::string> CombineIds(
+ const base::flat_set<std::string>& ids_first,
+ const base::flat_set<std::string>& ids_second) {
+ base::flat_set<std::string> combined_ids;
+ combined_ids.insert(ids_first.begin(), ids_first.end());
+ combined_ids.insert(ids_second.begin(), ids_second.end());
+ return combined_ids;
+}
+
+#if !BUILDFLAG(IS_CHROMEOS)
+// Returns the calculated priority of the policy entry based on the policy's
+// scope and source, in addition to external factors such as precedence
+// metapolicy values. Used for browser policies.
+PolicyPriorityBrowser GetPriority(
+ PolicySource source,
+ PolicyScope scope,
+ bool cloud_policy_overrides_platform_policy,
+ bool cloud_user_policy_overrides_cloud_machine_policy,
+ bool is_user_affiliated) {
+ switch (source) {
+ case POLICY_SOURCE_ENTERPRISE_DEFAULT:
+ return POLICY_PRIORITY_BROWSER_ENTERPRISE_DEFAULT;
+ case POLICY_SOURCE_COMMAND_LINE:
+ return POLICY_PRIORITY_BROWSER_COMMAND_LINE;
+ case POLICY_SOURCE_CLOUD:
+ if (scope == POLICY_SCOPE_MACHINE) {
+ // Raise the priority of cloud machine policies only when the metapolicy
+ // CloudPolicyOverridesPlatformPolicy is set to true.
+ return cloud_policy_overrides_platform_policy
+ ? POLICY_PRIORITY_BROWSER_CLOUD_MACHINE_RAISED
+ : POLICY_PRIORITY_BROWSER_CLOUD_MACHINE;
+ }
+ if (cloud_user_policy_overrides_cloud_machine_policy &&
+ is_user_affiliated) {
+ // Raise the priority of cloud user policies only when the metapolicy
+ // CloudUserPolicyOverridesCloudMachinePolicy is set to true and the
+ // user is affiliated. Its priority relative to cloud machine policies
+ // also depends on the value of CloudPolicyOverridesPlatformPolicy.
+ return cloud_policy_overrides_platform_policy
+ ? POLICY_PRIORITY_BROWSER_CLOUD_USER_DOUBLE_RAISED
+ : POLICY_PRIORITY_BROWSER_CLOUD_USER_RAISED;
+ }
+ return POLICY_PRIORITY_BROWSER_CLOUD_USER;
+ case POLICY_SOURCE_PRIORITY_CLOUD_DEPRECATED:
+ return POLICY_PRIORITY_BROWSER_CLOUD_MACHINE_RAISED;
+ case POLICY_SOURCE_PLATFORM:
+ return scope == POLICY_SCOPE_MACHINE
+ ? POLICY_PRIORITY_BROWSER_PLATFORM_MACHINE
+ : POLICY_PRIORITY_BROWSER_PLATFORM_USER;
+ case POLICY_SOURCE_MERGED:
+ return POLICY_PRIORITY_BROWSER_MERGED;
+ default:
+ NOTREACHED();
+ return POLICY_PRIORITY_BROWSER_ENTERPRISE_DEFAULT;
+ }
+}
+#endif // BUILDFLAG(IS_CHROMEOS)
+
+} // namespace
+
+PolicyMap::Entry::Entry() = default;
+PolicyMap::Entry::Entry(
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ absl::optional<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher)
+ : level(level),
+ scope(scope),
+ source(source),
+ external_data_fetcher(std::move(external_data_fetcher)),
+ value_(std::move(value)) {}
+
+PolicyMap::Entry::~Entry() = default;
+
+PolicyMap::Entry::Entry(Entry&&) noexcept = default;
+PolicyMap::Entry& PolicyMap::Entry::operator=(Entry&&) noexcept = default;
+
+PolicyMap::Entry PolicyMap::Entry::DeepCopy() const {
+ Entry copy(level, scope, source,
+ value_ ? absl::make_optional<base::Value>(value_->Clone())
+ : absl::nullopt,
+ external_data_fetcher
+ ? std::make_unique<ExternalDataFetcher>(*external_data_fetcher)
+ : nullptr);
+ copy.ignored_ = ignored_;
+ copy.message_ids_ = message_ids_;
+ copy.is_default_value_ = is_default_value_;
+ copy.conflicts.reserve(conflicts.size());
+ for (const auto& conflict : conflicts) {
+ copy.AddConflictingPolicy(conflict.entry().DeepCopy());
+ }
+ return copy;
+}
+
+const base::Value* PolicyMap::Entry::value(base::Value::Type value_type) const {
+ const base::Value* value = value_unsafe();
+ return value && value->type() == value_type ? value : nullptr;
+}
+
+base::Value* PolicyMap::Entry::value(base::Value::Type value_type) {
+ base::Value* value = value_unsafe();
+ return value && value->type() == value_type ? value : nullptr;
+}
+
+const base::Value* PolicyMap::Entry::value_unsafe() const {
+ return base::OptionalOrNullptr(value_);
+}
+
+base::Value* PolicyMap::Entry::value_unsafe() {
+ return base::OptionalOrNullptr(value_);
+}
+
+void PolicyMap::Entry::set_value(absl::optional<base::Value> val) {
+ value_ = std::move(val);
+}
+
+bool PolicyMap::Entry::Equals(const PolicyMap::Entry& other) const {
+ bool conflicts_are_equal = conflicts.size() == other.conflicts.size();
+ for (size_t i = 0; conflicts_are_equal && i < conflicts.size(); ++i)
+ conflicts_are_equal &=
+ conflicts[i].entry().Equals(other.conflicts[i].entry());
+
+ const bool equals =
+ conflicts_are_equal && level == other.level && scope == other.scope &&
+ source == other.source && // Necessary for PolicyUIHandler observers.
+ // They have to update when sources change.
+ message_ids_ == other.message_ids_ &&
+ is_default_value_ == other.is_default_value_ &&
+ ((!value_ && !other.value_unsafe()) ||
+ (value_ && other.value_unsafe() && *value_ == *other.value_unsafe())) &&
+ ExternalDataFetcher::Equals(external_data_fetcher.get(),
+ other.external_data_fetcher.get());
+ return equals;
+}
+
+void PolicyMap::Entry::AddMessage(MessageType type, int message_id) {
+ message_ids_[type].emplace(message_id, absl::nullopt);
+}
+
+void PolicyMap::Entry::AddMessage(MessageType type,
+ int message_id,
+ std::vector<std::u16string>&& message_args) {
+ message_ids_[type].emplace(message_id, std::move(message_args));
+}
+
+void PolicyMap::Entry::ClearMessage(MessageType type, int message_id) {
+ if (message_ids_.find(type) == message_ids_.end() ||
+ message_ids_[type].find(message_id) == message_ids_[type].end()) {
+ return;
+ }
+ message_ids_[type].erase(message_id);
+ if (message_ids_[type].size() == 0)
+ message_ids_.erase(type);
+}
+
+void PolicyMap::Entry::AddConflictingPolicy(Entry&& conflict) {
+ // Move all of the newly conflicting Entry's conflicts into this Entry.
+ std::move(conflict.conflicts.begin(), conflict.conflicts.end(),
+ std::back_inserter(conflicts));
+
+ bool is_value_equal = (!this->value_unsafe() && !conflict.value_unsafe()) ||
+ (this->value_unsafe() && conflict.value_unsafe() &&
+ *this->value_unsafe() == *conflict.value_unsafe());
+
+ ConflictType type =
+ is_value_equal ? ConflictType::Supersede : ConflictType::Override;
+
+ // Clean up conflict Entry to ensure there's no duplication since entire Entry
+ // is moved and treated as a freshly constructed Entry.
+ conflict.ClearConflicts();
+ conflict.is_default_value_ = false;
+ conflict.message_ids_.clear();
+
+ // Avoid conflict nesting
+ conflicts.emplace_back(type, std::move(conflict));
+}
+
+void PolicyMap::Entry::ClearConflicts() {
+ conflicts.clear();
+ ClearMessage(MessageType::kInfo, IDS_POLICY_CONFLICT_SAME_VALUE);
+ ClearMessage(MessageType::kWarning, IDS_POLICY_CONFLICT_DIFF_VALUE);
+}
+
+bool PolicyMap::Entry::HasMessage(MessageType type) const {
+ return message_ids_.find(type) != message_ids_.end();
+}
+
+std::u16string PolicyMap::Entry::GetLocalizedMessages(
+ MessageType type,
+ L10nLookupFunction lookup) const {
+ if (!HasMessage(type)) {
+ return std::u16string();
+ }
+ return GetLocalizedString(lookup, message_ids_.at(type));
+}
+
+bool PolicyMap::Entry::ignored() const {
+ return ignored_;
+}
+
+void PolicyMap::Entry::SetIgnored() {
+ ignored_ = true;
+}
+
+void PolicyMap::Entry::SetBlocked() {
+ SetIgnored();
+ AddMessage(MessageType::kError, IDS_POLICY_BLOCKED);
+}
+
+void PolicyMap::Entry::SetInvalid() {
+ SetIgnored();
+ AddMessage(MessageType::kError, IDS_POLICY_INVALID);
+}
+
+void PolicyMap::Entry::SetIgnoredByPolicyAtomicGroup() {
+ SetIgnored();
+ AddMessage(MessageType::kError, IDS_POLICY_IGNORED_BY_GROUP_MERGING);
+}
+
+bool PolicyMap::Entry::IsIgnoredByAtomicGroup() const {
+ return message_ids_.find(MessageType::kError) != message_ids_.end() &&
+ message_ids_.at(MessageType::kError)
+ .find(IDS_POLICY_IGNORED_BY_GROUP_MERGING) !=
+ message_ids_.at(MessageType::kError).end();
+}
+
+void PolicyMap::Entry::SetIsDefaultValue() {
+ is_default_value_ = true;
+}
+
+bool PolicyMap::Entry::IsDefaultValue() const {
+ return is_default_value_;
+}
+
+PolicyMap::EntryConflict::EntryConflict() = default;
+PolicyMap::EntryConflict::EntryConflict(ConflictType type, Entry&& entry)
+ : conflict_type_(type), entry_(std::move(entry)) {}
+
+PolicyMap::EntryConflict::~EntryConflict() = default;
+
+PolicyMap::EntryConflict::EntryConflict(EntryConflict&&) noexcept = default;
+PolicyMap::EntryConflict& PolicyMap::EntryConflict::operator=(
+ EntryConflict&&) noexcept = default;
+
+void PolicyMap::EntryConflict::SetConflictType(ConflictType type) {
+ conflict_type_ = type;
+}
+
+PolicyMap::ConflictType PolicyMap::EntryConflict::conflict_type() const {
+ return conflict_type_;
+}
+
+const PolicyMap::Entry& PolicyMap::EntryConflict::entry() const {
+ return entry_;
+}
+
+PolicyMap::PolicyMap() = default;
+PolicyMap::PolicyMap(PolicyMap&&) noexcept = default;
+PolicyMap& PolicyMap::operator=(PolicyMap&&) noexcept = default;
+PolicyMap::~PolicyMap() = default;
+
+const PolicyMap::Entry* PolicyMap::Get(const std::string& policy) const {
+ auto entry = map_.find(policy);
+ return entry != map_.end() && !entry->second.ignored() ? &entry->second
+ : nullptr;
+}
+
+PolicyMap::Entry* PolicyMap::GetMutable(const std::string& policy) {
+ auto entry = map_.find(policy);
+ return entry != map_.end() && !entry->second.ignored() ? &entry->second
+ : nullptr;
+}
+
+const base::Value* PolicyMap::GetValue(const std::string& policy,
+ base::Value::Type value_type) const {
+ auto* entry = Get(policy);
+ return entry ? entry->value(value_type) : nullptr;
+}
+
+base::Value* PolicyMap::GetMutableValue(const std::string& policy,
+ base::Value::Type value_type) {
+ auto* entry = GetMutable(policy);
+ return entry ? entry->value(value_type) : nullptr;
+}
+
+const base::Value* PolicyMap::GetValueUnsafe(const std::string& policy) const {
+ auto* entry = Get(policy);
+ return entry ? entry->value_unsafe() : nullptr;
+}
+
+base::Value* PolicyMap::GetMutableValueUnsafe(const std::string& policy) {
+ auto* entry = GetMutable(policy);
+ return entry ? entry->value_unsafe() : nullptr;
+}
+
+const PolicyMap::Entry* PolicyMap::GetUntrusted(
+ const std::string& policy) const {
+ auto entry = map_.find(policy);
+ return entry != map_.end() ? &entry->second : nullptr;
+}
+
+PolicyMap::Entry* PolicyMap::GetMutableUntrusted(const std::string& policy) {
+ auto entry = map_.find(policy);
+ return entry != map_.end() ? &entry->second : nullptr;
+}
+
+bool PolicyMap::IsPolicySet(const std::string& policy) const {
+ return GetValueUnsafe(policy) != nullptr;
+}
+
+void PolicyMap::Set(
+ const std::string& policy,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ absl::optional<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher) {
+ Entry entry(level, scope, source, std::move(value),
+ std::move(external_data_fetcher));
+ Set(policy, std::move(entry));
+}
+
+void PolicyMap::Set(const std::string& policy, Entry entry) {
+ map_[policy] = std::move(entry);
+}
+
+void PolicyMap::AddMessage(const std::string& policy,
+ MessageType type,
+ int message_id) {
+ map_[policy].AddMessage(type, message_id);
+}
+
+void PolicyMap::AddMessage(const std::string& policy,
+ MessageType type,
+ int message_id,
+ std::vector<std::u16string>&& message_args) {
+ map_[policy].AddMessage(type, message_id, std::move(message_args));
+}
+
+bool PolicyMap::IsPolicyIgnoredByAtomicGroup(const std::string& policy) const {
+ const auto& entry = map_.find(policy);
+ return entry != map_.end() && entry->second.IsIgnoredByAtomicGroup();
+}
+
+void PolicyMap::SetSourceForAll(PolicySource source) {
+ for (auto& it : map_) {
+ it.second.source = source;
+ }
+}
+
+void PolicyMap::SetAllInvalid() {
+ for (auto& it : map_) {
+ it.second.SetInvalid();
+ }
+}
+
+void PolicyMap::Erase(const std::string& policy) {
+ map_.erase(policy);
+}
+
+PolicyMap::iterator PolicyMap::EraseIt(const_iterator it) {
+ return map_.erase(it);
+}
+
+void PolicyMap::EraseMatching(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter) {
+ FilterErase(filter, true);
+}
+
+void PolicyMap::EraseNonmatching(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter) {
+ FilterErase(filter, false);
+}
+
+void PolicyMap::Swap(PolicyMap* other) {
+ map_.swap(other->map_);
+}
+
+PolicyMap PolicyMap::Clone() const {
+ PolicyMap clone;
+ for (const auto& it : map_)
+ clone.Set(it.first, it.second.DeepCopy());
+
+ clone.cloud_policy_overrides_platform_policy_ =
+ cloud_policy_overrides_platform_policy_;
+ clone.cloud_user_policy_overrides_cloud_machine_policy_ =
+ cloud_user_policy_overrides_cloud_machine_policy_;
+ clone.SetUserAffiliationIds(user_affiliation_ids_);
+ clone.SetDeviceAffiliationIds(device_affiliation_ids_);
+
+ return clone;
+}
+
+void PolicyMap::MergePolicy(const std::string& policy_name,
+ const PolicyMap& other,
+ bool using_default_precedence) {
+ const Entry* other_policy = other.GetUntrusted(policy_name);
+ if (!other_policy)
+ return;
+
+ Entry* policy = GetMutableUntrusted(policy_name);
+ Entry other_policy_copy = other_policy->DeepCopy();
+
+ if (!policy) {
+ Set(policy_name, std::move(other_policy_copy));
+ return;
+ }
+
+#if BUILDFLAG(IS_CHROMEOS)
+ const bool other_is_higher_priority =
+ EntryHasHigherPriority(other_policy_copy, *policy);
+#else // BUILDFLAG(IS_CHROMEOS)
+ const bool other_is_higher_priority = EntryHasHigherPriority(
+ other_policy_copy, *policy, using_default_precedence);
+#endif // BUILDFLAG(IS_CHROMEOS)
+
+ Entry& higher_policy = other_is_higher_priority ? other_policy_copy : *policy;
+ Entry& conflicting_policy =
+ other_is_higher_priority ? *policy : other_policy_copy;
+
+ const bool overwriting_default_policy =
+ higher_policy.source != conflicting_policy.source &&
+ conflicting_policy.source == POLICY_SOURCE_ENTERPRISE_DEFAULT;
+ if (!overwriting_default_policy) {
+ policy->value_unsafe() &&
+ *other_policy_copy.value_unsafe() == *policy->value_unsafe()
+ ? higher_policy.AddMessage(MessageType::kInfo,
+ IDS_POLICY_CONFLICT_SAME_VALUE)
+ : higher_policy.AddMessage(MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ higher_policy.AddConflictingPolicy(std::move(conflicting_policy));
+ }
+
+ if (other_is_higher_priority)
+ *policy = std::move(other_policy_copy);
+}
+
+void PolicyMap::MergeFrom(const PolicyMap& other) {
+ DCHECK_NE(this, &other);
+ // Set affiliation IDs before merging policy values because user affiliation
+ // affects the policy precedence check.
+ SetUserAffiliationIds(
+ CombineIds(GetUserAffiliationIds(), other.GetUserAffiliationIds()));
+ SetDeviceAffiliationIds(
+ CombineIds(GetDeviceAffiliationIds(), other.GetDeviceAffiliationIds()));
+
+ // Precedence metapolicies are merged before all other policies, including
+ // merging metapolicies, because their value affects policy overriding.
+ for (auto* policy : metapolicy::kPrecedence) {
+ // Default precedence is used during merging of precedence metapolicies to
+ // prevent circular dependencies.
+ MergePolicy(policy, other, true);
+ }
+
+ UpdateStoredComputedMetapolicies();
+
+ for (const auto& policy_and_entry : other) {
+ // Skip precedence metapolicies since they have already been merged into the
+ // current PolicyMap.
+ if (std::find(std::begin(metapolicy::kPrecedence),
+ std::end(metapolicy::kPrecedence), policy_and_entry.first) !=
+ std::end(metapolicy::kPrecedence)) {
+ continue;
+ }
+
+ // External factors, such as the values of metapolicies, are considered.
+ MergePolicy(policy_and_entry.first, other, false);
+ }
+}
+
+void PolicyMap::MergeValues(const std::vector<PolicyMerger*>& mergers) {
+ for (const auto* it : mergers)
+ it->Merge(this);
+}
+
+void PolicyMap::LoadFrom(const base::DictionaryValue* policies,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source) {
+ for (base::DictionaryValue::Iterator it(*policies); !it.IsAtEnd();
+ it.Advance()) {
+ Set(it.key(), level, scope, source, it.value().Clone(), nullptr);
+ }
+}
+
+bool PolicyMap::Equals(const PolicyMap& other) const {
+ return other.size() == size() &&
+ std::equal(begin(), end(), other.begin(), MapEntryEquals);
+}
+
+bool PolicyMap::empty() const {
+ return map_.empty();
+}
+
+size_t PolicyMap::size() const {
+ return map_.size();
+}
+
+PolicyMap::const_iterator PolicyMap::begin() const {
+ return map_.begin();
+}
+
+PolicyMap::const_iterator PolicyMap::end() const {
+ return map_.end();
+}
+
+PolicyMap::iterator PolicyMap::begin() {
+ return map_.begin();
+}
+
+PolicyMap::iterator PolicyMap::end() {
+ return map_.end();
+}
+
+void PolicyMap::Clear() {
+ map_.clear();
+}
+
+// static
+bool PolicyMap::MapEntryEquals(const PolicyMap::PolicyMapType::value_type& a,
+ const PolicyMap::PolicyMapType::value_type& b) {
+ bool equals = a.first == b.first && a.second.Equals(b.second);
+ return equals;
+}
+
+void PolicyMap::FilterErase(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter,
+ bool deletion_value) {
+ auto iter(map_.begin());
+ while (iter != map_.end()) {
+ if (filter.Run(iter) == deletion_value) {
+ map_.erase(iter++);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+bool PolicyMap::EntryHasHigherPriority(const PolicyMap::Entry& lhs,
+ const PolicyMap::Entry& rhs) const {
+ return EntryHasHigherPriority(lhs, rhs, false);
+}
+
+bool PolicyMap::EntryHasHigherPriority(const PolicyMap::Entry& lhs,
+ const PolicyMap::Entry& rhs,
+ bool using_default_precedence) const {
+#if BUILDFLAG(IS_CHROMEOS)
+ return std::tie(lhs.level, lhs.scope, lhs.source) >
+ std::tie(rhs.level, rhs.scope, rhs.source);
+#else // BUILDFLAG(IS_CHROMEOS)
+ PolicyPriorityBrowser lhs_priority =
+ using_default_precedence
+ ? GetPriority(lhs.source, lhs.scope, false, false, false)
+ : GetPriority(lhs.source, lhs.scope,
+ cloud_policy_overrides_platform_policy_,
+ cloud_user_policy_overrides_cloud_machine_policy_,
+ is_user_affiliated_);
+ PolicyPriorityBrowser rhs_priority =
+ using_default_precedence
+ ? GetPriority(rhs.source, rhs.scope, false, false, false)
+ : GetPriority(rhs.source, rhs.scope,
+ cloud_policy_overrides_platform_policy_,
+ cloud_user_policy_overrides_cloud_machine_policy_,
+ is_user_affiliated_);
+ return std::tie(lhs.level, lhs_priority) > std::tie(rhs.level, rhs_priority);
+#endif // BUILDFLAG(IS_CHROMEOS)
+}
+
+bool PolicyMap::IsUserAffiliated() const {
+ return is_user_affiliated_;
+}
+
+void PolicyMap::SetUserAffiliationIds(
+ const base::flat_set<std::string>& user_ids) {
+ user_affiliation_ids_ = {user_ids.begin(), user_ids.end()};
+ UpdateStoredUserAffiliation();
+}
+
+const base::flat_set<std::string>& PolicyMap::GetUserAffiliationIds() const {
+ return user_affiliation_ids_;
+}
+
+void PolicyMap::SetDeviceAffiliationIds(
+ const base::flat_set<std::string>& device_ids) {
+ device_affiliation_ids_ = {device_ids.begin(), device_ids.end()};
+ UpdateStoredUserAffiliation();
+}
+
+const base::flat_set<std::string>& PolicyMap::GetDeviceAffiliationIds() const {
+ return device_affiliation_ids_;
+}
+
+void PolicyMap::UpdateStoredComputedMetapolicies() {
+ cloud_policy_overrides_platform_policy_ =
+ GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value::Type::BOOLEAN) &&
+ GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value::Type::BOOLEAN)
+ ->GetBool();
+
+ cloud_user_policy_overrides_cloud_machine_policy_ =
+ GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value::Type::BOOLEAN) &&
+ GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value::Type::BOOLEAN)
+ ->GetBool();
+}
+
+void PolicyMap::UpdateStoredUserAffiliation() {
+ is_user_affiliated_ =
+ IsAffiliated(user_affiliation_ids_, device_affiliation_ids_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_map.h b/chromium/components/policy/core/common/policy_map.h
new file mode 100644
index 00000000000..e11b2ba59a4
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_map.h
@@ -0,0 +1,371 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+class PolicyMerger;
+
+class PolicyMapTest;
+FORWARD_DECLARE_TEST(PolicyMapTest, BlockedEntry);
+FORWARD_DECLARE_TEST(PolicyMapTest, InvalidEntry);
+FORWARD_DECLARE_TEST(PolicyMapTest, MergeFrom);
+
+// A mapping of policy names to policy values for a given policy namespace.
+class POLICY_EXPORT PolicyMap {
+ public:
+ // Types of messages that can be associated with policies. New types must be
+ // added here in order to appear in the policy table.
+ enum class MessageType { kInfo, kWarning, kError };
+
+ // Types of conflicts that can be associated with policies. New conflict types
+ // must be added here in order to appear in the policy table.
+ enum class ConflictType { None, Override, Supersede };
+
+ // Forward declare class so that it can be used in Entry.
+ class EntryConflict;
+
+ // Each policy maps to an Entry which keeps the policy value as well as other
+ // relevant data about the policy.
+ class POLICY_EXPORT Entry {
+ public:
+ PolicyLevel level = POLICY_LEVEL_RECOMMENDED;
+ PolicyScope scope = POLICY_SCOPE_USER;
+ // For debugging and displaying only. Set by provider delivering the policy.
+ PolicySource source = POLICY_SOURCE_ENTERPRISE_DEFAULT;
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher;
+ std::vector<EntryConflict> conflicts;
+
+ Entry();
+ Entry(PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ absl::optional<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher);
+ ~Entry();
+
+ Entry(Entry&&) noexcept;
+ Entry& operator=(Entry&&) noexcept;
+
+ // Returns a copy of |this|.
+ Entry DeepCopy() const;
+
+ // Retrieves the stored value if its type matches the desired type,
+ // otherwise returns |nullptr|.
+ const base::Value* value(base::Value::Type value_type) const;
+ base::Value* value(base::Value::Type value_type);
+
+ // Retrieves the stored value without performing type checking. Use the
+ // type-checking versions above where possible.
+ const base::Value* value_unsafe() const;
+ base::Value* value_unsafe();
+
+ void set_value(absl::optional<base::Value> val);
+
+ // Returns true if |this| equals |other|.
+ bool Equals(const Entry& other) const;
+
+ // Add a localized message given its l10n message ID.
+ void AddMessage(MessageType type, int message_id);
+
+ // Add a localized message given its l10n message ID and placeholder
+ // args.
+ void AddMessage(MessageType type,
+ int message_id,
+ std::vector<std::u16string>&& message_args);
+
+ // Clear a message of a specific type given its l10n message ID.
+ void ClearMessage(MessageType type, int message_id);
+
+ // Adds a conflicting policy.
+ void AddConflictingPolicy(Entry&& conflict);
+
+ // Removes all the conflicts.
+ void ClearConflicts();
+
+ // Getter for |ignored_|.
+ bool ignored() const;
+ // Sets |ignored_| to true.
+ void SetIgnored();
+
+ // Marks the policy as blocked because it is not supported in the current
+ // environment.
+ void SetBlocked();
+
+ // Marks the policy as invalid because it failed to validate against the
+ // current schema.
+ void SetInvalid();
+
+ // Marks the policy as ignored because it does not share the priority of
+ // its policy atomic group.
+ void SetIgnoredByPolicyAtomicGroup();
+ bool IsIgnoredByAtomicGroup() const;
+
+ // Sets that the policy's value is a default value set by the policy
+ // provider.
+ void SetIsDefaultValue();
+ bool IsDefaultValue() const;
+
+ // Callback used to look up a localized string given its l10n message ID. It
+ // should return a UTF-16 string.
+ typedef base::RepeatingCallback<std::u16string(int message_id)>
+ L10nLookupFunction;
+
+ // Returns true if there is any message for |type|.
+ bool HasMessage(MessageType type) const;
+
+ // Returns localized messages as UTF-16 separated with LF characters. The
+ // messages are organized according to message types (Warning, Error, etc).
+ std::u16string GetLocalizedMessages(MessageType type,
+ L10nLookupFunction lookup) const;
+
+ private:
+ absl::optional<base::Value> value_;
+ bool ignored_ = false;
+ bool is_default_value_ = false;
+
+ // Stores all message IDs separated by message types.
+ std::map<MessageType,
+ std::map<int, absl::optional<std::vector<std::u16string>>>>
+ message_ids_;
+ };
+
+ // Associates an Entry with a ConflictType.
+ class POLICY_EXPORT EntryConflict {
+ public:
+ EntryConflict();
+ EntryConflict(ConflictType type, Entry&& entry);
+ ~EntryConflict();
+
+ EntryConflict(EntryConflict&&) noexcept;
+ EntryConflict& operator=(EntryConflict&&) noexcept;
+
+ // Accessor methods for conflict type.
+ void SetConflictType(ConflictType type);
+ ConflictType conflict_type() const;
+
+ // Accessor method for entry.
+ const Entry& entry() const;
+
+ private:
+ ConflictType conflict_type_;
+ Entry entry_;
+ };
+
+ typedef std::map<std::string, Entry> PolicyMapType;
+ typedef PolicyMapType::const_iterator const_iterator;
+ typedef PolicyMapType::iterator iterator;
+
+ PolicyMap();
+ PolicyMap(const PolicyMap&) = delete;
+ PolicyMap& operator=(const PolicyMap&) = delete;
+ PolicyMap(PolicyMap&& other) noexcept;
+ PolicyMap& operator=(PolicyMap&& other) noexcept;
+ ~PolicyMap();
+
+ // Returns a weak reference to the entry currently stored for key |policy|,
+ // or NULL if untrusted or not found. Ownership is retained by the PolicyMap.
+ const Entry* Get(const std::string& policy) const;
+ Entry* GetMutable(const std::string& policy);
+
+ // Returns a weak reference to the value currently stored for key |policy| if
+ // the value type matches the requested type, otherwise returns |nullptr| if
+ // not found or there is a type mismatch. Ownership is retained by the
+ // |PolicyMap|.
+ const base::Value* GetValue(const std::string& policy,
+ base::Value::Type value_type) const;
+ base::Value* GetMutableValue(const std::string& policy,
+ base::Value::Type value_type);
+
+ // Returns a weak reference to the value currently stored for key |policy|
+ // without performing type checking, otherwise returns |nullptr| if not found.
+ // Ownership is retained by the |PolicyMap|. Use the type-checking versions
+ // above where possible.
+ const base::Value* GetValueUnsafe(const std::string& policy) const;
+ base::Value* GetMutableValueUnsafe(const std::string& policy);
+
+ // Returns true if the policy has a non-null value set.
+ bool IsPolicySet(const std::string& policy) const;
+
+ // Overwrites any existing information stored in the map for the key |policy|.
+ // Resets the error for that policy to the empty string.
+ void Set(const std::string& policy,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source,
+ absl::optional<base::Value> value,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher);
+
+ void Set(const std::string& policy, Entry entry);
+
+ // Adds a localized message with |message_id| to the map for the key |policy|
+ // that should be shown to the user alongisde the value in the policy UI. This
+ // should only be called for policies that are already stored in the map.
+ void AddMessage(const std::string& policy, MessageType type, int message_id);
+
+ // Adds a localized message with |message_id| and placeholder arguments
+ // |message_args| to the map for the key |policy| that should be shown to the
+ // user alongisde the value in the policy UI. The number of placeholders in
+ // the policy string corresponding to |message_id| must be equal to the number
+ // of arguments in |message_args|. This should only be called for policies
+ // that are already stored in the map.
+ void AddMessage(const std::string& policy,
+ MessageType type,
+ int message_id,
+ std::vector<std::u16string>&& message_args);
+
+ // Return True if the policy is set but its value is ignored because it does
+ // not share the highest priority from its atomic group. Returns False if the
+ // policy is active or not set.
+ bool IsPolicyIgnoredByAtomicGroup(const std::string& policy) const;
+
+ // For all policies, overwrite the PolicySource with |source|.
+ void SetSourceForAll(PolicySource source);
+
+ // For all policies, mark them as invalid, e.g. when a required schema failed
+ // to load.
+ void SetAllInvalid();
+
+ // Erase the given |policy|, if it exists in this map.
+ void Erase(const std::string& policy);
+
+ // Erase the given iterator |it|. Returns the iterator following |it| (which
+ // could be `map_.end()`).
+ iterator EraseIt(const_iterator it);
+
+ // Erase all entries for which |filter| returns true.
+ void EraseMatching(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter);
+
+ // Erase all entries for which |filter| returns false.
+ void EraseNonmatching(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter);
+
+ // Swaps the internal representation of |this| with |other|.
+ void Swap(PolicyMap* other);
+
+ // Returns a copy of |this|.
+ PolicyMap Clone() const;
+
+ // Helper method used to merge entries corresponding to the same policy.
+ // Setting |using_default_precedence| to true results in external factors,
+ // such as the value of precedence metapolicies and user affiliation, to be
+ // considered during the priority check.
+ void MergePolicy(const std::string& policy_name,
+ const PolicyMap& other,
+ bool using_default_precedence);
+
+ // Merges policies from |other| into |this|. Existing policies are only
+ // overridden by those in |other| if they have a higher priority, as defined
+ // by EntryHasHigherPriority(). If a policy is contained in both maps with the
+ // same priority, the current value in |this| is preserved.
+ void MergeFrom(const PolicyMap& other);
+
+ // Merge the policy values that are coming from different sources.
+ void MergeValues(const std::vector<PolicyMerger*>& mergers);
+
+ // Loads the values in |policies| into this PolicyMap. All policies loaded
+ // will have |level|, |scope| and |source| in their entries. Existing entries
+ // are replaced.
+ void LoadFrom(const base::DictionaryValue* policies,
+ PolicyLevel level,
+ PolicyScope scope,
+ PolicySource source);
+
+ // Returns true if |lhs| has higher priority than |rhs|. The priority of the
+ // fields are |level| > |PolicyPriority| for browser and |level| > |scope| >
+ // |source| for OS. External factors such as metapolicy values are considered
+ // by default for browser policies.
+ bool EntryHasHigherPriority(const PolicyMap::Entry& lhs,
+ const PolicyMap::Entry& rhs) const;
+
+ // Returns true if |lhs| has higher priority than |rhs|. The priority of the
+ // fields are |level| > |PolicyPriority| for browser and |level| > |scope| >
+ // |source| for OS. External factors such as metapolicy values and user
+ // affiliation are optionally considered.
+ bool EntryHasHigherPriority(const PolicyMap::Entry& lhs,
+ const PolicyMap::Entry& rhs,
+ bool using_default_precedence) const;
+
+ // Returns True if at least one shared ID is found in the user and device
+ // affiliation ID sets.
+ bool IsUserAffiliated() const;
+
+ // Populates the set containing user affiliation ID strings.
+ void SetUserAffiliationIds(const base::flat_set<std::string>& user_ids);
+
+ // Returns the set containing user affiliation ID strings.
+ const base::flat_set<std::string>& GetUserAffiliationIds() const;
+
+ // Populates the set containing device affiliation ID strings.
+ void SetDeviceAffiliationIds(const base::flat_set<std::string>& device_ids);
+
+ // Returns the set containing device affiliation ID strings.
+ const base::flat_set<std::string>& GetDeviceAffiliationIds() const;
+
+ bool Equals(const PolicyMap& other) const;
+ bool empty() const;
+ size_t size() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+ iterator begin();
+ iterator end();
+ void Clear();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(PolicyMapTest, BlockedEntry);
+ FRIEND_TEST_ALL_PREFIXES(PolicyMapTest, InvalidEntry);
+ FRIEND_TEST_ALL_PREFIXES(PolicyMapTest, MergeFrom);
+
+ // Returns a weak reference to the entry currently stored for key |policy|,
+ // or NULL if not found. Ownership is retained by the PolicyMap.
+ const Entry* GetUntrusted(const std::string& policy) const;
+ Entry* GetMutableUntrusted(const std::string& policy);
+
+ // Helper function for Equals().
+ static bool MapEntryEquals(const PolicyMapType::value_type& a,
+ const PolicyMapType::value_type& b);
+
+ // Erase all entries for which |filter| returns |deletion_value|.
+ void FilterErase(
+ const base::RepeatingCallback<bool(const const_iterator)>& filter,
+ bool deletion_value);
+
+ // Updates the stored state of computed metapolicies.
+ void UpdateStoredComputedMetapolicies();
+
+ // Updates the stored state of user affiliation.
+ void UpdateStoredUserAffiliation();
+
+ PolicyMapType map_;
+
+ // Affiliation
+ bool is_user_affiliated_ = false;
+ bool cloud_policy_overrides_platform_policy_ = false;
+ bool cloud_user_policy_overrides_cloud_machine_policy_ = false;
+ base::flat_set<std::string> user_affiliation_ids_;
+ base::flat_set<std::string> device_affiliation_ids_;
+};
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_MAP_H_
diff --git a/chromium/components/policy/core/common/policy_map_unittest.cc b/chromium/components/policy/core/common/policy_map_unittest.cc
new file mode 100644
index 00000000000..6501adf495b
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_map_unittest.cc
@@ -0,0 +1,1807 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_map.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/core/common/policy_merger.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+namespace {
+
+// Dummy policy names.
+const char kTestPolicyName1[] = "policy.test.1";
+const char kTestPolicyName2[] = "policy.test.2";
+const char kTestPolicyName3[] = "policy.test.3";
+const char kTestPolicyName4[] = "policy.test.4";
+const char kTestPolicyName5[] = "policy.test.5";
+const char kTestPolicyName6[] = "policy.test.6";
+const char kTestPolicyName7[] = "policy.test.7";
+const char kTestPolicyName8[] = "policy.test.8";
+
+// Dummy error message.
+const char16_t kTestError[] = u"Test error message";
+
+// Utility functions for the tests.
+void SetPolicy(PolicyMap* map, const char* name, base::Value value) {
+ map->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(value), nullptr);
+}
+
+void SetPolicy(PolicyMap* map,
+ const char* name,
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher) {
+ map->Set(name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ absl::nullopt, std::move(external_data_fetcher));
+}
+
+template <class T>
+std::vector<base::Value> GetListStorage(const std::vector<T> entry) {
+ std::vector<base::Value> result;
+ for (const auto& it : entry)
+ result.emplace_back(base::Value(it));
+ return result;
+}
+
+} // namespace
+
+class PolicyMapTestBase {
+ protected:
+ std::unique_ptr<ExternalDataFetcher> CreateExternalDataFetcher(
+ const std::string& policy) const;
+};
+
+std::unique_ptr<ExternalDataFetcher>
+PolicyMapTestBase::CreateExternalDataFetcher(const std::string& policy) const {
+ return std::make_unique<ExternalDataFetcher>(
+ base::WeakPtr<ExternalDataManager>(), policy);
+}
+
+class PolicyMapTest : public PolicyMapTestBase, public testing::Test {};
+
+TEST_F(PolicyMapTest, SetAndGet) {
+ PolicyMap map;
+ EXPECT_FALSE(map.IsPolicySet(kTestPolicyName1));
+ SetPolicy(&map, kTestPolicyName1, base::Value("aaa"));
+ EXPECT_TRUE(map.IsPolicySet(kTestPolicyName1));
+ const base::Value kExpectedStringA("aaa");
+ EXPECT_EQ(kExpectedStringA, *map.GetValueUnsafe(kTestPolicyName1));
+ EXPECT_EQ(kExpectedStringA,
+ *map.GetValue(kTestPolicyName1, base::Value::Type::STRING));
+ EXPECT_EQ(nullptr,
+ map.GetValue(kTestPolicyName1, base::Value::Type::BOOLEAN));
+
+ SetPolicy(&map, kTestPolicyName1, base::Value("bbb"));
+ EXPECT_TRUE(map.IsPolicySet(kTestPolicyName1));
+ const base::Value kExpectedStringB("bbb");
+ EXPECT_EQ(kExpectedStringB, *map.GetValueUnsafe(kTestPolicyName1));
+ EXPECT_EQ(kExpectedStringB,
+ *map.GetValue(kTestPolicyName1, base::Value::Type::STRING));
+ EXPECT_EQ(nullptr,
+ map.GetValue(kTestPolicyName1, base::Value::Type::BOOLEAN));
+
+ SetPolicy(&map, kTestPolicyName1, base::Value(true));
+ EXPECT_TRUE(map.IsPolicySet(kTestPolicyName1));
+ const base::Value kExpectedBool(true);
+ EXPECT_EQ(kExpectedBool, *map.GetValueUnsafe(kTestPolicyName1));
+ EXPECT_EQ(nullptr, map.GetValue(kTestPolicyName1, base::Value::Type::STRING));
+ EXPECT_EQ(kExpectedBool,
+ *map.GetValue(kTestPolicyName1, base::Value::Type::BOOLEAN));
+
+ SetPolicy(&map, kTestPolicyName1, CreateExternalDataFetcher("dummy"));
+ EXPECT_FALSE(map.IsPolicySet(kTestPolicyName1));
+ map.AddMessage(kTestPolicyName1, PolicyMap::MessageType::kError,
+ IDS_POLICY_STORE_STATUS_VALIDATION_ERROR, {kTestError});
+ EXPECT_EQ(nullptr, map.GetValueUnsafe(kTestPolicyName1));
+ const PolicyMap::Entry* entry = map.Get(kTestPolicyName1);
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(POLICY_LEVEL_MANDATORY, entry->level);
+ EXPECT_EQ(POLICY_SCOPE_USER, entry->scope);
+ EXPECT_EQ(POLICY_SOURCE_CLOUD, entry->source);
+ std::u16string error_string =
+ base::StrCat({u"Validation error: ", kTestError});
+ PolicyMap::Entry::L10nLookupFunction lookup = base::BindRepeating(
+ static_cast<std::u16string (*)(int)>(&base::NumberToString16));
+ EXPECT_EQ(error_string, entry->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_TRUE(
+ ExternalDataFetcher::Equals(entry->external_data_fetcher.get(),
+ CreateExternalDataFetcher("dummy").get()));
+ map.Set(kTestPolicyName1, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, absl::nullopt, nullptr);
+ EXPECT_FALSE(map.IsPolicySet(kTestPolicyName1));
+ EXPECT_EQ(nullptr, map.GetValueUnsafe(kTestPolicyName1));
+ entry = map.Get(kTestPolicyName1);
+ ASSERT_NE(nullptr, entry);
+ EXPECT_EQ(POLICY_LEVEL_RECOMMENDED, entry->level);
+ EXPECT_EQ(POLICY_SCOPE_MACHINE, entry->scope);
+ EXPECT_EQ(POLICY_SOURCE_ENTERPRISE_DEFAULT, entry->source);
+ EXPECT_EQ(std::u16string(), entry->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_FALSE(entry->external_data_fetcher);
+}
+
+TEST_F(PolicyMapTest, AddMessage_Error) {
+ PolicyMap map;
+ SetPolicy(&map, kTestPolicyName1, base::Value(0));
+ PolicyMap::Entry* entry1 = map.GetMutable(kTestPolicyName1);
+ PolicyMap::Entry::L10nLookupFunction lookup = base::BindRepeating(
+ static_cast<std::u16string (*)(int)>(&base::NumberToString16));
+ EXPECT_EQ(std::u16string(), entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ map.AddMessage(kTestPolicyName1, PolicyMap::MessageType::kError, 1234);
+ EXPECT_EQ(u"1234", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ map.AddMessage(kTestPolicyName1, PolicyMap::MessageType::kError, 5678);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+
+ // Add second entry to make sure errors are added individually.
+ SetPolicy(&map, kTestPolicyName2, base::Value(0));
+ PolicyMap::Entry* entry2 = map.GetMutable(kTestPolicyName2);
+ // Test adding Error message with placeholder replacement (one arg)
+ map.AddMessage(kTestPolicyName2, PolicyMap::MessageType::kError,
+ IDS_POLICY_MIGRATED_OLD_POLICY, {u"SomeNewPolicy"});
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_EQ(
+ u"This policy is deprecated. You should use the "
+ u"SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kError, lookup));
+ map.AddMessage(kTestPolicyName2, PolicyMap::MessageType::kError, 1357);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kError, lookup));
+ // Test adding Error message with placeholder replacement (two args)
+ map.AddMessage(kTestPolicyName1, PolicyMap::MessageType::kError,
+ IDS_POLICY_DLP_CLIPBOARD_BLOCKED_ON_COPY_VM,
+ {u"SomeSource", u"SomeDestination"});
+ EXPECT_EQ(
+ u"1234\n5678\nSharing from SomeSource to SomeDestination has "
+ u"been blocked by administrator policy",
+ entry1->GetLocalizedMessages(PolicyMap::MessageType::kError, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kError, lookup));
+
+ // Ensure other message types are empty
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+}
+
+TEST_F(PolicyMapTest, AddMessage_Warning) {
+ PolicyMap map;
+ SetPolicy(&map, kTestPolicyName1, base::Value(0));
+ PolicyMap::Entry* entry1 = map.GetMutable(kTestPolicyName1);
+ PolicyMap::Entry::L10nLookupFunction lookup = base::BindRepeating(
+ static_cast<std::u16string (*)(int)>(&base::NumberToString16));
+ EXPECT_EQ(std::u16string(), entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+ entry1->AddMessage(PolicyMap::MessageType::kWarning, 1234);
+ EXPECT_EQ(u"1234", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+ entry1->AddMessage(PolicyMap::MessageType::kWarning, 5678);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+
+ // Add second entry to make sure warnings are added individually.
+ SetPolicy(&map, kTestPolicyName2, base::Value(0));
+ PolicyMap::Entry* entry2 = map.GetMutable(kTestPolicyName2);
+ // Test adding Warning message with placeholder replacement (one arg)
+ entry2->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_MIGRATED_OLD_POLICY, {u"SomeNewPolicy"});
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+ EXPECT_EQ(
+ u"This policy is deprecated. You should use the "
+ u"SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kWarning, lookup));
+ entry2->AddMessage(PolicyMap::MessageType::kWarning, 1357);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kWarning, lookup));
+ // Test adding Warning message with placeholder replacement (two args)
+ entry1->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_DLP_CLIPBOARD_BLOCKED_ON_COPY_VM,
+ {u"SomeSource", u"SomeDestination"});
+ EXPECT_EQ(
+ u"1234\n5678\nSharing from SomeSource to SomeDestination has "
+ u"been blocked by administrator policy",
+ entry1->GetLocalizedMessages(PolicyMap::MessageType::kWarning, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kWarning, lookup));
+
+ // Ensure other message types are empty
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+}
+
+TEST_F(PolicyMapTest, AddMessage_Info) {
+ PolicyMap map;
+ SetPolicy(&map, kTestPolicyName1, base::Value(0));
+ PolicyMap::Entry* entry1 = map.GetMutable(kTestPolicyName1);
+ PolicyMap::Entry::L10nLookupFunction lookup = base::BindRepeating(
+ static_cast<std::u16string (*)(int)>(&base::NumberToString16));
+ EXPECT_EQ(std::u16string(), entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+ entry1->AddMessage(PolicyMap::MessageType::kInfo, 1234);
+ EXPECT_EQ(u"1234", entry1->GetLocalizedMessages(PolicyMap::MessageType::kInfo,
+ lookup));
+ entry1->AddMessage(PolicyMap::MessageType::kInfo, 5678);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+
+ // Add second entry to make sure messages are added individually.
+ SetPolicy(&map, kTestPolicyName2, base::Value(0));
+ PolicyMap::Entry* entry2 = map.GetMutable(kTestPolicyName2);
+ // Test adding Info message with placeholder replacement (one arg)
+ entry2->AddMessage(PolicyMap::MessageType::kInfo,
+ IDS_POLICY_MIGRATED_OLD_POLICY, {u"SomeNewPolicy"});
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+ EXPECT_EQ(
+ u"This policy is deprecated. You should use the "
+ u"SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kInfo, lookup));
+ entry2->AddMessage(PolicyMap::MessageType::kInfo, 1357);
+ EXPECT_EQ(u"1234\n5678", entry1->GetLocalizedMessages(
+ PolicyMap::MessageType::kInfo, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kInfo, lookup));
+ // Test adding Info message with placeholder replacement (two args)
+ entry1->AddMessage(PolicyMap::MessageType::kInfo,
+ IDS_POLICY_DLP_CLIPBOARD_BLOCKED_ON_COPY_VM,
+ {u"SomeSource", u"SomeDestination"});
+ EXPECT_EQ(
+ u"1234\n5678\nSharing from SomeSource to SomeDestination has "
+ u"been blocked by administrator policy",
+ entry1->GetLocalizedMessages(PolicyMap::MessageType::kInfo, lookup));
+ EXPECT_EQ(
+ u"1357\nThis policy is deprecated. You should use "
+ u"the SomeNewPolicy policy instead.",
+ entry2->GetLocalizedMessages(PolicyMap::MessageType::kInfo, lookup));
+
+ // Ensure other message types are empty
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kError, lookup));
+ EXPECT_EQ(std::u16string(), entry2->GetLocalizedMessages(
+ PolicyMap::MessageType::kWarning, lookup));
+}
+
+TEST_F(PolicyMapTest, Equals) {
+ PolicyMap a;
+ SetPolicy(&a, kTestPolicyName1, base::Value("aaa"));
+ PolicyMap a2;
+ SetPolicy(&a2, kTestPolicyName1, base::Value("aaa"));
+ PolicyMap b;
+ SetPolicy(&b, kTestPolicyName1, base::Value("bbb"));
+ PolicyMap c;
+ SetPolicy(&c, kTestPolicyName1, base::Value("aaa"));
+ SetPolicy(&c, kTestPolicyName2, base::Value(true));
+ PolicyMap d;
+ SetPolicy(&d, kTestPolicyName1, CreateExternalDataFetcher("ddd"));
+ PolicyMap d2;
+ SetPolicy(&d2, kTestPolicyName1, CreateExternalDataFetcher("ddd"));
+ PolicyMap e;
+ SetPolicy(&e, kTestPolicyName1, CreateExternalDataFetcher("eee"));
+ EXPECT_FALSE(a.Equals(b));
+ EXPECT_FALSE(a.Equals(c));
+ EXPECT_FALSE(a.Equals(d));
+ EXPECT_FALSE(a.Equals(e));
+ EXPECT_FALSE(b.Equals(a));
+ EXPECT_FALSE(b.Equals(c));
+ EXPECT_FALSE(b.Equals(d));
+ EXPECT_FALSE(b.Equals(e));
+ EXPECT_FALSE(c.Equals(a));
+ EXPECT_FALSE(c.Equals(b));
+ EXPECT_FALSE(c.Equals(d));
+ EXPECT_FALSE(c.Equals(e));
+ EXPECT_FALSE(d.Equals(a));
+ EXPECT_FALSE(d.Equals(b));
+ EXPECT_FALSE(d.Equals(c));
+ EXPECT_FALSE(d.Equals(e));
+ EXPECT_FALSE(e.Equals(a));
+ EXPECT_FALSE(e.Equals(b));
+ EXPECT_FALSE(e.Equals(c));
+ EXPECT_FALSE(e.Equals(d));
+ EXPECT_TRUE(a.Equals(a2));
+ EXPECT_TRUE(a2.Equals(a));
+ EXPECT_TRUE(d.Equals(d2));
+ EXPECT_TRUE(d2.Equals(d));
+ PolicyMap empty1;
+ PolicyMap empty2;
+ EXPECT_TRUE(empty1.Equals(empty2));
+ EXPECT_TRUE(empty2.Equals(empty1));
+ EXPECT_FALSE(empty1.Equals(a));
+ EXPECT_FALSE(a.Equals(empty1));
+}
+
+TEST_F(PolicyMapTest, Swap) {
+ PolicyMap a;
+ SetPolicy(&a, kTestPolicyName1, base::Value("aaa"));
+ SetPolicy(&a, kTestPolicyName2, CreateExternalDataFetcher("dummy"));
+ PolicyMap b;
+ SetPolicy(&b, kTestPolicyName1, base::Value("bbb"));
+ SetPolicy(&b, kTestPolicyName3, base::Value(true));
+
+ a.Swap(&b);
+ const base::Value kExpectedStringB("bbb");
+ EXPECT_EQ(kExpectedStringB,
+ *a.GetValue(kTestPolicyName1, base::Value::Type::STRING));
+ const base::Value kExpectedBool(true);
+ EXPECT_EQ(kExpectedBool,
+ *a.GetValue(kTestPolicyName3, base::Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, a.GetValueUnsafe(kTestPolicyName2));
+ EXPECT_EQ(nullptr, a.Get(kTestPolicyName2));
+ const base::Value kExpectedStringA("aaa");
+ EXPECT_EQ(kExpectedStringA,
+ *b.GetValue(kTestPolicyName1, base::Value::Type::STRING));
+ EXPECT_EQ(nullptr, b.GetValueUnsafe(kTestPolicyName3));
+ EXPECT_EQ(nullptr, a.GetValueUnsafe(kTestPolicyName2));
+ const PolicyMap::Entry* entry = b.Get(kTestPolicyName2);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(
+ ExternalDataFetcher::Equals(CreateExternalDataFetcher("dummy").get(),
+ entry->external_data_fetcher.get()));
+
+ b.Clear();
+ a.Swap(&b);
+ PolicyMap empty;
+ EXPECT_TRUE(a.Equals(empty));
+ EXPECT_FALSE(b.Equals(empty));
+}
+
+#if !BUILDFLAG(IS_CHROMEOS)
+// Policy precedence changes are not supported on Chrome OS.
+TEST_F(PolicyMapTest, MergeFrom_CloudMetapolicies) {
+ // The two precedence metapolicies, CloudPolicyOverridesPlatformPolicy and
+ // CloudUserPolicyOverridesCloudMachinePolicy, are set as cloud policies in
+ // the incoming |policy_map_2|.
+ PolicyMap policy_map_1;
+ policy_map_1.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("platform_machine"), nullptr);
+ policy_map_1.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("cloud_user"), nullptr);
+ policy_map_1.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("cloud_user"), nullptr);
+ policy_map_1.Set(kTestPolicyName4, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_machine"), nullptr);
+
+ PolicyMap policy_map_2;
+ // Set matching user and device affiliation IDs to allow cloud user policies
+ // to take precedence over cloud machine policies.
+ base::flat_set<std::string> affiliation_ids;
+ affiliation_ids.insert("a");
+ policy_map_2.SetUserAffiliationIds(affiliation_ids);
+ policy_map_2.SetDeviceAffiliationIds(affiliation_ids);
+
+ policy_map_2.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ policy_map_2.Set(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ policy_map_2.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_machine"), nullptr);
+ policy_map_2.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value("platform_user"),
+ nullptr);
+ policy_map_2.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("platform_machine"), nullptr);
+ policy_map_2.Set(kTestPolicyName4, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("cloud_user"), nullptr);
+
+ auto conflicting_policy_1 = policy_map_1.Get(kTestPolicyName1)->DeepCopy();
+ auto conflicting_policy_2 = policy_map_2.Get(kTestPolicyName2)->DeepCopy();
+ auto conflicting_policy_3 = policy_map_2.Get(kTestPolicyName3)->DeepCopy();
+ auto conflicting_policy_4 = policy_map_1.Get(kTestPolicyName4)->DeepCopy();
+
+ policy_map_1.MergeFrom(policy_map_2);
+
+ PolicyMap policy_map_expected;
+
+ policy_map_expected.SetUserAffiliationIds(affiliation_ids);
+ policy_map_expected.SetDeviceAffiliationIds(affiliation_ids);
+ policy_map_expected.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ policy_map_expected.Set(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ // Cloud machine overrides platform machine.
+ policy_map_expected.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_machine"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddConflictingPolicy(std::move(conflicting_policy_1));
+ // Cloud user overrides platform user.
+ policy_map_expected.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_user"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName2)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName2)
+ ->AddConflictingPolicy(std::move(conflicting_policy_2));
+ // Cloud user overrides platform machine.
+ policy_map_expected.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_user"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName3)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName3)
+ ->AddConflictingPolicy(std::move(conflicting_policy_3));
+ // Cloud user overrides cloud machine.
+ policy_map_expected.Set(kTestPolicyName4, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("cloud_user"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName4)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName4)
+ ->AddConflictingPolicy(std::move(conflicting_policy_4));
+
+ EXPECT_TRUE(policy_map_1.Equals(policy_map_expected));
+}
+#endif // BUILDFLAG(IS_CHROMEOS)
+
+TEST_F(PolicyMapTest, MergeValuesList) {
+ std::vector<base::Value> abcd =
+ GetListStorage<std::string>({"a", "b", "c", "d"});
+ std::vector<base::Value> abc = GetListStorage<std::string>({"a", "b", "c"});
+ std::vector<base::Value> ab = GetListStorage<std::string>({"a", "b"});
+ std::vector<base::Value> cd = GetListStorage<std::string>({"c", "d"});
+ std::vector<base::Value> ef = GetListStorage<std::string>({"e", "f"});
+
+ std::vector<base::Value> int12 = GetListStorage<int>({1, 2});
+ std::vector<base::Value> int34 = GetListStorage<int>({3, 4});
+ std::vector<base::Value> int56 = GetListStorage<int>({5, 6});
+ std::vector<base::Value> int1234 = GetListStorage<int>({1, 2, 3, 4});
+
+ base::Value dict_ab(base::Value::Type::DICTIONARY);
+ dict_ab.SetBoolKey("a", true);
+ dict_ab.SetBoolKey("b", false);
+ base::Value dict_c(base::Value::Type::DICTIONARY);
+ dict_c.SetBoolKey("c", false);
+ base::Value dict_d(base::Value::Type::DICTIONARY);
+ dict_d.SetBoolKey("d", false);
+
+ std::vector<base::Value> list_dict_abd;
+ list_dict_abd.emplace_back(dict_ab.Clone());
+ list_dict_abd.emplace_back(dict_d.Clone());
+ std::vector<base::Value> list_dict_c;
+ list_dict_c.emplace_back(dict_c.Clone());
+
+ std::vector<base::Value> list_dict_abcd;
+ list_dict_abcd.emplace_back(dict_ab.Clone());
+ list_dict_abcd.emplace_back(dict_d.Clone());
+ list_dict_abcd.emplace_back(dict_c.Clone());
+
+ // Case 1 - kTestPolicyName1
+ // Enterprise default policies should not be merged with other sources.
+ PolicyMap::Entry case1(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(abc), nullptr);
+
+ case1.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_COMMAND_LINE, base::Value(cd), nullptr));
+
+ case1.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(ef), nullptr));
+
+ case1.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(ef), nullptr));
+
+ PolicyMap::Entry expected_case1(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, base::Value(abcd),
+ nullptr);
+ expected_case1.AddConflictingPolicy(case1.DeepCopy());
+
+ // Case 2 - kTestPolicyName2
+ // Policies should only be merged with other policies with the same target,
+ // level and scope.
+ PolicyMap::Entry case2(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(int12), nullptr);
+
+ case2.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(int34), nullptr));
+
+ case2.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(int56), nullptr));
+
+ PolicyMap::Entry expected_case2(POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_MERGED,
+ base::Value(int1234), nullptr);
+ expected_case2.AddConflictingPolicy(case2.DeepCopy());
+
+ // Case 3 - kTestPolicyName3
+ // Trivial case with 2 sources.
+ PolicyMap::Entry case3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(ab), nullptr);
+
+ case3.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(cd), nullptr));
+
+ PolicyMap::Entry expected_case3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, base::Value(abcd),
+ nullptr);
+ auto case3_blocked_by_group = expected_case3.DeepCopy();
+ case3_blocked_by_group.SetIgnoredByPolicyAtomicGroup();
+ expected_case3.AddConflictingPolicy(case3.DeepCopy());
+
+ // Case 4 - kTestPolicyName4
+ // Policies with a single source should stay the same.
+ PolicyMap::Entry case4(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(ef), nullptr);
+ PolicyMap::Entry expected_case4(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, base::Value(ef),
+ nullptr);
+ expected_case4.AddConflictingPolicy(case4.DeepCopy());
+
+ // 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 case5(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value("bad stuff"),
+ nullptr);
+
+ PolicyMap::Entry expected_case5(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value("bad stuff"), nullptr);
+ expected_case5.AddMessage(
+ PolicyMap::MessageType::kError,
+ IDS_POLICY_LIST_MERGING_WRONG_POLICY_TYPE_SPECIFIED);
+
+ // Case 6 - kTestPolicyName6
+ // User cloud policies should not be merged with other sources.
+ PolicyMap::Entry case6(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ab), nullptr);
+ case6.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(cd), nullptr));
+ case6.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(ef), nullptr));
+ PolicyMap::Entry expected_case6(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_MERGED, base::Value(ab),
+ nullptr);
+ expected_case6.AddConflictingPolicy(case6.DeepCopy());
+
+ // Case 7 - kTestPolicyName7
+ // User platform policies should not be merged under any circumstances.
+ PolicyMap::Entry case7(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ab), nullptr);
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(cd), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(cd), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(ef), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_COMMAND_LINE, base::Value(ef), nullptr));
+ PolicyMap::Entry expected_case7(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_MERGED, base::Value(ab),
+ nullptr);
+ expected_case7.AddConflictingPolicy(case7.DeepCopy());
+
+ // Case 8 - kTestPolicyName8
+ // Lists of dictionaries should not have duplicates.
+ PolicyMap::Entry case8(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(list_dict_abd),
+ nullptr);
+
+ case8.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(list_dict_abd), nullptr));
+
+ case8.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_COMMAND_LINE,
+ base::Value(list_dict_c), nullptr));
+
+ PolicyMap::Entry expected_case8(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED,
+ base::Value(list_dict_abcd), nullptr);
+ expected_case8.AddConflictingPolicy(case8.DeepCopy());
+
+ PolicyMap policy_not_merged;
+ policy_not_merged.Set(kTestPolicyName1, case1.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName2, case2.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName3, case3.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName4, case4.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName5, case5.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName6, case6.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName7, case7.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName8, case8.DeepCopy());
+
+ PolicyMap expected_list_merged;
+ expected_list_merged.Set(kTestPolicyName1, expected_case1.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName2, expected_case2.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName3, expected_case3.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName4, expected_case4.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName5, expected_case5.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName6, expected_case6.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName7, expected_case7.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName8, expected_case8.DeepCopy());
+
+ PolicyMap list_merged = policy_not_merged.Clone();
+
+ PolicyMap list_merged_wildcard = policy_not_merged.Clone();
+
+ // Merging with no restrictions specified
+ PolicyListMerger empty_policy_list({});
+ list_merged.MergeValues({&empty_policy_list});
+ EXPECT_TRUE(list_merged.Equals(policy_not_merged));
+
+ PolicyListMerger bad_policy_list({"unknown"});
+ // Merging with wrong restrictions specified
+ list_merged.MergeValues({&bad_policy_list});
+ EXPECT_TRUE(list_merged.Equals(policy_not_merged));
+
+ // Merging lists restrictions specified
+ PolicyListMerger good_policy_list(
+ {kTestPolicyName1, kTestPolicyName2, kTestPolicyName3, kTestPolicyName4,
+ kTestPolicyName5, kTestPolicyName6, kTestPolicyName7, kTestPolicyName8});
+ 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.Clone();
+ expected_list_merged_wildcard.Set(kTestPolicyName5, case5.DeepCopy());
+ list_merged_wildcard.MergeValues({&wildcard_policy_list});
+ EXPECT_TRUE(list_merged_wildcard.Equals(expected_list_merged_wildcard));
+}
+
+TEST_F(PolicyMapTest, MergeValuesDictionary) {
+ base::Value dict_a(base::Value::Type::DICTIONARY);
+ dict_a.SetBoolKey("keyA", true);
+
+ base::Value dict_b(base::Value::Type::DICTIONARY);
+ dict_b.SetStringKey("keyB", "ValueB2");
+ dict_b.SetStringKey("keyC", "ValueC2");
+ dict_b.SetStringKey("keyD", "ValueD2");
+
+ base::Value dict_c(base::Value::Type::DICTIONARY);
+ dict_c.SetStringKey("keyA", "ValueA");
+ dict_c.SetStringKey("keyB", "ValueB");
+ dict_c.SetStringKey("keyC", "ValueC");
+ dict_c.SetStringKey("keyD", "ValueD");
+ dict_c.SetStringKey("keyZ", "ValueZ");
+
+ base::Value dict_d(base::Value::Type::DICTIONARY);
+ dict_d.SetStringKey("keyC", "ValueC3");
+
+ base::Value dict_e(base::Value::Type::DICTIONARY);
+ dict_e.SetStringKey("keyD", "ValueD4");
+ dict_e.SetIntKey("keyE", 123);
+
+ base::Value dict_f(base::Value::Type::DICTIONARY);
+ dict_f.SetStringKey("keyX", "ValueX");
+ dict_f.SetStringKey("keyE", "ValueE5");
+
+ // Case 1: kTestPolicyName1 - Merging should only keep keys with the highest
+ // priority
+ PolicyMap::Entry case1(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, dict_a.Clone(), nullptr);
+ case1.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, dict_b.Clone(), nullptr));
+ case1.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_COMMAND_LINE, dict_c.Clone(), nullptr));
+
+ base::Value merged_dict_case1(base::Value::Type::DICTIONARY);
+ merged_dict_case1.MergeDictionary(&dict_c);
+ merged_dict_case1.MergeDictionary(&dict_b);
+ merged_dict_case1.MergeDictionary(&dict_a);
+
+ PolicyMap::Entry expected_case1(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED,
+ merged_dict_case1.Clone(), nullptr);
+ expected_case1.AddConflictingPolicy(case1.DeepCopy());
+
+ // Case 2 - kTestPolicyName2
+ // Policies should only be merged with other policies with the same target,
+ // level and scope.
+ PolicyMap::Entry case2(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, dict_e.Clone(), nullptr);
+
+ case2.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, dict_f.Clone(), nullptr));
+
+ case2.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, dict_a.Clone(), nullptr));
+
+ base::Value merged_dict_case2(base::Value::Type::DICTIONARY);
+ merged_dict_case2.MergeDictionary(&dict_f);
+ merged_dict_case2.MergeDictionary(&dict_e);
+
+ PolicyMap::Entry expected_case2(POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_MERGED,
+ merged_dict_case2.Clone(), nullptr);
+ expected_case2.AddConflictingPolicy(case2.DeepCopy());
+
+ // Case 3 - kTestPolicyName3
+ // Enterprise default policies should not be merged with other sources.
+ PolicyMap::Entry case3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, dict_a.Clone(), nullptr);
+
+ case3.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_COMMAND_LINE, dict_b.Clone(), nullptr));
+
+ case3.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, dict_e.Clone(), nullptr));
+
+ case3.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, dict_f.Clone(), nullptr));
+
+ base::Value merged_dict_case3(base::Value::Type::DICTIONARY);
+ merged_dict_case3.MergeDictionary(&dict_b);
+ merged_dict_case3.MergeDictionary(&dict_a);
+
+ PolicyMap::Entry expected_case3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED,
+ merged_dict_case3.Clone(), nullptr);
+ expected_case3.AddConflictingPolicy(case3.DeepCopy());
+
+ // Case 4 - kTestPolicyName4
+ // Policies with a single source should be merged.
+ PolicyMap::Entry case4(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, dict_a.Clone(), nullptr);
+ PolicyMap::Entry expected_case4(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, dict_a.Clone(),
+ nullptr);
+ expected_case4.AddConflictingPolicy(case4.DeepCopy());
+
+ // Case 5 - kTestPolicyName5
+ // Policies that are not dictionaries 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 case5(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value("bad stuff"),
+ nullptr);
+
+ PolicyMap::Entry expected_case5(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value("bad stuff"), nullptr);
+ expected_case5.AddMessage(
+ PolicyMap::MessageType::kError,
+ IDS_POLICY_DICTIONARY_MERGING_WRONG_POLICY_TYPE_SPECIFIED);
+
+ // Case 6 - kTestPolicyName6
+ // User cloud policies should not be merged with other sources.
+ PolicyMap::Entry case6(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, dict_a.Clone(), nullptr);
+ case6.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, dict_e.Clone(), nullptr));
+ case6.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, dict_f.Clone(), nullptr));
+ PolicyMap::Entry expected_case6(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_MERGED, dict_a.Clone(),
+ nullptr);
+ expected_case6.AddConflictingPolicy(case6.DeepCopy());
+
+ // Case 7 - kTestPolicyName7
+ // User platform policies should not be merged under any circumstances.
+ PolicyMap::Entry case7(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, dict_a.Clone(), nullptr);
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, dict_b.Clone(), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, dict_c.Clone(), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, dict_d.Clone(), nullptr));
+ case7.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_COMMAND_LINE, dict_e.Clone(), nullptr));
+ PolicyMap::Entry expected_case7(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_MERGED, dict_a.Clone(),
+ nullptr);
+ expected_case7.AddConflictingPolicy(case7.DeepCopy());
+
+ // Case 8 - kTestPolicyName8
+ // Policies that are not dictionaries 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 case8(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, dict_a.Clone(), nullptr);
+
+ PolicyMap::Entry expected_case8 = case8.DeepCopy();
+
+ expected_case8.AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_DICTIONARY_MERGING_POLICY_NOT_ALLOWED);
+
+ PolicyMap policy_not_merged;
+ policy_not_merged.Set(kTestPolicyName1, case1.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName2, case2.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName3, case3.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName4, case4.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName5, case5.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName6, case6.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName7, case7.DeepCopy());
+ policy_not_merged.Set(kTestPolicyName8, case8.DeepCopy());
+
+ PolicyMap expected_list_merged;
+ expected_list_merged.Set(kTestPolicyName1, expected_case1.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName2, expected_case2.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName3, expected_case3.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName4, expected_case4.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName5, expected_case5.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName6, expected_case6.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName7, expected_case7.DeepCopy());
+ expected_list_merged.Set(kTestPolicyName8, expected_case8.DeepCopy());
+
+ PolicyMap list_merged = policy_not_merged.Clone();
+
+ PolicyMap list_merged_wildcard = policy_not_merged.Clone();
+
+ // Merging with no restrictions specified
+ PolicyDictionaryMerger empty_policy_list({});
+ list_merged.MergeValues({&empty_policy_list});
+ EXPECT_TRUE(list_merged.Equals(policy_not_merged));
+
+ PolicyDictionaryMerger bad_policy_list({"unknown"});
+ // Merging with wrong restrictions specified
+ list_merged.MergeValues({&bad_policy_list});
+ EXPECT_TRUE(list_merged.Equals(policy_not_merged));
+
+ // Merging lists restrictions specified
+ PolicyDictionaryMerger good_policy_list(
+ {kTestPolicyName1, kTestPolicyName2, kTestPolicyName3, kTestPolicyName4,
+ kTestPolicyName5, kTestPolicyName6, kTestPolicyName7, kTestPolicyName8});
+ good_policy_list.SetAllowedPoliciesForTesting(
+ {kTestPolicyName1, kTestPolicyName2, kTestPolicyName3, kTestPolicyName4,
+ kTestPolicyName5, kTestPolicyName6, kTestPolicyName7});
+ PolicyDictionaryMerger wildcard_policy_list({"*"});
+ wildcard_policy_list.SetAllowedPoliciesForTesting(
+ {kTestPolicyName1, kTestPolicyName2, kTestPolicyName3, kTestPolicyName4,
+ kTestPolicyName5, kTestPolicyName6, kTestPolicyName7});
+ list_merged.MergeValues({&good_policy_list});
+ EXPECT_TRUE(list_merged.Equals(expected_list_merged));
+
+ PolicyMap expected_list_merged_wildcard = expected_list_merged.Clone();
+ expected_list_merged_wildcard.Set(kTestPolicyName5, case5.DeepCopy());
+ expected_list_merged_wildcard.Set(kTestPolicyName8, case8.DeepCopy());
+ list_merged_wildcard.MergeValues({&wildcard_policy_list});
+ EXPECT_TRUE(list_merged_wildcard.Equals(expected_list_merged_wildcard));
+}
+
+TEST_F(PolicyMapTest, MergeValuesGroup) {
+ std::vector<base::Value> abc = GetListStorage<std::string>({"a", "b", "c"});
+ std::vector<base::Value> ab = GetListStorage<std::string>({"a", "b"});
+ std::vector<base::Value> cd = GetListStorage<std::string>({"c", "d"});
+ std::vector<base::Value> ef = GetListStorage<std::string>({"e", "f"});
+
+ // Case 1 - kTestPolicyName1
+ // Should not be affected by the atomic groups
+ PolicyMap::Entry platform_user_mandatory(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ base::Value(abc), nullptr);
+
+ platform_user_mandatory.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(cd), nullptr));
+
+ platform_user_mandatory.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(ef), nullptr));
+
+ platform_user_mandatory.AddConflictingPolicy(PolicyMap::Entry(
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(ef), nullptr));
+
+ // Case 2 - policy::key::kExtensionInstallBlocklist
+ // This policy is part of the atomic group "Extensions" and has the highest
+ // source in its group, its value should remain the same.
+ PolicyMap::Entry platform_machine_mandatory(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(ab), nullptr);
+
+ platform_machine_mandatory.AddConflictingPolicy(
+ PolicyMap::Entry(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(cd), nullptr));
+
+ // Case 3 - policy::key::kExtensionInstallAllowlist
+ // This policy is part of the atomic group "Extensions" and has a lower
+ // source than policy::key::kExtensionInstallBlocklist from the same group,
+ // its value should be ignored.
+ PolicyMap::Entry cloud_machine_mandatory(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(ef), nullptr);
+ auto cloud_machine_mandatory_ignored = cloud_machine_mandatory.DeepCopy();
+ cloud_machine_mandatory_ignored.SetIgnoredByPolicyAtomicGroup();
+
+ // Case 4 - policy::key::kExtensionInstallBlocklist
+ // This policy is part of the atomic group "Extensions" and has the highest
+ // source in its group, its value should remain the same.
+ PolicyMap::Entry platform_machine_recommended(
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(ab), nullptr);
+
+ PolicyMap policy_not_merged;
+ policy_not_merged.Set(kTestPolicyName1, platform_user_mandatory.DeepCopy());
+ policy_not_merged.Set(policy::key::kExtensionInstallBlocklist,
+ platform_machine_mandatory.DeepCopy());
+ policy_not_merged.Set(policy::key::kExtensionInstallAllowlist,
+ cloud_machine_mandatory.DeepCopy());
+ policy_not_merged.Set(policy::key::kExtensionInstallForcelist,
+ platform_machine_recommended.DeepCopy());
+
+ PolicyMap group_merged = policy_not_merged.Clone();
+ PolicyGroupMerger group_merger;
+ group_merged.MergeValues({&group_merger});
+
+ PolicyMap expected_group_merged;
+ expected_group_merged.Set(kTestPolicyName1,
+ platform_user_mandatory.DeepCopy());
+ expected_group_merged.Set(policy::key::kExtensionInstallBlocklist,
+ platform_machine_mandatory.DeepCopy());
+ expected_group_merged.Set(policy::key::kExtensionInstallAllowlist,
+ cloud_machine_mandatory_ignored.DeepCopy());
+ expected_group_merged.Set(policy::key::kExtensionInstallForcelist,
+ platform_machine_recommended.DeepCopy());
+
+ EXPECT_TRUE(group_merged.Equals(expected_group_merged));
+}
+
+TEST_F(PolicyMapTest, LoadFromSetsLevelScopeAndSource) {
+ base::DictionaryValue policies;
+ policies.SetStringKey("TestPolicy1", "google.com");
+ policies.SetBoolKey("TestPolicy2", true);
+ policies.SetIntKey("TestPolicy3", -12321);
+
+ PolicyMap loaded;
+ loaded.LoadFrom(&policies,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM);
+
+ PolicyMap expected;
+ expected.Set("TestPolicy1", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value("google.com"), nullptr);
+ expected.Set("TestPolicy2", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
+ expected.Set("TestPolicy3", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(-12321), nullptr);
+ EXPECT_TRUE(loaded.Equals(expected));
+}
+
+bool IsMandatory(const PolicyMap::PolicyMapType::const_iterator iter) {
+ return iter->second.level == POLICY_LEVEL_MANDATORY;
+}
+
+TEST_F(PolicyMapTest, EraseNonmatching) {
+ PolicyMap a;
+ a.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("google.com"), nullptr);
+ a.Set(kTestPolicyName2, POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+
+ a.EraseNonmatching(base::BindRepeating(&IsMandatory));
+
+ PolicyMap b;
+ b.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("google.com"), nullptr);
+ EXPECT_TRUE(a.Equals(b));
+}
+
+TEST_F(PolicyMapTest, EntryAddConflict) {
+ std::vector<base::Value> ab = GetListStorage<std::string>({"a", "b"});
+ std::vector<base::Value> cd = GetListStorage<std::string>({"c", "d"});
+ std::vector<base::Value> ef = GetListStorage<std::string>({"e", "f"});
+ std::vector<base::Value> gh = GetListStorage<std::string>({"g", "h"});
+
+ // Case 1: Non-nested conflicts
+ PolicyMap::Entry case1(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ab), nullptr);
+ PolicyMap::Entry conflict11(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(cd), nullptr);
+ PolicyMap::Entry conflict12(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ef), nullptr);
+ PolicyMap::Entry conflict13(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(gh), nullptr);
+ PolicyMap::Entry conflict14(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ab), nullptr);
+
+ case1.AddConflictingPolicy(conflict11.DeepCopy());
+ case1.AddConflictingPolicy(conflict12.DeepCopy());
+ case1.AddConflictingPolicy(conflict13.DeepCopy());
+ case1.AddConflictingPolicy(conflict14.DeepCopy());
+
+ EXPECT_TRUE(case1.conflicts.size() == 4);
+ EXPECT_TRUE(case1.conflicts.at(0).entry().Equals(conflict11));
+ EXPECT_TRUE(case1.conflicts.at(1).entry().Equals(conflict12));
+ EXPECT_TRUE(case1.conflicts.at(2).entry().Equals(conflict13));
+ EXPECT_TRUE(case1.conflicts.at(3).entry().Equals(conflict14));
+ EXPECT_EQ(case1.conflicts.at(0).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(case1.conflicts.at(1).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(case1.conflicts.at(2).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(case1.conflicts.at(3).conflict_type(),
+ PolicyMap::ConflictType::Supersede);
+
+ // Case 2: Nested conflicts
+ PolicyMap::Entry case2(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ab), nullptr);
+ PolicyMap::Entry conflict21(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(cd), nullptr);
+ PolicyMap::Entry conflict22(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(cd), nullptr);
+ PolicyMap::Entry conflict23(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(ef), nullptr);
+ PolicyMap::Entry conflict24(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(gh), nullptr);
+
+ conflict21.AddConflictingPolicy(conflict22.DeepCopy());
+ conflict21.AddConflictingPolicy(conflict23.DeepCopy());
+ conflict21.AddConflictingPolicy(conflict24.DeepCopy());
+ case2.AddConflictingPolicy(conflict21.DeepCopy());
+
+ EXPECT_TRUE(case2.conflicts.size() == 4);
+ EXPECT_TRUE(case2.conflicts.at(0).entry().Equals(conflict22));
+ EXPECT_TRUE(case2.conflicts.at(1).entry().Equals(conflict23));
+ EXPECT_TRUE(case2.conflicts.at(2).entry().Equals(conflict24));
+ EXPECT_TRUE(conflict21.conflicts.at(0).entry().Equals(conflict22));
+ EXPECT_TRUE(conflict21.conflicts.at(1).entry().Equals(conflict23));
+ EXPECT_TRUE(conflict21.conflicts.at(2).entry().Equals(conflict24));
+ EXPECT_EQ(case2.conflicts.at(0).conflict_type(),
+ PolicyMap::ConflictType::Supersede);
+ EXPECT_EQ(case2.conflicts.at(1).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(case2.conflicts.at(2).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(case2.conflicts.at(3).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(conflict21.conflicts.at(0).conflict_type(),
+ PolicyMap::ConflictType::Supersede);
+ EXPECT_EQ(conflict21.conflicts.at(1).conflict_type(),
+ PolicyMap::ConflictType::Override);
+ EXPECT_EQ(conflict21.conflicts.at(2).conflict_type(),
+ PolicyMap::ConflictType::Override);
+}
+
+TEST_F(PolicyMapTest, BlockedEntry) {
+ PolicyMap::Entry entry_a(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("a"), nullptr);
+ PolicyMap::Entry entry_b = entry_a.DeepCopy();
+ entry_b.set_value(base::Value("b"));
+ PolicyMap::Entry entry_c_blocked = entry_a.DeepCopy();
+ entry_c_blocked.set_value(base::Value("c"));
+ entry_c_blocked.SetBlocked();
+
+ PolicyMap policies;
+ policies.Set("a", entry_a.DeepCopy());
+ policies.Set("b", entry_b.DeepCopy());
+ policies.Set("c", entry_c_blocked.DeepCopy());
+
+ const size_t expected_size = 3;
+ EXPECT_EQ(policies.size(), expected_size);
+
+ EXPECT_TRUE(policies.Get("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.Get("b")->Equals(entry_b));
+ EXPECT_EQ(policies.Get("c"), nullptr);
+
+ EXPECT_TRUE(policies.GetMutable("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.GetMutable("b")->Equals(entry_b));
+ EXPECT_EQ(policies.GetMutable("c"), nullptr);
+
+ EXPECT_EQ(*policies.GetValue("a", base::Value::Type::STRING),
+ *entry_a.value(base::Value::Type::STRING));
+ EXPECT_EQ(*policies.GetValue("b", base::Value::Type::STRING),
+ *entry_b.value(base::Value::Type::STRING));
+ EXPECT_EQ(policies.GetValue("c", base::Value::Type::STRING), nullptr);
+
+ EXPECT_EQ(*policies.GetValueUnsafe("a"), *entry_a.value_unsafe());
+ EXPECT_EQ(*policies.GetValueUnsafe("b"), *entry_b.value_unsafe());
+ EXPECT_EQ(policies.GetValueUnsafe("c"), nullptr);
+
+ EXPECT_EQ(*policies.GetMutableValue("a", base::Value::Type::STRING),
+ *entry_a.value(base::Value::Type::STRING));
+ EXPECT_EQ(*policies.GetMutableValue("b", base::Value::Type::STRING),
+ *entry_b.value(base::Value::Type::STRING));
+ EXPECT_EQ(policies.GetMutableValue("c", base::Value::Type::STRING), nullptr);
+
+ EXPECT_EQ(*policies.GetMutableValueUnsafe("a"), *entry_a.value_unsafe());
+ EXPECT_EQ(*policies.GetMutableValueUnsafe("b"), *entry_b.value_unsafe());
+ EXPECT_EQ(policies.GetMutableValueUnsafe("c"), nullptr);
+
+ EXPECT_TRUE(policies.GetUntrusted("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.GetUntrusted("b")->Equals(entry_b));
+ EXPECT_TRUE(policies.GetUntrusted("c")->Equals(entry_c_blocked));
+
+ EXPECT_TRUE(policies.GetMutableUntrusted("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.GetMutableUntrusted("b")->Equals(entry_b));
+ EXPECT_TRUE(policies.GetMutableUntrusted("c")->Equals(entry_c_blocked));
+
+ EXPECT_FALSE(policies.GetUntrusted("a")->ignored());
+ EXPECT_FALSE(policies.GetUntrusted("b")->ignored());
+ EXPECT_TRUE(policies.GetUntrusted("c")->ignored());
+
+ size_t iterated_values = 0;
+ for (auto it = policies.begin(); it != policies.end();
+ ++it, ++iterated_values) {
+ }
+ EXPECT_TRUE(iterated_values == expected_size);
+}
+
+TEST_F(PolicyMapTest, InvalidEntry) {
+ PolicyMap::Entry entry_a(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("a"), nullptr);
+ PolicyMap::Entry entry_b_invalid = entry_a.DeepCopy();
+ entry_b_invalid.set_value(base::Value("b"));
+ entry_b_invalid.SetInvalid();
+
+ PolicyMap policies;
+ policies.Set("a", entry_a.DeepCopy());
+ policies.Set("b", entry_b_invalid.DeepCopy());
+
+ const size_t expected_size = 2;
+ EXPECT_EQ(policies.size(), expected_size);
+
+ EXPECT_TRUE(policies.Get("a")->Equals(entry_a));
+ EXPECT_EQ(policies.Get("b"), nullptr);
+
+ EXPECT_TRUE(policies.GetMutable("a")->Equals(entry_a));
+ EXPECT_EQ(policies.GetMutable("b"), nullptr);
+
+ EXPECT_EQ(*policies.GetValue("a", base::Value::Type::STRING),
+ *entry_a.value(base::Value::Type::STRING));
+ EXPECT_EQ(policies.GetValue("b", base::Value::Type::STRING), nullptr);
+
+ EXPECT_EQ(*policies.GetValueUnsafe("a"), *entry_a.value_unsafe());
+ EXPECT_EQ(policies.GetValueUnsafe("b"), nullptr);
+
+ EXPECT_EQ(*policies.GetMutableValue("a", base::Value::Type::STRING),
+ *entry_a.value(base::Value::Type::STRING));
+ EXPECT_EQ(policies.GetMutableValue("b", base::Value::Type::STRING), nullptr);
+
+ EXPECT_EQ(*policies.GetMutableValueUnsafe("a"), *entry_a.value_unsafe());
+ EXPECT_EQ(policies.GetMutableValueUnsafe("b"), nullptr);
+
+ EXPECT_TRUE(policies.GetUntrusted("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.GetUntrusted("b")->Equals(entry_b_invalid));
+
+ EXPECT_TRUE(policies.GetMutableUntrusted("a")->Equals(entry_a));
+ EXPECT_TRUE(policies.GetMutableUntrusted("b")->Equals(entry_b_invalid));
+
+ EXPECT_FALSE(policies.GetUntrusted("a")->ignored());
+ EXPECT_TRUE(policies.GetUntrusted("b")->ignored());
+
+ size_t iterated_values = 0;
+ for (auto it = policies.begin(); it != policies.end();
+ ++it, ++iterated_values) {
+ }
+ EXPECT_EQ(iterated_values, expected_size);
+
+ policies.SetAllInvalid();
+ EXPECT_TRUE(policies.GetUntrusted("a")->ignored());
+ EXPECT_TRUE(policies.GetUntrusted("b")->ignored());
+}
+
+TEST_F(PolicyMapTest, Affiliation) {
+ PolicyMap policies;
+ EXPECT_FALSE(policies.IsUserAffiliated());
+
+ base::flat_set<std::string> user_ids;
+ user_ids.insert("a");
+ base::flat_set<std::string> device_ids;
+ device_ids.insert("b");
+ policies.SetUserAffiliationIds(user_ids);
+ policies.SetDeviceAffiliationIds(device_ids);
+
+ // Affiliation check fails because user and device IDs don't have at least one
+ // ID in common.
+ EXPECT_FALSE(policies.IsUserAffiliated());
+
+ user_ids.insert("b");
+ device_ids.insert("c");
+ policies.SetUserAffiliationIds(user_ids);
+ policies.SetDeviceAffiliationIds(device_ids);
+
+ // Affiliation check succeeds now that 'a' is present in user and device IDs.
+ EXPECT_TRUE(policies.IsUserAffiliated());
+}
+
+class PolicyMapMergeTest
+ : public PolicyMapTestBase,
+ public testing::TestWithParam<
+ std::tuple</*cloud_policy_overrides_platform_policy=*/bool,
+ /*cloud_user_policy_overrides_cloud_machine_policy=*/bool,
+ /*is_user_affiliated=*/bool,
+ /*metapolicies_are_incoming=*/bool>> {
+ public:
+ bool CloudPolicyOverridesPlatformPolicy() const {
+ return std::get<0>(GetParam());
+ }
+
+ bool CloudUserPolicyOverridesCloudMachinePolicy() const {
+ return std::get<1>(GetParam());
+ }
+
+ bool IsUserAffiliated() const { return std::get<2>(GetParam()); }
+
+ bool MetapoliciesAreIncoming() const { return std::get<3>(GetParam()); }
+
+ void PopulateExpectedPolicyMap(PolicyMap& policy_map_expected,
+ const PolicyMap& policy_map_1,
+ const PolicyMap& policy_map_2) {
+ // Setting the metapolicies.
+ policy_map_expected.Set(
+ key::kCloudPolicyOverridesPlatformPolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map_expected.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+
+ // Expected behavior independent of metapolicy values and affiliation.
+ // -------------------------------------------------------------------
+ // |policy_map_1| has precedence over |policy_map_2|.
+ policy_map_expected.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName2)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName2)
+ ->AddConflictingPolicy(policy_map_2.Get(kTestPolicyName2)->DeepCopy());
+ policy_map_expected.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, absl::nullopt,
+ CreateExternalDataFetcher("a"));
+ policy_map_expected.GetMutable(kTestPolicyName3)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName3)
+ ->AddConflictingPolicy(policy_map_2.Get(kTestPolicyName3)->DeepCopy());
+ // Cloud machine over platform user for recommended policies.
+ policy_map_expected.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName4)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName4)
+ ->AddConflictingPolicy(policy_map_1.Get(kTestPolicyName4)->DeepCopy());
+ // Mandatory over recommended level.
+ policy_map_expected.Set(kTestPolicyName5, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(std::string()), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName5)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName5)
+ ->AddConflictingPolicy(policy_map_1.Get(kTestPolicyName5)->DeepCopy());
+ // Merge new policy.
+ policy_map_expected.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ // Platform over default source.
+ policy_map_expected.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ base::Value(true), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName7)->SetBlocked();
+
+ // Expected behavior that depends on metapolicy values and affiliation.
+ // --------------------------------------------------------------------
+#if !BUILDFLAG(IS_CHROMEOS)
+ if (CloudPolicyOverridesPlatformPolicy() &&
+ CloudUserPolicyOverridesCloudMachinePolicy() && IsUserAffiliated()) {
+ // Cloud user over cloud machine source.
+ policy_map_expected.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("google.com"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(kTestPolicyName1)->DeepCopy());
+ // Cloud machine over platform machine when platform is blocked.
+ policy_map_expected.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("non-blocked cloud policy"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(kTestPolicyName8)->DeepCopy());
+ // policy_map_expected.GetMutable(kTestPolicyName8)->SetBlocked();
+ } else if (CloudUserPolicyOverridesCloudMachinePolicy() &&
+ IsUserAffiliated()) {
+ // Cloud user over cloud machine source.
+ policy_map_expected.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("google.com"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(kTestPolicyName1)->DeepCopy());
+ // Platform machine over cloud machine even when platform is blocked.
+ policy_map_expected.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("blocked platform policy"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(kTestPolicyName8)->DeepCopy());
+ policy_map_expected.GetMutable(kTestPolicyName8)->SetBlocked();
+ } else if (CloudPolicyOverridesPlatformPolicy()) {
+ // Machine over user scope.
+ policy_map_expected.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("chromium.org"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(kTestPolicyName1)->DeepCopy());
+ // Cloud machine over platform machine when platform is blocked.
+ policy_map_expected.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("non-blocked cloud policy"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(kTestPolicyName8)->DeepCopy());
+ // policy_map_expected.GetMutable(kTestPolicyName8)->SetBlocked();
+ } else {
+#endif // !BUILDFLAG(IS_CHROMEOS)
+ // Machine over user scope.
+ policy_map_expected.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("chromium.org"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName1)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(kTestPolicyName1)->DeepCopy());
+ // Platform machine over cloud machine even when platform is blocked.
+ policy_map_expected.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("blocked platform policy"), nullptr);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(kTestPolicyName8)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(kTestPolicyName8)->DeepCopy());
+ policy_map_expected.GetMutable(kTestPolicyName8)->SetBlocked();
+#if !BUILDFLAG(IS_CHROMEOS)
+ }
+#endif // !BUILDFLAG(IS_CHROMEOS)
+ }
+
+ void PopulateExpectedMetapolicyMap(
+ PolicyMap& policy_map_expected,
+ const PolicyMap& policy_map_1,
+ const PolicyMap& policy_map_2,
+ std::unique_ptr<base::ListValue> merge_list_1,
+ std::unique_ptr<base::ListValue> merge_list_2) {
+ // Platform machine overrides cloud machine because modified priorities
+ // don't apply to precedence metapolicies.
+ policy_map_expected.Set(
+ key::kCloudPolicyOverridesPlatformPolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map_expected.GetMutable(key::kCloudPolicyOverridesPlatformPolicy)
+ ->AddMessage(CloudPolicyOverridesPlatformPolicy()
+ ? PolicyMap::MessageType::kWarning
+ : PolicyMap::MessageType::kInfo,
+ CloudPolicyOverridesPlatformPolicy()
+ ? IDS_POLICY_CONFLICT_DIFF_VALUE
+ : IDS_POLICY_CONFLICT_SAME_VALUE);
+ policy_map_expected.GetMutable(key::kCloudPolicyOverridesPlatformPolicy)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(key::kCloudPolicyOverridesPlatformPolicy)
+ ->DeepCopy());
+ policy_map_expected.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+ policy_map_expected
+ .GetMutable(key::kCloudUserPolicyOverridesCloudMachinePolicy)
+ ->AddMessage(CloudUserPolicyOverridesCloudMachinePolicy()
+ ? PolicyMap::MessageType::kWarning
+ : PolicyMap::MessageType::kInfo,
+ CloudUserPolicyOverridesCloudMachinePolicy()
+ ? IDS_POLICY_CONFLICT_DIFF_VALUE
+ : IDS_POLICY_CONFLICT_SAME_VALUE);
+ policy_map_expected
+ .GetMutable(key::kCloudUserPolicyOverridesCloudMachinePolicy)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(key::kCloudUserPolicyOverridesCloudMachinePolicy)
+ ->DeepCopy());
+#if !BUILDFLAG(IS_CHROMEOS)
+ if (CloudPolicyOverridesPlatformPolicy()) {
+ // Cloud machine overrides platform machine because modified priorities
+ // apply to merging metapolicies.
+ policy_map_expected.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, merge_list_2->Clone(),
+ nullptr);
+ policy_map_expected.GetMutable(key::kPolicyListMultipleSourceMergeList)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(key::kPolicyListMultipleSourceMergeList)
+ ->AddConflictingPolicy(
+ policy_map_1.Get(key::kPolicyListMultipleSourceMergeList)
+ ->DeepCopy());
+ } else {
+#endif // !BUILDFLAG(IS_CHROMEOS)
+ // Platform machine overrides cloud machine with default precedence.
+ policy_map_expected.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, merge_list_1->Clone(),
+ nullptr);
+ policy_map_expected.GetMutable(key::kPolicyListMultipleSourceMergeList)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy_map_expected.GetMutable(key::kPolicyListMultipleSourceMergeList)
+ ->AddConflictingPolicy(
+ policy_map_2.Get(key::kPolicyListMultipleSourceMergeList)
+ ->DeepCopy());
+#if !BUILDFLAG(IS_CHROMEOS)
+ }
+#endif // !BUILDFLAG(IS_CHROMEOS)
+ }
+};
+
+TEST_P(PolicyMapMergeTest, MergeFrom) {
+ PolicyMap policy_map_1;
+ if (IsUserAffiliated()) {
+ base::flat_set<std::string> affiliation_ids;
+ affiliation_ids.insert("12345");
+ // Treat user as affiliated by setting identical user and device IDs.
+ policy_map_1.SetUserAffiliationIds(affiliation_ids);
+ policy_map_1.SetDeviceAffiliationIds(affiliation_ids);
+ }
+ if (!MetapoliciesAreIncoming()) {
+ // Metapolicies are set in the base PolicyMap.
+ policy_map_1.Set(
+ key::kCloudPolicyOverridesPlatformPolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map_1.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+ }
+ policy_map_1.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("google.com"), nullptr);
+ policy_map_1.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD, base::Value(true),
+ nullptr);
+ policy_map_1.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ absl::nullopt, CreateExternalDataFetcher("a"));
+ policy_map_1.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ base::Value(false), nullptr);
+ policy_map_1.Set(kTestPolicyName5, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("google.com/q={x}"), nullptr);
+ policy_map_1.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(false),
+ nullptr);
+ policy_map_1.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value("blocked platform policy"), nullptr);
+
+ PolicyMap policy_map_2;
+ if (MetapoliciesAreIncoming()) {
+ // Metapolicies are set in the incoming PolicyMap.
+ policy_map_2.Set(
+ key::kCloudPolicyOverridesPlatformPolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map_2.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+ }
+ policy_map_2.Set(kTestPolicyName1, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("chromium.org"), nullptr);
+ policy_map_2.Set(kTestPolicyName2, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(false), nullptr);
+ policy_map_2.Set(kTestPolicyName3, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ absl::nullopt, CreateExternalDataFetcher("b"));
+ policy_map_2.Set(kTestPolicyName4, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD, base::Value(true),
+ nullptr);
+ policy_map_2.Set(kTestPolicyName5, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(std::string()), nullptr);
+ policy_map_2.Set(kTestPolicyName6, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
+ nullptr);
+ policy_map_2.Set(kTestPolicyName7, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
+ policy_map_2.Set(kTestPolicyName8, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value("non-blocked cloud policy"), nullptr);
+
+ PolicyMap policy_map_expected;
+ PopulateExpectedPolicyMap(policy_map_expected, policy_map_1, policy_map_2);
+
+ policy_map_1.GetMutable(kTestPolicyName7)->SetBlocked();
+ policy_map_2.GetMutable(kTestPolicyName7)->SetBlocked();
+ policy_map_1.GetMutable(kTestPolicyName8)->SetBlocked();
+ policy_map_1.MergeFrom(policy_map_2);
+
+ EXPECT_TRUE(policy_map_1.Equals(policy_map_expected));
+}
+
+TEST_P(PolicyMapMergeTest, MergeFrom_Metapolicies) {
+ // Define the lists of policies that will be used by the merging metapolicies.
+ std::unique_ptr<base::ListValue> merge_list_1 =
+ std::make_unique<base::ListValue>();
+ merge_list_1->Append(base::Value(kTestPolicyName1));
+ std::unique_ptr<base::ListValue> merge_list_2 =
+ std::make_unique<base::ListValue>();
+ merge_list_2->Append(base::Value(kTestPolicyName2));
+
+ PolicyMap policy_map_1;
+ if (IsUserAffiliated()) {
+ base::flat_set<std::string> affiliation_ids;
+ affiliation_ids.insert("12345");
+ policy_map_1.SetUserAffiliationIds(affiliation_ids);
+ policy_map_1.SetDeviceAffiliationIds(affiliation_ids);
+ }
+ policy_map_1.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map_1.Set(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ policy_map_1.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, merge_list_1->Clone(), nullptr);
+
+ PolicyMap policy_map_2;
+ policy_map_2.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ policy_map_2.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+ policy_map_2.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, merge_list_2->Clone(), nullptr);
+
+ PolicyMap policy_map_expected;
+ PopulateExpectedMetapolicyMap(policy_map_expected, policy_map_1, policy_map_2,
+ std::move(merge_list_1),
+ std::move(merge_list_2));
+
+ policy_map_1.MergeFrom(policy_map_2);
+
+ EXPECT_TRUE(policy_map_1.Equals(policy_map_expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(PolicyMapMergeTestInstance,
+ PolicyMapMergeTest,
+ testing::Combine(testing::Values(false, true),
+ testing::Values(false, true),
+ testing::Values(false, true),
+ testing::Values(false, true)));
+
+#if !BUILDFLAG(IS_CHROMEOS)
+class PolicyMapPriorityTest
+ : public testing::TestWithParam<
+ std::tuple</*cloud_policy_overrides_platform_policy=*/bool,
+ /*cloud_user_policy_overrides_cloud_machine_policy=*/bool,
+ /*is_user_affiliated=*/bool>> {
+ public:
+ bool CloudPolicyOverridesPlatformPolicy() { return std::get<0>(GetParam()); }
+
+ bool CloudUserPolicyOverridesCloudMachinePolicy() {
+ return std::get<1>(GetParam());
+ }
+
+ bool IsUserAffiliated() { return std::get<2>(GetParam()); }
+
+ void CheckPriorityConditions(PolicyMap& policy_map) {
+ PolicyMap::Entry platform_machine(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(), nullptr);
+ PolicyMap::Entry platform_user(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(),
+ nullptr);
+ PolicyMap::Entry cloud_machine(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value(), nullptr);
+ PolicyMap::Entry cloud_user(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(), nullptr);
+ PolicyMap::Entry command_line(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_COMMAND_LINE, base::Value(),
+ nullptr);
+ PolicyMap::Entry enterprise_default(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(), nullptr);
+
+ // The policy priority conditions depend on the precedence metapolicy values
+ // and user affiliation.
+ if (CloudPolicyOverridesPlatformPolicy() &&
+ CloudUserPolicyOverridesCloudMachinePolicy() && IsUserAffiliated()) {
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, platform_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_machine));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_machine, platform_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(cloud_machine, cloud_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(platform_user, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_user, command_line));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_user, enterprise_default));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(command_line, enterprise_default));
+ } else if (CloudUserPolicyOverridesCloudMachinePolicy() &&
+ IsUserAffiliated()) {
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, platform_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_machine));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_machine, platform_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(cloud_machine, cloud_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(platform_user, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_user, command_line));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_user, enterprise_default));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(command_line, enterprise_default));
+ } else if (CloudPolicyOverridesPlatformPolicy()) {
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, platform_user));
+ EXPECT_FALSE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_machine));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_machine, platform_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_machine, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(platform_user, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_user, command_line));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_user, enterprise_default));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(command_line, enterprise_default));
+ } else {
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, platform_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_machine));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(platform_machine, cloud_user));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_machine, platform_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_machine, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(platform_user, cloud_user));
+ EXPECT_TRUE(policy_map.EntryHasHigherPriority(cloud_user, command_line));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(cloud_user, enterprise_default));
+ EXPECT_TRUE(
+ policy_map.EntryHasHigherPriority(command_line, enterprise_default));
+ }
+ }
+};
+
+TEST_P(PolicyMapPriorityTest, PriorityCheck) {
+ PolicyMap policy_map;
+
+ // Update the metapolicy values.
+ policy_map.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM,
+ base::Value(CloudPolicyOverridesPlatformPolicy()), nullptr);
+ policy_map.Set(
+ key::kCloudUserPolicyOverridesCloudMachinePolicy, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(CloudUserPolicyOverridesCloudMachinePolicy()), nullptr);
+ // Causes the stored metapolicy values to be updated.
+ PolicyMap policy_map_empty;
+ policy_map.MergeFrom(policy_map_empty);
+
+ if (IsUserAffiliated()) {
+ base::flat_set<std::string> affiliation_ids;
+ affiliation_ids.insert("a");
+ policy_map.SetUserAffiliationIds(affiliation_ids);
+ policy_map.SetDeviceAffiliationIds(affiliation_ids);
+ }
+
+ CheckPriorityConditions(policy_map);
+}
+
+INSTANTIATE_TEST_SUITE_P(PolicyMapPriorityTestInstance,
+ PolicyMapPriorityTest,
+ testing::Combine(testing::Values(false, true),
+ testing::Values(false, true),
+ testing::Values(false, true)));
+#endif // !BUILDFLAG(IS_CHROMEOS)
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_merger.cc b/chromium/components/policy/core/common/policy_merger.cc
new file mode 100644
index 00000000000..31846f0e838
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_merger.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <array>
+#include <map>
+#include <set>
+
+#include "components/policy/core/common/policy_merger.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace {
+
+constexpr std::array<const char*, 6> kDictionaryPoliciesToMerge{
+ key::kExtensionSettings,
+ key::kDeviceLoginScreenPowerManagement,
+ key::kKeyPermissions,
+ key::kPowerManagementIdleSettings,
+ key::kScreenBrightnessPercent,
+ key::kScreenLockDelays,
+};
+
+} // namespace
+
+// static
+bool PolicyMerger::EntriesCanBeMerged(
+ const PolicyMap::Entry& entry_1,
+ const PolicyMap::Entry& entry_2,
+ const bool is_user_cloud_merging_enabled) {
+ if (entry_1.value_unsafe()->type() != entry_2.value_unsafe()->type())
+ return false;
+
+ if (entry_1.ignored() || entry_2.ignored() ||
+ entry_1.source == POLICY_SOURCE_ENTERPRISE_DEFAULT ||
+ entry_2.source == POLICY_SOURCE_ENTERPRISE_DEFAULT ||
+ entry_1.level != entry_2.level)
+ return false;
+
+ // If the policies have matching scope and are non-user, they can be merged.
+ if (entry_1.scope == entry_2.scope && entry_1.scope != POLICY_SCOPE_USER)
+ return true;
+
+ // Merging of user-level GPO policies is not permitted to prevent unexpected
+ // behavior. If such merging is desired, it will be implemented in a similar
+ // way as user cloud merging.
+ if ((entry_1.scope == POLICY_SCOPE_USER &&
+ entry_1.source == POLICY_SOURCE_PLATFORM) ||
+ (entry_2.scope == POLICY_SCOPE_USER &&
+ entry_2.source == POLICY_SOURCE_PLATFORM))
+ return false;
+
+ // On desktop, the user cloud policy potentially comes from a different
+ // domain than e.g. GPO policy or machine-level cloud policy. Merging a user
+ // cloud policy with policies from other sources is only permitted if both of
+ // the following conditions are met:
+ // 1. The CloudUserPolicyMerge metapolicy is set to True.
+ // 2. The user is affiliated with the machine-level cloud policy provider.
+ const bool has_user_cloud_policy = (entry_1.scope == POLICY_SCOPE_USER &&
+ entry_1.source == POLICY_SOURCE_CLOUD) ||
+ (entry_2.scope == POLICY_SCOPE_USER &&
+ entry_2.source == POLICY_SOURCE_CLOUD);
+ const bool is_user_cloud_condition_satisfied =
+ !has_user_cloud_policy || is_user_cloud_merging_enabled;
+
+ // For the scope condition to be satisfied, either the scopes of the two
+ // policies should match or the policy override should be enabled. The scope
+ // check override is only enabled when a user cloud policy is present and user
+ // cloud merging is enabled -- this allows user cloud policies to merge with
+ // machine-level policies.
+ const bool is_scope_overriden =
+ has_user_cloud_policy && is_user_cloud_merging_enabled;
+ const bool is_scope_condition_satisfied =
+ entry_1.scope == entry_2.scope || is_scope_overriden;
+
+ return is_user_cloud_condition_satisfied && is_scope_condition_satisfied;
+}
+
+PolicyMerger::PolicyMerger() = default;
+PolicyMerger::~PolicyMerger() = default;
+
+PolicyListMerger::PolicyListMerger(
+ base::flat_set<std::string> policies_to_merge)
+ : policies_to_merge_(std::move(policies_to_merge)) {}
+PolicyListMerger::~PolicyListMerger() = default;
+
+PolicyGroupMerger::PolicyGroupMerger() = default;
+PolicyGroupMerger::~PolicyGroupMerger() = default;
+
+void PolicyListMerger::Merge(PolicyMap* policies) const {
+ DCHECK(policies);
+ for (auto& it : *policies) {
+ if (CanMerge(it.first, it.second))
+ DoMerge(&it.second);
+ }
+}
+
+void PolicyListMerger::SetAllowUserCloudPolicyMerging(bool allowed) {
+ allow_user_cloud_policy_merging_ = allowed;
+}
+
+bool PolicyListMerger::CanMerge(const std::string& policy_name,
+ PolicyMap::Entry& policy) const {
+ if (policy.source == POLICY_SOURCE_MERGED)
+ return false;
+
+ if (policies_to_merge_.find("*") != policies_to_merge_.end())
+ return policy.value(base::Value::Type::LIST) != nullptr;
+
+ if (policies_to_merge_.find(policy_name) == policies_to_merge_.end())
+ return false;
+
+ if (!policy.value(base::Value::Type::LIST)) {
+ policy.AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_LIST_MERGING_WRONG_POLICY_TYPE_SPECIFIED);
+ return false;
+ }
+
+ return true;
+}
+
+bool PolicyListMerger::AllowUserCloudPolicyMerging() const {
+ return allow_user_cloud_policy_merging_;
+}
+
+void PolicyListMerger::DoMerge(PolicyMap::Entry* policy) const {
+ std::vector<const base::Value*> merged_values;
+ auto compare_value_ptr = [](const base::Value* a, const base::Value* b) {
+ return *a < *b;
+ };
+ std::set<const base::Value*, decltype(compare_value_ptr)> duplicates(
+ compare_value_ptr);
+ bool value_changed = false;
+
+ for (const base::Value& val :
+ policy->value(base::Value::Type::LIST)->GetListDeprecated()) {
+ if (duplicates.find(&val) != duplicates.end())
+ continue;
+ duplicates.insert(&val);
+ merged_values.push_back(&val);
+ }
+
+ // Concatenates the values from accepted conflicting sources to the policy
+ // value while avoiding duplicates.
+ for (const auto& it : policy->conflicts) {
+ if (!PolicyMerger::EntriesCanBeMerged(it.entry(), *policy,
+ AllowUserCloudPolicyMerging())) {
+ continue;
+ }
+
+ for (const base::Value& val :
+ it.entry().value(base::Value::Type::LIST)->GetListDeprecated()) {
+ if (duplicates.find(&val) != duplicates.end())
+ continue;
+ duplicates.insert(&val);
+ merged_values.push_back(&val);
+ }
+
+ value_changed = true;
+ }
+
+ auto new_conflict = policy->DeepCopy();
+ if (value_changed) {
+ base::Value new_value(base::Value::Type::LIST);
+ for (const base::Value* it : merged_values)
+ new_value.Append(it->Clone());
+
+ policy->set_value(std::move(new_value));
+ }
+ policy->ClearConflicts();
+ policy->AddConflictingPolicy(std::move(new_conflict));
+ policy->source = POLICY_SOURCE_MERGED;
+}
+
+PolicyDictionaryMerger::PolicyDictionaryMerger(
+ base::flat_set<std::string> policies_to_merge)
+ : policies_to_merge_(std::move(policies_to_merge)),
+ allowed_policies_(kDictionaryPoliciesToMerge.begin(),
+ kDictionaryPoliciesToMerge.end()) {}
+PolicyDictionaryMerger::~PolicyDictionaryMerger() = default;
+
+void PolicyDictionaryMerger::Merge(PolicyMap* policies) const {
+ DCHECK(policies);
+ for (auto& it : *policies) {
+ if (CanMerge(it.first, it.second))
+ DoMerge(&it.second, *policies);
+ }
+}
+
+void PolicyDictionaryMerger::SetAllowedPoliciesForTesting(
+ base::flat_set<std::string> allowed_policies) {
+ allowed_policies_ = std::move(allowed_policies);
+}
+
+void PolicyDictionaryMerger::SetAllowUserCloudPolicyMerging(bool allowed) {
+ allow_user_cloud_policy_merging_ = allowed;
+}
+
+bool PolicyDictionaryMerger::CanMerge(const std::string& policy_name,
+ PolicyMap::Entry& policy) const {
+ if (policy.source == POLICY_SOURCE_MERGED)
+ return false;
+
+ const bool allowed_to_merge =
+ allowed_policies_.find(policy_name) != allowed_policies_.end();
+
+ if (policies_to_merge_.find("*") != policies_to_merge_.end())
+ return allowed_to_merge && policy.value(base::Value::Type::DICT);
+
+ if (policies_to_merge_.find(policy_name) == policies_to_merge_.end())
+ return false;
+
+ if (!allowed_to_merge) {
+ policy.AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_DICTIONARY_MERGING_POLICY_NOT_ALLOWED);
+ return false;
+ }
+
+ if (!policy.value(base::Value::Type::DICT)) {
+ policy.AddMessage(
+ PolicyMap::MessageType::kError,
+ IDS_POLICY_DICTIONARY_MERGING_WRONG_POLICY_TYPE_SPECIFIED);
+ return false;
+ }
+
+ return true;
+}
+
+bool PolicyDictionaryMerger::AllowUserCloudPolicyMerging() const {
+ return allow_user_cloud_policy_merging_;
+}
+
+void PolicyDictionaryMerger::DoMerge(PolicyMap::Entry* policy,
+ const PolicyMap& policy_map) const {
+ // Keep priority sorted list of potential merge targets.
+ std::vector<const PolicyMap::Entry*> policies;
+ policies.push_back(policy);
+ for (const auto& it : policy->conflicts)
+ policies.push_back(&it.entry());
+ std::sort(
+ policies.begin(), policies.end(),
+ [&policy_map](const PolicyMap::Entry* a, const PolicyMap::Entry* b) {
+ return policy_map.EntryHasHigherPriority(*b, *a);
+ });
+
+ base::DictionaryValue merged_dictionary;
+ bool value_changed = false;
+
+ // Merges all the keys from the policies from different sources.
+ for (const auto* it : policies) {
+ if (it != policy && !PolicyMerger::EntriesCanBeMerged(
+ *it, *policy, AllowUserCloudPolicyMerging()))
+ continue;
+
+ const base::DictionaryValue* dict = nullptr;
+
+ it->value(base::Value::Type::DICT)->GetAsDictionary(&dict);
+ DCHECK(dict);
+
+ for (auto pair : dict->DictItems()) {
+ const auto& key = pair.first;
+ const auto& val = pair.second;
+ merged_dictionary.SetKey(key, val.Clone());
+ }
+
+ value_changed |= it != policy;
+ }
+
+ auto new_conflict = policy->DeepCopy();
+ if (value_changed)
+ policy->set_value(std::move(merged_dictionary));
+
+ policy->ClearConflicts();
+ policy->AddConflictingPolicy(std::move(new_conflict));
+ policy->source = POLICY_SOURCE_MERGED;
+}
+
+void PolicyGroupMerger::Merge(PolicyMap* policies) const {
+ for (size_t i = 0; i < kPolicyAtomicGroupMappingsLength; ++i) {
+ const AtomicGroup& group = kPolicyAtomicGroupMappings[i];
+ bool use_highest_set_priority = false;
+
+ // Defaults to the lowest priority.
+ PolicyMap::Entry highest_set_priority;
+
+ // Find the policy with the highest priority that is both in |policies| and
+ // |group.policies|, an array ending with a nullptr.
+ for (const char* const* policy_name = group.policies; *policy_name;
+ ++policy_name) {
+ const auto* policy = policies->Get(*policy_name);
+ if (!policy)
+ continue;
+
+ use_highest_set_priority = true;
+
+ if (!policies->EntryHasHigherPriority(*policy, highest_set_priority))
+ continue;
+
+ // Do not set POLICY_SOURCE_MERGED as the highest acceptable source
+ // because it is a computed source. In case of an already merged policy,
+ // the highest acceptable source must be the highest of the ones used to
+ // compute the merged value.
+ if (policy->source != POLICY_SOURCE_MERGED) {
+ highest_set_priority = policy->DeepCopy();
+ } else {
+ for (const auto& conflict : policy->conflicts) {
+ if (policies->EntryHasHigherPriority(conflict.entry(),
+ highest_set_priority) &&
+ conflict.entry().source > highest_set_priority.source) {
+ highest_set_priority = conflict.entry().DeepCopy();
+ }
+ }
+ }
+ }
+
+ if (!use_highest_set_priority)
+ continue;
+
+ // Ignore the policies from |group.policies|, an array ending with a
+ // nullptr, that do not share the same source as the one with the highest
+ // priority.
+ for (const char* const* policy_name = group.policies; *policy_name;
+ ++policy_name) {
+ auto* policy = policies->GetMutable(*policy_name);
+ if (!policy)
+ continue;
+
+ if (policy->source < highest_set_priority.source)
+ policy->SetIgnoredByPolicyAtomicGroup();
+ }
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_merger.h b/chromium/components/policy/core/common/policy_merger.h
new file mode 100644
index 00000000000..d3ae158c917
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_merger.h
@@ -0,0 +1,125 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_MERGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_MERGER_H_
+
+#include <stddef.h>
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Abstract class that provides an interface to apply custom merging logic on a
+// set of policies.
+class POLICY_EXPORT PolicyMerger {
+ public:
+ PolicyMerger();
+
+ // Determines if two policy entries are eligible for merging with each other
+ // depending on several factors including its scope, source, and level.
+ static bool EntriesCanBeMerged(const PolicyMap::Entry& entry_1,
+ const PolicyMap::Entry& entry_2,
+ const bool is_user_cloud_merging_enabled);
+
+ virtual ~PolicyMerger();
+ virtual void Merge(PolicyMap* policies) const = 0;
+};
+
+// PolicyListMerger allows the merging of policy lists that have multiple
+// sources. Each policy that has to be merged will have the values from its
+// multiple sources concatenated without duplicates.
+class POLICY_EXPORT PolicyListMerger : public PolicyMerger {
+ public:
+ explicit PolicyListMerger(base::flat_set<std::string> policies_to_merge);
+ PolicyListMerger(const PolicyListMerger&) = delete;
+ PolicyListMerger& operator=(const PolicyListMerger&) = delete;
+ ~PolicyListMerger() override;
+
+ // Merges the list policies from |policies| that have multiple sources.
+ void Merge(PolicyMap* policies) const override;
+
+ // Sets the variable used for determining if user cloud merging is enabled.
+ void SetAllowUserCloudPolicyMerging(bool allowed);
+
+ private:
+ // Returns True if |policy_name| is in the list of policies to merge and if
+ // |policy| has values from different sources that share the same level,
+ // target and scope.
+ bool CanMerge(const std::string& policy_name, PolicyMap::Entry& policy) const;
+
+ // Returns True if user cloud policy merging is enabled through the
+ // CloudUserPolicyMerge policy and the current user is affiliated.
+ bool AllowUserCloudPolicyMerging() const;
+
+ // Merges the values of |policy| if they come from multiple sources. Keeps
+ // track of the original values by leaving them as conflicts. |policy| must
+ // remain unchanged if there is nothing to merge.
+ void DoMerge(PolicyMap::Entry* policy) const;
+
+ bool allow_user_cloud_policy_merging_ = false;
+ const base::flat_set<std::string> policies_to_merge_;
+};
+
+// PolicyDictionaryMerger allows the merging of policy dictionaries that have
+// multiple sources. Each policy that has to be merged will have its first level
+// keys merged into one dictionary, each conflict will be resolved by
+// using the key coming from the highest priority source.
+class POLICY_EXPORT PolicyDictionaryMerger : public PolicyMerger {
+ public:
+ explicit PolicyDictionaryMerger(
+ base::flat_set<std::string> policies_to_merge);
+ PolicyDictionaryMerger(const PolicyDictionaryMerger&) = delete;
+ PolicyDictionaryMerger& operator=(const PolicyDictionaryMerger&) = delete;
+ ~PolicyDictionaryMerger() override;
+
+ // Merges the dictionary policies from |policies| that have multiple sources.
+ void Merge(PolicyMap* policies) const override;
+ void SetAllowedPoliciesForTesting(
+ base::flat_set<std::string> allowed_policies);
+
+ // Sets the variable used for determining if user cloud merging is enabled.
+ void SetAllowUserCloudPolicyMerging(bool allowed);
+
+ private:
+ // Returns True if |policy_name| is in the list of policies to merge and if
+ // |policy| has values from different sources that share the same level,
+ // target and scope.
+ bool CanMerge(const std::string& policy_name, PolicyMap::Entry& policy) const;
+
+ // Returns True if user cloud policy merging is enabled through the
+ // CloudUserPolicyMerge policy and the current user is affiliated.
+ bool AllowUserCloudPolicyMerging() const;
+
+ // Merges the values of |policy| if they come from multiple sources. Keeps
+ // track of the original values by leaving them as conflicts. |policy| stays
+ // intact if there is nothing to merge.
+ void DoMerge(PolicyMap::Entry* policy, const PolicyMap& policy_map) const;
+
+ bool allow_user_cloud_policy_merging_ = false;
+ const base::flat_set<std::string> policies_to_merge_;
+ base::flat_set<std::string> allowed_policies_;
+};
+
+// PolicyGroupMerger enforces atomic policy groups. It disables the policies
+// from a group that do not share the highest priority from that group.
+class POLICY_EXPORT PolicyGroupMerger : public PolicyMerger {
+ public:
+ PolicyGroupMerger();
+ PolicyGroupMerger(const PolicyGroupMerger&) = delete;
+ PolicyGroupMerger& operator=(const PolicyGroupMerger&) = delete;
+ ~PolicyGroupMerger() override;
+
+ // Disables policies from atomic groups that do not share the highest priority
+ // from that group.
+ void Merge(PolicyMap* result) const override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_MERGER_H_
diff --git a/chromium/components/policy/core/common/policy_migrator.cc b/chromium/components/policy/core/common/policy_migrator.cc
new file mode 100644
index 00000000000..51398cad31e
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_migrator.cc
@@ -0,0 +1,65 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_migrator.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace policy {
+
+namespace {
+
+void DoNothing(base::Value* val) {}
+
+} // namespace
+
+PolicyMigrator::~PolicyMigrator() = default;
+
+void PolicyMigrator::CopyPolicyIfUnset(PolicyMap& source,
+ PolicyMap* dest,
+ const Migration& migration) {
+ PolicyMap::Entry* entry = source.GetMutable(migration.old_name);
+ if (entry) {
+ if (!dest->Get(migration.new_name)) {
+ VLOG(3) << "Legacy policy '" << migration.old_name
+ << "' has been copied to '" << migration.new_name << "'.";
+ auto new_entry = entry->DeepCopy();
+ migration.transform.Run(new_entry.value_unsafe());
+ new_entry.AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_MIGRATED_NEW_POLICY,
+ {base::UTF8ToUTF16(migration.old_name)});
+ dest->Set(migration.new_name, std::move(new_entry));
+ } else {
+ VLOG(3) << "Legacy policy '" << migration.old_name
+ << "' is ignored because '" << migration.new_name
+ << "' is also set. ";
+ }
+ entry->AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_MIGRATED_OLD_POLICY,
+ {base::UTF8ToUTF16(migration.new_name)});
+ } else {
+ VLOG(3) << "Legacy policy '" << migration.old_name << "' is not set.";
+ }
+}
+
+PolicyMigrator::Migration::Migration(Migration&&) = default;
+
+PolicyMigrator::Migration::Migration(const char* old_name, const char* new_name)
+ : Migration(old_name, new_name, base::BindRepeating(&DoNothing)) {}
+
+PolicyMigrator::Migration::Migration(const char* old_name,
+ const char* new_name,
+ ValueTransform transform)
+ : old_name(old_name), new_name(new_name), transform(std::move(transform)) {}
+
+PolicyMigrator::Migration::~Migration() = default;
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_migrator.h b/chromium/components/policy/core/common/policy_migrator.h
new file mode 100644
index 00000000000..77d0e5ecfd1
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_migrator.h
@@ -0,0 +1,60 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_MIGRATOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_MIGRATOR_H_
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A helper class that migrates a deprecated policy to a new policy -
+// potentially across domain boundaries, by setting up the new policy based on
+// the old one. It can migrate a deprecated policy to a new policy.
+//
+// For migrations that are only in the Chrome domain and which are accessed via
+// prefs: you should use |LegacyPoliciesDeprecatingPolicyHandler| instead.
+class POLICY_EXPORT PolicyMigrator {
+ public:
+ virtual ~PolicyMigrator();
+
+ // If there are deprecated policies in |bundle|, set the value of the new
+ // policies accordingly.
+ virtual void Migrate(PolicyBundle* bundle) = 0;
+
+ // Indicates how to rename a policy when migrating the old policy to the new
+ // policy.
+ struct POLICY_EXPORT Migration {
+ using ValueTransform = base::RepeatingCallback<void(base::Value*)>;
+
+ Migration(Migration&&);
+ Migration(const char* old_name, const char* new_name);
+ Migration(const char* old_name,
+ const char* new_name,
+ ValueTransform transform);
+ ~Migration();
+
+ // Old name for the policy
+ const char* old_name;
+ // New name for the policy, in the Chrome domain.
+ const char* new_name;
+ // Function to use to convert values from the old policy to the new
+ // policy (e.g. convert value types). It should mutate the Value in
+ // place. By default, it does no transform.
+ ValueTransform transform;
+ };
+
+ protected:
+ static void CopyPolicyIfUnset(PolicyMap& source,
+ PolicyMap* dest,
+ const Migration& migration);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_MIGRATOR_H_
diff --git a/chromium/components/policy/core/common/policy_namespace.cc b/chromium/components/policy/core/common/policy_namespace.cc
new file mode 100644
index 00000000000..33a99928354
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_namespace.cc
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_namespace.h"
+
+#include <tuple>
+
+namespace policy {
+
+PolicyNamespace::PolicyNamespace() {}
+
+PolicyNamespace::PolicyNamespace(PolicyDomain domain,
+ const std::string& component_id)
+ : domain(domain),
+ component_id(component_id) {}
+
+PolicyNamespace::PolicyNamespace(const PolicyNamespace& other)
+ : domain(other.domain),
+ component_id(other.component_id) {}
+
+PolicyNamespace::~PolicyNamespace() {}
+
+PolicyNamespace& PolicyNamespace::operator=(const PolicyNamespace& other) {
+ domain = other.domain;
+ component_id = other.component_id;
+ return *this;
+}
+
+bool PolicyNamespace::operator<(const PolicyNamespace& other) const {
+ return std::tie(domain, component_id) <
+ std::tie(other.domain, other.component_id);
+}
+
+bool PolicyNamespace::operator==(const PolicyNamespace& other) const {
+ return domain == other.domain && component_id == other.component_id;
+}
+
+bool PolicyNamespace::operator!=(const PolicyNamespace& other) const {
+ return !(*this == other);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_namespace.h b/chromium/components/policy/core/common/policy_namespace.h
new file mode 100644
index 00000000000..4a0637689ff
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_namespace.h
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Policies are namespaced by a (PolicyDomain, ID) pair. The meaning of the ID
+// string depends on the domain; for example, if the PolicyDomain is
+// "extensions" then the ID identifies the extension that the policies control.
+enum PolicyDomain {
+ // The component ID for chrome policies is always the empty string.
+ POLICY_DOMAIN_CHROME,
+
+ // The component ID for the extension policies is equal to the extension ID.
+ POLICY_DOMAIN_EXTENSIONS,
+
+ // The namespace that corresponds to the policies for extensions running
+ // under Chrome OS signin profile. The component ID is equal to the extension
+ // ID.
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS,
+
+ // Must be the last entry.
+ POLICY_DOMAIN_SIZE,
+};
+
+// Groups a policy domain and a component ID in a single object representing
+// a policy namespace. Objects of this class can be used as keys in std::maps.
+struct POLICY_EXPORT PolicyNamespace {
+ PolicyNamespace();
+ PolicyNamespace(PolicyDomain domain, const std::string& component_id);
+ PolicyNamespace(const PolicyNamespace& other);
+ ~PolicyNamespace();
+
+ PolicyNamespace& operator=(const PolicyNamespace& other);
+ bool operator<(const PolicyNamespace& other) const;
+ bool operator==(const PolicyNamespace& other) const;
+ bool operator!=(const PolicyNamespace& other) const;
+
+ PolicyDomain domain;
+ std::string component_id;
+};
+
+typedef std::vector<PolicyNamespace> PolicyNamespaceList;
+
+struct PolicyNamespaceHash {
+ size_t operator()(const policy::PolicyNamespace& ns) const {
+ return std::hash<std::string>()(ns.component_id) ^
+ (UINT64_C(1) << ns.domain);
+ }
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_NAMESPACE_H_
diff --git a/chromium/components/policy/core/common/policy_pref_names.cc b/chromium/components/policy/core/common/policy_pref_names.cc
new file mode 100644
index 00000000000..f7b947ab693
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_pref_names.cc
@@ -0,0 +1,114 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_pref_names.h"
+
+#include "build/build_config.h"
+
+namespace policy {
+namespace policy_prefs {
+
+#if BUILDFLAG(IS_WIN)
+// Integer pref that stores Azure Active Directory management authority.
+const char kAzureActiveDirectoryManagement[] =
+ "management.platform.azure_active_directory";
+
+// Integer pref that stores the Windows enterprise MDM management authority.
+const char kEnterpriseMDMManagementWindows[] =
+ "management.platform.enterprise_mdm_win";
+#elif BUILDFLAG(IS_MAC)
+// Integer pref that stores the Mac enterprise MDM management authority.
+const char kEnterpriseMDMManagementMac[] =
+ "management.platform.enterprise_mdm_mac";
+#endif
+
+// 64-bit serialization of the time last policy usage statistics were collected
+// by UMA_HISTOGRAM_ENUMERATION.
+const char kLastPolicyStatisticsUpdate[] = "policy.last_statistics_update";
+
+// Enum specifying if/how the SafeSites content filter should be applied.
+// See the SafeSitesFilterBehavior policy for details.
+const char kSafeSitesFilterBehavior[] = "policy.safe_sites_filter_behavior";
+
+// A list of system features to be disabled (see policy
+// "SystemFeaturesDisableList").
+const char kSystemFeaturesDisableList[] = "policy.system_features_disable_list";
+
+// Enum specifying the user experience of disabled features.
+// See the SystemFeaturesDisableMode policy for details.
+const char kSystemFeaturesDisableMode[] = "policy.system_features_disable_mode";
+
+// Blocks access to the listed host patterns.
+const char kUrlBlocklist[] = "policy.url_blocklist";
+
+// Allows access to the listed host patterns, as exceptions to the blacklist.
+const char kUrlAllowlist[] = "policy.url_allowlist";
+
+// Integer that specifies the policy refresh rate for user-policy in
+// milliseconds. Not all values are meaningful, so it is clamped to a sane range
+// by the cloud policy subsystem.
+const char kUserPolicyRefreshRate[] = "policy.user_refresh_rate";
+
+// Boolean indicates whether the cloud management enrollment is mandatory or
+// not.
+const char kCloudManagementEnrollmentMandatory[] =
+ "policy.cloud_management_enrollment_mandatory";
+
+// Integer that sets the minimal limit on the data size in the clipboard to be
+// checked against Data Leak Prevention rules.
+const char kDlpClipboardCheckSizeLimit[] =
+ "policy.dlp_clipboard_check_size_limit";
+
+// Boolean policy preference to enable reporting of data leak prevention events.
+const char kDlpReportingEnabled[] = "policy.dlp_reporting_enabled";
+
+// A list of Data leak prevention rules.
+const char kDlpRulesList[] = "policy.dlp_rules_list";
+
+// A boolean value that can be used to disable native window occlusion
+// calculation, even if the Finch feature is enabled.
+const char kNativeWindowOcclusionEnabled[] =
+ "policy.native_window_occlusion_enabled";
+
+// Boolean policy preference for force enabling or disabling the
+// IntensiveWakeUpThrottling web feature. Only applied if the policy is managed.
+const char kIntensiveWakeUpThrottlingEnabled[] =
+ "policy.intensive_wake_up_throttling_enabled";
+
+// Boolean policy preference for force enabling or disabling the
+// SetTimeoutWithoutClamp web feature.
+const char kSetTimeoutWithout1MsClampEnabled[] =
+ "policy.set_timeout_without_1ms_clamp";
+
+#if BUILDFLAG(IS_ANDROID)
+// Boolean policy preference to disable the BackForwardCache feature.
+const char kBackForwardCacheEnabled[] = "policy.back_forward_cache_enabled";
+#endif // BUILDFLAG(IS_ANDROID)
+
+// Boolean policy preference to disable the User-Agent Client Hints
+// updated GREASE algorithm feature.
+const char kUserAgentClientHintsGREASEUpdateEnabled[] =
+ "policy.user_agent_client_hints_grease_update_enabled";
+
+// Boolean policy preference to disable the URL parameter
+// filter.
+const char kUrlParamFilterEnabled[] = "policy.url_param_filter_enabled";
+
+// Boolean policy to allow isolated apps developer mode.
+const char kIsolatedAppsDeveloperModeAllowed[] =
+ "policy.isolated_apps_developer_mode_allowed";
+
+// Boolean policy to force WebSQL to be enabled.
+const char kWebSQLAccess[] = "policy.web_sql_access";
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+// Last time that a check for cloud policy management was done. This time is
+// recorded on Android and iOS so that retries aren't attempted on every
+// startup. Instead the cloud policy registration is retried at least 1 or 3
+// days later.
+const char kLastPolicyCheckTime[] = "policy.last_policy_check_time";
+#endif
+
+} // namespace policy_prefs
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_pref_names.h b/chromium/components/policy/core/common/policy_pref_names.h
new file mode 100644
index 00000000000..6dc51003113
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_pref_names.h
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
+
+#include "build/build_config.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace policy_prefs {
+
+#if BUILDFLAG(IS_WIN)
+POLICY_EXPORT extern const char kAzureActiveDirectoryManagement[];
+POLICY_EXPORT extern const char kEnterpriseMDMManagementWindows[];
+#endif
+POLICY_EXPORT extern const char kCloudManagementEnrollmentMandatory[];
+POLICY_EXPORT extern const char kDlpClipboardCheckSizeLimit[];
+POLICY_EXPORT extern const char kDlpReportingEnabled[];
+POLICY_EXPORT extern const char kDlpRulesList[];
+#if BUILDFLAG(IS_MAC)
+POLICY_EXPORT extern const char kEnterpriseMDMManagementMac[];
+#endif
+POLICY_EXPORT extern const char kLastPolicyStatisticsUpdate[];
+POLICY_EXPORT extern const char kNativeWindowOcclusionEnabled[];
+POLICY_EXPORT extern const char kSafeSitesFilterBehavior[];
+POLICY_EXPORT extern const char kSystemFeaturesDisableList[];
+POLICY_EXPORT extern const char kSystemFeaturesDisableMode[];
+POLICY_EXPORT extern const char kUrlBlocklist[];
+POLICY_EXPORT extern const char kUrlAllowlist[];
+POLICY_EXPORT extern const char kUserPolicyRefreshRate[];
+POLICY_EXPORT extern const char kIntensiveWakeUpThrottlingEnabled[];
+POLICY_EXPORT extern const char kUserAgentClientHintsGREASEUpdateEnabled[];
+POLICY_EXPORT extern const char kUrlParamFilterEnabled[];
+POLICY_EXPORT extern const char kSetTimeoutWithout1MsClampEnabled[];
+#if BUILDFLAG(IS_ANDROID)
+POLICY_EXPORT extern const char kBackForwardCacheEnabled[];
+#endif // BUILDFLAG(IS_ANDROID)
+POLICY_EXPORT extern const char kIsolatedAppsDeveloperModeAllowed[];
+POLICY_EXPORT extern const char kWebSQLAccess[];
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+POLICY_EXPORT extern const char kLastPolicyCheckTime[];
+#endif
+
+} // namespace policy_prefs
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_PREF_NAMES_H_
diff --git a/chromium/components/policy/core/common/policy_proto_decoders.cc b/chromium/components/policy/core/common/policy_proto_decoders.cc
new file mode 100644
index 00000000000..11521b3b083
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_proto_decoders.cc
@@ -0,0 +1,213 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_proto_decoders.h"
+
+#include <cstring>
+#include <limits>
+#include <memory>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+namespace em = enterprise_management;
+
+namespace {
+
+// Returns true and sets |level| to a PolicyLevel if the policy has been set
+// at that level. Returns false if the policy is not set, or has been set at
+// the level of PolicyOptions::UNSET.
+template <class AnyPolicyProto>
+bool GetPolicyLevel(const AnyPolicyProto& policy_proto, PolicyLevel* level) {
+ if (!policy_proto.has_value()) {
+ return false;
+ }
+ if (!policy_proto.has_policy_options()) {
+ *level = POLICY_LEVEL_MANDATORY; // Default level.
+ return true;
+ }
+ switch (policy_proto.policy_options().mode()) {
+ case em::PolicyOptions::MANDATORY:
+ *level = POLICY_LEVEL_MANDATORY;
+ return true;
+ case em::PolicyOptions::RECOMMENDED:
+ *level = POLICY_LEVEL_RECOMMENDED;
+ return true;
+ case em::PolicyOptions::UNSET:
+ return false;
+ }
+}
+
+// Convert a BooleanPolicyProto to a bool base::Value.
+base::Value DecodeBooleanProto(const em::BooleanPolicyProto& proto) {
+ return base::Value(proto.value());
+}
+
+// Convert an IntegerPolicyProto to an int base::Value.
+base::Value DecodeIntegerProto(const em::IntegerPolicyProto& proto,
+ std::string* error) {
+ google::protobuf::int64 value = proto.value();
+
+ if (value < std::numeric_limits<int>::min() ||
+ value > std::numeric_limits<int>::max()) {
+ LOG(WARNING) << "Integer value " << value << " out of numeric limits";
+ *error = "Number out of range - invalid int32";
+ return base::Value(base::NumberToString(value));
+ }
+
+ return base::Value(static_cast<int>(value));
+}
+
+// Convert a StringPolicyProto to a string base::Value.
+base::Value DecodeStringProto(const em::StringPolicyProto& proto) {
+ return base::Value(proto.value());
+}
+
+// Convert a StringListPolicyProto to a List base::Value, where each list value
+// is of Type::STRING.
+base::Value DecodeStringListProto(const em::StringListPolicyProto& proto) {
+ base::Value list_value(base::Value::Type::LIST);
+ for (const auto& entry : proto.value().entries())
+ list_value.Append(entry);
+ return list_value;
+}
+
+// Convert a StringPolicyProto to a base::Value of any type (for example,
+// Type::DICTIONARY or Type::LIST) by parsing it as JSON.
+base::Value DecodeJsonProto(const em::StringPolicyProto& proto,
+ std::string* error) {
+ const std::string& json = proto.value();
+ base::JSONReader::ValueWithError value_with_error =
+ base::JSONReader::ReadAndReturnValueWithError(
+ json, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+
+ if (!value_with_error.value) {
+ // Can't parse as JSON so return it as a string, and leave it to the handler
+ // to validate.
+ LOG(WARNING) << "Invalid JSON: " << json;
+ *error = value_with_error.error_message;
+ return base::Value(json);
+ }
+
+ // Accept any Value type that parsed as JSON, and leave it to the handler to
+ // convert and check the concrete type.
+ error->clear();
+ return std::move(value_with_error.value.value());
+}
+
+bool PerProfileMatches(bool policy_per_profile,
+ PolicyPerProfileFilter per_profile_enum) {
+ switch (per_profile_enum) {
+ case PolicyPerProfileFilter::kTrue:
+ return policy_per_profile;
+ case PolicyPerProfileFilter::kFalse:
+ return !policy_per_profile;
+ case PolicyPerProfileFilter::kAny:
+ return true;
+ }
+}
+
+} // namespace
+
+void DecodeProtoFields(
+ const em::CloudPolicySettings& policy,
+ base::WeakPtr<CloudExternalDataManager> external_data_manager,
+ PolicySource source,
+ PolicyScope scope,
+ PolicyMap* map,
+ PolicyPerProfileFilter per_profile) {
+ PolicyLevel level;
+
+ for (const BooleanPolicyAccess& access : kBooleanPolicyAccess) {
+ if (!PerProfileMatches(access.per_profile, per_profile) ||
+ !access.has_proto(policy))
+ continue;
+
+ const em::BooleanPolicyProto& proto = access.get_proto(policy);
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ map->Set(access.policy_key, level, scope, source, DecodeBooleanProto(proto),
+ nullptr);
+ }
+
+ for (const IntegerPolicyAccess& access : kIntegerPolicyAccess) {
+ if (!PerProfileMatches(access.per_profile, per_profile) ||
+ !access.has_proto(policy))
+ continue;
+
+ const em::IntegerPolicyProto& proto = access.get_proto(policy);
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ std::string error;
+ map->Set(access.policy_key, level, scope, source,
+ DecodeIntegerProto(proto, &error), nullptr);
+ if (!error.empty())
+ map->AddMessage(access.policy_key, PolicyMap::MessageType::kError,
+ IDS_POLICY_PROTO_PARSING_ERROR,
+ {base::UTF8ToUTF16(error)});
+ }
+
+ for (const StringPolicyAccess& access : kStringPolicyAccess) {
+ if (!PerProfileMatches(access.per_profile, per_profile) ||
+ !access.has_proto(policy))
+ continue;
+
+ const em::StringPolicyProto& proto = access.get_proto(policy);
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ std::string error;
+ base::Value value = (access.type == StringPolicyType::STRING)
+ ? DecodeStringProto(proto)
+ : DecodeJsonProto(proto, &error);
+
+ // EXTERNAL policies represent a single piece of external data that is
+ // retrieved by an ExternalDataFetcher.
+ // kWebAppInstallForceList is currently the only policy that is a JSON
+ // policy (containing mostly non-external data) which can contain
+ // references to multiple pieces of external data as well. For that it
+ // needs an ExternalDataFetcher. If we ever create a second such policy,
+ // create a new type for it instead of special-casing the policies here.
+ std::unique_ptr<ExternalDataFetcher> external_data_fetcher =
+ (access.type == StringPolicyType::EXTERNAL ||
+ strcmp(access.policy_key, key::kWebAppInstallForceList) == 0)
+ ? std::make_unique<ExternalDataFetcher>(external_data_manager,
+ access.policy_key)
+ : nullptr;
+
+ map->Set(access.policy_key, level, scope, source, std::move(value),
+ std::move(external_data_fetcher));
+ if (!error.empty())
+ map->AddMessage(access.policy_key, PolicyMap::MessageType::kError,
+ IDS_POLICY_PROTO_PARSING_ERROR,
+ {base::UTF8ToUTF16(error)});
+ }
+
+ for (const StringListPolicyAccess& access : kStringListPolicyAccess) {
+ if (!PerProfileMatches(access.per_profile, per_profile) ||
+ !access.has_proto(policy))
+ continue;
+
+ const em::StringListPolicyProto& proto = access.get_proto(policy);
+ if (!GetPolicyLevel(proto, &level))
+ continue;
+
+ map->Set(access.policy_key, level, scope, source,
+ DecodeStringListProto(proto), nullptr);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_proto_decoders.h b/chromium/components/policy/core/common/policy_proto_decoders.h
new file mode 100644
index 00000000000..4a47256f188
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_proto_decoders.h
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
+
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_export.h"
+
+namespace enterprise_management {
+class CloudPolicySettings;
+} // namespace enterprise_management
+
+namespace policy {
+
+class CloudExternalDataManager;
+class PolicyMap;
+
+enum class PolicyPerProfileFilter {
+ // Applies to the browser profile.
+ kTrue,
+ // Applies to all browser instances.
+ kFalse,
+ // Any user policy.
+ kAny
+};
+
+// Decode all the fields in |policy| that match the needed |per_profile| flag
+// which are recognized (see the metadata in policy_constants.cc) and store them
+// in the given |map|, with the given |source| and |scope|. The value of
+// |per_profile| parameter specifies which fields have to be included based on
+// per_profile flag.
+POLICY_EXPORT void DecodeProtoFields(
+ const enterprise_management::CloudPolicySettings& policy,
+ base::WeakPtr<CloudExternalDataManager> external_data_manager,
+ PolicySource source,
+ PolicyScope scope,
+ PolicyMap* map,
+ PolicyPerProfileFilter per_profile);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_PROTO_DECODERS_H_
diff --git a/chromium/components/policy/core/common/policy_proto_decoders_unittest.cc b/chromium/components/policy/core/common/policy_proto_decoders_unittest.cc
new file mode 100644
index 00000000000..afba91ee7e2
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_proto_decoders_unittest.cc
@@ -0,0 +1,259 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_proto_decoders.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class PolicyProtoDecodersTest : public testing::Test {
+ public:
+ PolicyMap policy_map_;
+ PolicyMap expected_policy_map_;
+ UserPolicyBuilder user_policy_;
+
+ base::WeakPtr<CloudExternalDataManager> external_data_manager_;
+};
+
+TEST_F(PolicyProtoDecodersTest, BooleanPolicy) {
+ expected_policy_map_.Set(key::kSearchSuggestEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, IntegerPolicy) {
+ expected_policy_map_.Set(key::kIncognitoModeAvailability,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+
+ user_policy_.payload().mutable_incognitomodeavailability()->set_value(1);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, StringPolicy) {
+ expected_policy_map_.Set(key::kDefaultSearchProviderName,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("Google"), nullptr);
+
+ user_policy_.payload().mutable_defaultsearchprovidername()->set_value(
+ "Google");
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, StringListPolicy) {
+ std::vector<base::Value> expected_disabled_sync_types;
+ expected_disabled_sync_types.emplace_back(base::Value("bookmarks"));
+ expected_disabled_sync_types.emplace_back(base::Value("readingList"));
+ expected_policy_map_.Set(key::kSyncTypesListDisabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(expected_disabled_sync_types), nullptr);
+
+ auto* disabled_sync_types =
+ user_policy_.payload().mutable_synctypeslistdisabled()->mutable_value();
+ disabled_sync_types->add_entries("bookmarks");
+ disabled_sync_types->add_entries("readingList");
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, PolicyWithOptionUnset) {
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ user_policy_.payload()
+ .mutable_searchsuggestenabled()
+ ->mutable_policy_options()
+ ->set_mode(em::PolicyOptions::UNSET);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ // Any values with PolicyOptions::UNSET will never set into policy_map_
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, PolicyWithOptionRecommended) {
+ expected_policy_map_.Set(key::kSearchSuggestEnabled, POLICY_LEVEL_RECOMMENDED,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ user_policy_.payload()
+ .mutable_searchsuggestenabled()
+ ->mutable_policy_options()
+ ->set_mode(em::PolicyOptions::RECOMMENDED);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, IntegerPolicyWithValueLowerThanMinLimit) {
+ std::string too_small_value =
+ base::NumberToString(std::numeric_limits<int32_t>::min() - 1LL);
+
+ expected_policy_map_.Set(key::kIncognitoModeAvailability,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(too_small_value),
+ nullptr);
+ expected_policy_map_.AddMessage(
+ key::kIncognitoModeAvailability, PolicyMap::MessageType::kError,
+ IDS_POLICY_PROTO_PARSING_ERROR, {u"Number out of range - invalid int32"});
+
+ user_policy_.payload().mutable_incognitomodeavailability()->set_value(
+ std::numeric_limits<int32_t>::min() - 1LL);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, IntegerPolicyWithValueUpperThanMaxLimit) {
+ std::string too_big_value =
+ base::NumberToString(std::numeric_limits<int32_t>::max() + 1LL);
+
+ expected_policy_map_.Set(key::kIncognitoModeAvailability,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(too_big_value),
+ nullptr);
+ expected_policy_map_.AddMessage(
+ key::kIncognitoModeAvailability, PolicyMap::MessageType::kError,
+ IDS_POLICY_PROTO_PARSING_ERROR, {u"Number out of range - invalid int32"});
+
+ user_policy_.payload().mutable_incognitomodeavailability()->set_value(
+ std::numeric_limits<int32_t>::max() + 1LL);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, JsonPolicy) {
+ base::Value jsonPolicy(base::Value::Type::DICTIONARY);
+ jsonPolicy.SetKey("key", base::Value("value"));
+
+ expected_policy_map_.Set(key::kManagedBookmarks, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(jsonPolicy), nullptr);
+
+ std::string jsonPolicyStr = R"({
+ "key": "value"
+ })";
+ auto* disabled_managed_bookmarks_settings =
+ user_policy_.payload().mutable_managedbookmarks()->mutable_value();
+ disabled_managed_bookmarks_settings->append(jsonPolicyStr);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, InvalidJsonPolicy) {
+ std::string invalidDummyJson = R"({
+ "key": "value"
+ )"; // lacks a close brace
+
+ expected_policy_map_.Set(key::kManagedBookmarks, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(invalidDummyJson), nullptr);
+ expected_policy_map_.AddMessage(
+ key::kManagedBookmarks, PolicyMap::MessageType::kError,
+ IDS_POLICY_PROTO_PARSING_ERROR, {u"Line: 3, column: 3, Syntax error."});
+
+ auto* disabled_managed_bookmarks_settings =
+ user_policy_.payload().mutable_managedbookmarks()->mutable_value();
+ disabled_managed_bookmarks_settings->append(invalidDummyJson);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, PolicyWithAnyFilter) {
+ expected_policy_map_.Set(key::kSearchSuggestEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ expected_policy_map_.Set(key::kCloudReportingEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ user_policy_.payload().mutable_cloudreportingenabled()->set_value(true);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kAny);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, PolicyWithTrueFilter) {
+ expected_policy_map_.Set(key::kSearchSuggestEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ user_policy_.payload().mutable_cloudreportingenabled()->set_value(true);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kTrue);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+TEST_F(PolicyProtoDecodersTest, PolicyWithFalseFilter) {
+ expected_policy_map_.Set(key::kCloudReportingEnabled, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+
+ user_policy_.payload().mutable_searchsuggestenabled()->set_value(true);
+ user_policy_.payload().mutable_cloudreportingenabled()->set_value(true);
+
+ DecodeProtoFields(user_policy_.payload(), external_data_manager_,
+ POLICY_SOURCE_CLOUD, POLICY_SCOPE_USER, &policy_map_,
+ PolicyPerProfileFilter::kFalse);
+
+ EXPECT_TRUE(expected_policy_map_.Equals(policy_map_));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_scheduler.cc b/chromium/components/policy/core/common/policy_scheduler.cc
new file mode 100644
index 00000000000..e7e94b0b61d
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_scheduler.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_scheduler.h"
+
+#include "base/bind.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace policy {
+
+PolicyScheduler::PolicyScheduler(Task task,
+ SchedulerCallback callback,
+ base::TimeDelta interval)
+ : task_(task), callback_(callback), interval_(interval) {
+ ScheduleTaskNow();
+}
+
+PolicyScheduler::~PolicyScheduler() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void PolicyScheduler::ScheduleTaskNow() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ScheduleDelayedTask(base::TimeDelta());
+}
+
+void PolicyScheduler::ScheduleDelayedTask(base::TimeDelta delay) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (job_) {
+ job_->Cancel();
+ }
+ job_ = std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
+ &PolicyScheduler::RunScheduledTask, weak_ptr_factory_.GetWeakPtr()));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE,
+ job_->callback(), delay);
+}
+
+void PolicyScheduler::ScheduleNextTask() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ base::TimeDelta interval = overlap_ ? base::TimeDelta() : interval_;
+ const base::TimeTicks now(base::TimeTicks::Now());
+ // Time uses saturated arithmetics thus no under/overflow possible.
+ const base::TimeDelta delay = last_task_ + interval - now;
+ // Clamping delay to non-negative values just to be on the safe side.
+ ScheduleDelayedTask(std::max(base::TimeDelta(), delay));
+}
+
+void PolicyScheduler::RunScheduledTask() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (task_in_progress_) {
+ overlap_ = true;
+ return;
+ }
+
+ overlap_ = false;
+ task_in_progress_ = true;
+ last_refresh_attempt_ = base::Time::Now();
+ task_.Run(base::BindOnce(&PolicyScheduler::OnTaskDone,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyScheduler::OnTaskDone(bool success) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ task_in_progress_ = false;
+ last_task_ = base::TimeTicks::Now();
+ callback_.Run(success);
+ ScheduleNextTask();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_scheduler.h b/chromium/components/policy/core/common/policy_scheduler.h
new file mode 100644
index 00000000000..fe3fcadfc98
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_scheduler.h
@@ -0,0 +1,102 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Scheduler for driving repeated asynchronous tasks such as e.g. policy
+// fetches. Subsequent tasks are guaranteed not to overlap. Tasks are posted to
+// the current thread and therefore must not block (suitable e.g. for
+// asynchronous D-Bus calls).
+// Tasks scheduling begins immediately after instantiation of the class. Upon
+// destruction, scheduled but not yet started tasks are cancelled. The result of
+// started but not finished tasks is NOT reported.
+class POLICY_EXPORT PolicyScheduler {
+ public:
+ // Callback for the task to report success or failure.
+ using TaskCallback = base::OnceCallback<void(bool success)>;
+
+ // Task to be performed at regular intervals. The task takes a |callback| to
+ // return success or failure.
+ using Task = base::RepeatingCallback<void(TaskCallback callback)>;
+
+ // Callback for PolicyScheduler to report success or failure of the tasks.
+ using SchedulerCallback = base::RepeatingCallback<void(bool success)>;
+
+ // Defines the |task| to be run every |interval| and the |callback| for the
+ // scheduler to report the result. (Intervals are computed as the time
+ // difference between the end of the previous and the start of the subsequent
+ // task.) Calling the constructor starts the loop and schedules the first task
+ // to be run without delay.
+ PolicyScheduler(Task task,
+ SchedulerCallback callback,
+ base::TimeDelta interval);
+ PolicyScheduler(const PolicyScheduler&) = delete;
+ PolicyScheduler& operator=(const PolicyScheduler&) = delete;
+ ~PolicyScheduler();
+
+ // Schedules a task to run immediately. Deletes any previously scheduled but
+ // not yet started tasks. In case a task is running currently, the new task is
+ // scheduled to run immediately after the end of the currently running task.
+ void ScheduleTaskNow();
+
+ base::TimeDelta interval() const { return interval_; }
+
+ base::Time last_refresh_attempt() const { return last_refresh_attempt_; }
+
+ private:
+ // Schedules next task to run in |delay|. Deletes any previously scheduled
+ // tasks.
+ void ScheduleDelayedTask(base::TimeDelta delay);
+
+ // Schedules next task to run in |interval_| or immediately in case of
+ // overlap. Deletes any previously scheduled tasks.
+ void ScheduleNextTask();
+
+ // Actually executes the scheduled task.
+ void RunScheduledTask();
+
+ // Reports back the |result| of the previous task and schedules the next one.
+ void OnTaskDone(bool result);
+
+ Task task_;
+ SchedulerCallback callback_;
+ // Tasks are being run every |interval_|.
+ const base::TimeDelta interval_;
+
+ // Whether a task is in progress.
+ bool task_in_progress_ = false;
+
+ // Whether there had been an overlap of tasks and thus the next task needs to
+ // be scheduled without delay.
+ bool overlap_ = false;
+
+ // End time of the previous task. Zero in case no task has ended yet.
+ base::TimeTicks last_task_;
+
+ // Last time refresh has been attempted.
+ base::Time last_refresh_attempt_;
+
+ std::unique_ptr<base::CancelableOnceClosure> job_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Must be last member.
+ base::WeakPtrFactory<PolicyScheduler> weak_ptr_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEDULER_H_
diff --git a/chromium/components/policy/core/common/policy_scheduler_unittest.cc b/chromium/components/policy/core/common/policy_scheduler_unittest.cc
new file mode 100644
index 00000000000..bc8b61eab54
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_scheduler_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_scheduler.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class PolicySchedulerTest : public testing::Test {
+ public:
+ void DoTask(PolicyScheduler::TaskCallback callback) {
+ do_counter_++;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), true));
+ }
+
+ void OnTaskDone(bool success) {
+ done_counter_++;
+
+ // Terminate PolicyScheduler after 5 iterations.
+ if (done_counter_ >= 5) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PolicySchedulerTest::Terminate,
+ base::Unretained(this)));
+ }
+ }
+
+ // To simulate a slow task the callback is captured instead of running it.
+ void CaptureCallbackForSlowTask(PolicyScheduler::TaskCallback callback) {
+ do_counter_++;
+ slow_callback_ = std::move(callback);
+ }
+
+ // Runs the captured callback to simulate the end of the slow task.
+ void PostSlowTaskCallback() {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(slow_callback_), true));
+ }
+
+ void Terminate() { scheduler_.reset(); }
+
+ protected:
+ int do_counter_ = 0;
+ int done_counter_ = 0;
+ std::unique_ptr<PolicyScheduler> scheduler_;
+
+ PolicyScheduler::TaskCallback slow_callback_;
+
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(PolicySchedulerTest, Run) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, Loop) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(5, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, Reschedule) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::DoTask, base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+
+ // Delayed action is not run.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, done_counter_);
+
+ // Rescheduling with 0 delay causes it to run.
+ scheduler_->ScheduleTaskNow();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, done_counter_);
+}
+
+TEST_F(PolicySchedulerTest, OverlappingTasks) {
+ scheduler_ = std::make_unique<PolicyScheduler>(
+ base::BindRepeating(&PolicySchedulerTest::CaptureCallbackForSlowTask,
+ base::Unretained(this)),
+ base::BindRepeating(&PolicySchedulerTest::OnTaskDone,
+ base::Unretained(this)),
+ base::TimeDelta::Max());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, do_counter_);
+ EXPECT_EQ(0, done_counter_);
+
+ // Second action doesn't start while first is still pending.
+ scheduler_->ScheduleTaskNow();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, do_counter_);
+ EXPECT_EQ(0, done_counter_);
+
+ // After first action has finished, the second is started.
+ PostSlowTaskCallback();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, do_counter_);
+ EXPECT_EQ(1, done_counter_);
+
+ // Let the second action finish.
+ PostSlowTaskCallback();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, do_counter_);
+ EXPECT_EQ(2, done_counter_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_service.cc b/chromium/components/policy/core/common/policy_service.cc
new file mode 100644
index 00000000000..5a74dd986b8
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service.h"
+
+#include "base/values.h"
+
+namespace policy {
+
+PolicyChangeRegistrar::PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns)
+ : policy_service_(policy_service),
+ ns_(ns) {}
+
+PolicyChangeRegistrar::~PolicyChangeRegistrar() {
+ if (!callback_map_.empty())
+ policy_service_->RemoveObserver(ns_.domain, this);
+}
+
+void PolicyChangeRegistrar::Observe(const std::string& policy_name,
+ const UpdateCallback& callback) {
+ if (callback_map_.empty())
+ policy_service_->AddObserver(ns_.domain, this);
+ callback_map_[policy_name] = callback;
+}
+
+void PolicyChangeRegistrar::OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ if (ns != ns_)
+ return;
+ for (auto it : callback_map_) {
+ // It's safe to use `GetValueUnsafe()` as multiple policy types are handled.
+ const base::Value* prev = previous.GetValueUnsafe(it.first);
+ const base::Value* cur = current.GetValueUnsafe(it.first);
+
+ // Check if the values pointed to by |prev| and |cur| are different.
+ if ((!prev ^ !cur) || (prev && cur && *prev != *cur))
+ it.second.Run(prev, cur);
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_service.h b/chromium/components/policy/core/common/policy_service.h
new file mode 100644
index 00000000000..a006e18c4ef
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list_types.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class ConfigurationPolicyProvider;
+
+#if BUILDFLAG(IS_ANDROID)
+namespace android {
+class PolicyServiceAndroid;
+}
+#endif
+
+// The PolicyService merges policies from all available sources, taking into
+// account their priorities. Policy clients can retrieve policy for their domain
+// and register for notifications on policy updates.
+//
+// The PolicyService is available from BrowserProcess as a global singleton.
+// There is also a PolicyService for browser-wide policies available from
+// BrowserProcess as a global singleton.
+class POLICY_EXPORT PolicyService {
+ public:
+ class POLICY_EXPORT Observer : public base::CheckedObserver {
+ public:
+ // Invoked whenever policies for the given |ns| namespace are modified.
+ // This is only invoked for changes that happen after AddObserver is called.
+ // |previous| contains the values of the policies before the update,
+ // and |current| contains the current values.
+ virtual void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {}
+
+ // Invoked at most once for each |domain|, when the PolicyService becomes
+ // ready. If IsInitializationComplete() is false, then this will be invoked
+ // once all the policy providers have finished loading their policies for
+ // |domain|. This does not handle failure to load policies from some
+ // providers, so it is possible for for the policy service to be initialised
+ // if the providers failed for example to load its policies cache.
+ virtual void OnPolicyServiceInitialized(PolicyDomain domain) {}
+
+ // Invoked at most once for each |domain|, when the PolicyService becomes
+ // ready. If IsFirstPolicyLoadComplete() is false, then this will be invoked
+ // once all the policy providers have finished loading their policies for
+ // |domain|. The difference from |OnPolicyServiceInitialized| is that this
+ // will wait for cloud policies to be fetched when the local cache is not
+ // available, which may take some time depending on user's network.
+ virtual void OnFirstPoliciesLoaded(PolicyDomain domain) {}
+ };
+
+ class POLICY_EXPORT ProviderUpdateObserver : public base::CheckedObserver {
+ public:
+ // Invoked when the contents of a policy update signaled by |provider| are
+ // available through PolicyService::GetPolicies.
+ // This is intentionally also called if the policy update signaled by
+ // |provider| did not change the effective policy values. Note that multiple
+ // policy updates by |provider| can result in a single call to this
+ // function, e.g. if a subsequent policy update is signaled before the
+ // previous one has been processed by the PolicyService.
+ // Also note that when this is called, PolicyService's Observers may not
+ // have been called with the update that triggered this call yet.
+ virtual void OnProviderUpdatePropagated(
+ ConfigurationPolicyProvider* provider) = 0;
+ };
+
+ virtual ~PolicyService() {}
+
+ // Observes changes to all components of the given |domain|.
+ virtual void AddObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ virtual void RemoveObserver(PolicyDomain domain, Observer* observer) = 0;
+
+ // Observes propagation of policy updates by ConfigurationPolicyProviders.
+ virtual void AddProviderUpdateObserver(ProviderUpdateObserver* observer) = 0;
+ virtual void RemoveProviderUpdateObserver(
+ ProviderUpdateObserver* observer) = 0;
+
+ // Returns true if this PolicyService uses |provider| as one of its sources of
+ // policies.
+ virtual bool HasProvider(ConfigurationPolicyProvider* provider) const = 0;
+
+ virtual const PolicyMap& GetPolicies(const PolicyNamespace& ns) const = 0;
+
+ // The PolicyService loads policy from several sources, and some require
+ // asynchronous loads. IsInitializationComplete() returns true once all
+ // sources have been initialized for the given |domain|.
+ // It is safe to read policy from the PolicyService even if
+ // IsInitializationComplete() is false; there will be an OnPolicyUpdated()
+ // notification once new policies become available.
+ //
+ // OnPolicyServiceInitialized() is called when IsInitializationComplete()
+ // becomes true, which happens at most once for each domain.
+ // If IsInitializationComplete() is already true for |domain| when an Observer
+ // is registered, then that Observer will not receive an
+ // OnPolicyServiceInitialized() notification.
+ virtual bool IsInitializationComplete(PolicyDomain domain) const = 0;
+
+ // The PolicyService loads policy from several sources, and some require
+ // asynchronous loads. IsFirstPolicyLoadComplete() returns true once all
+ // sources have loaded their initial policies for the given |domain|.
+ // It is safe to read policy from the PolicyService even if
+ // IsFirstPolicyLoadComplete() is false; there will be an OnPolicyUpdated()
+ // notification once new policies become available.
+ //
+ // OnFirstPoliciesLoaded() is called when IsFirstPolicyLoadComplete()
+ // becomes true, which happens at most once for each domain.
+ // If IsFirstPolicyLoadComplete() is already true for |domain| when an
+ // Observer is registered, then that Observer will not receive an
+ // OnFirstPoliciesLoaded() notification.
+ virtual bool IsFirstPolicyLoadComplete(PolicyDomain domain) const = 0;
+
+ // Asks the PolicyService to reload policy from all available policy sources.
+ // |callback| is invoked once every source has reloaded its policies, and
+ // GetPolicies() is guaranteed to return the updated values at that point.
+ virtual void RefreshPolicies(base::OnceClosure callback) = 0;
+
+#if BUILDFLAG(IS_ANDROID)
+ // Get the PolicyService JNI bridge instance.
+ virtual android::PolicyServiceAndroid* GetPolicyServiceAndroid() = 0;
+#endif
+};
+
+// A registrar that only observes changes to particular policies within the
+// PolicyMap for the given policy namespace.
+class POLICY_EXPORT PolicyChangeRegistrar : public PolicyService::Observer {
+ public:
+ typedef base::RepeatingCallback<void(const base::Value*, const base::Value*)>
+ UpdateCallback;
+
+ // Observes updates to the given (domain, component_id) namespace in the given
+ // |policy_service|, and notifies |observer| whenever any of the registered
+ // policy keys changes. Both the |policy_service| and the |observer| must
+ // outlive |this|.
+ PolicyChangeRegistrar(PolicyService* policy_service,
+ const PolicyNamespace& ns);
+ PolicyChangeRegistrar(const PolicyChangeRegistrar&) = delete;
+ PolicyChangeRegistrar& operator=(const PolicyChangeRegistrar&) = delete;
+
+ ~PolicyChangeRegistrar() override;
+
+ // Will invoke |callback| whenever |policy_name| changes its value, as long
+ // as this registrar exists.
+ // Only one callback can be registed per policy name; a second call with the
+ // same |policy_name| will overwrite the previous callback.
+ void Observe(const std::string& policy_name, const UpdateCallback& callback);
+
+ // Implementation of PolicyService::Observer:
+ void OnPolicyUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) override;
+
+ private:
+ typedef std::map<std::string, UpdateCallback> CallbackMap;
+
+ raw_ptr<PolicyService> policy_service_;
+ PolicyNamespace ns_;
+ CallbackMap callback_map_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_H_
diff --git a/chromium/components/policy/core/common/policy_service_impl.cc b/chromium/components/policy/core/common/policy_service_impl.cc
new file mode 100644
index 00000000000..7a664ff4b8b
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service_impl.cc
@@ -0,0 +1,479 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_impl.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/feature_list.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/observer_list.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "components/policy/core/common/features.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_merger.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/proxy_settings_constants.h"
+#include "components/policy/core/common/values_util.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "extensions/buildflags/buildflags.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include "components/policy/core/common/android/policy_service_android.h"
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "components/policy/core/common/default_chrome_apps_migrator.h"
+#endif
+
+namespace policy {
+
+namespace {
+
+// Precedence policies cannot be set at the user cloud level regardless of
+// affiliation status. This is done to prevent cloud users from potentially
+// giving themselves increased priority, causing a security issue.
+void IgnoreUserCloudPrecedencePolicies(PolicyMap* policies) {
+ for (auto* policy_name : metapolicy::kPrecedence) {
+ const PolicyMap::Entry* policy_entry = policies->Get(policy_name);
+ if (policy_entry && policy_entry->scope == POLICY_SCOPE_USER &&
+ policy_entry->source == POLICY_SOURCE_CLOUD) {
+ PolicyMap::Entry* policy_entry_mutable =
+ policies->GetMutable(policy_name);
+ policy_entry_mutable->SetIgnored();
+ policy_entry_mutable->AddMessage(PolicyMap::MessageType::kError,
+ IDS_POLICY_IGNORED_CHROME_PROFILE);
+ }
+ }
+}
+
+// Metrics should not be enforced so if this policy is set as mandatory
+// downgrade it to a recommended level policy.
+void DowngradeMetricsReportingToRecommendedPolicy(PolicyMap* policies) {
+ // Capture both the Chrome-only and device-level policies on Chrome OS.
+ const std::vector<const char*> metrics_keys = {
+ policy::key::kMetricsReportingEnabled,
+ policy::key::kDeviceMetricsReportingEnabled};
+ for (const char* policy_key : metrics_keys) {
+ PolicyMap::Entry* policy = policies->GetMutable(policy_key);
+ if (policy && policy->level != POLICY_LEVEL_RECOMMENDED &&
+ policy->value(base::Value::Type::BOOLEAN) &&
+ policy->value(base::Value::Type::BOOLEAN)->GetBool()) {
+ policy->level = POLICY_LEVEL_RECOMMENDED;
+ policy->AddMessage(PolicyMap::MessageType::kInfo,
+ IDS_POLICY_IGNORED_MANDATORY_REPORTING_POLICY);
+ }
+ }
+}
+
+// Returns the string values of |policy|. Returns an empty set if the values are
+// not strings.
+base::flat_set<std::string> GetStringListPolicyItems(
+ const PolicyBundle& bundle,
+ const PolicyNamespace& space,
+ const std::string& policy) {
+ return ValueToStringSet(
+ bundle.Get(space).GetValue(policy, base::Value::Type::LIST));
+}
+
+} // namespace
+
+PolicyServiceImpl::PolicyServiceImpl(Providers providers, Migrators migrators)
+ : PolicyServiceImpl(std::move(providers),
+ std::move(migrators),
+ /*initialization_throttled=*/false) {}
+
+PolicyServiceImpl::PolicyServiceImpl(Providers providers,
+ Migrators migrators,
+ bool initialization_throttled)
+ : providers_(std::move(providers)),
+ migrators_(std::move(migrators)),
+ initialization_throttled_(initialization_throttled) {
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
+ policy_domain_status_[domain] = PolicyDomainStatus::kUninitialized;
+
+ for (auto* provider : providers_)
+ provider->AddObserver(this);
+ // There are no observers yet, but calls to GetPolicies() should already get
+ // the processed policy values.
+ MergeAndTriggerUpdates();
+}
+
+// static
+std::unique_ptr<PolicyServiceImpl>
+PolicyServiceImpl::CreateWithThrottledInitialization(Providers providers,
+ Migrators migrators) {
+ return base::WrapUnique(
+ new PolicyServiceImpl(std::move(providers), std::move(migrators),
+ /*initialization_throttled=*/true));
+}
+
+PolicyServiceImpl::~PolicyServiceImpl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto* provider : providers_)
+ provider->RemoveObserver(this);
+}
+
+void PolicyServiceImpl::AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_[domain].AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = observers_.find(domain);
+ if (it == observers_.end())
+ return;
+ it->second.RemoveObserver(observer);
+ if (it->second.empty()) {
+ observers_.erase(it);
+ }
+}
+
+void PolicyServiceImpl::AddProviderUpdateObserver(
+ ProviderUpdateObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ provider_update_observers_.AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveProviderUpdateObserver(
+ ProviderUpdateObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ provider_update_observers_.RemoveObserver(observer);
+}
+
+bool PolicyServiceImpl::HasProvider(
+ ConfigurationPolicyProvider* provider) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return base::Contains(providers_, provider);
+}
+
+const PolicyMap& PolicyServiceImpl::GetPolicies(
+ const PolicyNamespace& ns) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return policy_bundle_.Get(ns);
+}
+
+bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
+ return !initialization_throttled_ &&
+ policy_domain_status_[domain] != PolicyDomainStatus::kUninitialized;
+}
+
+bool PolicyServiceImpl::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE);
+ return !initialization_throttled_ &&
+ policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady;
+}
+
+void PolicyServiceImpl::RefreshPolicies(base::OnceClosure callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ VLOG(2) << "Policy refresh starting";
+
+ if (!callback.is_null())
+ refresh_callbacks_.push_back(std::move(callback));
+
+ if (providers_.empty()) {
+ // Refresh is immediately complete if there are no providers. See the note
+ // on OnUpdatePolicy() about why this is a posted task.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+
+ VLOG(2) << "Policy refresh has no providers";
+ } else {
+ // Some providers might invoke OnUpdatePolicy synchronously while handling
+ // RefreshPolicies. Mark all as pending before refreshing.
+ for (auto* provider : providers_)
+ refresh_pending_.insert(provider);
+ for (auto* provider : providers_)
+ provider->RefreshPolicies();
+ }
+}
+
+#if BUILDFLAG(IS_ANDROID)
+android::PolicyServiceAndroid* PolicyServiceImpl::GetPolicyServiceAndroid() {
+ if (!policy_service_android_)
+ policy_service_android_ =
+ std::make_unique<android::PolicyServiceAndroid>(this);
+ return policy_service_android_.get();
+}
+#endif
+
+void PolicyServiceImpl::UnthrottleInitialization() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!initialization_throttled_)
+ return;
+
+ initialization_throttled_ = false;
+ std::vector<PolicyDomain> updated_domains;
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain)
+ updated_domains.push_back(static_cast<PolicyDomain>(domain));
+ MaybeNotifyPolicyDomainStatusChange(updated_domains);
+}
+
+void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(1, std::count(providers_.begin(), providers_.end(), provider));
+ refresh_pending_.erase(provider);
+ provider_update_pending_.insert(provider);
+
+ // Note: a policy change may trigger further policy changes in some providers.
+ // For example, disabling SigninAllowed would cause the CloudPolicyManager to
+ // drop all its policies, which makes this method enter again for that
+ // provider.
+ //
+ // Therefore this update is posted asynchronously, to prevent reentrancy in
+ // MergeAndTriggerUpdates. Also, cancel a pending update if there is any,
+ // since both will produce the same PolicyBundle.
+ update_task_ptr_factory_.InvalidateWeakPtrs();
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&PolicyServiceImpl::MergeAndTriggerUpdates,
+ update_task_ptr_factory_.GetWeakPtr()));
+}
+
+void PolicyServiceImpl::NotifyNamespaceUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto iterator = observers_.find(ns.domain);
+ if (iterator != observers_.end()) {
+ for (auto& observer : iterator->second)
+ observer.OnPolicyUpdated(ns, previous, current);
+ }
+}
+
+void PolicyServiceImpl::NotifyProviderUpdatesPropagated() {
+ if (provider_update_pending_.empty())
+ return;
+
+ for (auto& provider_update_observer : provider_update_observers_) {
+ for (ConfigurationPolicyProvider* provider : provider_update_pending_) {
+ provider_update_observer.OnProviderUpdatePropagated(provider);
+ }
+ }
+ provider_update_pending_.clear();
+}
+
+void PolicyServiceImpl::MergeAndTriggerUpdates() {
+ // Merge from each provider in their order of priority.
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+ PolicyBundle bundle;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ DefaultChromeAppsMigrator chrome_apps_migrator;
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+ for (auto* provider : providers_) {
+ PolicyBundle provided_bundle;
+ provided_bundle.CopyFrom(provider->policies());
+ IgnoreUserCloudPrecedencePolicies(&provided_bundle.Get(chrome_namespace));
+ DowngradeMetricsReportingToRecommendedPolicy(
+ &provided_bundle.Get(chrome_namespace));
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ if (base::FeatureList::IsEnabled(
+ policy::features::kDefaultChromeAppsMigration)) {
+ chrome_apps_migrator.Migrate(&provided_bundle.Get(chrome_namespace));
+ }
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+ bundle.MergeFrom(provided_bundle);
+ }
+
+ // Merges all the mergeable policies
+ base::flat_set<std::string> policy_lists_to_merge = GetStringListPolicyItems(
+ bundle, chrome_namespace, key::kPolicyListMultipleSourceMergeList);
+ base::flat_set<std::string> policy_dictionaries_to_merge =
+ GetStringListPolicyItems(bundle, chrome_namespace,
+ key::kPolicyDictionaryMultipleSourceMergeList);
+
+ auto& chrome_policies = bundle.Get(chrome_namespace);
+
+ // This has to be done after setting enterprise default values since it is
+ // enabled by default for enterprise users.
+ auto* atomic_policy_group_enabled_entry =
+ chrome_policies.Get(key::kPolicyAtomicGroupsEnabled);
+
+ // This policy has to be ignored if it comes from a user signed-in profile.
+ bool atomic_policy_group_enabled =
+ atomic_policy_group_enabled_entry &&
+ atomic_policy_group_enabled_entry->value(base::Value::Type::BOOLEAN) &&
+ atomic_policy_group_enabled_entry->value(base::Value::Type::BOOLEAN)
+ ->GetBool() &&
+ !(atomic_policy_group_enabled_entry->source == POLICY_SOURCE_CLOUD &&
+ atomic_policy_group_enabled_entry->scope == POLICY_SCOPE_USER);
+
+ PolicyListMerger policy_list_merger(std::move(policy_lists_to_merge));
+ PolicyDictionaryMerger policy_dictionary_merger(
+ std::move(policy_dictionaries_to_merge));
+
+ // Pass affiliation and CloudUserPolicyMerge values to both mergers.
+ const bool is_user_affiliated = chrome_policies.IsUserAffiliated();
+ const base::Value* cloud_user_policy_merge_value = chrome_policies.GetValue(
+ key::kCloudUserPolicyMerge, base::Value::Type::BOOLEAN);
+ const bool is_user_cloud_merging_enabled =
+ cloud_user_policy_merge_value && cloud_user_policy_merge_value->GetBool();
+ policy_list_merger.SetAllowUserCloudPolicyMerging(
+ is_user_affiliated && is_user_cloud_merging_enabled);
+ policy_dictionary_merger.SetAllowUserCloudPolicyMerging(
+ is_user_affiliated && is_user_cloud_merging_enabled);
+
+ std::vector<PolicyMerger*> mergers{&policy_list_merger,
+ &policy_dictionary_merger};
+
+ PolicyGroupMerger policy_group_merger;
+ if (atomic_policy_group_enabled)
+ mergers.push_back(&policy_group_merger);
+
+ for (auto& entry : bundle)
+ entry.second.MergeValues(mergers);
+
+ for (auto& migrator : migrators_)
+ migrator->Migrate(&bundle);
+
+ // Swap first, so that observers that call GetPolicies() see the current
+ // values.
+ policy_bundle_.Swap(&bundle);
+
+ // Only notify observers of namespaces that have been modified.
+ const PolicyMap kEmpty;
+ PolicyBundle::const_iterator it_new = policy_bundle_.begin();
+ PolicyBundle::const_iterator end_new = policy_bundle_.end();
+ PolicyBundle::const_iterator it_old = bundle.begin();
+ PolicyBundle::const_iterator end_old = bundle.end();
+ while (it_new != end_new && it_old != end_old) {
+ if (it_new->first < it_old->first) {
+ // A new namespace is available.
+ NotifyNamespaceUpdated(it_new->first, kEmpty, it_new->second);
+ ++it_new;
+ } else if (it_old->first < it_new->first) {
+ // A previously available namespace is now gone.
+ NotifyNamespaceUpdated(it_old->first, it_old->second, kEmpty);
+ ++it_old;
+ } else {
+ if (!it_new->second.Equals(it_old->second)) {
+ // An existing namespace's policies have changed.
+ NotifyNamespaceUpdated(it_new->first, it_old->second, it_new->second);
+ }
+ ++it_new;
+ ++it_old;
+ }
+ }
+
+ // Send updates for the remaining new namespaces, if any.
+ for (; it_new != end_new; ++it_new)
+ NotifyNamespaceUpdated(it_new->first, kEmpty, it_new->second);
+
+ // Sends updates for the remaining removed namespaces, if any.
+ for (; it_old != end_old; ++it_old)
+ NotifyNamespaceUpdated(it_old->first, it_old->second, kEmpty);
+
+ const std::vector<PolicyDomain> updated_domains = UpdatePolicyDomainStatus();
+ CheckRefreshComplete();
+ NotifyProviderUpdatesPropagated();
+ // This has to go last as one of the observers might actually destroy `this`.
+ // See https://crbug.com/747817
+ MaybeNotifyPolicyDomainStatusChange(updated_domains);
+}
+
+std::vector<PolicyDomain> PolicyServiceImpl::UpdatePolicyDomainStatus() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::vector<PolicyDomain> updated_domains;
+
+ // Check if all the providers just became initialized for each domain; if so,
+ // notify that domain's observers. If they were initialized, check if they had
+ // their first policies loaded.
+ for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
+ PolicyDomain policy_domain = static_cast<PolicyDomain>(domain);
+ if (policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady)
+ continue;
+
+ PolicyDomainStatus new_status = PolicyDomainStatus::kPolicyReady;
+
+ for (auto* provider : providers_) {
+ if (!provider->IsInitializationComplete(policy_domain)) {
+ new_status = PolicyDomainStatus::kUninitialized;
+ break;
+ } else if (!provider->IsFirstPolicyLoadComplete(policy_domain)) {
+ new_status = PolicyDomainStatus::kInitialized;
+ }
+ }
+
+ if (new_status == policy_domain_status_[domain])
+ continue;
+
+ policy_domain_status_[domain] = new_status;
+ updated_domains.push_back(static_cast<PolicyDomain>(domain));
+ }
+ return updated_domains;
+}
+
+void PolicyServiceImpl::MaybeNotifyPolicyDomainStatusChange(
+ const std::vector<PolicyDomain>& updated_domains) {
+ if (initialization_throttled_)
+ return;
+
+ for (const auto policy_domain : updated_domains) {
+ if (policy_domain_status_[policy_domain] ==
+ PolicyDomainStatus::kUninitialized) {
+ continue;
+ }
+
+ auto iter = observers_.find(policy_domain);
+ if (iter == observers_.end())
+ continue;
+
+ // If and when crbug.com/1221454 gets fixed, we should drop the WeakPtr
+ // construction and checks here.
+ const auto weak_this = weak_ptr_factory_.GetWeakPtr();
+ for (auto& observer : iter->second) {
+ observer.OnPolicyServiceInitialized(policy_domain);
+ if (!weak_this) {
+ VLOG(1) << "PolicyService destroyed while notifying observers.";
+ return;
+ }
+ if (policy_domain_status_[policy_domain] ==
+ PolicyDomainStatus::kPolicyReady) {
+ observer.OnFirstPoliciesLoaded(policy_domain);
+ // If this gets hit, it implies that some OnFirstPoliciesLoaded()
+ // observer was changed to trigger the deletion of |this|. See
+ // crbug.com/1221454 for a similar problem with
+ // OnPolicyServiceInitialized().
+ CHECK(weak_this);
+ }
+ }
+ }
+}
+
+void PolicyServiceImpl::CheckRefreshComplete() {
+ if (refresh_pending_.empty()) {
+ VLOG(2) << "Policy refresh complete";
+ }
+
+ // Invoke all the callbacks if a refresh has just fully completed.
+ if (refresh_pending_.empty() && !refresh_callbacks_.empty()) {
+ std::vector<base::OnceClosure> callbacks;
+ callbacks.swap(refresh_callbacks_);
+ for (auto& callback : callbacks)
+ std::move(callback).Run();
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_service_impl.h b/chromium/components/policy/core/common/policy_service_impl.h
new file mode 100644
index 00000000000..a20473a17c4
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service_impl.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_migrator.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyMap;
+
+#if BUILDFLAG(IS_ANDROID)
+namespace android {
+class PolicyServiceAndroid;
+}
+#endif
+
+class POLICY_EXPORT PolicyServiceImpl
+ : public PolicyService,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ using Providers = std::vector<ConfigurationPolicyProvider*>;
+ using Migrators = std::vector<std::unique_ptr<PolicyMigrator>>;
+
+ // Creates a new PolicyServiceImpl with the list of
+ // ConfigurationPolicyProviders, in order of decreasing priority.
+ explicit PolicyServiceImpl(
+ Providers providers,
+ Migrators migrators = std::vector<std::unique_ptr<PolicyMigrator>>());
+
+ // Creates a new PolicyServiceImpl with the list of
+ // ConfigurationPolicyProviders, in order of decreasing priority.
+ // The created PolicyServiceImpl will only notify observers that
+ // initialization has completed (for any domain) after
+ // |UnthrottleInitialization| has been called.
+ static std::unique_ptr<PolicyServiceImpl> CreateWithThrottledInitialization(
+ Providers providers,
+ Migrators migrators = std::vector<std::unique_ptr<PolicyMigrator>>());
+
+ PolicyServiceImpl(const PolicyServiceImpl&) = delete;
+ PolicyServiceImpl& operator=(const PolicyServiceImpl&) = delete;
+
+ ~PolicyServiceImpl() override;
+
+ // PolicyService overrides:
+ void AddObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) override;
+ void RemoveObserver(PolicyDomain domain,
+ PolicyService::Observer* observer) override;
+ void AddProviderUpdateObserver(ProviderUpdateObserver* observer) override;
+ void RemoveProviderUpdateObserver(ProviderUpdateObserver* observer) override;
+ bool HasProvider(ConfigurationPolicyProvider* provider) const override;
+ const PolicyMap& GetPolicies(const PolicyNamespace& ns) const override;
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+ void RefreshPolicies(base::OnceClosure callback) override;
+#if BUILDFLAG(IS_ANDROID)
+ android::PolicyServiceAndroid* GetPolicyServiceAndroid() override;
+#endif
+
+ // If this PolicyServiceImpl has been created using
+ // |CreateWithThrottledInitialization|, calling UnthrottleInitialization will
+ // allow notification of observers that initialization has completed. If
+ // initialization has actually completed previously but observers were not
+ // notified yet because it was throttled, will notify observers synchronously.
+ // Has no effect if initialization was not throttled.
+ void UnthrottleInitialization();
+
+ private:
+ enum class PolicyDomainStatus { kUninitialized, kInitialized, kPolicyReady };
+
+ // This constructor is not publicly visible so callers that want a
+ // PolicyServiceImpl with throttled initialization use
+ // |CreateWithInitializationThrottled| for clarity.
+ // If |initialization_throttled| is true, this PolicyServiceImpl will only
+ // notify observers that initialization has completed (for any domain) after
+ // |UnthrottleInitialization| has been called.
+ PolicyServiceImpl(Providers providers,
+ Migrators migrators,
+ bool initialization_throttled);
+
+ // ConfigurationPolicyProvider::Observer overrides:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ // Posts a task to notify observers of |ns| that its policies have changed,
+ // passing along the |previous| and the |current| policies.
+ void NotifyNamespaceUpdated(const PolicyNamespace& ns,
+ const PolicyMap& previous,
+ const PolicyMap& current);
+
+ void NotifyProviderUpdatesPropagated();
+
+ // Combines the policies from all the providers, and notifies the observers
+ // of namespaces whose policies have been modified.
+ void MergeAndTriggerUpdates();
+
+ // Checks if all providers are initialized or have loaded their policies and
+ // sets |policy_domain_status_| accordingly.
+ // Returns the updated domains. The returned domains should be passed to
+ // MaybeNotifyPolicyDomainStatusChange.
+ std::vector<PolicyDomain> UpdatePolicyDomainStatus();
+
+ // If initialization is not throttled, observers of |updated_domains| of the
+ // initialization will be notified of the domains' initialization and of the
+ // first policies being loaded. This function should only be called when
+ // |updated_domains| just became initialized, just got its first policies or
+ // when initialization has been unthrottled.
+ void MaybeNotifyPolicyDomainStatusChange(
+ const std::vector<PolicyDomain>& updated_domains);
+
+ // Invokes all the refresh callbacks if there are no more refreshes pending.
+ void CheckRefreshComplete();
+
+ // The providers, in order of decreasing priority.
+ Providers providers_;
+
+ Migrators migrators_;
+
+ // Maps each policy namespace to its current policies.
+ PolicyBundle policy_bundle_;
+
+ // Maps each policy domain to its observer list.
+ std::map<PolicyDomain,
+ base::ObserverList<PolicyService::Observer, /*check_empty=*/true>>
+ observers_;
+
+ // The status of all the providers for the indexed policy domain.
+ PolicyDomainStatus policy_domain_status_[POLICY_DOMAIN_SIZE];
+
+ // Set of providers that have a pending update that was triggered by a
+ // call to RefreshPolicies().
+ std::set<ConfigurationPolicyProvider*> refresh_pending_;
+
+ // List of callbacks to invoke once all providers refresh after a
+ // RefreshPolicies() call.
+ std::vector<base::OnceClosure> refresh_callbacks_;
+
+ // Observers for propagation of policy updates by
+ // ConfigurationPolicyProviders.
+ base::ObserverList<ProviderUpdateObserver> provider_update_observers_;
+
+ // Contains all ConfigurationPolicyProviders that have signaled a policy
+ // update which is still being processed (i.e. for which a notification to
+ // |provider_update_observers_| has not been sent out yet).
+ // Note that this is intentionally a set - if multiple updates from the same
+ // provider come in faster than they can be processed, they should only
+ // trigger one notification to |provider_update_observers_|.
+ std::set<ConfigurationPolicyProvider*> provider_update_pending_;
+
+ // If this is true, IsInitializationComplete should be returning false for all
+ // policy domains because the owner of this PolicyService is delaying the
+ // initialization signal.
+ bool initialization_throttled_;
+
+#if BUILDFLAG(IS_ANDROID)
+ std::unique_ptr<android::PolicyServiceAndroid> policy_service_android_;
+#endif
+
+ // Used to verify usage in correct sequence.
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Used to create tasks to delay new policy updates while we may be already
+ // processing previous policy updates.
+ // All WeakPtrs will be reset in |RefreshPolicies| and |OnUpdatePolicy|.
+ base::WeakPtrFactory<PolicyServiceImpl> update_task_ptr_factory_{this};
+
+ // Used to protect against crbug.com/747817 until crbug.com/1221454 is done.
+ base::WeakPtrFactory<PolicyServiceImpl> weak_ptr_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_IMPL_H_
diff --git a/chromium/components/policy/core/common/policy_service_impl_unittest.cc b/chromium/components/policy/core/common/policy_service_impl_unittest.cc
new file mode 100644
index 00000000000..28e4800386e
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service_impl_unittest.cc
@@ -0,0 +1,2248 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_migrator.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::Return;
+
+namespace policy {
+
+namespace {
+
+const char kExtension[] = "extension-id";
+const char kSameLevelPolicy[] = "policy-same-level-and-scope";
+const char kDiffLevelPolicy[] = "chrome-diff-level-and-scope";
+const std::string kUrl1 = "example.com";
+const std::string kUrl2 = "gmail.com";
+const std::string kUrl3 = "google.com";
+const std::string kUrl4 = "youtube.com";
+const std::string kAffiliationId1 = "abc";
+const std::string kAffiliationId2 = "def";
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyUpdated() with
+// their expected values.
+MATCHER_P(PolicyEquals, expected, "") {
+ return arg.Equals(*expected);
+}
+
+// Helper to compare the arguments to an EXPECT_CALL of OnPolicyValueUpdated()
+// with their expected values.
+MATCHER_P(ValueEquals, expected, "") {
+ return *expected == *arg;
+}
+
+// Helper that fills |bundle| with test policies.
+void AddTestPolicies(PolicyBundle* bundle,
+ const char* value,
+ PolicyLevel level,
+ PolicyScope scope) {
+ PolicyMap* policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(value),
+ nullptr);
+ policy_map->Set(kDiffLevelPolicy, level, scope, POLICY_SOURCE_CLOUD,
+ base::Value(value), nullptr);
+ policy_map =
+ &bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension));
+ policy_map->Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(value),
+ nullptr);
+ policy_map->Set(kDiffLevelPolicy, level, scope, POLICY_SOURCE_CLOUD,
+ base::Value(value), nullptr);
+}
+
+// Observer class that changes the policy in the passed provider when the
+// callback is invoked.
+class ChangePolicyObserver : public PolicyService::Observer {
+ public:
+ explicit ChangePolicyObserver(MockConfigurationPolicyProvider* provider)
+ : provider_(provider),
+ observer_invoked_(false) {}
+
+ void OnPolicyUpdated(const PolicyNamespace&,
+ const PolicyMap& previous,
+ const PolicyMap& current) override {
+ PolicyMap new_policy;
+ new_policy.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(14), nullptr);
+ provider_->UpdateChromePolicy(new_policy);
+ observer_invoked_ = true;
+ }
+
+ bool observer_invoked() const { return observer_invoked_; }
+
+ private:
+ raw_ptr<MockConfigurationPolicyProvider> provider_;
+ bool observer_invoked_;
+};
+
+class MockPolicyMigrator : public PolicyMigrator {
+ public:
+ MOCK_METHOD1(Migrate, void(PolicyBundle* bundle));
+};
+
+} // namespace
+
+class PolicyServiceTest : public testing::Test {
+ public:
+ PolicyServiceTest() = default;
+ PolicyServiceTest(const PolicyServiceTest&) = delete;
+ PolicyServiceTest& operator=(const PolicyServiceTest&) = delete;
+ void SetUp() override {
+ EXPECT_CALL(provider0_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider0_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(true));
+
+ provider0_.Init();
+ provider1_.Init();
+ provider2_.Init();
+
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(13), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ auto migrator = std::make_unique<MockPolicyMigrator>();
+ EXPECT_CALL(*migrator, Migrate(_))
+ .WillRepeatedly(Invoke([](PolicyBundle* bundle) {
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+ }));
+ PolicyServiceImpl::Migrators migrators;
+ migrators.push_back(std::move(migrator));
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers),
+ std::move(migrators));
+ }
+
+ void TearDown() override {
+ provider0_.Shutdown();
+ provider1_.Shutdown();
+ provider2_.Shutdown();
+ }
+
+ MOCK_METHOD2(OnPolicyValueUpdated, void(const base::Value*,
+ const base::Value*));
+
+ MOCK_METHOD0(OnPolicyRefresh, void());
+
+ // Returns true if the policies for namespace |ns| match |expected|.
+ bool VerifyPolicies(const PolicyNamespace& ns,
+ const PolicyMap& expected) {
+ return policy_service_->GetPolicies(ns).Equals(expected);
+ }
+
+ std::unique_ptr<PolicyBundle> CreateBundle(
+ PolicyScope scope,
+ PolicySource source,
+ std::vector<std::pair<std::string, base::Value>> policies,
+ PolicyNamespace policy_namespace) {
+ auto policy_bundle = std::make_unique<PolicyBundle>();
+ PolicyMap& policy_map = policy_bundle->Get(policy_namespace);
+
+ for (auto& policy : policies) {
+ policy_map.Set(std::move(policy.first), POLICY_LEVEL_MANDATORY, scope,
+ source, std::move(policy.second), nullptr);
+ }
+ return policy_bundle;
+ }
+
+ void RunUntilIdle() {
+ base::RunLoop loop;
+ loop.RunUntilIdle();
+ }
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ MockConfigurationPolicyProvider provider0_;
+ MockConfigurationPolicyProvider provider1_;
+ MockConfigurationPolicyProvider provider2_;
+ PolicyMap policy0_;
+ PolicyMap policy1_;
+ PolicyMap policy2_;
+ std::unique_ptr<PolicyServiceImpl> policy_service_;
+};
+
+TEST_F(PolicyServiceTest, LoadsPoliciesBeforeProvidersRefresh) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(13), nullptr);
+ expected.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, NotifyObservers) {
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+
+ PolicyMap expectedPrevious;
+ expectedPrevious.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(13),
+ nullptr);
+ expectedPrevious.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap expectedCurrent;
+ expectedCurrent = expectedPrevious.Clone();
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ // New policy.
+ expectedPrevious = expectedCurrent.Clone();
+ expectedCurrent.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(456), nullptr);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(456), nullptr);
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Removed policy.
+ expectedPrevious = expectedCurrent.Clone();
+ expectedCurrent.Erase("bbb");
+ policy0_.Erase("bbb");
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Changed policy.
+ expectedPrevious = expectedCurrent.Clone();
+ expectedCurrent.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(789), nullptr);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(789), nullptr);
+
+ EXPECT_CALL(observer, OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string()),
+ PolicyEquals(&expectedPrevious),
+ PolicyEquals(&expectedCurrent)));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // No changes again.
+ EXPECT_CALL(observer, OnPolicyUpdated(_, _, _)).Times(0);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expectedCurrent));
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+}
+
+TEST_F(PolicyServiceTest, NotifyObserversInMultipleNamespaces) {
+ const std::string kExtension0("extension-0");
+ const std::string kExtension1("extension-1");
+ const std::string kExtension2("extension-2");
+ MockPolicyServiceObserver chrome_observer;
+ MockPolicyServiceObserver extension_observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &extension_observer);
+
+ PolicyMap previous_policy_map;
+ previous_policy_map.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(13),
+ nullptr);
+ previous_policy_map.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+ PolicyMap policy_map;
+ policy_map = previous_policy_map.Clone();
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+
+ auto bundle = std::make_unique<PolicyBundle>();
+ // The initial setup includes a policy for chrome that is now changing.
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy_map.Clone();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0)) =
+ policy_map.Clone();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1)) =
+ policy_map.Clone();
+
+ const PolicyMap kEmptyPolicyMap;
+ EXPECT_CALL(
+ chrome_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(std::move(bundle));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ // Chrome policy stays the same, kExtension0 is gone, kExtension1 changes,
+ // and kExtension2 is new.
+ previous_policy_map = policy_map.Clone();
+ bundle = std::make_unique<PolicyBundle>();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
+ policy_map.Clone();
+ policy_map.Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("another value"), nullptr);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1)) =
+ policy_map.Clone();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2)) =
+ policy_map.Clone();
+
+ EXPECT_CALL(chrome_observer, OnPolicyUpdated(_, _, _)).Times(0);
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension0),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&kEmptyPolicyMap)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension1),
+ PolicyEquals(&previous_policy_map),
+ PolicyEquals(&policy_map)));
+ EXPECT_CALL(
+ extension_observer,
+ OnPolicyUpdated(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension2),
+ PolicyEquals(&kEmptyPolicyMap),
+ PolicyEquals(&policy_map)));
+ provider0_.UpdatePolicy(std::move(bundle));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(&chrome_observer);
+ Mock::VerifyAndClearExpectations(&extension_observer);
+
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &chrome_observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS,
+ &extension_observer);
+}
+
+TEST_F(PolicyServiceTest, ObserverChangesPolicy) {
+ ChangePolicyObserver observer(&provider0_);
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1234), nullptr);
+ // Should not crash.
+ provider0_.UpdateChromePolicy(policy0_);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ EXPECT_TRUE(observer.observer_invoked());
+}
+
+TEST_F(PolicyServiceTest, HasProvider) {
+ MockConfigurationPolicyProvider other_provider;
+ EXPECT_TRUE(policy_service_->HasProvider(&provider0_));
+ EXPECT_TRUE(policy_service_->HasProvider(&provider1_));
+ EXPECT_TRUE(policy_service_->HasProvider(&provider2_));
+ EXPECT_FALSE(policy_service_->HasProvider(&other_provider));
+}
+
+TEST_F(PolicyServiceTest, NotifyProviderUpdateObserver) {
+ MockPolicyServiceProviderUpdateObserver provider_update_observer;
+ policy_service_->AddProviderUpdateObserver(&provider_update_observer);
+
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(123), nullptr);
+ EXPECT_CALL(provider_update_observer,
+ OnProviderUpdatePropagated(&provider0_));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&provider_update_observer);
+
+ // No changes, ProviderUpdateObserver still notified.
+ EXPECT_CALL(provider_update_observer,
+ OnProviderUpdatePropagated(&provider0_));
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(&provider_update_observer);
+
+ policy_service_->RemoveProviderUpdateObserver(&provider_update_observer);
+}
+
+TEST_F(PolicyServiceTest, Priorities) {
+ PolicyMap expected;
+ expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value(13), nullptr);
+ expected.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ expected.GetMutable("aaa")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable("aaa")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ policy1_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ policy2_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(2), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ provider2_.UpdateChromePolicy(policy2_);
+ expected.GetMutable("aaa")->AddConflictingPolicy(
+ policy1_.Get("aaa")->DeepCopy());
+ expected.GetMutable("aaa")->AddConflictingPolicy(
+ policy2_.Get("aaa")->DeepCopy());
+
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ expected.GetMutable("aaa")->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ expected.GetMutable("aaa")->AddConflictingPolicy(
+ policy2_.Get("aaa")->DeepCopy());
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+
+ expected.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(2), nullptr);
+ expected.GetMutable("aaa")->AddMessage(PolicyMap::MessageType::kInfo,
+ IDS_POLICY_CONFLICT_SAME_VALUE);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ expected.GetMutable("aaa")->AddConflictingPolicy(
+ policy2_.Get("aaa")->DeepCopy());
+ provider1_.UpdateChromePolicy(policy2_);
+ EXPECT_TRUE(VerifyPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()), expected));
+}
+
+TEST_F(PolicyServiceTest, PolicyChangeRegistrar) {
+ std::unique_ptr<PolicyChangeRegistrar> registrar(new PolicyChangeRegistrar(
+ policy_service_.get(),
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())));
+
+ // Starting to observe existing policies doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar->Observe(
+ "pre", base::BindRepeating(&PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ registrar->Observe(
+ "aaa", base::BindRepeating(&PolicyServiceTest::OnPolicyValueUpdated,
+ base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing it now triggers a notification.
+ base::Value kValue0(0);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(NULL, ValueEquals(&kValue0)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Changing other values doesn't trigger a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ policy0_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Modifying the value triggers a notification.
+ base::Value kValue1(1);
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue0),
+ ValueEquals(&kValue1)));
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Removing the value triggers a notification.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(ValueEquals(&kValue1), NULL));
+ policy0_.Erase("aaa");
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // No more notifications after destroying the registrar.
+ EXPECT_CALL(*this, OnPolicyValueUpdated(_, _)).Times(0);
+ registrar.reset();
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.Clone(), nullptr);
+ policy0_.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, kValue1.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+}
+
+TEST_F(PolicyServiceTest, RefreshPolicies) {
+ EXPECT_CALL(provider0_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider1_, RefreshPolicies()).Times(AnyNumber());
+ EXPECT_CALL(provider2_, RefreshPolicies()).Times(AnyNumber());
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::BindOnce(
+ &PolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+ // Let any queued observer tasks run.
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::Value kValue0(0);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ base::Value kValue1(1);
+ policy1_.Set("aaa", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.Clone(), nullptr);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // A provider can refresh more than once after a RefreshPolicies call, but
+ // OnPolicyRefresh should be triggered only after all providers are
+ // refreshed.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy1_.Set("bbb", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue1.Clone(), nullptr);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // If another RefreshPolicies() call happens while waiting for a previous
+ // one to complete, then all providers must refresh again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy_service_->RefreshPolicies(base::BindOnce(
+ &PolicyServiceTest::OnPolicyRefresh, base::Unretained(this)));
+ RunUntilIdle();
+ Mock::VerifyAndClearExpectations(this);
+
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(0);
+ policy2_.Set("bbb", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue0.Clone(), nullptr);
+ provider2_.UpdateChromePolicy(policy2_);
+ Mock::VerifyAndClearExpectations(this);
+
+ // Providers 0 and 1 must reload again.
+ EXPECT_CALL(*this, OnPolicyRefresh()).Times(2);
+ base::Value kValue2(2);
+ policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, kValue2.Clone(), nullptr);
+ provider0_.UpdateChromePolicy(policy0_);
+ provider1_.UpdateChromePolicy(policy1_);
+ Mock::VerifyAndClearExpectations(this);
+
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ EXPECT_EQ(kValue2, *policies.GetValue("aaa", base::Value::Type::INTEGER));
+ EXPECT_EQ(kValue0, *policies.GetValue("bbb", base::Value::Type::INTEGER));
+}
+
+TEST_F(PolicyServiceTest, NamespaceMerge) {
+ auto bundle0 = std::make_unique<PolicyBundle>();
+ auto bundle1 = std::make_unique<PolicyBundle>();
+ auto bundle2 = std::make_unique<PolicyBundle>();
+
+ AddTestPolicies(bundle0.get(), "bundle0",
+ POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle1.get(), "bundle1",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
+ AddTestPolicies(bundle2.get(), "bundle2",
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
+
+ PolicyMap expected;
+ // For policies of the same level and scope, the first provider takes
+ // precedence, on every namespace.
+ expected.Set(kSameLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT, base::Value("bundle0"),
+ nullptr);
+ expected.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+ expected.GetMutable(kSameLevelPolicy)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable(kSameLevelPolicy)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable(kSameLevelPolicy)
+ ->AddConflictingPolicy(
+ bundle1->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Get(kSameLevelPolicy)
+ ->DeepCopy());
+ expected.GetMutable(kSameLevelPolicy)
+ ->AddConflictingPolicy(
+ bundle2->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Get(kSameLevelPolicy)
+ ->DeepCopy());
+ // For policies with different levels and scopes, the highest priority
+ // level/scope combination takes precedence, on every namespace.
+ expected.Set(kDiffLevelPolicy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, base::Value("bundle2"), nullptr);
+ expected.GetMutable(kDiffLevelPolicy)
+ ->AddMessage(PolicyMap::MessageType::kWarning,
+ IDS_POLICY_CONFLICT_DIFF_VALUE);
+ expected.GetMutable(kDiffLevelPolicy)
+ ->AddConflictingPolicy(
+ bundle0->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Get(kDiffLevelPolicy)
+ ->DeepCopy());
+ expected.GetMutable(kDiffLevelPolicy)
+ ->AddConflictingPolicy(
+ bundle1->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Get(kDiffLevelPolicy)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(bundle0));
+ provider1_.UpdatePolicy(std::move(bundle1));
+ provider2_.UpdatePolicy(std::move(bundle2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())).Equals(expected));
+ expected.Erase("migrated");
+ EXPECT_TRUE(policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, kExtension)).Equals(expected));
+}
+
+TEST_F(PolicyServiceTest, IsInitializationComplete) {
+ // |provider0_| has all domains initialized.
+ Mock::VerifyAndClearExpectations(&provider1_);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
+ // the initialization status of that domain won't change.
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ const PolicyMap kPolicyMap;
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Now initialize POLICY_DOMAIN_CHROME on all the providers.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ // Other domains are still not initialized.
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Initialize the remaining domains.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer,
+ OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+}
+
+using DomainParameters = std::tuple<bool, // provider initialized
+ bool, // first policy fetched
+ bool // observer present
+ >;
+using ObserverTestParameters = std::tuple<DomainParameters, // CHROME
+ DomainParameters, // EXTENSIONS
+ DomainParameters // SIGNIN_EXTENSIONS
+ >;
+
+class PolicyServiceTestForObservers
+ : public testing::Test,
+ public testing::WithParamInterface<ObserverTestParameters> {
+ public:
+ PolicyServiceTestForObservers() = default;
+ PolicyServiceTestForObservers(const PolicyServiceTestForObservers& other) =
+ delete;
+ PolicyServiceTestForObservers& operator=(
+ const PolicyServiceTestForObservers& other) = delete;
+ ~PolicyServiceTestForObservers() override = default;
+
+ void SetUp() override {
+ SetupDomain<POLICY_DOMAIN_CHROME>();
+ SetupDomain<POLICY_DOMAIN_EXTENSIONS>();
+ SetupDomain<POLICY_DOMAIN_SIGNIN_EXTENSIONS>();
+
+ provider_.Init();
+ }
+
+ void AddObservers(PolicyService* service) {
+ AddObserver<POLICY_DOMAIN_CHROME>(service);
+ AddObserver<POLICY_DOMAIN_EXTENSIONS>(service);
+ AddObserver<POLICY_DOMAIN_SIGNIN_EXTENSIONS>(service);
+ }
+
+ void RemoveObservers(PolicyService* service) {
+ RemoveObserver<POLICY_DOMAIN_CHROME>(service);
+ RemoveObserver<POLICY_DOMAIN_EXTENSIONS>(service);
+ RemoveObserver<POLICY_DOMAIN_SIGNIN_EXTENSIONS>(service);
+ }
+
+ void TearDown() override { provider_.Shutdown(); }
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ MockConfigurationPolicyProvider provider_;
+ MockPolicyServiceObserver observer_;
+
+ private:
+ template <PolicyDomain domain>
+ void SetupDomain() {
+ DomainParameters params = std::get<domain>(GetParam());
+
+ EXPECT_CALL(provider_, IsInitializationComplete(domain))
+ .WillRepeatedly(Return(std::get<0>(params)));
+ EXPECT_CALL(provider_, IsFirstPolicyLoadComplete(domain))
+ .WillRepeatedly(Return(std::get<1>(params)));
+ }
+ template <PolicyDomain domain>
+ void AddObserver(PolicyService* service) {
+ DomainParameters params = std::get<domain>(GetParam());
+
+ const bool isInitialized = std::get<0>(params);
+ const bool isPolicyFetched = std::get<1>(params);
+ const bool hasObserver = std::get<2>(params);
+ if (hasObserver)
+ service->AddObserver(domain, &observer_);
+ EXPECT_CALL(observer_, OnPolicyServiceInitialized(domain))
+ .Times(isInitialized && hasObserver);
+ EXPECT_CALL(observer_, OnFirstPoliciesLoaded(domain))
+ .Times(isInitialized && isPolicyFetched && hasObserver);
+ }
+ template <PolicyDomain domain>
+ void RemoveObserver(PolicyService* service) {
+ DomainParameters params = std::get<domain>(GetParam());
+
+ const bool hasObserver = std::get<2>(params);
+ if (hasObserver)
+ service->RemoveObserver(domain, &observer_);
+ }
+};
+
+TEST_P(PolicyServiceTestForObservers, MaybeNotifyPolicyDomainStatusChange) {
+ auto local_policy_service =
+ PolicyServiceImpl::CreateWithThrottledInitialization(
+ PolicyServiceImpl::Providers{&provider_});
+
+ AddObservers(local_policy_service.get());
+
+ local_policy_service->UnthrottleInitialization();
+
+ Mock::VerifyAndClearExpectations(&observer_);
+ Mock::VerifyAndClearExpectations(&provider_);
+
+ RemoveObservers(local_policy_service.get());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AllDomains,
+ PolicyServiceTestForObservers,
+ testing::Combine(
+ testing::Combine(testing::Bool(), testing::Bool(), testing::Bool()),
+ testing::Combine(testing::Bool(), testing::Bool(), testing::Bool()),
+ testing::Combine(testing::Bool(), testing::Bool(), testing::Bool())));
+
+TEST_F(PolicyServiceTest, IsInitializationCompleteMightDestroyThis) {
+ Mock::VerifyAndClearExpectations(&provider0_);
+ EXPECT_CALL(provider0_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider0_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(true));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ auto local_policy_service =
+ PolicyServiceImpl::CreateWithThrottledInitialization(
+ std::move(providers));
+ EXPECT_FALSE(
+ local_policy_service->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+
+ MockPolicyServiceObserver observer;
+ local_policy_service->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+
+ // Now initialize policy domains on provider0.
+ // One of our observers destroys the policy service.
+ // This happens in the wild: https://crbug.com/747817
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME))
+ .WillOnce([&local_policy_service, &observer](auto) {
+ local_policy_service->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ local_policy_service.reset();
+ });
+
+ local_policy_service->UnthrottleInitialization();
+ EXPECT_FALSE(local_policy_service);
+
+ Mock::VerifyAndClearExpectations(&observer);
+ Mock::VerifyAndClearExpectations(&provider0_);
+}
+
+// Tests initialization throttling of PolicyServiceImpl.
+// This actually tests two cases:
+// (1) A domain was initialized before UnthrottleInitialization is called.
+// Observers only get notified after calling UnthrottleInitialization.
+// This is tested on POLICY_DOMAIN_CHROME.
+// (2) A domain becomes initialized after UnthrottleInitialization has already
+// been called. Because initialization is not throttled anymore, observers
+// get notified immediately when the domain becomes initialized.
+// This is tested on POLICY_DOMAIN_EXTENSIONS and
+// POLICY_DOMAIN_SIGNIN_EXTENSIONS.
+TEST_F(PolicyServiceTest, InitializationThrottled) {
+ // |provider0_| and |provider1_| has all domains initialized, |provider2_| has
+ // no domain initialized.
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = PolicyServiceImpl::CreateWithThrottledInitialization(
+ std::move(providers));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+
+ // Now additionally initialize POLICY_DOMAIN_CHROME on |provider2_|.
+ // Note: VerifyAndClearExpectations is called to reset the previously set
+ // action for IsInitializationComplete and IsFirstPolicyLoadComplete on
+ // |provider_2|.
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_,
+ IsInitializationComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_,
+ IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+
+ // Nothing will happen because initialization is still throttled.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(_)).Times(0);
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
+ const PolicyMap kPolicyMap;
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Unthrottle initialization. This will signal that POLICY_DOMAIN_CHROME is
+ // initialized, the other domains should still not be initialized because
+ // |provider2_| is returning false in IsInitializationComplete and
+ // IsFirstPolicyLoadComplete for them.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
+ policy_service_->UnthrottleInitialization();
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Initialize the remaining domains.
+ // Note: VerifyAndClearExpectations is called to reset the previously set
+ // action for IsInitializationComplete and IsFirstPolicyLoadComplete on
+ // |provider_2|.
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer,
+ OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+}
+
+TEST_F(PolicyServiceTest, InitializationThrottledProvidersAlreadyInitialized) {
+ // All providers have all domains initialized.
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = PolicyServiceImpl::CreateWithThrottledInitialization(
+ std::move(providers));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+
+ // Unthrottle initialization. This will signal that all domains are
+ // initialized.
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_CHROME));
+ EXPECT_CALL(observer, OnPolicyServiceInitialized(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer,
+ OnPolicyServiceInitialized(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ policy_service_->UnthrottleInitialization();
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsInitializationComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsInitializationComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+}
+
+TEST_F(PolicyServiceTest, IsFirstPolicyLoadComplete) {
+ // |provider0_| has all domains initialized.
+ Mock::VerifyAndClearExpectations(&provider1_);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(_))
+ .WillRepeatedly(Return(false));
+ PolicyServiceImpl::Providers providers;
+ providers.push_back(&provider0_);
+ providers.push_back(&provider1_);
+ providers.push_back(&provider2_);
+ policy_service_ = std::make_unique<PolicyServiceImpl>(std::move(providers));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // |provider2_| still doesn't have POLICY_DOMAIN_CHROME initialized, so
+ // the initialization status of that domain won't change.
+ MockPolicyServiceObserver observer;
+ policy_service_->AddObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->AddObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider1_,
+ IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(false));
+ const PolicyMap kPolicyMap;
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Same if |provider1_| doesn't have POLICY_DOMAIN_EXTENSIONS initialized.
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(_)).Times(0);
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Now initialize POLICY_DOMAIN_CHROME on all the providers.
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&provider2_);
+ EXPECT_CALL(provider2_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider2_,
+ IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider2_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ // Other domains are still not initialized.
+ EXPECT_FALSE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Initialize the remaining domains.
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_CALL(observer, OnFirstPoliciesLoaded(POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+ Mock::VerifyAndClearExpectations(&provider1_);
+ EXPECT_CALL(provider1_, IsInitializationComplete(_))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_, IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(provider1_,
+ IsFirstPolicyLoadComplete(POLICY_DOMAIN_SIGNIN_EXTENSIONS))
+ .WillRepeatedly(Return(true));
+ provider1_.UpdateChromePolicy(kPolicyMap);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(
+ policy_service_->IsFirstPolicyLoadComplete(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(policy_service_->IsFirstPolicyLoadComplete(
+ POLICY_DOMAIN_SIGNIN_EXTENSIONS));
+
+ // Cleanup.
+ policy_service_->RemoveObserver(POLICY_DOMAIN_CHROME, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_EXTENSIONS, &observer);
+ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer);
+}
+
+TEST_F(PolicyServiceTest, DictionaryPoliciesMerging) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value dict1 = base::Value(base::Value::Type::DICTIONARY);
+ dict1.SetBoolKey(kUrl3, false);
+ dict1.SetBoolKey(kUrl2, true);
+ base::Value dict2 = base::Value(base::Value::Type::DICTIONARY);
+ dict2.SetBoolKey(kUrl1, true);
+ dict2.SetBoolKey(kUrl2, false);
+ base::Value result = base::Value(base::Value::Type::DICTIONARY);
+ result.SetBoolKey(kUrl1, true);
+ result.SetBoolKey(kUrl2, true);
+ result.SetBoolKey(kUrl3, false);
+
+ std::unique_ptr<base::Value> policy =
+ std::make_unique<base::Value>(base::Value::Type::LIST);
+ policy->Append(base::Value(key::kExtensionSettings));
+
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyDictionaryMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionSettings, std::move(dict1));
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionSettings, std::move(dict2));
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyDictionaryMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionSettings, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, DictionaryPoliciesMerging_InvalidType) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value dict_value(base::Value::Type::DICTIONARY);
+ dict_value.SetBoolKey(kUrl1, true);
+ base::Value result(base::Value::Type::DICTIONARY);
+ result.SetBoolKey(kUrl1, true);
+
+ std::unique_ptr<base::Value> policy =
+ std::make_unique<base::Value>(base::Value::Type::LIST);
+ policy->Append(base::Value(policy::key::kExtensionSettings));
+
+ // policy_bundle_1 is treated as a machine platform bundle.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyDictionaryMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionSettings, dict_value.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. A string value is set
+ // instead of the expected dictionary value.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionSettings, base::Value(kUrl2));
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ // The expected_chrome PolicyMap only contains the URLs from policy_bundle_1.
+ // The string value stored in policy_bundle_2 is ignored during merging since
+ // its type does not match the expected dictionary type.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyDictionaryMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionSettings, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+#if !BUILDFLAG(IS_CHROMEOS)
+// Policy precedence changes are not supported on Chrome OS.
+TEST_F(PolicyServiceTest, DictionaryPoliciesMerging_PrecedenceChange) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize affiliation IDs. User and device ID is identical.
+ base::flat_set<std::string> ids;
+ ids.insert(kAffiliationId1);
+
+ // Initialize dictionaries of URLs used for ExtensionSettings policy values.
+ base::Value dict1 = base::Value(base::Value::Type::DICTIONARY);
+ dict1.SetBoolKey(kUrl2, true);
+ dict1.SetBoolKey(kUrl3, false);
+ base::Value dict2 = base::Value(base::Value::Type::DICTIONARY);
+ dict2.SetBoolKey(kUrl1, true);
+ dict2.SetBoolKey(kUrl2, false);
+ base::Value dict3 = base::Value(base::Value::Type::DICTIONARY);
+ dict3.SetBoolKey(kUrl3, true);
+ dict3.SetBoolKey(kUrl4, false);
+ base::Value result = base::Value(base::Value::Type::DICTIONARY);
+ result.SetBoolKey(kUrl1, true);
+ result.SetBoolKey(kUrl2, false);
+ result.SetBoolKey(kUrl3, true);
+ result.SetBoolKey(kUrl4, false);
+
+ std::unique_ptr<base::Value> policy =
+ std::make_unique<base::Value>(base::Value::Type::LIST);
+ policy->Append(base::Value(key::kExtensionSettings));
+
+ // policy_bundle_1 is treated as a machine platform bundle. The metapolicies
+ // are defined here.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyDictionaryMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value(true));
+ policies_1.emplace_back(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value(true));
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_1.emplace_back(key::kExtensionSettings, std::move(dict1));
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. The device
+ // affiliation IDs are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionSettings, std::move(dict2));
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionSettings, std::move(dict3));
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(ids);
+
+ // The expected_chrome PolicyMap contains the combined URLs from all three
+ // policy bundles. The affiliation IDs don't need to be added as they're not
+ // compared in the PolicyMap equality check.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyDictionaryMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set(key::kCloudPolicyOverridesPlatformPolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
+ expected_chrome.Set(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, base::Value(true), nullptr);
+ expected_chrome.Set(key::kCloudUserPolicyMerge, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ base::Value(true), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionSettings)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionSettings, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+#endif // !BUILDFLAG(IS_CHROMEOS)
+
+TEST_F(PolicyServiceTest, ListsPoliciesMerging) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value list1(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl3));
+ list1.Append(base::Value(kUrl2));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl1));
+ list2.Append(base::Value(kUrl2));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl3));
+ result.Append(base::Value(kUrl2));
+ result.Append(base::Value(kUrl1));
+
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, ListsPoliciesMerging_InvalidType) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value list_value(base::Value::Type::LIST);
+ list_value.Append(base::Value(kUrl1));
+ base::Value result(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list_value.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. A string value is set
+ // instead of the expected list value.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, base::Value(kUrl2));
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ // The expected_chrome PolicyMap only contains the URLs from policy_bundle_1.
+ // The string value stored in policy_bundle_2 is ignored during merging since
+ // its type does not match the expected list type.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+#if !BUILDFLAG(IS_CHROMEOS)
+// The cloud user policy merging metapolicy is not applicable in Chrome OS.
+TEST_F(PolicyServiceTest, ListsPoliciesMerging_CloudMetapolicy) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize affiliation IDs. User and device ID is identical.
+ base::flat_set<std::string> ids;
+ ids.insert(kAffiliationId1);
+
+ base::Value list1(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ list1.Append(base::Value(kUrl2));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl2));
+ list2.Append(base::Value(kUrl3));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl3));
+ list2.Append(base::Value(kUrl4));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+ result.Append(base::Value(kUrl2));
+ result.Append(base::Value(kUrl3));
+ result.Append(base::Value(kUrl4));
+
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. In addition to the
+ // device affiliation IDs, the metapolicies are also defined here to simulate
+ // being set through CBCM.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_2.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(ids);
+
+ // The expected_chrome PolicyMap contains the combined URLs from all three
+ // policy bundles. The affiliation IDs don't need to be added as they're not
+ // compared in the PolicyMap equality check.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, policy->Clone(), nullptr);
+ expected_chrome.Set(key::kCloudUserPolicyMerge, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_3));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_1));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+#endif // !BUILDFLAG(IS_CHROMEOS)
+
+TEST_F(PolicyServiceTest, GroupPoliciesMergingDisabledForCloudUsers) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value list1(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl3));
+ base::Value list2(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl1));
+ base::Value list3(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl4));
+ base::Value result(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl3));
+ result.Append(base::Value(kUrl1));
+
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+ policy->Append(base::Value(policy::key::kExtensionInstallBlocklist));
+
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ policies_1.emplace_back(key::kExtensionInstallBlocklist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+ // Unlike the rest of the bundle, this policy is set at the cloud user level.
+ PolicyMap::Entry atomic_policy_enabled(POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ policy_bundle_1->Get(chrome_namespace)
+ .Set(key::kPolicyAtomicGroupsEnabled, atomic_policy_enabled.DeepCopy());
+
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ policies_2.emplace_back(key::kExtensionInstallBlocklist, list2.Clone());
+ policies_2.emplace_back(key::kExtensionInstallAllowlist, list3.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, merged.DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallBlocklist, std::move(merged));
+ expected_chrome.Set(key::kExtensionInstallAllowlist,
+ policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallAllowlist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kPolicyAtomicGroupsEnabled,
+ atomic_policy_enabled.DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, GroupPoliciesMergingEnabled) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ base::Value list1(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl3));
+ base::Value list2(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl1));
+ base::Value list3(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl4));
+ base::Value result(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl3));
+ result.Append(base::Value(kUrl1));
+
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+ policy->Append(base::Value(policy::key::kExtensionInstallBlocklist));
+
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ policies_1.emplace_back(key::kExtensionInstallBlocklist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+ // Unlike the rest of the bundle, this policy is set at the cloud user level.
+ PolicyMap::Entry atomic_policy_enabled(
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ base::Value(true), nullptr);
+ policy_bundle_1->Get(chrome_namespace)
+ .Set(key::kPolicyAtomicGroupsEnabled, atomic_policy_enabled.DeepCopy());
+
+ PolicyMap::Entry entry_list_3(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_CLOUD, list3.Clone(), nullptr);
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ policies_2.emplace_back(key::kExtensionInstallBlocklist, list2.Clone());
+ policies_2.emplace_back(key::kExtensionInstallAllowlist,
+ entry_list_3.value(base::Value::Type::LIST)->Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ entry_list_3.SetIgnoredByPolicyAtomicGroup();
+ expected_chrome.Set(key::kExtensionInstallForcelist, merged.DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallBlocklist, std::move(merged));
+ expected_chrome.Set(key::kExtensionInstallAllowlist, std::move(entry_list_3));
+ expected_chrome.Set(key::kPolicyAtomicGroupsEnabled,
+ atomic_policy_enabled.DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, CloudUserListPolicyMerge_Successful) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize affiliation IDs. User and device ID is identical.
+ base::flat_set<std::string> ids;
+ ids.insert(kAffiliationId1);
+
+ // Initialize lists of URLs used for ExtensionInstallForcelist policy values.
+ base::Value list1 = base::Value(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ list1.Append(base::Value(kUrl2));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl2));
+ list2.Append(base::Value(kUrl3));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl3));
+ list3.Append(base::Value(kUrl4));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+ result.Append(base::Value(kUrl2));
+ result.Append(base::Value(kUrl3));
+ result.Append(base::Value(kUrl4));
+
+ // Populate separate policy bundles.
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle. The metadata
+ // policies (PolicyListMultipleSourceMergeList, CloudUserPolicyMerge) are
+ // defined here.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. The device
+ // affiliation IDs are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(ids);
+
+ // The expected_chrome PolicyMap contains the combined URLs from all three
+ // policy bundles. The affiliation IDs don't need to be added as they're not
+ // compared in the PolicyMap equality check.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+ expected_chrome.Set(key::kCloudUserPolicyMerge,
+ policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kCloudUserPolicyMerge)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, CloudUserListPolicyMerge_Unaffiliated) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize user and device affiliation IDs with no common ID.
+ base::flat_set<std::string> user_ids;
+ user_ids.insert(kAffiliationId1);
+ base::flat_set<std::string> device_ids;
+ device_ids.insert(kAffiliationId2);
+
+ // Initialize lists of URLs used for ExtensionInstallForcelist policy values.
+ base::Value list1 = base::Value(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ list1.Append(base::Value(kUrl2));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl3));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl4));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+ result.Append(base::Value(kUrl2));
+ result.Append(base::Value(kUrl3));
+
+ // Populate separate policy bundles.
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle. The metadata
+ // policies (PolicyListMultipleSourceMergeList, CloudUserPolicyMerge) are
+ // defined here.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. The device
+ // affiliation IDs are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(device_ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(user_ids);
+
+ // The expected_chrome PolicyMap contains the combined URLs from the non-user
+ // policy bundles. The policy values from the user cloud bundle aren't merged
+ // as there is no common affiliation ID between the user and device.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+ expected_chrome.Set(key::kCloudUserPolicyMerge,
+ policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kCloudUserPolicyMerge)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, CloudUserListPolicyMerge_FalsePolicy) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize affiliation IDs. User and device ID is identical.
+ base::flat_set<std::string> ids;
+ ids.insert(kAffiliationId1);
+
+ // Initialize lists of URLs used for ExtensionInstallForcelist policy values.
+ base::Value list1 = base::Value(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl2));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl3));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+ result.Append(base::Value(kUrl2));
+
+ // Populate separate policy bundles.
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle. The metadata
+ // policies (PolicyListMultipleSourceMergeList, CloudUserPolicyMerge) are
+ // defined here. CloudUserPolicyMerge is set to false, preventing user cloud
+ // policy values from being merged with values from other sources.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(false));
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. The device
+ // affiliation IDs are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(ids);
+
+ // The expected_chrome PolicyMap contains the combined URLs from the non-user
+ // policy bundles. The policy values from the user cloud bundle aren't merged
+ // because CloudUserPolicyMerge is set to false.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+ expected_chrome.Set(key::kCloudUserPolicyMerge,
+ policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kCloudUserPolicyMerge)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, PlatformUserListPolicyMerge_Affiliated) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize affiliation IDs. User and device ID is identical.
+ base::flat_set<std::string> ids;
+ ids.insert(kAffiliationId1);
+
+ // Initialize lists of URLs used for ExtensionInstallForcelist policy values.
+ base::Value list1 = base::Value(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl2));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl3));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl2));
+ result.Append(base::Value(kUrl3));
+
+ // Populate separate policy bundles.
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a user platform bundle. The metadata policies
+ // (PolicyListMultipleSourceMergeList, CloudUserPolicyMerge) are defined here.
+ // Policy values with a user GPO source are currently not merged with values
+ // from any other source(s).
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a machine cloud bundle. The device
+ // affiliation IDs are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::move(policies_2), chrome_namespace);
+ policy_bundle_2->Get(chrome_namespace).SetDeviceAffiliationIds(ids);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.t,
+ // entry_list_3.DeepCopy()); policy_map_3.SetUserAffiliationIds(ids);
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(ids);
+
+ // The expected_chrome PolicyMap contains the merged values from machine and
+ // user policy sources. User platform policy values are not merged.
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+ expected_chrome.Set(key::kCloudUserPolicyMerge,
+ policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kCloudUserPolicyMerge)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, PlatformUserListPolicyMerge_Unaffiliated) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // Initialize user affiliation IDs. This test doesn't contain a machine cloud
+ // source, so no device affiliation IDs are set.
+ base::flat_set<std::string> user_ids;
+ user_ids.insert(kAffiliationId1);
+
+ // Initialize lists of URLs used for ExtensionInstallForcelist policy values.
+ base::Value list1 = base::Value(base::Value::Type::LIST);
+ list1.Append(base::Value(kUrl1));
+ base::Value list2 = base::Value(base::Value::Type::LIST);
+ list2.Append(base::Value(kUrl2));
+ base::Value list3 = base::Value(base::Value::Type::LIST);
+ list3.Append(base::Value(kUrl3));
+ base::Value result = base::Value(base::Value::Type::LIST);
+ result.Append(base::Value(kUrl1));
+
+ // Populate separate policy bundles.
+ std::unique_ptr<base::ListValue> policy = std::make_unique<base::ListValue>();
+ policy->Append(base::Value(policy::key::kExtensionInstallForcelist));
+
+ // policy_bundle_1 is treated as a machine platform bundle. The metadata
+ // policies (PolicyListMultipleSourceMergeList, CloudUserPolicyMerge) are
+ // defined here.
+ std::vector<std::pair<std::string, base::Value>> policies_1;
+ policies_1.emplace_back(key::kPolicyListMultipleSourceMergeList,
+ policy->Clone());
+ policies_1.emplace_back(key::kCloudUserPolicyMerge, base::Value(true));
+ policies_1.emplace_back(key::kExtensionInstallForcelist, list1.Clone());
+ auto policy_bundle_1 =
+ CreateBundle(POLICY_SCOPE_MACHINE, POLICY_SOURCE_PLATFORM,
+ std::move(policies_1), chrome_namespace);
+
+ // policy_bundle_2 is treated as a user platform bundle. Policy values with a
+ // user GPO source are currently not merged with values from any other
+ // source(s).
+ std::vector<std::pair<std::string, base::Value>> policies_2;
+ policies_2.emplace_back(key::kExtensionInstallForcelist, list2.Clone());
+ auto policy_bundle_2 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_PLATFORM,
+ std::move(policies_2), chrome_namespace);
+
+ // policy_bundle_3 is treated as a user cloud bundle. The user affiliation IDs
+ // are defined here to reflect what would happen in reality.
+ std::vector<std::pair<std::string, base::Value>> policies_3;
+ policies_3.emplace_back(key::kExtensionInstallForcelist, list3.Clone());
+ auto policy_bundle_3 = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies_3), chrome_namespace);
+ policy_bundle_3->Get(chrome_namespace).SetUserAffiliationIds(user_ids);
+
+ // The expected_chrome PolicyMap only contains the URLs from the platform
+ // machine policy source. Values from the user platform policy are not
+ // mergeable. Values from the user cloud policy are not merged since the user
+ // is not affiliated (browser isn't enrolled in CBCM).
+ PolicyMap expected_chrome;
+ expected_chrome.Set(key::kPolicyListMultipleSourceMergeList,
+ POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_PLATFORM, policy->Clone(), nullptr);
+ expected_chrome.Set("migrated", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_PLATFORM, base::Value(15), nullptr);
+
+ PolicyMap::Entry merged(POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
+ POLICY_SOURCE_MERGED, std::move(result), nullptr);
+ merged.AddConflictingPolicy(policy_bundle_2->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_3->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ merged.AddConflictingPolicy(policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kExtensionInstallForcelist)
+ ->DeepCopy());
+ expected_chrome.Set(key::kExtensionInstallForcelist, std::move(merged));
+ expected_chrome.Set(key::kCloudUserPolicyMerge,
+ policy_bundle_1->Get(chrome_namespace)
+ .Get(key::kCloudUserPolicyMerge)
+ ->DeepCopy());
+
+ provider0_.UpdatePolicy(std::move(policy_bundle_1));
+ provider1_.UpdatePolicy(std::move(policy_bundle_2));
+ provider2_.UpdatePolicy(std::move(policy_bundle_3));
+ RunUntilIdle();
+
+ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome));
+}
+
+TEST_F(PolicyServiceTest, IgnoreUserCloudPrecedencePolicies) {
+ const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
+
+ // The policies are set by a user cloud source.
+ std::vector<std::pair<std::string, base::Value>> policies;
+ policies.emplace_back(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value(true));
+ policies.emplace_back(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value(true));
+ policies.emplace_back(key::kBookmarkBarEnabled, base::Value(true));
+ auto policy_bundle = CreateBundle(POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ std::move(policies), chrome_namespace);
+
+ provider0_.UpdatePolicy(std::move(policy_bundle));
+ RunUntilIdle();
+
+ // Precedence metapolicies set from a user cloud source are ignored.
+ EXPECT_EQ(nullptr, policy_service_->GetPolicies(chrome_namespace)
+ .GetValue(key::kCloudPolicyOverridesPlatformPolicy,
+ base::Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr,
+ policy_service_->GetPolicies(chrome_namespace)
+ .GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
+ base::Value::Type::BOOLEAN));
+
+ // Other policies set from a user cloud source are not ignored.
+ EXPECT_NE(nullptr, policy_service_->GetPolicies(chrome_namespace)
+ .GetValue(key::kBookmarkBarEnabled,
+ base::Value::Type::BOOLEAN));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_service_stub.cc b/chromium/components/policy/core/common/policy_service_stub.cc
new file mode 100644
index 00000000000..551510efeb4
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service_stub.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_service_stub.h"
+
+
+namespace policy {
+
+PolicyServiceStub::PolicyServiceStub() {}
+
+PolicyServiceStub::~PolicyServiceStub() {}
+
+void PolicyServiceStub::AddObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+void PolicyServiceStub::RemoveObserver(PolicyDomain domain,
+ Observer* observer) {}
+
+const PolicyMap& PolicyServiceStub::GetPolicies(
+ const PolicyNamespace& ns) const {
+ return kEmpty_;
+}
+
+bool PolicyServiceStub::IsInitializationComplete(PolicyDomain domain) const {
+ return true;
+}
+
+void PolicyServiceStub::RefreshPolicies(base::OnceClosure callback) {
+ if (!callback.is_null())
+ std::move(callback).Run();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_service_stub.h b/chromium/components/policy/core/common/policy_service_stub.h
new file mode 100644
index 00000000000..2ab3d016245
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_service_stub.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
+
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A stub implementation, that is used when ENABLE_CONFIGURATION_POLICY is not
+// set. This allows client code to compile without requiring #ifdefs.
+class POLICY_EXPORT PolicyServiceStub : public PolicyService {
+ public:
+ PolicyServiceStub();
+ PolicyServiceStub(const PolicyServiceStub&) = delete;
+ PolicyServiceStub& operator=(const PolicyServiceStub&) = delete;
+ ~PolicyServiceStub() override;
+
+ void AddObserver(PolicyDomain domain,
+ Observer* observer) override;
+
+ void RemoveObserver(PolicyDomain domain,
+ Observer* observer) override;
+
+ const PolicyMap& GetPolicies(
+ const PolicyNamespace& ns) const override;
+
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+
+ void RefreshPolicies(base::OnceClosure callback) override;
+
+ private:
+ const PolicyMap kEmpty_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SERVICE_STUB_H_
diff --git a/chromium/components/policy/core/common/policy_statistics_collector.cc b/chromium/components/policy/core/common/policy_statistics_collector.cc
new file mode 100644
index 00000000000..c322e0ddce0
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_statistics_collector.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_statistics_collector.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
+#include "base/task/task_runner.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace policy {
+
+const int PolicyStatisticsCollector::kStatisticsUpdateRate =
+ 24 * 60 * 60 * 1000; // 24 hours.
+
+PolicyStatisticsCollector::PolicyStatisticsCollector(
+ const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : get_details_(get_details),
+ chrome_schema_(chrome_schema),
+ policy_service_(policy_service),
+ prefs_(prefs),
+ task_runner_(task_runner) {
+}
+
+PolicyStatisticsCollector::~PolicyStatisticsCollector() {
+}
+
+void PolicyStatisticsCollector::Initialize() {
+ using base::Time;
+
+ base::TimeDelta update_rate = base::Milliseconds(kStatisticsUpdateRate);
+ Time last_update = prefs_->GetTime(policy_prefs::kLastPolicyStatisticsUpdate);
+ base::TimeDelta delay = std::max(Time::Now() - last_update, base::Days(0));
+ if (delay >= update_rate)
+ CollectStatistics();
+ else
+ ScheduleUpdate(update_rate - delay);
+}
+
+// static
+void PolicyStatisticsCollector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterInt64Pref(policy_prefs::kLastPolicyStatisticsUpdate, 0);
+}
+
+void PolicyStatisticsCollector::RecordPolicyUse(int id, Condition condition) {
+ std::string suffix;
+ switch (condition) {
+ case kDefault:
+ break;
+ case kMandatory:
+ suffix = ".Mandatory";
+ break;
+ case kRecommended:
+ suffix = ".Recommended";
+ break;
+ case kIgnoredByAtomicGroup:
+ suffix = ".IgnoredByPolicyGroup";
+ break;
+ }
+ base::UmaHistogramSparse("Enterprise.Policies" + suffix, id);
+}
+
+void PolicyStatisticsCollector::CollectStatistics() {
+ const PolicyMap& policies = policy_service_->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+
+ // Collect statistics.
+ for (Schema::Iterator it(chrome_schema_.GetPropertiesIterator());
+ !it.IsAtEnd(); it.Advance()) {
+ if (policies.Get(it.key())) {
+ const PolicyDetails* details = get_details_.Run(it.key());
+ if (details) {
+ RecordPolicyUse(details->id, kDefault);
+ if (policies.Get(it.key())->level == POLICY_LEVEL_MANDATORY) {
+ RecordPolicyUse(details->id, kMandatory);
+ } else {
+ RecordPolicyUse(details->id, kRecommended);
+ }
+ } else {
+ NOTREACHED();
+ }
+ }
+ }
+
+ for (size_t i = 0; i < kPolicyAtomicGroupMappingsLength; ++i) {
+ const AtomicGroup& group = kPolicyAtomicGroupMappings[i];
+ // Find the policy with the highest priority that is both in |policies|
+ // and |group.policies|, an array ending with a nullptr.
+ for (const char* const* policy_name = group.policies; *policy_name;
+ ++policy_name) {
+ if (policies.IsPolicyIgnoredByAtomicGroup(*policy_name)) {
+ const PolicyDetails* details = get_details_.Run(*policy_name);
+ if (details)
+ RecordPolicyUse(details->id, kIgnoredByAtomicGroup);
+ else
+ NOTREACHED();
+ }
+ }
+ }
+
+ // Take care of next update.
+ prefs_->SetTime(policy_prefs::kLastPolicyStatisticsUpdate, base::Time::Now());
+ ScheduleUpdate(base::Milliseconds(kStatisticsUpdateRate));
+}
+
+void PolicyStatisticsCollector::ScheduleUpdate(base::TimeDelta delay) {
+ update_callback_.Reset(base::BindOnce(
+ &PolicyStatisticsCollector::CollectStatistics, base::Unretained(this)));
+ task_runner_->PostDelayedTask(FROM_HERE, update_callback_.callback(), delay);
+}
+
+} // namespace policy \ No newline at end of file
diff --git a/chromium/components/policy/core/common/policy_statistics_collector.h b/chromium/components/policy/core/common/policy_statistics_collector.h
new file mode 100644
index 00000000000..5f5552b1146
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_statistics_collector.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_
+
+#include "base/cancelable_callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace policy {
+
+class PolicyService;
+
+// Different types of policies
+enum Condition {
+ kDefault,
+ kMandatory,
+ kRecommended,
+ kIgnoredByAtomicGroup,
+};
+
+// Manages regular updates of policy usage UMA histograms.
+class POLICY_EXPORT PolicyStatisticsCollector {
+ public:
+ // Policy usage statistics update rate, in milliseconds.
+ static const int kStatisticsUpdateRate;
+
+ // Neither |policy_service| nor |prefs| can be NULL and must stay valid
+ // throughout the lifetime of PolicyStatisticsCollector.
+ PolicyStatisticsCollector(const GetChromePolicyDetailsCallback& get_details,
+ const Schema& chrome_schema,
+ PolicyService* policy_service,
+ PrefService* prefs,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+ PolicyStatisticsCollector(const PolicyStatisticsCollector&) = delete;
+ PolicyStatisticsCollector& operator=(const PolicyStatisticsCollector&) =
+ delete;
+ virtual ~PolicyStatisticsCollector();
+
+ // Completes initialization and starts periodical statistic updates.
+ void Initialize();
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+ // protected virtual for mocking.
+ virtual void RecordPolicyUse(int id, Condition condition);
+
+ private:
+ void CollectStatistics();
+ void ScheduleUpdate(base::TimeDelta delay);
+
+ GetChromePolicyDetailsCallback get_details_;
+ Schema chrome_schema_;
+ raw_ptr<PolicyService> policy_service_;
+ raw_ptr<PrefService> prefs_;
+
+ base::CancelableOnceClosure update_callback_;
+
+ const scoped_refptr<base::TaskRunner> task_runner_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_STATISTICS_COLLECTOR_H_ \ No newline at end of file
diff --git a/chromium/components/policy/core/common/policy_statistics_collector_unittest.cc b/chromium/components/policy/core/common/policy_statistics_collector_unittest.cc
new file mode 100644
index 00000000000..422ddc68844
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_statistics_collector_unittest.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_statistics_collector.h"
+
+#include <cstring>
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_policy_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_test_utils.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+using testing::ReturnRef;
+
+// Arbitrary policy names used for testing.
+const char kTestPolicy1[] = "Test Policy 1";
+const char kTestPolicy2[] = "Test Policy 2";
+const char* kTestPolicy3 = key::kExtensionInstallBlocklist;
+
+const int kTestPolicy1Id = 42;
+const int kTestPolicy2Id = 123;
+const int kTestPolicy3Id = 32;
+
+const char kTestChromeSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"Test Policy 1\": { \"type\": \"string\" },"
+ " \"Test Policy 2\": { \"type\": \"string\" },"
+ " \"ExtensionInstallBlocklist\": { \"type\": \"string\" },"
+ " }"
+ "}";
+
+const PolicyDetails kTestPolicyDetails[] = {
+ // is_deprecated is_future is_device_policy id max_external_data_size
+ {false, false, false, kTestPolicy1Id, 0},
+ {false, false, false, kTestPolicy2Id, 0},
+ {false, false, false, kTestPolicy3Id, 0},
+};
+
+} // namespace
+
+class PolicyStatisticsCollectorTest : public testing::Test {
+ protected:
+ PolicyStatisticsCollectorTest()
+ : update_delay_(base::Milliseconds(
+ PolicyStatisticsCollector::kStatisticsUpdateRate)),
+ task_runner_(new base::TestSimpleTaskRunner()) {}
+
+ void SetUp() override {
+ std::string error;
+ chrome_schema_ = Schema::Parse(kTestChromeSchema, &error);
+ ASSERT_TRUE(chrome_schema_.valid()) << error;
+
+ policy_details_.SetDetails(kTestPolicy1, &kTestPolicyDetails[0]);
+ policy_details_.SetDetails(kTestPolicy2, &kTestPolicyDetails[1]);
+ policy_details_.SetDetails(kTestPolicy3, &kTestPolicyDetails[2]);
+
+ prefs_.registry()->RegisterInt64Pref(
+ policy_prefs::kLastPolicyStatisticsUpdate, 0);
+
+ // Set up default function behaviour.
+ EXPECT_CALL(policy_service_,
+ GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
+ std::string())))
+ .WillRepeatedly(ReturnRef(policy_map_));
+
+ // Arbitrary negative value (so it'll be different from |update_delay_|).
+ last_delay_ = base::Days(-1);
+ policy_map_.Clear();
+ policy_statistics_collector_ = std::make_unique<PolicyStatisticsCollector>(
+ policy_details_.GetCallback(), chrome_schema_, &policy_service_,
+ &prefs_, task_runner_);
+ }
+
+ void SetPolicy(const std::string& name,
+ PolicyLevel level = POLICY_LEVEL_MANDATORY) {
+ policy_map_.Set(name, level, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value(true), nullptr);
+ }
+
+ void SetPolicyIgnoredByAtomicGroup(const std::string& name) {
+ SetPolicy(name, POLICY_LEVEL_MANDATORY);
+ auto* policy = policy_map_.GetMutable(name);
+ policy->SetIgnoredByPolicyAtomicGroup();
+ }
+
+ base::TimeDelta GetFirstDelay() const {
+ if (!task_runner_->HasPendingTask()) {
+ ADD_FAILURE();
+ return base::TimeDelta();
+ }
+ return task_runner_->NextPendingTaskDelay();
+ }
+
+ const base::TimeDelta update_delay_;
+
+ base::TimeDelta last_delay_;
+
+ PolicyDetailsMap policy_details_;
+ Schema chrome_schema_;
+ TestingPrefServiceSimple prefs_;
+ MockPolicyService policy_service_;
+ PolicyMap policy_map_;
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ std::unique_ptr<PolicyStatisticsCollector> policy_statistics_collector_;
+
+ base::HistogramTester histogram_tester_;
+};
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPending) {
+ SetPolicy(kTestPolicy1, POLICY_LEVEL_MANDATORY);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectBucketCount("Enterprise.Policies", kTestPolicy1Id, 1);
+
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectPendingVeryOld) {
+ SetPolicy(kTestPolicy1, POLICY_LEVEL_MANDATORY);
+
+ // Must not be 0.0 (read comment for Time::FromDoubleT).
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::FromDoubleT(1.0));
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectBucketCount("Enterprise.Policies", kTestPolicy1Id, 1);
+
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, CollectLater) {
+ SetPolicy(kTestPolicy1, POLICY_LEVEL_MANDATORY);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_ / 2);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectTotalCount("Enterprise.Policies", 0);
+
+ EXPECT_EQ(1u, task_runner_->NumPendingTasks());
+}
+
+TEST_F(PolicyStatisticsCollectorTest, MultiplePolicies) {
+ SetPolicy(kTestPolicy1, POLICY_LEVEL_MANDATORY);
+ SetPolicy(kTestPolicy2, POLICY_LEVEL_RECOMMENDED);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectBucketCount("Enterprise.Policies", kTestPolicy1Id, 1);
+ histogram_tester_.ExpectBucketCount("Enterprise.Policies", kTestPolicy2Id, 1);
+ histogram_tester_.ExpectTotalCount("Enterprise.Policies", 2);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, PolicyIgnoredByAtomicGroup) {
+ SetPolicyIgnoredByAtomicGroup(kTestPolicy3);
+ const AtomicGroup* extensions = nullptr;
+
+ for (size_t i = 0; i < kPolicyAtomicGroupMappingsLength; ++i) {
+ if (kPolicyAtomicGroupMappings[i].policy_group == group::kExtensions) {
+ extensions = &kPolicyAtomicGroupMappings[i];
+ break;
+ }
+ }
+
+ DCHECK(extensions);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectUniqueSample(
+ "Enterprise.Policies.IgnoredByPolicyGroup", kTestPolicy3Id, 1);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, MandatoryPolicy) {
+ SetPolicy(kTestPolicy1, POLICY_LEVEL_MANDATORY);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectUniqueSample("Enterprise.Policies.Mandatory",
+ kTestPolicy1Id, 1);
+ histogram_tester_.ExpectTotalCount("Enterprise.Policies.Recommended", 0);
+}
+
+TEST_F(PolicyStatisticsCollectorTest, RecommendedPolicy) {
+ SetPolicy(kTestPolicy2, POLICY_LEVEL_RECOMMENDED);
+
+ prefs_.SetTime(policy_prefs::kLastPolicyStatisticsUpdate,
+ base::Time::Now() - update_delay_);
+
+ policy_statistics_collector_->Initialize();
+
+ histogram_tester_.ExpectUniqueSample("Enterprise.Policies.Recommended",
+ kTestPolicy2Id, 1);
+ histogram_tester_.ExpectTotalCount("Enterprise.Policies.Mandatory", 0);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_switches.cc b/chromium/components/policy/core/common/policy_switches.cc
new file mode 100644
index 00000000000..ff69ab42bdd
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_switches.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_switches.h"
+
+namespace policy {
+namespace switches {
+
+// Specifies the URL at which to communicate with the device management backend
+// to fetch configuration policies and perform other device tasks.
+const char kDeviceManagementUrl[] = "device-management-url";
+
+// Specifies the URL at which to upload real-time reports.
+const char kRealtimeReportingUrl[] = "realtime-reporting-url";
+
+// Specifies the URL at which to upload encrypted reports.
+const char kEncryptedReportingUrl[] = "encrypted-reporting-url";
+
+// Set policy value by command line.
+const char kChromePolicy[] = "policy";
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Disables the verification of policy signing keys. It just works on Chrome OS
+// test images and crashes otherwise.
+// TODO(crbug.com/1225054): This flag might introduce security risks. Find a
+// better solution to enable policy tast test for Family Link account.
+const char kDisablePolicyKeyVerification[] = "disable-policy-key-verification";
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+// Specifies the base URL to contact the secure connect Api.
+const char kSecureConnectApiUrl[] = "secure-connect-api-url";
+} // namespace switches
+} // namespace policy
diff --git a/chromium/components/policy/core/common/policy_switches.h b/chromium/components/policy/core/common/policy_switches.h
new file mode 100644
index 00000000000..46b42ef79c3
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_switches.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
+
+#include "components/policy/policy_export.h"
+
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+
+namespace policy {
+namespace switches {
+
+POLICY_EXPORT extern const char kDeviceManagementUrl[];
+POLICY_EXPORT extern const char kRealtimeReportingUrl[];
+POLICY_EXPORT extern const char kEncryptedReportingUrl[];
+POLICY_EXPORT extern const char kChromePolicy[];
+POLICY_EXPORT extern const char kSecureConnectApiUrl[];
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+POLICY_EXPORT extern const char kDisablePolicyKeyVerification[];
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+
+} // namespace switches
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SWITCHES_H_
diff --git a/chromium/components/policy/core/common/policy_test_utils.cc b/chromium/components/policy/core/common/policy_test_utils.cc
new file mode 100644
index 00000000000..59cf85cd92f
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_test_utils.cc
@@ -0,0 +1,190 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_test_utils.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/policy_constants.h"
+
+#if BUILDFLAG(IS_APPLE)
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#endif
+
+namespace policy {
+
+PolicyDetailsMap::PolicyDetailsMap() {}
+
+PolicyDetailsMap::~PolicyDetailsMap() {}
+
+GetChromePolicyDetailsCallback PolicyDetailsMap::GetCallback() const {
+ return base::BindRepeating(&PolicyDetailsMap::Lookup, base::Unretained(this));
+}
+
+void PolicyDetailsMap::SetDetails(const std::string& policy,
+ const PolicyDetails* details) {
+ map_[policy] = details;
+}
+
+const PolicyDetails* PolicyDetailsMap::Lookup(const std::string& policy) const {
+ auto it = map_.find(policy);
+ return it == map_.end() ? NULL : it->second;
+}
+
+bool PolicyServiceIsEmpty(const PolicyService* service) {
+ const PolicyMap& map = service->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ if (!map.empty()) {
+ base::DictionaryValue dict;
+ for (const auto& it : map)
+ dict.SetKey(it.first, it.second.value_unsafe()->Clone());
+ LOG(WARNING) << "There are pre-existing policies in this machine: " << dict;
+#if BUILDFLAG(IS_WIN)
+ LOG(WARNING) << "From: " << kRegistryChromePolicyKey;
+#endif
+ }
+ return map.empty();
+}
+
+#if BUILDFLAG(IS_APPLE)
+CFPropertyListRef ValueToProperty(const base::Value& value) {
+ switch (value.type()) {
+ case base::Value::Type::NONE:
+ return kCFNull;
+
+ case base::Value::Type::BOOLEAN:
+ return value.GetBool() ? kCFBooleanTrue : kCFBooleanFalse;
+
+ case base::Value::Type::INTEGER: {
+ const int int_value = value.GetInt();
+ return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &int_value);
+ }
+
+ case base::Value::Type::DOUBLE: {
+ const double double_value = value.GetDouble();
+ return CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType,
+ &double_value);
+ }
+
+ case base::Value::Type::STRING: {
+ const std::string& string_value = value.GetString();
+ return base::SysUTF8ToCFStringRef(string_value).release();
+ }
+
+ case base::Value::Type::DICTIONARY: {
+ // |dict| is owned by the caller.
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, value.DictSize(), &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ for (const auto key_value_pair : value.DictItems()) {
+ // CFDictionaryAddValue() retains both |key| and |value|, so make sure
+ // the references are balanced.
+ base::ScopedCFTypeRef<CFStringRef> key(
+ base::SysUTF8ToCFStringRef(key_value_pair.first));
+ base::ScopedCFTypeRef<CFPropertyListRef> cf_value(
+ ValueToProperty(key_value_pair.second));
+ if (cf_value)
+ CFDictionaryAddValue(dict, key, cf_value);
+ }
+ return dict;
+ }
+
+ case base::Value::Type::LIST: {
+ base::Value::ConstListView list_view = value.GetListDeprecated();
+ CFMutableArrayRef array =
+ CFArrayCreateMutable(NULL, list_view.size(), &kCFTypeArrayCallBacks);
+ for (const base::Value& entry : list_view) {
+ // CFArrayAppendValue() retains |cf_value|, so make sure the reference
+ // created by ValueToProperty() is released.
+ base::ScopedCFTypeRef<CFPropertyListRef> cf_value(
+ ValueToProperty(entry));
+ if (cf_value)
+ CFArrayAppendValue(array, cf_value);
+ }
+ return array;
+ }
+
+ case base::Value::Type::BINARY:
+ // This type isn't converted (though it can be represented as CFData)
+ // because there's no equivalent JSON type, and policy values can only
+ // take valid JSON values.
+ break;
+ }
+
+ return NULL;
+}
+#endif // BUILDFLAG(IS_APPLE)
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle) {
+ os << "{" << std::endl;
+ for (const auto& entry : bundle)
+ os << " \"" << entry.first << "\": " << entry.second << "," << std::endl;
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope) {
+ switch (scope) {
+ case policy::POLICY_SCOPE_USER:
+ return os << "POLICY_SCOPE_USER";
+ case policy::POLICY_SCOPE_MACHINE:
+ return os << "POLICY_SCOPE_MACHINE";
+ }
+ return os << "POLICY_SCOPE_UNKNOWN(" << int(scope) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level) {
+ switch (level) {
+ case policy::POLICY_LEVEL_RECOMMENDED:
+ return os << "POLICY_LEVEL_RECOMMENDED";
+ case policy::POLICY_LEVEL_MANDATORY:
+ return os << "POLICY_LEVEL_MANDATORY";
+ }
+ return os << "POLICY_LEVEL_UNKNOWN(" << int(level) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain) {
+ switch (domain) {
+ case policy::POLICY_DOMAIN_CHROME:
+ return os << "POLICY_DOMAIN_CHROME";
+ case policy::POLICY_DOMAIN_EXTENSIONS:
+ return os << "POLICY_DOMAIN_EXTENSIONS";
+ case policy::POLICY_DOMAIN_SIGNIN_EXTENSIONS:
+ return os << "POLICY_DOMAIN_SIGNIN_EXTENSIONS";
+ case policy::POLICY_DOMAIN_SIZE:
+ break;
+ }
+ return os << "POLICY_DOMAIN_UNKNOWN(" << int(domain) << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies) {
+ os << "{" << std::endl;
+ for (const auto& iter : policies)
+ os << " \"" << iter.first << "\": " << iter.second << "," << std::endl;
+ os << "}";
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e) {
+ return os << "{" << std::endl
+ << " \"level\": " << e.level << "," << std::endl
+ << " \"scope\": " << e.scope << "," << std::endl
+ << " \"value\": " << *e.value_unsafe() << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns) {
+ return os << ns.domain << "/" << ns.component_id;
+}
diff --git a/chromium/components/policy/core/common/policy_test_utils.h b/chromium/components/policy/core/common/policy_test_utils.h
new file mode 100644
index 00000000000..e3ae7d62ace
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_test_utils.h
@@ -0,0 +1,72 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
+
+#include <map>
+#include <ostream>
+#include <string>
+
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/core/common/policy_types.h"
+
+#if BUILDFLAG(IS_APPLE)
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+namespace policy {
+
+class PolicyBundle;
+struct PolicyNamespace;
+
+// A mapping of policy names to PolicyDetails that can be used to set the
+// PolicyDetails for test policies.
+class PolicyDetailsMap {
+ public:
+ PolicyDetailsMap();
+ PolicyDetailsMap(const PolicyDetailsMap&) = delete;
+ PolicyDetailsMap& operator=(const PolicyDetailsMap&) = delete;
+ ~PolicyDetailsMap();
+
+ // The returned callback's lifetime is tied to |this| object.
+ GetChromePolicyDetailsCallback GetCallback() const;
+
+ // Does not take ownership of |details|.
+ void SetDetails(const std::string& policy, const PolicyDetails* details);
+
+ private:
+ typedef std::map<std::string, const PolicyDetails*> PolicyDetailsMapping;
+
+ const PolicyDetails* Lookup(const std::string& policy) const;
+
+ PolicyDetailsMapping map_;
+};
+
+// Returns true if |service| is not serving any policies. Otherwise logs the
+// current policies and returns false.
+bool PolicyServiceIsEmpty(const PolicyService* service);
+
+#if BUILDFLAG(IS_APPLE)
+
+// Converts a base::Value to the equivalent CFPropertyListRef.
+// The returned value is owned by the caller.
+CFPropertyListRef ValueToProperty(const base::Value& value);
+
+#endif
+
+} // namespace policy
+
+std::ostream& operator<<(std::ostream& os, const policy::PolicyBundle& bundle);
+std::ostream& operator<<(std::ostream& os, policy::PolicyScope scope);
+std::ostream& operator<<(std::ostream& os, policy::PolicyLevel level);
+std::ostream& operator<<(std::ostream& os, policy::PolicyDomain domain);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap& policies);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyMap::Entry& e);
+std::ostream& operator<<(std::ostream& os, const policy::PolicyNamespace& ns);
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_TEST_UTILS_H_
diff --git a/chromium/components/policy/core/common/policy_types.h b/chromium/components/policy/core/common/policy_types.h
new file mode 100644
index 00000000000..60cf114d591
--- /dev/null
+++ b/chromium/components/policy/core/common/policy_types.h
@@ -0,0 +1,114 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_TYPES_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_TYPES_H_
+
+namespace policy {
+
+// The scope of a policy flags whether it is meant to be applied to the current
+// user or to the machine. Note that this property pertains to the source of
+// the policy and has no direct correspondence to the distinction between User
+// Policy and Device Policy.
+enum PolicyScope {
+ // USER policies apply to sessions of the current user.
+ POLICY_SCOPE_USER,
+
+ // MACHINE policies apply to any users of the current machine.
+ POLICY_SCOPE_MACHINE,
+};
+
+// The level of a policy determines its enforceability and whether users can
+// override it or not. The values are listed in increasing order of priority.
+enum PolicyLevel {
+ // RECOMMENDED policies can be overridden by users. They are meant as a
+ // default value configured by admins, that users can customize.
+ POLICY_LEVEL_RECOMMENDED,
+
+ // MANDATORY policies must be enforced and users can't circumvent them.
+ POLICY_LEVEL_MANDATORY,
+};
+
+// The source of a policy indicates where its value is originating from. The
+// sources are ordered by priority (with weakest policy first).
+enum PolicySource {
+ // The policy was set because we are running in an enterprise environment.
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+
+ // The policy was set by command line flag for testing purpose.
+ POLICY_SOURCE_COMMAND_LINE,
+
+ // The policy was set by a cloud source.
+ POLICY_SOURCE_CLOUD,
+
+ // The policy was set by an Active Directory source.
+ POLICY_SOURCE_ACTIVE_DIRECTORY,
+
+ // Any non-platform policy was overridden because we are running in a
+ // public session or kiosk mode.
+ // TODO(crbug.com/1225922): Remove deprecated policy source.
+ POLICY_SOURCE_DEVICE_LOCAL_ACCOUNT_OVERRIDE_DEPRECATED,
+
+ // The policy was set by a platform source.
+ POLICY_SOURCE_PLATFORM,
+
+ // The policy was set by a cloud source that has higher priroity.
+ // TODO(crbug.com/1249611): Remove deprecated policy source.
+ POLICY_SOURCE_PRIORITY_CLOUD_DEPRECATED,
+
+ // The policy coming from multiple sources and its value has been merged.
+ POLICY_SOURCE_MERGED,
+
+ // The policy was set by Cloud in Ash and piped to Lacros.
+ POLICY_SOURCE_CLOUD_FROM_ASH,
+
+ // The policy was set by the DeviceRestrictedManagedGuestSessionEnabled
+ // policy. This source should be kept as highest priority source.
+ POLICY_SOURCE_RESTRICTED_MANAGED_GUEST_SESSION_OVERRIDE,
+
+ // Number of source types. Has to be the last element.
+ POLICY_SOURCE_COUNT
+};
+
+// The priorities are set in order, with lowest priority first. A similar enum
+// doesn't exist for Chrome OS since there is a 1:1 mapping from source to
+// priority, so source is used directly in the priority comparison.
+enum PolicyPriorityBrowser {
+ // The policy was set through remapping or debugging.
+ POLICY_PRIORITY_BROWSER_ENTERPRISE_DEFAULT,
+
+ // The policy was set by command line flag for testing purposes.
+ POLICY_PRIORITY_BROWSER_COMMAND_LINE,
+
+ // The policy was set by a cloud source at the user level.
+ POLICY_PRIORITY_BROWSER_CLOUD_USER,
+
+ // The policy was set by a platform source at the user level.
+ POLICY_PRIORITY_BROWSER_PLATFORM_USER,
+
+ // The policy was set by a cloud source at the machine level.
+ POLICY_PRIORITY_BROWSER_CLOUD_MACHINE,
+
+ // The policy was set by a cloud source at the user level. Its priority is
+ // raised above that of cloud machine policies.
+ POLICY_PRIORITY_BROWSER_CLOUD_USER_RAISED,
+
+ // The policy was set by a platform source at the machine level.
+ POLICY_PRIORITY_BROWSER_PLATFORM_MACHINE,
+
+ // The policy was set by a platform source at the machine level. Its priority
+ // is raised above that of platform machine policies.
+ POLICY_PRIORITY_BROWSER_CLOUD_MACHINE_RAISED,
+
+ // The policy was set by a cloud source at the user level. Its priority is
+ // raised above that of platform and cloud machine policies.
+ POLICY_PRIORITY_BROWSER_CLOUD_USER_DOUBLE_RAISED,
+
+ // The policy coming from multiple sources and its value has been merged.
+ POLICY_PRIORITY_BROWSER_MERGED,
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_TYPES_H_
diff --git a/chromium/components/policy/core/common/preferences_mac.cc b/chromium/components/policy/core/common/preferences_mac.cc
new file mode 100644
index 00000000000..e65b1623460
--- /dev/null
+++ b/chromium/components/policy/core/common/preferences_mac.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preferences_mac.h"
+
+Boolean MacPreferences::AppSynchronize(CFStringRef applicationID) {
+ return CFPreferencesAppSynchronize(applicationID);
+}
+
+CFPropertyListRef MacPreferences::CopyAppValue(CFStringRef key,
+ CFStringRef applicationID) {
+ return CFPreferencesCopyAppValue(key, applicationID);
+}
+
+Boolean MacPreferences::AppValueIsForced(CFStringRef key,
+ CFStringRef applicationID) {
+ return CFPreferencesAppValueIsForced(key, applicationID);
+}
diff --git a/chromium/components/policy/core/common/preferences_mac.h b/chromium/components/policy/core/common/preferences_mac.h
new file mode 100644
index 00000000000..baa7d7e156c
--- /dev/null
+++ b/chromium/components/policy/core/common/preferences_mac.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MAC_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MAC_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "components/policy/policy_export.h"
+
+// Wraps a small part of the CFPreferences API surface in a very thin layer, to
+// allow it to be mocked out for testing.
+
+// See CFPreferences documentation for function documentation, as these call
+// through directly to their CFPreferences equivalents (Foo ->
+// CFPreferencesFoo).
+class POLICY_EXPORT MacPreferences {
+ public:
+ MacPreferences() {}
+ MacPreferences(const MacPreferences&) = delete;
+ MacPreferences& operator=(const MacPreferences&) = delete;
+ virtual ~MacPreferences() {}
+
+ virtual Boolean AppSynchronize(CFStringRef applicationID);
+
+ virtual CFPropertyListRef CopyAppValue(CFStringRef key,
+ CFStringRef applicationID);
+
+ virtual Boolean AppValueIsForced(CFStringRef key, CFStringRef applicationID);
+};
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MAC_H_
diff --git a/chromium/components/policy/core/common/preferences_mock_mac.cc b/chromium/components/policy/core/common/preferences_mock_mac.cc
new file mode 100644
index 00000000000..bc51c9a3523
--- /dev/null
+++ b/chromium/components/policy/core/common/preferences_mock_mac.cc
@@ -0,0 +1,47 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preferences_mock_mac.h"
+
+MockPreferences::MockPreferences() {
+ values_.reset(CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+ forced_.reset(CFSetCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeSetCallBacks));
+}
+
+MockPreferences::~MockPreferences() {
+}
+
+Boolean MockPreferences::AppSynchronize(CFStringRef applicationID) {
+ return true;
+}
+
+CFPropertyListRef MockPreferences::CopyAppValue(CFStringRef key,
+ CFStringRef applicationID) {
+ CFPropertyListRef value;
+ Boolean found = CFDictionaryGetValueIfPresent(values_,
+ key,
+ &value);
+ if (!found || !value)
+ return NULL;
+ CFRetain(value);
+ return value;
+}
+
+Boolean MockPreferences::AppValueIsForced(CFStringRef key,
+ CFStringRef applicationID) {
+ return CFSetContainsValue(forced_, key);
+}
+
+void MockPreferences::AddTestItem(CFStringRef key,
+ CFPropertyListRef value,
+ bool is_forced) {
+ CFDictionarySetValue(values_, key, value);
+ if (is_forced)
+ CFSetAddValue(forced_, key);
+}
diff --git a/chromium/components/policy/core/common/preferences_mock_mac.h b/chromium/components/policy/core/common/preferences_mock_mac.h
new file mode 100644
index 00000000000..8f3df0c8c77
--- /dev/null
+++ b/chromium/components/policy/core/common/preferences_mock_mac.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MOCK_MAC_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MOCK_MAC_H_
+
+#include "base/mac/scoped_cftyperef.h"
+#include "components/policy/core/common/preferences_mac.h"
+#include "components/policy/policy_export.h"
+
+// Mock preferences wrapper for testing code that interacts with CFPreferences.
+class POLICY_EXPORT MockPreferences : public MacPreferences {
+ public:
+ MockPreferences();
+ ~MockPreferences() override;
+
+ Boolean AppSynchronize(CFStringRef applicationID) override;
+
+ CFPropertyListRef CopyAppValue(CFStringRef key,
+ CFStringRef applicationID) override;
+
+ Boolean AppValueIsForced(CFStringRef key, CFStringRef applicationID) override;
+
+ // Adds a preference item with the given info to the test set.
+ void AddTestItem(CFStringRef key, CFPropertyListRef value, bool is_forced);
+
+ private:
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> values_;
+ base::ScopedCFTypeRef<CFMutableSetRef> forced_;
+};
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PREFERENCES_MOCK_MAC_H_
diff --git a/chromium/components/policy/core/common/preg_parser.cc b/chromium/components/policy/core/common/preg_parser.cc
new file mode 100644
index 00000000000..13bef21697b
--- /dev/null
+++ b/chromium/components/policy/core/common/preg_parser.cc
@@ -0,0 +1,408 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preg_parser.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/registry_dict.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+#else
+// Registry data type constants.
+#define REG_NONE 0
+#define REG_SZ 1
+#define REG_EXPAND_SZ 2
+#define REG_BINARY 3
+#define REG_DWORD_LITTLE_ENDIAN 4
+#define REG_DWORD_BIG_ENDIAN 5
+#define REG_LINK 6
+#define REG_MULTI_SZ 7
+#define REG_RESOURCE_LIST 8
+#define REG_FULL_RESOURCE_DESCRIPTOR 9
+#define REG_RESOURCE_REQUIREMENTS_LIST 10
+#define REG_QWORD_LITTLE_ENDIAN 11
+#endif
+
+using RegistryDict = policy::RegistryDict;
+
+namespace {
+
+// Maximum PReg file size we're willing to accept.
+const int64_t kMaxPRegFileSize = 1024 * 1024 * 16;
+static_assert(kMaxPRegFileSize <= std::numeric_limits<ptrdiff_t>::max(),
+ "Max PReg file size too large.");
+
+// Maximum number of components in registry key names. This corresponds to the
+// maximum nesting level of RegistryDict trees.
+const size_t kMaxKeyNameComponents = 1024;
+
+// Constants for PReg file delimiters.
+const char16_t kDelimBracketOpen = u'[';
+const char16_t kDelimBracketClose = u']';
+const char16_t kDelimSemicolon = u';';
+
+// Registry path separator.
+const char16_t kRegistryPathSeparator[] = u"\\";
+
+// Magic strings for the PReg value field to trigger special actions.
+const char kActionTriggerPrefix[] = "**";
+const char kActionTriggerDeleteValues[] = "deletevalues";
+const char kActionTriggerDel[] = "del.";
+const char kActionTriggerDelVals[] = "delvals";
+const char kActionTriggerDeleteKeys[] = "deletekeys";
+const char kActionTriggerSecureKey[] = "securekey";
+const char kActionTriggerSoft[] = "soft";
+
+// Returns the character at |cursor| and increments it, unless the end is here
+// in which case -1 is returned. The calling code must guarantee that
+// end - *cursor does not overflow ptrdiff_t.
+int NextChar(const uint8_t** cursor, const uint8_t* end) {
+ // Only read the character if a full char16_t is available.
+ // This comparison makes sure no overflow can happen.
+ if (*cursor >= end ||
+ end - *cursor < static_cast<ptrdiff_t>(sizeof(char16_t)))
+ return -1;
+
+ int result = **cursor | (*(*cursor + 1) << 8);
+ *cursor += sizeof(char16_t);
+ return result;
+}
+
+// Reads a fixed-size field from a PReg file. The calling code must guarantee
+// that both end - *cursor and size do not overflow ptrdiff_t.
+bool ReadFieldBinary(const uint8_t** cursor,
+ const uint8_t* end,
+ uint32_t size,
+ uint8_t* data) {
+ if (size == 0)
+ return true;
+
+ // Be careful to prevent possible overflows here (don't do *cursor + size).
+ if (*cursor >= end || end - *cursor < static_cast<ptrdiff_t>(size))
+ return false;
+ const uint8_t* field_end = *cursor + size;
+ std::copy(*cursor, field_end, data);
+ *cursor = field_end;
+ return true;
+}
+
+bool ReadField32(const uint8_t** cursor, const uint8_t* end, uint32_t* data) {
+ uint32_t value = 0;
+ if (!ReadFieldBinary(cursor, end, sizeof(uint32_t),
+ reinterpret_cast<uint8_t*>(&value))) {
+ return false;
+ }
+ *data = base::ByteSwapToLE32(value);
+ return true;
+}
+
+// Reads a string field from a file.
+bool ReadFieldString(const uint8_t** cursor,
+ const uint8_t* end,
+ std::u16string* str) {
+ int current = -1;
+ while ((current = NextChar(cursor, end)) > 0x0000)
+ *str += current;
+
+ return current == L'\0';
+}
+
+// Converts the UTF16 |data| to an UTF8 string |value|. Returns false if the
+// resulting UTF8 string contains invalid characters.
+bool DecodePRegStringValue(const std::vector<uint8_t>& data,
+ std::string* value) {
+ size_t len = data.size() / sizeof(char16_t);
+ if (len <= 0) {
+ value->clear();
+ return true;
+ }
+
+ const char16_t* chars = reinterpret_cast<const char16_t*>(data.data());
+ std::u16string utf16_str;
+ std::transform(chars, chars + len - 1, std::back_inserter(utf16_str),
+ base::ByteSwapToLE16);
+ // Note: UTF16ToUTF8() only checks whether all chars are valid code points,
+ // but not whether they're valid characters. IsStringUTF8(), however, does.
+ *value = base::UTF16ToUTF8(utf16_str);
+ if (!base::IsStringUTF8(*value)) {
+ LOG(ERROR) << "String '" << *value << "' is not a valid UTF8 string";
+ value->clear();
+ return false;
+ }
+ return true;
+}
+
+// Decodes a value from a PReg file given as a uint8_t vector.
+bool DecodePRegValue(uint32_t type,
+ const std::vector<uint8_t>& data,
+ base::Value& value) {
+ std::string data_utf8;
+ switch (type) {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ if (!DecodePRegStringValue(data, &data_utf8))
+ return false;
+ value = base::Value(data_utf8);
+ return true;
+ case REG_DWORD_LITTLE_ENDIAN:
+ case REG_DWORD_BIG_ENDIAN:
+ if (data.size() == sizeof(uint32_t)) {
+ uint32_t val = *reinterpret_cast<const uint32_t*>(data.data());
+ if (type == REG_DWORD_BIG_ENDIAN)
+ val = base::NetToHost32(val);
+ else
+ val = base::ByteSwapToLE32(val);
+ value = base::Value(static_cast<int>(val));
+ return true;
+ } else {
+ LOG(ERROR) << "Bad data size " << data.size();
+ }
+ break;
+ case REG_NONE:
+ case REG_LINK:
+ case REG_MULTI_SZ:
+ case REG_RESOURCE_LIST:
+ case REG_FULL_RESOURCE_DESCRIPTOR:
+ case REG_RESOURCE_REQUIREMENTS_LIST:
+ case REG_QWORD_LITTLE_ENDIAN:
+ default:
+ LOG(ERROR) << "Unsupported registry data type " << type;
+ }
+
+ return false;
+}
+
+// Returns true if the registry key |key_name| belongs to the sub-tree specified
+// by the key |root|.
+bool KeyRootEquals(const std::u16string& key_name, const std::u16string& root) {
+ if (root.empty())
+ return true;
+
+ if (!base::StartsWith(key_name, root, base::CompareCase::INSENSITIVE_ASCII))
+ return false;
+
+ // Handle the case where |root| == "ABC" and |key_name| == "ABCDE\FG". This
+ // should not be interpreted as a match.
+ return key_name.length() == root.length() ||
+ key_name.at(root.length()) == kRegistryPathSeparator[0];
+}
+
+// Adds |value| and |data| to |dict| or an appropriate sub-dictionary indicated
+// by |key_name|. Creates sub-dictionaries if necessary. Also handles special
+// action triggers, see |kActionTrigger*|, that can, for instance, remove an
+// existing value.
+void HandleRecord(const std::u16string& key_name,
+ const std::u16string& value,
+ uint32_t type,
+ const std::vector<uint8_t>& data,
+ RegistryDict* dict) {
+ // Locate/create the dictionary to place the value in.
+ std::vector<std::u16string> path;
+
+ std::vector<base::StringPiece16> key_name_components =
+ base::SplitStringPiece(key_name, kRegistryPathSeparator,
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (key_name_components.size() > kMaxKeyNameComponents) {
+ LOG(ERROR) << "Encountered a key which has more than "
+ << kMaxKeyNameComponents << " components.";
+ return;
+ }
+ for (const base::StringPiece16& key_name_component : key_name_components) {
+ if (key_name_component.empty())
+ continue;
+
+ const std::string name = base::UTF16ToUTF8(key_name_component);
+ RegistryDict* subdict = dict->GetKey(name);
+ if (!subdict) {
+ subdict = new RegistryDict();
+ dict->SetKey(name, base::WrapUnique(subdict));
+ }
+ dict = subdict;
+ }
+
+ if (value.empty())
+ return;
+
+ std::string value_name(base::UTF16ToUTF8(value));
+ if (!base::StartsWith(value_name, kActionTriggerPrefix,
+ base::CompareCase::SENSITIVE)) {
+ base::Value value;
+ if (DecodePRegValue(type, data, value))
+ dict->SetValue(value_name, std::move(value));
+ return;
+ }
+
+ std::string data_utf8;
+ std::string action_trigger(base::ToLowerASCII(
+ value_name.substr(std::size(kActionTriggerPrefix) - 1)));
+ if (action_trigger == kActionTriggerDeleteValues) {
+ if (DecodePRegStringValue(data, &data_utf8)) {
+ for (const std::string& value_str :
+ base::SplitString(data_utf8, ";", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY))
+ dict->RemoveValue(value_str);
+ }
+ } else if (base::StartsWith(action_trigger, kActionTriggerDeleteKeys,
+ base::CompareCase::SENSITIVE)) {
+ if (DecodePRegStringValue(data, &data_utf8)) {
+ for (const std::string& key :
+ base::SplitString(data_utf8, ";", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY))
+ dict->RemoveKey(key);
+ }
+ } else if (base::StartsWith(action_trigger, kActionTriggerDel,
+ base::CompareCase::SENSITIVE)) {
+ dict->RemoveValue(value_name.substr(std::size(kActionTriggerPrefix) - 1 +
+ std::size(kActionTriggerDel) - 1));
+ } else if (base::StartsWith(action_trigger, kActionTriggerDelVals,
+ base::CompareCase::SENSITIVE)) {
+ // Delete all values.
+ dict->ClearValues();
+ } else if (base::StartsWith(action_trigger, kActionTriggerSecureKey,
+ base::CompareCase::SENSITIVE) ||
+ base::StartsWith(action_trigger, kActionTriggerSoft,
+ base::CompareCase::SENSITIVE)) {
+ // Doesn't affect values.
+ } else {
+ LOG(ERROR) << "Bad action trigger " << value_name;
+ }
+}
+
+} // namespace
+
+namespace policy {
+namespace preg_parser {
+
+const char kPRegFileHeader[8] = {'P', 'R', 'e', 'g',
+ '\x01', '\x00', '\x00', '\x00'};
+
+bool ReadFile(const base::FilePath& file_path,
+ const std::u16string& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status) {
+ base::MemoryMappedFile mapped_file;
+ if (!mapped_file.Initialize(file_path) || !mapped_file.IsValid()) {
+ PLOG(ERROR) << "Failed to map " << file_path.value();
+ status->Add(POLICY_LOAD_STATUS_READ_ERROR);
+ return false;
+ }
+
+ return ReadDataInternal(
+ mapped_file.data(), mapped_file.length(), root, dict, status,
+ base::StringPrintf("file '%" PRFilePath "'", file_path.value().c_str()));
+}
+
+POLICY_EXPORT bool ReadDataInternal(const uint8_t* preg_data,
+ size_t preg_data_size,
+ const std::u16string& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status,
+ const std::string& debug_name) {
+ DCHECK(status);
+ DCHECK(root.empty() || root.back() != kRegistryPathSeparator[0]);
+
+ // Check data size.
+ if (preg_data_size > kMaxPRegFileSize) {
+ LOG(ERROR) << "PReg " << debug_name << " too large: " << preg_data_size;
+ status->Add(POLICY_LOAD_STATUS_TOO_BIG);
+ return false;
+ }
+
+ // Check the header.
+ const int kHeaderSize = std::size(kPRegFileHeader);
+ if (!preg_data || preg_data_size < kHeaderSize ||
+ memcmp(kPRegFileHeader, preg_data, kHeaderSize) != 0) {
+ LOG(ERROR) << "Bad PReg " << debug_name;
+ status->Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ return false;
+ }
+
+ // Parse data, which is expected to be UCS-2 and little-endian. The latter I
+ // couldn't find documentation on, but the example I saw were all
+ // little-endian. It'd be interesting to check on big-endian hardware.
+ const uint8_t* cursor = preg_data + kHeaderSize;
+ const uint8_t* end = preg_data + preg_data_size;
+ while (true) {
+ if (cursor == end)
+ return true;
+
+ if (NextChar(&cursor, end) != kDelimBracketOpen)
+ break;
+
+ // Read the record fields.
+ std::u16string key_name;
+ std::u16string value;
+ uint32_t type = 0;
+ uint32_t size = 0;
+ std::vector<uint8_t> data;
+
+ if (!ReadFieldString(&cursor, end, &key_name))
+ break;
+
+ int current = NextChar(&cursor, end);
+ if (current == kDelimSemicolon) {
+ if (!ReadFieldString(&cursor, end, &value))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (!ReadField32(&cursor, end, &type))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (!ReadField32(&cursor, end, &size))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current == kDelimSemicolon) {
+ if (size > kMaxPRegFileSize)
+ break;
+ data.resize(size);
+ if (!ReadFieldBinary(&cursor, end, size, data.data()))
+ break;
+ current = NextChar(&cursor, end);
+ }
+
+ if (current != kDelimBracketClose)
+ break;
+
+ // Process the record if it is within the |root| subtree.
+ if (KeyRootEquals(key_name, root))
+ HandleRecord(key_name.substr(root.size()), value, type, data, dict);
+ }
+
+ LOG(ERROR) << "Error parsing PReg " << debug_name << " at offset "
+ << (reinterpret_cast<const uint8_t*>(cursor - 1) - preg_data);
+ status->Add(POLICY_LOAD_STATUS_PARSE_ERROR);
+ return false;
+}
+
+} // namespace preg_parser
+} // namespace policy
diff --git a/chromium/components/policy/core/common/preg_parser.h b/chromium/components/policy/core/common/preg_parser.h
new file mode 100644
index 00000000000..7a39284ba7e
--- /dev/null
+++ b/chromium/components/policy/core/common/preg_parser.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file provides a parser for PReg files which are used for storing group
+// policy settings in the file system. The file format is documented here:
+//
+// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374407(v=vs.85).aspx
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
+
+#include <memory>
+#include <string>
+
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace policy {
+
+class RegistryDict;
+
+namespace preg_parser {
+
+// The magic header in PReg files: ASCII "PReg" + version (0x0001).
+POLICY_EXPORT extern const char kPRegFileHeader[8];
+
+// Reads the PReg file at |file_path| and writes the registry data to |dict|.
+// |root| specifies the registry subtree the caller is interested in, everything
+// else gets ignored. It may be empty if all keys should be returned, but it
+// must NOT end with a backslash.
+POLICY_EXPORT bool ReadFile(const base::FilePath& file_path,
+ const std::u16string& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status);
+
+// Similar to ReadFile, but reads from |preg_data| of length |preg_data_size|
+// instead of a file. |debug_name| is printed out along with error messages.
+// Used internally and for testing only. All other callers should use ReadFile
+// instead.
+POLICY_EXPORT bool ReadDataInternal(const uint8_t* preg_data,
+ size_t preg_data_size,
+ const std::u16string& root,
+ RegistryDict* dict,
+ PolicyLoadStatusSampler* status,
+ const std::string& debug_name);
+
+} // namespace preg_parser
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PREG_PARSER_H_
diff --git a/chromium/components/policy/core/common/preg_parser_fuzzer.cc b/chromium/components/policy/core/common/preg_parser_fuzzer.cc
new file mode 100644
index 00000000000..bcfbc5fd2ba
--- /dev/null
+++ b/chromium/components/policy/core/common/preg_parser_fuzzer.cc
@@ -0,0 +1,46 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/preg_parser.h"
+#include "components/policy/core/common/registry_dict.h"
+
+namespace {
+
+const char16_t kRegistryChromePolicyKey[] = u"SOFTWARE\\Policies\\Chromium";
+
+} // namespace
+
+namespace policy {
+namespace preg_parser {
+
+// Disable logging.
+struct Environment {
+ Environment() : root(kRegistryChromePolicyKey) {
+ logging::SetMinLogLevel(logging::LOG_FATAL);
+ }
+
+ const std::u16string root;
+};
+
+Environment* env = new Environment();
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ // Note: Don't use PolicyLoadStatusUmaReporter here, it leaks!
+ PolicyLoadStatusSampler status;
+ RegistryDict dict;
+ ReadDataInternal(data, size, env->root, &dict, &status, "data");
+ return 0;
+}
+
+} // namespace preg_parser
+} // namespace policy
diff --git a/chromium/components/policy/core/common/preg_parser_unittest.cc b/chromium/components/policy/core/common/preg_parser_unittest.cc
new file mode 100644
index 00000000000..3e752652d3c
--- /dev/null
+++ b/chromium/components/policy/core/common/preg_parser_unittest.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/preg_parser.h"
+
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/policy/core/common/policy_load_status.h"
+#include "components/policy/core/common/registry_dict.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace preg_parser {
+namespace {
+
+// Preg files are relative to |kRegistryPolBaseDir|.
+const char kRegistryPolBaseDir[] = "chrome/test/data/policy/gpo";
+const char kRegistryPolFile[] = "parser_test/registry.pol";
+const char kInvalidEncodingRegistryPolFile[] = "invalid_encoding/registry.pol";
+const char kNonExistingRegistryPolFile[] = "does_not_exist.pol";
+
+const char16_t kRegistryKey[] = u"SOFTWARE\\Policies\\Chromium";
+
+// Check whether two RegistryDicts equal each other.
+testing::AssertionResult RegistryDictEquals(const RegistryDict& a,
+ const RegistryDict& b) {
+ auto iter_key_a = a.keys().begin();
+ auto iter_key_b = b.keys().begin();
+ for (; iter_key_a != a.keys().end() && iter_key_b != b.keys().end();
+ ++iter_key_a, ++iter_key_b) {
+ if (iter_key_a->first != iter_key_b->first) {
+ return testing::AssertionFailure() << "Key mismatch " << iter_key_a->first
+ << " vs. " << iter_key_b->first;
+ }
+ testing::AssertionResult result =
+ RegistryDictEquals(*iter_key_a->second, *iter_key_b->second);
+ if (!result)
+ return result;
+ }
+ if (iter_key_a != a.keys().end())
+ return testing::AssertionFailure()
+ << "key mismatch, a has extra key " << iter_key_a->first;
+ if (iter_key_b != b.keys().end())
+ return testing::AssertionFailure()
+ << "key mismatch, b has extra key " << iter_key_b->first;
+
+ auto iter_value_a = a.values().begin();
+ auto iter_value_b = b.values().begin();
+ for (; iter_value_a != a.values().end() && iter_value_b != b.values().end();
+ ++iter_value_a, ++iter_value_b) {
+ if (iter_value_a->first != iter_value_b->first ||
+ iter_value_a->second != iter_value_b->second) {
+ return testing::AssertionFailure()
+ << "Value mismatch " << iter_value_a->first << "="
+ << iter_value_a->second << " vs. " << iter_value_b->first << "="
+ << iter_value_b->second;
+ }
+ }
+ if (iter_value_a != a.values().end())
+ return testing::AssertionFailure()
+ << "Value mismatch, a has extra value " << iter_value_a->first << "="
+ << iter_value_a->second;
+ if (iter_value_b != b.values().end())
+ return testing::AssertionFailure()
+ << "Value mismatch, b has extra value " << iter_value_b->first << "="
+ << iter_value_b->second;
+
+ return testing::AssertionSuccess();
+}
+
+void SetInteger(RegistryDict* dict, const std::string& name, int value) {
+ dict->SetValue(name, base::Value(value));
+}
+
+void SetString(RegistryDict* dict,
+ const std::string& name,
+ const std::string& value) {
+ dict->SetValue(name, base::Value(value));
+}
+
+class PRegParserTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir_));
+ test_data_dir_ = test_data_dir_.AppendASCII(kRegistryPolBaseDir);
+ }
+
+ base::FilePath test_data_dir_;
+};
+
+TEST_F(PRegParserTest, TestParseFile) {
+ // Prepare the test dictionary with some data so the test can check that the
+ // PReg action triggers work, i.e. remove these items.
+ RegistryDict dict;
+ SetInteger(&dict, "DeleteValuesTest1", 1);
+ SetString(&dict, "DeleteValuesTest2", "2");
+ dict.SetKey("DeleteKeysTest1", std::make_unique<RegistryDict>());
+ std::unique_ptr<RegistryDict> delete_keys_test(new RegistryDict());
+ SetInteger(delete_keys_test.get(), "DeleteKeysTest2Entry", 1);
+ dict.SetKey("DeleteKeysTest2", std::move(delete_keys_test));
+ SetInteger(&dict, "DelTest", 1);
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ SetInteger(subdict.get(), "DelValsTest1", 1);
+ SetString(subdict.get(), "DelValsTest2", "2");
+ subdict->SetKey("DelValsTest3", std::make_unique<RegistryDict>());
+ dict.SetKey("DelValsTest", std::move(subdict));
+
+ // Run the parser.
+ base::FilePath test_file(test_data_dir_.AppendASCII(kRegistryPolFile));
+ PolicyLoadStatusUmaReporter status;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, kRegistryKey, &dict, &status));
+
+ // Build the expected output dictionary.
+ RegistryDict expected;
+ std::unique_ptr<RegistryDict> del_vals_dict(new RegistryDict());
+ del_vals_dict->SetKey("DelValsTest3", std::make_unique<RegistryDict>());
+ expected.SetKey("DelValsTest", std::move(del_vals_dict));
+ SetInteger(&expected, "HomepageIsNewTabPage", 1);
+ SetString(&expected, "HomepageLocation", "http://www.example.com");
+ SetInteger(&expected, "RestoreOnStartup", 4);
+ std::unique_ptr<RegistryDict> startup_urls(new RegistryDict());
+ SetString(startup_urls.get(), "1", "http://www.chromium.org");
+ SetString(startup_urls.get(), "2", "http://www.example.com");
+ expected.SetKey("RestoreOnStartupURLs", std::move(startup_urls));
+ SetInteger(&expected, "ShowHomeButton", 1);
+ SetString(&expected, "Snowman", "\xE2\x98\x83");
+ SetString(&expected, "Empty", "");
+
+ EXPECT_TRUE(RegistryDictEquals(dict, expected));
+}
+
+TEST_F(PRegParserTest, SubstringRootInvalid) {
+ // A root of "Aa/Bb/Cc" should not be considered a valid root for a
+ // key like "Aa/Bb/C".
+ base::FilePath test_file(test_data_dir_.AppendASCII(kRegistryPolFile));
+ RegistryDict empty;
+ PolicyLoadStatusUmaReporter status;
+
+ // No data should be loaded for partial roots ("Aa/Bb/C").
+ RegistryDict dict1;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, u"SOFTWARE\\Policies\\Chro",
+ &dict1, &status));
+ EXPECT_TRUE(RegistryDictEquals(dict1, empty));
+
+ // Safety check with kRegistryKey (dict should not be empty).
+ RegistryDict dict2;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, kRegistryKey, &dict2, &status));
+ EXPECT_FALSE(RegistryDictEquals(dict2, empty));
+}
+
+TEST_F(PRegParserTest, RejectInvalidStrings) {
+ // Tests whether strings with invalid characters are rejected.
+ base::FilePath test_file(
+ test_data_dir_.AppendASCII(kInvalidEncodingRegistryPolFile));
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict dict;
+ ASSERT_TRUE(preg_parser::ReadFile(test_file, kRegistryKey, &dict, &status));
+
+ RegistryDict empty;
+ EXPECT_TRUE(RegistryDictEquals(dict, empty));
+}
+
+TEST_F(PRegParserTest, LoadStatusSampling) {
+ // Tests load status sampling.
+ PolicyLoadStatusUmaReporter status;
+ RegistryDict dict;
+ base::FilePath test_file(
+ test_data_dir_.AppendASCII(kNonExistingRegistryPolFile));
+ ASSERT_FALSE(preg_parser::ReadFile(test_file, kRegistryKey, &dict, &status));
+
+ PolicyLoadStatusSampler::StatusSet expected_status_set;
+ expected_status_set[POLICY_LOAD_STATUS_STARTED] = true;
+ expected_status_set[POLICY_LOAD_STATUS_READ_ERROR] = true;
+ EXPECT_EQ(expected_status_set, status.GetStatusSet());
+}
+
+} // namespace
+} // namespace preg_parser
+} // namespace policy
diff --git a/chromium/components/policy/core/common/proxy_policy_provider.cc b/chromium/components/policy/core/common/proxy_policy_provider.cc
new file mode 100644
index 00000000000..4e0ccf47b05
--- /dev/null
+++ b/chromium/components/policy/core/common/proxy_policy_provider.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/proxy_policy_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/check_op.h"
+#include "components/policy/core/common/policy_bundle.h"
+
+namespace policy {
+
+ProxyPolicyProvider::ProxyPolicyProvider() : delegate_(nullptr) {}
+
+ProxyPolicyProvider::~ProxyPolicyProvider() {
+ DCHECK(!delegate_);
+}
+
+void ProxyPolicyProvider::SetDelegate(ConfigurationPolicyProvider* delegate) {
+ if (delegate_)
+ delegate_->RemoveObserver(this);
+ delegate_ = delegate;
+ if (delegate_) {
+ delegate_->AddObserver(this);
+ OnUpdatePolicy(delegate_);
+ } else {
+ UpdatePolicy(std::make_unique<PolicyBundle>());
+ }
+}
+
+void ProxyPolicyProvider::Shutdown() {
+ // Note: the delegate is not owned by the proxy provider, so this call is not
+ // forwarded. The same applies for the Init() call.
+ // Just drop the delegate without propagating updates here.
+ if (delegate_) {
+ delegate_->RemoveObserver(this);
+ delegate_ = nullptr;
+ }
+ ConfigurationPolicyProvider::Shutdown();
+}
+
+void ProxyPolicyProvider::RefreshPolicies() {
+ if (delegate_) {
+ delegate_->RefreshPolicies();
+ } else {
+ // Subtle: if a RefreshPolicies() call comes after Shutdown() then the
+ // current bundle should be served instead. This also does the right thing
+ // if SetDelegate() was never called before.
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ bundle->CopyFrom(policies());
+ UpdatePolicy(std::move(bundle));
+ }
+}
+
+bool ProxyPolicyProvider::IsFirstPolicyLoadComplete(PolicyDomain domain) const {
+ return delegate_ && delegate_->IsInitializationComplete(domain);
+}
+
+void ProxyPolicyProvider::OnUpdatePolicy(
+ ConfigurationPolicyProvider* provider) {
+ if (block_policy_updates_for_testing_)
+ return;
+
+ DCHECK_EQ(delegate_, provider);
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ bundle->CopyFrom(delegate_->policies());
+ UpdatePolicy(std::move(bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/proxy_policy_provider.h b/chromium/components/policy/core/common/proxy_policy_provider.h
new file mode 100644
index 00000000000..f59dc0df163
--- /dev/null
+++ b/chromium/components/policy/core/common/proxy_policy_provider.h
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A policy provider implementation that acts as a proxy for another policy
+// provider, swappable at any point.
+//
+// Note that ProxyPolicyProvider correctly forwards RefreshPolicies() calls to
+// the delegate if present. If there is no delegate, the refresh results in an
+// immediate (empty) policy update.
+//
+// Furthermore, IsInitializationComplete() is implemented trivially - it always
+// returns true. Given that the delegate may be swapped at any point, there's no
+// point in trying to carry over initialization status from the delegate.
+//
+// This policy provider implementation is used to inject browser-global policy
+// originating from the user policy configured on the primary Chrome OS user
+// (i.e. the user logging in from the login screen). This way, policy settings
+// on the primary user propagate into g_browser_process->local_state_().
+//
+// The bizarre situation of user-scoped policy settings which are implemented
+// browser-global wouldn't exist in an ideal world. However, for historic
+// and technical reasons there are policy settings that are scoped to the user
+// but are implemented to take effect for the entire browser instance. A good
+// example for this are policies that affect the Chrome network stack in areas
+// where there's no profile-specific context. The meta data in
+// policy_templates.json allows to identify the policies in this bucket; they'll
+// have per_profile set to False, supported_on including chrome_os, and
+// dynamic_refresh set to True.
+class POLICY_EXPORT ProxyPolicyProvider
+ : public ConfigurationPolicyProvider,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ ProxyPolicyProvider();
+ ProxyPolicyProvider(const ProxyPolicyProvider&) = delete;
+ ProxyPolicyProvider& operator=(const ProxyPolicyProvider&) = delete;
+ ~ProxyPolicyProvider() override;
+
+ // Updates the provider this proxy delegates to.
+ void SetDelegate(ConfigurationPolicyProvider* delegate);
+
+ // ConfigurationPolicyProvider:
+ void Shutdown() override;
+ void RefreshPolicies() override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+
+ // ConfigurationPolicyProvider::Observer:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ // When set to true, this ProxyPolicyProvider will ignore subsequent policy
+ // updates.
+ void SetBlockPolicyUpdatesForTesting(bool block_policy_updates_for_testing) {
+ block_policy_updates_for_testing_ = block_policy_updates_for_testing;
+ }
+
+ private:
+ raw_ptr<ConfigurationPolicyProvider> delegate_;
+ bool block_policy_updates_for_testing_ = false;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PROXY_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/proxy_policy_provider_unittest.cc b/chromium/components/policy/core/common/proxy_policy_provider_unittest.cc
new file mode 100644
index 00000000000..7c42db871af
--- /dev/null
+++ b/chromium/components/policy/core/common/proxy_policy_provider_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/proxy_policy_provider.h"
+#include <memory>
+#include "base/callback.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+
+namespace policy {
+
+class ProxyPolicyProviderTest : public testing::Test {
+ protected:
+ ProxyPolicyProviderTest() {
+ mock_provider_.Init();
+ proxy_provider_.Init(&schema_registry_);
+ proxy_provider_.AddObserver(&observer_);
+ }
+ ProxyPolicyProviderTest(const ProxyPolicyProviderTest&) = delete;
+ ProxyPolicyProviderTest& operator=(const ProxyPolicyProviderTest&) = delete;
+
+ ~ProxyPolicyProviderTest() override {
+ proxy_provider_.RemoveObserver(&observer_);
+ proxy_provider_.Shutdown();
+ mock_provider_.Shutdown();
+ }
+
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ MockConfigurationPolicyProvider mock_provider_;
+ ProxyPolicyProvider proxy_provider_;
+
+ static std::unique_ptr<PolicyBundle> CopyBundle(const PolicyBundle& bundle) {
+ std::unique_ptr<PolicyBundle> copy(new PolicyBundle());
+ copy->CopyFrom(bundle);
+ return copy;
+ }
+};
+
+TEST_F(ProxyPolicyProviderTest, Init) {
+ EXPECT_TRUE(proxy_provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME));
+ EXPECT_TRUE(PolicyBundle().Equals(proxy_provider_.policies()));
+}
+
+TEST_F(ProxyPolicyProviderTest, Delegate) {
+ PolicyBundle bundle;
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+ mock_provider_.UpdatePolicy(CopyBundle(bundle));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(bundle.Equals(proxy_provider_.policies()));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+ .Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("new value"), nullptr);
+ mock_provider_.UpdatePolicy(CopyBundle(bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(bundle.Equals(proxy_provider_.policies()));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(NULL);
+ EXPECT_TRUE(PolicyBundle().Equals(proxy_provider_.policies()));
+}
+
+TEST_F(ProxyPolicyProviderTest, RefreshPolicies) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ proxy_provider_.SetDelegate(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_)).Times(0);
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ proxy_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&observer_);
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&proxy_provider_));
+ mock_provider_.UpdatePolicy(std::make_unique<PolicyBundle>());
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/proxy_settings_constants.cc b/chromium/components/policy/core/common/proxy_settings_constants.cc
new file mode 100644
index 00000000000..8578c714101
--- /dev/null
+++ b/chromium/components/policy/core/common/proxy_settings_constants.cc
@@ -0,0 +1,11 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/proxy_settings_constants.h"
+
+namespace policy {
+
+const char kProxyPacMandatory[] = "ProxyPacMandatory";
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/proxy_settings_constants.h b/chromium/components/policy/core/common/proxy_settings_constants.h
new file mode 100644
index 00000000000..c9835d477ae
--- /dev/null
+++ b/chromium/components/policy/core/common/proxy_settings_constants.h
@@ -0,0 +1,15 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_PROXY_SETTINGS_CONSTANTS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_PROXY_SETTINGS_CONSTANTS_H_
+
+namespace policy {
+
+// A key used in ProxySettings dictionary.
+extern const char kProxyPacMandatory[];
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_PROXY_SETTINGS_CONSTANTS_H_
diff --git a/chromium/components/policy/core/common/registry_dict.cc b/chromium/components/policy/core/common/registry_dict.cc
new file mode 100644
index 00000000000..ff487c9a2f3
--- /dev/null
+++ b/chromium/components/policy/core/common/registry_dict.cc
@@ -0,0 +1,361 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/registry_dict.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/schema.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/win/registry.h"
+
+using base::win::RegistryKeyIterator;
+using base::win::RegistryValueIterator;
+#endif // BUILDFLAG(IS_WIN)
+
+namespace policy {
+
+namespace {
+
+// Validates that a key is numerical. Used for lists below.
+bool IsKeyNumerical(const std::string& key) {
+ int temp = 0;
+ return base::StringToInt(key, &temp);
+}
+
+} // namespace
+
+absl::optional<base::Value> ConvertRegistryValue(const base::Value& value,
+ const Schema& schema) {
+ if (!schema.valid())
+ return value.Clone();
+
+ // If the type is good already, go with it.
+ if (value.type() == schema.type()) {
+ // Recurse for complex types.
+ if (value.is_dict()) {
+ base::Value result(base::Value::Type::DICTIONARY);
+ for (auto entry : value.DictItems()) {
+ absl::optional<base::Value> converted =
+ ConvertRegistryValue(entry.second, schema.GetProperty(entry.first));
+ if (converted.has_value())
+ result.SetKey(entry.first, std::move(converted.value()));
+ }
+ return result;
+ } else if (value.is_list()) {
+ base::Value result(base::Value::Type::LIST);
+ for (const auto& entry : value.GetListDeprecated()) {
+ absl::optional<base::Value> converted =
+ ConvertRegistryValue(entry, schema.GetItems());
+ if (converted.has_value())
+ result.Append(std::move(converted.value()));
+ }
+ return result;
+ }
+ return value.Clone();
+ }
+
+ // Else, do some conversions to map windows registry data types to JSON types.
+ int int_value = 0;
+ switch (schema.type()) {
+ case base::Value::Type::NONE: {
+ return base::Value();
+ }
+ case base::Value::Type::BOOLEAN: {
+ // Accept booleans encoded as either string or integer.
+ if (value.is_int())
+ return base::Value(value.GetInt() != 0);
+ if (value.is_string() &&
+ base::StringToInt(value.GetString(), &int_value)) {
+ return base::Value(int_value != 0);
+ }
+ break;
+ }
+ case base::Value::Type::INTEGER: {
+ // Integers may be string-encoded.
+ if (value.is_string() &&
+ base::StringToInt(value.GetString(), &int_value)) {
+ return base::Value(int_value);
+ }
+ break;
+ }
+ case base::Value::Type::DOUBLE: {
+ // Doubles may be string-encoded or integer-encoded.
+ if (value.is_double() || value.is_int())
+ return base::Value(value.GetDouble());
+ double double_value = 0;
+ if (value.is_string() &&
+ base::StringToDouble(value.GetString(), &double_value)) {
+ return base::Value(double_value);
+ }
+ break;
+ }
+ case base::Value::Type::LIST: {
+ // Lists are encoded as subkeys with numbered value in the registry
+ // (non-numerical keys are ignored).
+ if (value.is_dict()) {
+ base::Value result(base::Value::Type::LIST);
+ for (auto it : value.DictItems()) {
+ if (!IsKeyNumerical(it.first))
+ continue;
+ absl::optional<base::Value> converted =
+ ConvertRegistryValue(it.second, schema.GetItems());
+ if (converted.has_value())
+ result.Append(std::move(converted.value()));
+ }
+ return result;
+ }
+ // Fall through in order to accept lists encoded as JSON strings.
+ [[fallthrough]];
+ }
+ case base::Value::Type::DICTIONARY: {
+ // Dictionaries may be encoded as JSON strings.
+ if (value.is_string()) {
+ absl::optional<base::Value> result = base::JSONReader::Read(
+ value.GetString(),
+ base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ if (result.has_value() && result.value().type() == schema.type())
+ return std::move(result.value());
+ }
+ break;
+ }
+ case base::Value::Type::STRING:
+ case base::Value::Type::BINARY:
+ // No conversion possible.
+ break;
+ }
+
+ LOG(WARNING) << "Failed to convert " << value.type() << " to "
+ << schema.type();
+ return absl::nullopt;
+}
+
+bool CaseInsensitiveStringCompare::operator()(const std::string& a,
+ const std::string& b) const {
+ return base::CompareCaseInsensitiveASCII(a, b) < 0;
+}
+
+RegistryDict::RegistryDict() {}
+
+RegistryDict::~RegistryDict() {
+ ClearKeys();
+ ClearValues();
+}
+
+RegistryDict* RegistryDict::GetKey(const std::string& name) {
+ auto entry = keys_.find(name);
+ return entry != keys_.end() ? entry->second.get() : nullptr;
+}
+
+const RegistryDict* RegistryDict::GetKey(const std::string& name) const {
+ auto entry = keys_.find(name);
+ return entry != keys_.end() ? entry->second.get() : nullptr;
+}
+
+void RegistryDict::SetKey(const std::string& name,
+ std::unique_ptr<RegistryDict> dict) {
+ if (!dict) {
+ RemoveKey(name);
+ return;
+ }
+
+ keys_[name] = std::move(dict);
+}
+
+std::unique_ptr<RegistryDict> RegistryDict::RemoveKey(const std::string& name) {
+ std::unique_ptr<RegistryDict> result;
+ auto entry = keys_.find(name);
+ if (entry != keys_.end()) {
+ result = std::move(entry->second);
+ keys_.erase(entry);
+ }
+ return result;
+}
+
+void RegistryDict::ClearKeys() {
+ keys_.clear();
+}
+
+base::Value* RegistryDict::GetValue(const std::string& name) {
+ auto entry = values_.find(name);
+ return entry != values_.end() ? &entry->second : nullptr;
+}
+
+const base::Value* RegistryDict::GetValue(const std::string& name) const {
+ auto entry = values_.find(name);
+ return entry != values_.end() ? &entry->second : nullptr;
+}
+
+void RegistryDict::SetValue(const std::string& name, base::Value&& dict) {
+ values_[name] = std::move(dict);
+}
+
+absl::optional<base::Value> RegistryDict::RemoveValue(const std::string& name) {
+ absl::optional<base::Value> result;
+ auto entry = values_.find(name);
+ if (entry != values_.end()) {
+ result = std::move(entry->second);
+ values_.erase(entry);
+ }
+ return result;
+}
+
+void RegistryDict::ClearValues() {
+ values_.clear();
+}
+
+void RegistryDict::Merge(const RegistryDict& other) {
+ for (auto entry(other.keys_.begin()); entry != other.keys_.end(); ++entry) {
+ std::unique_ptr<RegistryDict>& subdict = keys_[entry->first];
+ if (!subdict)
+ subdict = std::make_unique<RegistryDict>();
+ subdict->Merge(*entry->second);
+ }
+
+ for (auto entry(other.values_.begin()); entry != other.values_.end();
+ ++entry) {
+ SetValue(entry->first, entry->second.Clone());
+ }
+}
+
+void RegistryDict::Swap(RegistryDict* other) {
+ keys_.swap(other->keys_);
+ values_.swap(other->values_);
+}
+
+#if BUILDFLAG(IS_WIN)
+void RegistryDict::ReadRegistry(HKEY hive, const std::wstring& root) {
+ ClearKeys();
+ ClearValues();
+
+ // First, read all the values of the key.
+ for (RegistryValueIterator it(hive, root.c_str()); it.Valid(); ++it) {
+ const std::string name = base::WideToUTF8(it.Name());
+ switch (it.Type()) {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ SetValue(name, base::Value(base::WideToUTF8(it.Value())));
+ continue;
+ case REG_DWORD_LITTLE_ENDIAN:
+ case REG_DWORD_BIG_ENDIAN:
+ if (it.ValueSize() == sizeof(DWORD)) {
+ DWORD dword_value = *(reinterpret_cast<const DWORD*>(it.Value()));
+ if (it.Type() == REG_DWORD_BIG_ENDIAN)
+ dword_value = base::NetToHost32(dword_value);
+ else
+ dword_value = base::ByteSwapToLE32(dword_value);
+ SetValue(name, base::Value(static_cast<int>(dword_value)));
+ continue;
+ }
+ [[fallthrough]];
+ case REG_NONE:
+ case REG_LINK:
+ case REG_MULTI_SZ:
+ case REG_RESOURCE_LIST:
+ case REG_FULL_RESOURCE_DESCRIPTOR:
+ case REG_RESOURCE_REQUIREMENTS_LIST:
+ case REG_QWORD_LITTLE_ENDIAN:
+ // Unsupported type, message gets logged below.
+ break;
+ }
+
+ LOG(WARNING) << "Failed to read hive " << hive << " at " << root << "\\"
+ << name << " type " << it.Type();
+ }
+
+ // Recurse for all subkeys.
+ for (RegistryKeyIterator it(hive, root.c_str()); it.Valid(); ++it) {
+ std::string name(base::WideToUTF8(it.Name()));
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->ReadRegistry(hive, root + L"\\" + it.Name());
+ SetKey(name, std::move(subdict));
+ }
+}
+
+std::unique_ptr<base::Value> RegistryDict::ConvertToJSON(
+ const Schema& schema) const {
+ base::Value::Type type =
+ schema.valid() ? schema.type() : base::Value::Type::DICTIONARY;
+ switch (type) {
+ case base::Value::Type::DICTIONARY: {
+ std::unique_ptr<base::DictionaryValue> result(
+ new base::DictionaryValue());
+ for (RegistryDict::ValueMap::const_iterator entry(values_.begin());
+ entry != values_.end(); ++entry) {
+ SchemaList matching_schemas =
+ schema.valid() ? schema.GetMatchingProperties(entry->first)
+ : SchemaList();
+ // Always try the empty schema if no other schemas exist.
+ if (matching_schemas.empty())
+ matching_schemas.push_back(Schema());
+ for (const Schema& subschema : matching_schemas) {
+ absl::optional<base::Value> converted =
+ ConvertRegistryValue(entry->second, subschema);
+ if (converted.has_value()) {
+ result->SetKey(entry->first, std::move(converted.value()));
+ break;
+ }
+ }
+ }
+ for (RegistryDict::KeyMap::const_iterator entry(keys_.begin());
+ entry != keys_.end(); ++entry) {
+ SchemaList matching_schemas =
+ schema.valid() ? schema.GetMatchingProperties(entry->first)
+ : SchemaList();
+ // Always try the empty schema if no other schemas exist.
+ if (matching_schemas.empty())
+ matching_schemas.push_back(Schema());
+ for (const Schema& subschema : matching_schemas) {
+ std::unique_ptr<base::Value> converted =
+ entry->second->ConvertToJSON(subschema);
+ if (converted) {
+ result->SetWithoutPathExpansion(entry->first, std::move(converted));
+ break;
+ }
+ }
+ }
+ return std::move(result);
+ }
+ case base::Value::Type::LIST: {
+ std::unique_ptr<base::ListValue> result(new base::ListValue());
+ Schema item_schema = schema.valid() ? schema.GetItems() : Schema();
+ for (RegistryDict::KeyMap::const_iterator entry(keys_.begin());
+ entry != keys_.end(); ++entry) {
+ if (!IsKeyNumerical(entry->first))
+ continue;
+ std::unique_ptr<base::Value> converted =
+ entry->second->ConvertToJSON(item_schema);
+ if (converted)
+ result->Append(std::move(converted));
+ }
+ for (RegistryDict::ValueMap::const_iterator entry(values_.begin());
+ entry != values_.end(); ++entry) {
+ if (!IsKeyNumerical(entry->first))
+ continue;
+ absl::optional<base::Value> converted =
+ ConvertRegistryValue(entry->second, item_schema);
+ if (converted.has_value())
+ result->Append(std::move(converted.value()));
+ }
+ return std::move(result);
+ }
+ default:
+ LOG(WARNING) << "Can't convert registry key to schema type " << type;
+ }
+
+ return nullptr;
+}
+#endif // #if BUILDFLAG(IS_WIN)
+} // namespace policy
diff --git a/chromium/components/policy/core/common/registry_dict.h b/chromium/components/policy/core/common/registry_dict.h
new file mode 100644
index 00000000000..7737c3242b8
--- /dev/null
+++ b/chromium/components/policy/core/common/registry_dict.h
@@ -0,0 +1,102 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REGISTRY_DICT_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REGISTRY_DICT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "build/build_config.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+#endif
+
+namespace base {
+class Value;
+}
+
+namespace policy {
+
+class Schema;
+
+// Converts a value (as read from the registry) to meet |schema|, converting
+// types as necessary. Unconvertible types will show up as null values in the
+// result.
+absl::optional<base::Value> POLICY_EXPORT
+ConvertRegistryValue(const base::Value& value, const Schema& schema);
+
+// A case-insensitive string comparison functor.
+struct POLICY_EXPORT CaseInsensitiveStringCompare {
+ bool operator()(const std::string& a, const std::string& b) const;
+};
+
+// In-memory representation of a registry subtree. Using a
+// base::DictionaryValue directly seems tempting, but that doesn't handle the
+// registry's case-insensitive-but-case-preserving semantics properly.
+class POLICY_EXPORT RegistryDict {
+ public:
+ using KeyMap = std::map<std::string,
+ std::unique_ptr<RegistryDict>,
+ CaseInsensitiveStringCompare>;
+ using ValueMap =
+ std::map<std::string, base::Value, CaseInsensitiveStringCompare>;
+
+ RegistryDict();
+ RegistryDict(const RegistryDict&) = delete;
+ RegistryDict& operator=(const RegistryDict&) = delete;
+ ~RegistryDict();
+
+ // Returns a pointer to an existing key, NULL if not present.
+ RegistryDict* GetKey(const std::string& name);
+ const RegistryDict* GetKey(const std::string& name) const;
+ // Sets a key. If |dict| is NULL, clears that key.
+ void SetKey(const std::string& name, std::unique_ptr<RegistryDict> dict);
+ // Removes a key. If the key doesn't exist, NULL is returned.
+ std::unique_ptr<RegistryDict> RemoveKey(const std::string& name);
+ // Clears all keys.
+ void ClearKeys();
+
+ // Returns a pointer to a value, NULL if not present.
+ base::Value* GetValue(const std::string& name);
+ const base::Value* GetValue(const std::string& name) const;
+ // Sets a value.
+ void SetValue(const std::string& name, base::Value&& value);
+ // Removes a value. If the value doesn't exist, nullopt is returned.
+ absl::optional<base::Value> RemoveValue(const std::string& name);
+ // Clears all values.
+ void ClearValues();
+
+ // Merge keys and values from |other|, giving precedence to |other|.
+ void Merge(const RegistryDict& other);
+
+ // Swap with |other|.
+ void Swap(RegistryDict* other);
+
+#if BUILDFLAG(IS_WIN)
+ // Read a Windows registry subtree into this registry dictionary object.
+ void ReadRegistry(HKEY hive, const std::wstring& root);
+
+ // Converts the dictionary to base::Value representation. For key/value name
+ // collisions, the key wins. |schema| is used to determine the expected type
+ // for each policy.
+ // The returned object is either a base::DictionaryValue or a base::ListValue.
+ std::unique_ptr<base::Value> ConvertToJSON(const class Schema& schema) const;
+#endif
+
+ const KeyMap& keys() const { return keys_; }
+ const ValueMap& values() const { return values_; }
+
+ private:
+ KeyMap keys_;
+ ValueMap values_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REGISTRY_DICT_H_
diff --git a/chromium/components/policy/core/common/registry_dict_unittest.cc b/chromium/components/policy/core/common/registry_dict_unittest.cc
new file mode 100644
index 00000000000..78db1774893
--- /dev/null
+++ b/chromium/components/policy/core/common/registry_dict_unittest.cc
@@ -0,0 +1,390 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/registry_dict.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/schema.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+namespace {
+
+TEST(RegistryDictTest, SetAndGetValue) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ test_dict.SetValue("one", int_value.Clone());
+ EXPECT_EQ(1u, test_dict.values().size());
+ EXPECT_EQ(int_value, *test_dict.GetValue("one"));
+ EXPECT_FALSE(test_dict.GetValue("two"));
+
+ test_dict.SetValue("two", string_value.Clone());
+ EXPECT_EQ(2u, test_dict.values().size());
+ EXPECT_EQ(int_value, *test_dict.GetValue("one"));
+ EXPECT_EQ(string_value, *test_dict.GetValue("two"));
+
+ absl::optional<base::Value> one(test_dict.RemoveValue("one"));
+ ASSERT_TRUE(one.has_value());
+ EXPECT_EQ(1u, test_dict.values().size());
+ EXPECT_EQ(int_value, one.value());
+ EXPECT_FALSE(test_dict.GetValue("one"));
+ EXPECT_EQ(string_value, *test_dict.GetValue("two"));
+
+ test_dict.ClearValues();
+ EXPECT_FALSE(test_dict.GetValue("one"));
+ EXPECT_FALSE(test_dict.GetValue("two"));
+ EXPECT_TRUE(test_dict.values().empty());
+}
+
+TEST(RegistryDictTest, CaseInsensitiveButPreservingValueNames) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ test_dict.SetValue("One", int_value.Clone());
+ EXPECT_EQ(1u, test_dict.values().size());
+ EXPECT_EQ(int_value, *test_dict.GetValue("oNe"));
+
+ auto entry = test_dict.values().begin();
+ ASSERT_NE(entry, test_dict.values().end());
+ EXPECT_EQ("One", entry->first);
+
+ test_dict.SetValue("ONE", string_value.Clone());
+ EXPECT_EQ(1u, test_dict.values().size());
+ EXPECT_EQ(string_value, *test_dict.GetValue("one"));
+
+ absl::optional<base::Value> removed_value(test_dict.RemoveValue("onE"));
+ ASSERT_TRUE(removed_value.has_value());
+ EXPECT_EQ(string_value, removed_value.value());
+ EXPECT_TRUE(test_dict.values().empty());
+}
+
+TEST(RegistryDictTest, SetAndGetKeys) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->SetValue("one", int_value.Clone());
+ test_dict.SetKey("two", std::move(subdict));
+ EXPECT_EQ(1u, test_dict.keys().size());
+ RegistryDict* actual_subdict = test_dict.GetKey("two");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(int_value, *actual_subdict->GetValue("one"));
+
+ subdict = std::make_unique<RegistryDict>();
+ subdict->SetValue("three", string_value.Clone());
+ test_dict.SetKey("four", std::move(subdict));
+ EXPECT_EQ(2u, test_dict.keys().size());
+ actual_subdict = test_dict.GetKey("two");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(int_value, *actual_subdict->GetValue("one"));
+ actual_subdict = test_dict.GetKey("four");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(string_value, *actual_subdict->GetValue("three"));
+
+ test_dict.ClearKeys();
+ EXPECT_FALSE(test_dict.GetKey("one"));
+ EXPECT_FALSE(test_dict.GetKey("three"));
+ EXPECT_TRUE(test_dict.keys().empty());
+}
+
+TEST(RegistryDictTest, CaseInsensitiveButPreservingKeyNames) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+
+ test_dict.SetKey("One", std::make_unique<RegistryDict>());
+ EXPECT_EQ(1u, test_dict.keys().size());
+ RegistryDict* actual_subdict = test_dict.GetKey("One");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_TRUE(actual_subdict->values().empty());
+
+ auto entry = test_dict.keys().begin();
+ ASSERT_NE(entry, test_dict.keys().end());
+ EXPECT_EQ("One", entry->first);
+
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->SetValue("two", int_value.Clone());
+ test_dict.SetKey("ONE", std::move(subdict));
+ EXPECT_EQ(1u, test_dict.keys().size());
+ actual_subdict = test_dict.GetKey("One");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(int_value, *actual_subdict->GetValue("two"));
+
+ std::unique_ptr<RegistryDict> removed_key(test_dict.RemoveKey("one"));
+ ASSERT_TRUE(removed_key);
+ EXPECT_EQ(int_value, *removed_key->GetValue("two"));
+ EXPECT_TRUE(test_dict.keys().empty());
+}
+
+TEST(RegistryDictTest, Merge) {
+ RegistryDict dict_a;
+ RegistryDict dict_b;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ dict_a.SetValue("one", int_value.Clone());
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->SetValue("two", string_value.Clone());
+ dict_a.SetKey("three", std::move(subdict));
+
+ dict_b.SetValue("four", string_value.Clone());
+ subdict = std::make_unique<RegistryDict>();
+ subdict->SetValue("two", int_value.Clone());
+ dict_b.SetKey("three", std::move(subdict));
+ subdict = std::make_unique<RegistryDict>();
+ subdict->SetValue("five", int_value.Clone());
+ dict_b.SetKey("six", std::move(subdict));
+
+ dict_a.Merge(dict_b);
+
+ EXPECT_EQ(int_value, *dict_a.GetValue("one"));
+ EXPECT_EQ(string_value, *dict_b.GetValue("four"));
+ RegistryDict* actual_subdict = dict_a.GetKey("three");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(int_value, *actual_subdict->GetValue("two"));
+ actual_subdict = dict_a.GetKey("six");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(int_value, *actual_subdict->GetValue("five"));
+}
+
+TEST(RegistryDictTest, Swap) {
+ RegistryDict dict_a;
+ RegistryDict dict_b;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ dict_a.SetValue("one", int_value.Clone());
+ dict_a.SetKey("two", std::make_unique<RegistryDict>());
+ dict_b.SetValue("three", string_value.Clone());
+
+ dict_a.Swap(&dict_b);
+
+ EXPECT_EQ(int_value, *dict_b.GetValue("one"));
+ EXPECT_TRUE(dict_b.GetKey("two"));
+ EXPECT_FALSE(dict_b.GetValue("two"));
+
+ EXPECT_EQ(string_value, *dict_a.GetValue("three"));
+ EXPECT_FALSE(dict_a.GetValue("one"));
+ EXPECT_FALSE(dict_a.GetKey("two"));
+}
+
+#if BUILDFLAG(IS_WIN)
+TEST(RegistryDictTest, ConvertToJSON) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+ base::Value string_zero("0");
+ base::Value string_dict("{ \"key\": [ \"value\" ] }");
+
+ test_dict.SetValue("one", int_value.Clone());
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->SetValue("two", string_value.Clone());
+ test_dict.SetKey("three", std::move(subdict));
+ std::unique_ptr<RegistryDict> list(new RegistryDict());
+ list->SetValue("1", string_value.Clone());
+ test_dict.SetKey("dict-to-list", std::move(list));
+ test_dict.SetValue("int-to-bool", int_value.Clone());
+ test_dict.SetValue("int-to-double", int_value.Clone());
+ test_dict.SetValue("string-to-bool", string_zero.Clone());
+ test_dict.SetValue("string-to-double", string_zero.Clone());
+ test_dict.SetValue("string-to-int", string_zero.Clone());
+ test_dict.SetValue("string-to-dict", string_dict.Clone());
+
+ std::string error;
+ Schema schema = Schema::Parse(
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"dict-to-list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " },"
+ " \"int-to-bool\": { \"type\": \"boolean\" },"
+ " \"int-to-double\": { \"type\": \"number\" },"
+ " \"string-to-bool\": { \"type\": \"boolean\" },"
+ " \"string-to-double\": { \"type\": \"number\" },"
+ " \"string-to-int\": { \"type\": \"integer\" },"
+ " \"string-to-dict\": { \"type\": \"object\" }"
+ " }"
+ "}",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ std::unique_ptr<base::Value> actual(test_dict.ConvertToJSON(schema));
+
+ base::DictionaryValue expected;
+ expected.SetKey("one", int_value.Clone());
+ auto expected_subdict = std::make_unique<base::DictionaryValue>();
+ expected_subdict->SetKey("two", string_value.Clone());
+ expected.Set("three", std::move(expected_subdict));
+ auto expected_list = std::make_unique<base::ListValue>();
+ expected_list->Append(std::make_unique<base::Value>(string_value.Clone()));
+ expected.Set("dict-to-list", std::move(expected_list));
+ expected.SetBoolKey("int-to-bool", true);
+ expected.SetDoubleKey("int-to-double", 42.0);
+ expected.SetBoolKey("string-to-bool", false);
+ expected.SetDoubleKey("string-to-double", 0.0);
+ expected.SetIntKey("string-to-int", static_cast<int>(0));
+ expected_list = std::make_unique<base::ListValue>();
+ expected_list->Append(std::make_unique<base::Value>("value"));
+ expected_subdict = std::make_unique<base::DictionaryValue>();
+ expected_subdict->Set("key", std::move(expected_list));
+ expected.Set("string-to-dict", std::move(expected_subdict));
+
+ EXPECT_EQ(expected, *actual);
+}
+
+TEST(RegistryDictTest, NonSequentialConvertToJSON) {
+ RegistryDict test_dict;
+
+ std::unique_ptr<RegistryDict> list(new RegistryDict());
+ list->SetValue("1", base::Value("1").Clone());
+ list->SetValue("2", base::Value("2").Clone());
+ list->SetValue("THREE", base::Value("3").Clone());
+ list->SetValue("4", base::Value("4").Clone());
+ test_dict.SetKey("dict-to-list", std::move(list));
+
+ std::string error;
+ Schema schema = Schema::Parse(
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"dict-to-list\": {"
+ " \"type\": \"array\","
+ " \"items\": { \"type\": \"string\" }"
+ " }"
+ " }"
+ "}",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ std::unique_ptr<base::Value> actual(test_dict.ConvertToJSON(schema));
+
+ base::DictionaryValue expected;
+ std::unique_ptr<base::ListValue> expected_list(new base::ListValue());
+ expected_list->Append(base::Value("1").Clone());
+ expected_list->Append(base::Value("2").Clone());
+ expected_list->Append(base::Value("4").Clone());
+ expected.Set("dict-to-list", std::move(expected_list));
+
+ EXPECT_EQ(expected, *actual);
+}
+
+TEST(RegistryDictTest, PatternPropertySchema) {
+ RegistryDict test_dict;
+
+ base::Value string_dict("[ \"*://*.google.com\" ]");
+ base::Value version_string("1.0.0");
+
+ std::unique_ptr<RegistryDict> policy_dict(new RegistryDict());
+ std::unique_ptr<RegistryDict> subdict_id(new RegistryDict());
+ // Values with schema are parsed even if the schema is a regexp property.
+ subdict_id->SetValue("runtime_blocked_hosts", string_dict.Clone());
+ subdict_id->SetValue("runtime_allowed_hosts", string_dict.Clone());
+ // Regexp. validated properties are valid too.
+ subdict_id->SetValue("minimum_version_required", version_string.Clone());
+ policy_dict->SetKey("aaaabbbbaaaabbbbaaaabbbbaaaabbbb",
+ std::move(subdict_id));
+ // Values that have no schema are left as strings regardless of structure.
+ policy_dict->SetValue("invalid_key", string_dict.Clone());
+ test_dict.SetKey("ExtensionSettings", std::move(policy_dict));
+
+ std::string error;
+ Schema schema = Schema::Parse(
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"ExtensionSettings\": {"
+ " \"type\": \"object\","
+ " \"patternProperties\": {"
+ " \"^[a-p]{32}(?:,[a-p]{32})*,?$\": {"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"minimum_version_required\": {"
+ " \"type\": \"string\","
+ " \"pattern\": \"^[0-9]+([.][0-9]+)*$\","
+ " },"
+ " \"runtime_blocked_hosts\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"string\""
+ " },"
+ " \"id\": \"ListOfUrlPatterns\""
+ " },"
+ " \"runtime_allowed_hosts\": {"
+ " \"$ref\": \"ListOfUrlPatterns\""
+ " },"
+ " },"
+ " },"
+ " },"
+ " },"
+ " },"
+ "}",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ std::unique_ptr<base::Value> actual(test_dict.ConvertToJSON(schema));
+
+ base::DictionaryValue expected;
+ std::unique_ptr<base::DictionaryValue> expected_extension_settings(
+ new base::DictionaryValue());
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue());
+ list_value->Append("*://*.google.com");
+ std::unique_ptr<base::DictionaryValue> restrictions_properties(
+ new base::DictionaryValue());
+ restrictions_properties->Set(
+ "runtime_blocked_hosts",
+ base::Value::ToUniquePtrValue(list_value->Clone()));
+ restrictions_properties->Set(
+ "runtime_allowed_hosts",
+ base::Value::ToUniquePtrValue(list_value->Clone()));
+ restrictions_properties->Set(
+ "minimum_version_required",
+ base::Value::ToUniquePtrValue(version_string.Clone()));
+ expected_extension_settings->Set("aaaabbbbaaaabbbbaaaabbbbaaaabbbb",
+ std::move(restrictions_properties));
+ expected_extension_settings->Set(
+ "invalid_key", std::make_unique<base::Value>(std::move(string_dict)));
+ expected.Set("ExtensionSettings", std::move(expected_extension_settings));
+
+ // Needed so that the EXPECT below prints good values in case of a mismatch.
+ const base::Value* expected_pointer = &expected;
+ EXPECT_EQ(*expected_pointer, *actual);
+}
+#endif
+
+TEST(RegistryDictTest, KeyValueNameClashes) {
+ RegistryDict test_dict;
+
+ base::Value int_value(42);
+ base::Value string_value("fortytwo");
+
+ test_dict.SetValue("one", int_value.Clone());
+ std::unique_ptr<RegistryDict> subdict(new RegistryDict());
+ subdict->SetValue("two", string_value.Clone());
+ test_dict.SetKey("one", std::move(subdict));
+
+ EXPECT_EQ(int_value, *test_dict.GetValue("one"));
+ RegistryDict* actual_subdict = test_dict.GetKey("one");
+ ASSERT_TRUE(actual_subdict);
+ EXPECT_EQ(string_value, *actual_subdict->GetValue("two"));
+}
+
+} // namespace
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_command_job.cc b/chromium/components/policy/core/common/remote_commands/remote_command_job.cc
new file mode 100644
index 00000000000..d60aca992eb
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_command_job.cc
@@ -0,0 +1,167 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/syslog_logging.h"
+
+namespace policy {
+
+namespace {
+
+constexpr base::TimeDelta kDefaultCommandTimeout = base::Minutes(10);
+constexpr base::TimeDelta kDefaultCommandExpirationTime = base::Minutes(10);
+
+} // namespace
+
+RemoteCommandJob::~RemoteCommandJob() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (status_ == RUNNING)
+ Terminate();
+}
+
+bool RemoteCommandJob::Init(
+ base::TimeTicks now,
+ const enterprise_management::RemoteCommand& command,
+ const enterprise_management::SignedData& signed_command) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(NOT_INITIALIZED, status_);
+
+ status_ = INVALID;
+
+ if (!command.has_type() || !command.has_command_id())
+ return false;
+ DCHECK_EQ(command.type(), GetType());
+
+ unique_id_ = command.command_id();
+ signed_command_ = signed_command;
+
+ if (command.has_age_of_command()) {
+ // Use age of command provided by server to estimate the command issued time
+ // as a local TimeTick. We need to store issued time instead of age of
+ // command, since the execution time of command might be different from the
+ // time we got it from server.
+ // It's just an estimation since we lost the time the response was
+ // transmitted over the network.
+ issued_time_ = now - base::Milliseconds(command.age_of_command());
+ } else {
+ SYSLOG(WARNING) << "No age_of_command provided by server for command "
+ << unique_id_ << ".";
+ // Otherwise, assuming the command was issued just now.
+ issued_time_ = now;
+ }
+
+ if (!ParseCommandPayload(command.payload())) {
+ SYSLOG(ERROR) << "Unable to parse command payload for type "
+ << command.type() << ": " << command.payload();
+ return false;
+ }
+
+ SYSLOG(INFO) << "Remote command type " << command.type() << " with id "
+ << command.command_id() << " initialized.";
+
+ status_ = NOT_STARTED;
+ return true;
+}
+
+bool RemoteCommandJob::Run(base::Time now,
+ base::TimeTicks now_ticks,
+ FinishedCallback finished_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (status_ == INVALID) {
+ SYSLOG(ERROR) << "Remote command " << unique_id_ << " is invalid.";
+ return false;
+ }
+
+ DCHECK_EQ(NOT_STARTED, status_);
+
+ if (IsExpired(now_ticks)) {
+ SYSLOG(ERROR) << "Remote command " << unique_id_
+ << " expired (it was issued " << now_ticks - issued_time_
+ << " ago).";
+ status_ = EXPIRED;
+ return false;
+ }
+
+ execution_started_time_ = now;
+ status_ = RUNNING;
+ finished_callback_ = std::move(finished_callback);
+
+ RunImpl(
+ base::BindOnce(&RemoteCommandJob::OnCommandExecutionFinishedWithResult,
+ weak_factory_.GetWeakPtr(), true),
+ base::BindOnce(&RemoteCommandJob::OnCommandExecutionFinishedWithResult,
+ weak_factory_.GetWeakPtr(), false));
+
+ // The command is expected to run asynchronously.
+ DCHECK_EQ(RUNNING, status_);
+
+ return true;
+}
+
+void RemoteCommandJob::Terminate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (IsExecutionFinished())
+ return;
+
+ DCHECK_EQ(RUNNING, status_);
+
+ status_ = TERMINATED;
+ weak_factory_.InvalidateWeakPtrs();
+
+ TerminateImpl();
+
+ if (finished_callback_)
+ std::move(finished_callback_).Run();
+}
+
+base::TimeDelta RemoteCommandJob::GetCommandTimeout() const {
+ return kDefaultCommandTimeout;
+}
+
+bool RemoteCommandJob::IsExecutionFinished() const {
+ return status_ == SUCCEEDED || status_ == FAILED || status_ == TERMINATED;
+}
+
+std::unique_ptr<std::string> RemoteCommandJob::GetResultPayload() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(status_ == SUCCEEDED || status_ == FAILED);
+
+ if (!result_payload_)
+ return nullptr;
+
+ return result_payload_->Serialize();
+}
+
+RemoteCommandJob::RemoteCommandJob() : status_(NOT_INITIALIZED) {}
+
+bool RemoteCommandJob::ParseCommandPayload(const std::string& command_payload) {
+ return true;
+}
+
+bool RemoteCommandJob::IsExpired(base::TimeTicks now) {
+ return now > issued_time() + kDefaultCommandExpirationTime;
+}
+
+void RemoteCommandJob::TerminateImpl() {}
+
+void RemoteCommandJob::OnCommandExecutionFinishedWithResult(
+ bool succeeded,
+ std::unique_ptr<RemoteCommandJob::ResultPayload> result_payload) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(RUNNING, status_);
+ status_ = succeeded ? SUCCEEDED : FAILED;
+
+ result_payload_ = std::move(result_payload);
+
+ if (finished_callback_)
+ std::move(finished_callback_).Run();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_command_job.h b/chromium/components/policy/core/common/remote_commands/remote_command_job.h
new file mode 100644
index 00000000000..ffa7f9769b5
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_command_job.h
@@ -0,0 +1,184 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMAND_JOB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMAND_JOB_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+// This class manages the execution of a remote command job. It's a base class
+// and actual implementations are expected to inherit from this class.
+class POLICY_EXPORT RemoteCommandJob {
+ public:
+ using UniqueIDType = int64_t;
+
+ // Status of this job.
+ // This enum is used to define the buckets for an enumerated UMA histogram.
+ // Hence,
+ // (a) existing enumerated constants should never be deleted or reordered
+ // (b) new constants should only be appended at the end of the enumeration
+ // (update RemoteCommandExecutuionStatus in
+ // tools/metrics/histograms/enums.xml as well).
+ enum Status {
+ NOT_INITIALIZED = 0, // The job is not initialized yet.
+ INVALID = 1, // The job was initialized from a malformed protobuf.
+ EXPIRED = 2, // The job is expired and won't be executed.
+ NOT_STARTED = 3, // The job is initialized and ready to be started.
+ RUNNING = 4, // The job was started and is running now.
+ SUCCEEDED = 5, // The job finished running successfully.
+ FAILED = 6, // The job finished running with failure.
+ TERMINATED = 7, // The job was terminated before finishing by itself.
+ STATUS_TYPE_SIZE // Used by UMA histograms. Shall be the last.
+ };
+
+ using FinishedCallback = base::OnceClosure;
+
+ RemoteCommandJob(const RemoteCommandJob&) = delete;
+ RemoteCommandJob& operator=(const RemoteCommandJob&) = delete;
+
+ virtual ~RemoteCommandJob();
+
+ // Initialize from a RemoteCommand protobuf definition, must be called before
+ // calling Run(). Returns true if the initialization is successful.
+ // |now| is the current time which will be used to estimate the command issued
+ // time. It must be consistent to the same parameter passed to Run() below.
+ // In order to minimize the error while estimating the command issued time,
+ // this method must be called immediately after the command is received from
+ // the server. |signed_command| contains the entire remote command and its
+ // signature, the way it was received from the server.
+ bool Init(base::TimeTicks now,
+ const enterprise_management::RemoteCommand& command,
+ const enterprise_management::SignedData& signed_command);
+
+ // Run the command asynchronously. |now| is the time used for marking the
+ // execution start. |now_ticks| is the time which will be used for command
+ // expiration checking.
+ // |finished_callback| will be called once the command finishes running,
+ // regardless of whether the command is successful, fails or is terminated
+ // prematurely.
+ // Returns true if the task is posted and the command marked as running.
+ // Returns false otherwise, for example if the command is invalid or expired.
+ // Subclasses should implement RunImpl() for actual work.
+ bool Run(base::Time now,
+ base::TimeTicks now_ticks,
+ FinishedCallback finished_callback);
+
+ // Attempts to terminate the running tasks associated with this command. Does
+ // nothing if the task is already terminated or finished. It's guaranteed that
+ // after calling this method, |finished_callback_| will never be called.
+ // Asynchronous tasks might still be running and subclasses that wish to
+ // actually terminate the tasks should implement TerminateImpl().
+ // This method is also intended to be used to handle timeout of command
+ // execution.
+ void Terminate();
+
+ // Returns the remote command type that this class is able to handle.
+ virtual enterprise_management::RemoteCommand_Type GetType() const = 0;
+
+ // Returns the remote command timeout. If the command takes longer than the
+ // returned time interval to execute, the command queue will kill it.
+ virtual base::TimeDelta GetCommandTimeout() const;
+
+ // Helpful accessors.
+ UniqueIDType unique_id() const { return unique_id_; }
+ base::TimeTicks issued_time() const { return issued_time_; }
+ base::Time execution_started_time() const { return execution_started_time_; }
+ Status status() const { return status_; }
+
+ // Returns whether execution of this command is finished.
+ bool IsExecutionFinished() const;
+
+ // Generate the result payload which will be sent back to the server.
+ // This method will only be called for successfully executed commands.
+ std::unique_ptr<std::string> GetResultPayload() const;
+
+ protected:
+ class ResultPayload {
+ public:
+ virtual ~ResultPayload() {}
+
+ virtual std::unique_ptr<std::string> Serialize() = 0;
+ };
+
+ using CallbackWithResult =
+ base::OnceCallback<void(std::unique_ptr<ResultPayload>)>;
+
+ RemoteCommandJob();
+
+ // The server can provide additional arguments for a command,
+ // serialized into a |command_payload| string. This method will be
+ // called with the payload sent by the server (or an empty string
+ // if there is no payload). Subclasses that expect command
+ // arguments should override this method and deserialize the
+ // |command_payload| string. The default implementation ignores any
+ // payload.
+ virtual bool ParseCommandPayload(const std::string& command_payload);
+
+ // Subclasses may use this method for customized command expiration
+ // checking. |now| is the current time obtained from a clock. Implementations
+ // are usually expected to compare |now| to the issued_time(), which is the
+ // timestamp when the command was issued on the server.
+ virtual bool IsExpired(base::TimeTicks now);
+
+ // Subclasses should implement this method for actual command execution logic.
+ // Implementations should execute commands asynchronously, possibly on a
+ // background thread. Execution should end by invoking either
+ // |succeeded_callback| or |failed_callback| on the thread that this method
+ // was called.
+ // Also see comments regarding Run().
+ virtual void RunImpl(CallbackWithResult succeed_callback,
+ CallbackWithResult failed_callback) = 0;
+
+ // Subclasses should implement this method for actual command execution
+ // termination. Be cautious that tasks might be running on another thread or
+ // even already be terminated. Implementations should expect destruction of
+ // the class soon. Also see comments regarding Terminate().
+ // The default implementation does nothing.
+ virtual void TerminateImpl();
+
+ const enterprise_management::SignedData& signed_command() const {
+ return signed_command_;
+ }
+
+ private:
+ // Posted tasks are expected to call this method.
+ void OnCommandExecutionFinishedWithResult(
+ bool succeeded,
+ std::unique_ptr<ResultPayload> result);
+
+ Status status_;
+
+ UniqueIDType unique_id_;
+ // The estimated time when the command was issued.
+ base::TimeTicks issued_time_;
+ // The time when the command started running.
+ base::Time execution_started_time_;
+
+ // Serialized command inside policy data proto with signature.
+ enterprise_management::SignedData signed_command_;
+
+ std::unique_ptr<ResultPayload> result_payload_;
+
+ FinishedCallback finished_callback_;
+
+ base::ThreadChecker thread_checker_;
+
+ base::WeakPtrFactory<RemoteCommandJob> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMAND_JOB_H_
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_factory.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_factory.cc
new file mode 100644
index 00000000000..3903eb8bada
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_factory.cc
@@ -0,0 +1,12 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_commands_factory.h"
+
+namespace policy {
+
+RemoteCommandsFactory::~RemoteCommandsFactory() {
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_factory.h b/chromium/components/policy/core/common/remote_commands/remote_commands_factory.h
new file mode 100644
index 00000000000..02242797dc2
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_factory.h
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_FACTORY_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_FACTORY_H_
+
+#include <memory>
+
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+class RemoteCommandJob;
+class RemoteCommandsService;
+
+// An interface class for creating remote commands based on command type.
+class POLICY_EXPORT RemoteCommandsFactory {
+ public:
+ RemoteCommandsFactory& operator=(const RemoteCommandsFactory&) = delete;
+
+ virtual ~RemoteCommandsFactory();
+
+ virtual std::unique_ptr<RemoteCommandJob> BuildJobForType(
+ enterprise_management::RemoteCommand_Type type,
+ RemoteCommandsService* service) = 0;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_FACTORY_H_
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_queue.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_queue.cc
new file mode 100644
index 00000000000..3628be8ed95
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_queue.cc
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check.h"
+#include "base/location.h"
+#include "base/observer_list.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+
+namespace policy {
+
+RemoteCommandsQueue::RemoteCommandsQueue()
+ : clock_(base::DefaultClock::GetInstance()),
+ tick_clock_(base::DefaultTickClock::GetInstance()) {}
+
+RemoteCommandsQueue::~RemoteCommandsQueue() {
+ while (!incoming_commands_.empty())
+ incoming_commands_.pop();
+ if (running_command_)
+ running_command_->Terminate();
+}
+
+void RemoteCommandsQueue::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void RemoteCommandsQueue::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void RemoteCommandsQueue::AddJob(std::unique_ptr<RemoteCommandJob> job) {
+ incoming_commands_.emplace(std::move(job));
+
+ if (!running_command_)
+ ScheduleNextJob();
+}
+
+void RemoteCommandsQueue::SetClocksForTesting(
+ const base::Clock* clock,
+ const base::TickClock* tick_clock) {
+ clock_ = clock;
+ tick_clock_ = tick_clock;
+}
+
+base::TimeTicks RemoteCommandsQueue::GetNowTicks() {
+ return tick_clock_->NowTicks();
+}
+
+void RemoteCommandsQueue::OnCommandTimeout() {
+ DCHECK(running_command_);
+
+ // Calling Terminate() will also trigger CurrentJobFinished() below.
+ running_command_->Terminate();
+}
+
+void RemoteCommandsQueue::CurrentJobFinished() {
+ DCHECK(running_command_);
+
+ execution_timeout_timer_.Stop();
+
+ for (auto& observer : observer_list_)
+ observer.OnJobFinished(running_command_.get());
+ running_command_.reset();
+
+ ScheduleNextJob();
+}
+
+void RemoteCommandsQueue::ScheduleNextJob() {
+ DCHECK(!running_command_);
+ if (incoming_commands_.empty())
+ return;
+ DCHECK(!execution_timeout_timer_.IsRunning());
+
+ running_command_ = std::move(incoming_commands_.front());
+ incoming_commands_.pop();
+
+ execution_timeout_timer_.Start(FROM_HERE,
+ running_command_->GetCommandTimeout(), this,
+ &RemoteCommandsQueue::OnCommandTimeout);
+
+ if (running_command_->Run(
+ clock_->Now(), tick_clock_->NowTicks(),
+ base::BindOnce(&RemoteCommandsQueue::CurrentJobFinished,
+ base::Unretained(this)))) {
+ for (auto& observer : observer_list_)
+ observer.OnJobStarted(running_command_.get());
+ } else {
+ CurrentJobFinished();
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_queue.h b/chromium/components/policy/core/common/remote_commands/remote_commands_queue.h
new file mode 100644
index 00000000000..ae65f775b81
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_queue.h
@@ -0,0 +1,91 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_QUEUE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_QUEUE_H_
+
+#include <memory>
+
+#include "base/containers/queue.h"
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/timer/timer.h"
+#include "components/policy/policy_export.h"
+
+namespace base {
+class Clock;
+class TickClock;
+} // namespace base
+
+namespace policy {
+
+class RemoteCommandJob;
+
+// This class manages the execution of multiple instances of RemoteCommandJob.
+// It runs all commands one by one in order from the same thread, i.e. there
+// will be at most one running command at any given time.
+// Although the queue lives on a single thread and manages the command execution
+// from the same thread, the actual command processing happens asynchronously in
+// the background.
+class POLICY_EXPORT RemoteCommandsQueue {
+ public:
+ // Interface for classes who would like to monitor remote command execution.
+ class POLICY_EXPORT Observer {
+ public:
+ // Called when a remote command starts running.
+ virtual void OnJobStarted(RemoteCommandJob* command) = 0;
+
+ // Called when a remote command finishes execution, fails or is being
+ // terminated. After this method has run, |command| is destroyed and must
+ // not be accessed anymore.
+ virtual void OnJobFinished(RemoteCommandJob* command) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ RemoteCommandsQueue();
+ RemoteCommandsQueue(const RemoteCommandsQueue&) = delete;
+ RemoteCommandsQueue& operator=(const RemoteCommandsQueue&) = delete;
+ ~RemoteCommandsQueue();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Add a |job| to the queue.
+ void AddJob(std::unique_ptr<RemoteCommandJob> job);
+
+ // Set alternative clocks for testing.
+ void SetClocksForTesting(const base::Clock* clock,
+ const base::TickClock* tick_clock);
+
+ // Helper function to get the current time.
+ base::TimeTicks GetNowTicks();
+
+ private:
+ // Callback function for the timer, used to terminate the running command
+ // after certain time.
+ void OnCommandTimeout();
+
+ // Called whenever the command currently scheduled to run finishes, regardless
+ // of reasons.
+ void CurrentJobFinished();
+
+ // Attempts to start a new command.
+ void ScheduleNextJob();
+
+ base::queue<std::unique_ptr<RemoteCommandJob>> incoming_commands_;
+
+ std::unique_ptr<RemoteCommandJob> running_command_;
+
+ raw_ptr<const base::Clock> clock_;
+ raw_ptr<const base::TickClock> tick_clock_;
+ base::OneShotTimer execution_timeout_timer_;
+
+ base::ObserverList<Observer, true>::Unchecked observer_list_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_QUEUE_H_
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_queue_unittest.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_queue_unittest.cc
new file mode 100644
index 00000000000..5f9d0d744be
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_queue_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace em = enterprise_management;
+
+namespace {
+
+const RemoteCommandJob::UniqueIDType kUniqueID = 123456789;
+const RemoteCommandJob::UniqueIDType kUniqueID2 = 987654321;
+const char kPayload[] = "_PAYLOAD_FOR_TESTING_";
+const char kPayload2[] = "_PAYLOAD_FOR_TESTING2_";
+
+em::RemoteCommand GenerateCommandProto(RemoteCommandJob::UniqueIDType unique_id,
+ base::TimeDelta age_of_command,
+ const std::string& payload) {
+ em::RemoteCommand command_proto;
+ command_proto.set_type(
+ enterprise_management::RemoteCommand_Type_COMMAND_ECHO_TEST);
+ command_proto.set_command_id(unique_id);
+ command_proto.set_age_of_command(age_of_command.InMilliseconds());
+ if (!payload.empty())
+ command_proto.set_payload(payload);
+ return command_proto;
+}
+
+// Mock class for RemoteCommandsQueue::Observer.
+class MockRemoteCommandsQueueObserver : public RemoteCommandsQueue::Observer {
+ public:
+ MockRemoteCommandsQueueObserver() = default;
+ MockRemoteCommandsQueueObserver(const MockRemoteCommandsQueueObserver&) =
+ delete;
+ MockRemoteCommandsQueueObserver& operator=(
+ const MockRemoteCommandsQueueObserver&) = delete;
+
+ // RemoteCommandsQueue::Observer:
+ MOCK_METHOD1(OnJobStarted, void(RemoteCommandJob* command));
+ MOCK_METHOD1(OnJobFinished, void(RemoteCommandJob* command));
+};
+
+} // namespace
+
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::Pointee;
+using ::testing::Property;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+class RemoteCommandsQueueTest : public testing::Test {
+ public:
+ RemoteCommandsQueueTest(const RemoteCommandsQueueTest&) = delete;
+ RemoteCommandsQueueTest& operator=(const RemoteCommandsQueueTest&) = delete;
+
+ protected:
+ RemoteCommandsQueueTest();
+
+ // testing::Test:
+ void SetUp() override;
+ void TearDown() override;
+
+ void InitializeJob(RemoteCommandJob* job,
+ RemoteCommandJob::UniqueIDType unique_id,
+ base::TimeTicks issued_time,
+ const std::string& payload);
+ void FailInitializeJob(RemoteCommandJob* job,
+ RemoteCommandJob::UniqueIDType unique_id,
+ base::TimeTicks issued_time,
+ const std::string& payload);
+
+ void AddJobAndVerifyRunningAfter(std::unique_ptr<RemoteCommandJob> job,
+ base::TimeDelta delta);
+
+ scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
+ RemoteCommandsQueue queue_;
+ StrictMock<MockRemoteCommandsQueueObserver> observer_;
+ base::TimeTicks test_start_time_;
+ raw_ptr<const base::Clock> clock_;
+ raw_ptr<const base::TickClock> tick_clock_;
+
+ private:
+ void VerifyCommandIssuedTime(RemoteCommandJob* job,
+ base::TimeTicks expected_issued_time);
+
+ base::ThreadTaskRunnerHandle runner_handle_;
+};
+
+RemoteCommandsQueueTest::RemoteCommandsQueueTest()
+ : task_runner_(new base::TestMockTimeTaskRunner()),
+ clock_(nullptr),
+ tick_clock_(nullptr),
+ runner_handle_(task_runner_) {}
+
+void RemoteCommandsQueueTest::SetUp() {
+ clock_ = task_runner_->GetMockClock();
+ tick_clock_ = task_runner_->GetMockTickClock();
+ test_start_time_ = tick_clock_->NowTicks();
+ queue_.SetClocksForTesting(clock_, tick_clock_);
+ queue_.AddObserver(&observer_);
+}
+
+void RemoteCommandsQueueTest::TearDown() {
+ queue_.RemoveObserver(&observer_);
+}
+
+void RemoteCommandsQueueTest::InitializeJob(
+ RemoteCommandJob* job,
+ RemoteCommandJob::UniqueIDType unique_id,
+ base::TimeTicks issued_time,
+ const std::string& payload) {
+ EXPECT_TRUE(
+ job->Init(tick_clock_->NowTicks(),
+ GenerateCommandProto(
+ unique_id, tick_clock_->NowTicks() - issued_time, payload),
+ em::SignedData()));
+ EXPECT_EQ(unique_id, job->unique_id());
+ VerifyCommandIssuedTime(job, issued_time);
+ EXPECT_EQ(RemoteCommandJob::NOT_STARTED, job->status());
+}
+
+void RemoteCommandsQueueTest::FailInitializeJob(
+ RemoteCommandJob* job,
+ RemoteCommandJob::UniqueIDType unique_id,
+ base::TimeTicks issued_time,
+ const std::string& payload) {
+ EXPECT_FALSE(
+ job->Init(tick_clock_->NowTicks(),
+ GenerateCommandProto(
+ unique_id, tick_clock_->NowTicks() - issued_time, payload),
+ em::SignedData()));
+ EXPECT_EQ(RemoteCommandJob::INVALID, job->status());
+}
+
+void RemoteCommandsQueueTest::AddJobAndVerifyRunningAfter(
+ std::unique_ptr<RemoteCommandJob> job,
+ base::TimeDelta delta) {
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ const base::Time now = clock_->Now();
+
+ // Add the job to the queue. It should start executing immediately.
+ EXPECT_CALL(
+ observer_,
+ OnJobStarted(
+ AllOf(Property(&RemoteCommandJob::status, RemoteCommandJob::RUNNING),
+ Property(&RemoteCommandJob::execution_started_time, now))));
+ queue_.AddJob(std::move(job));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // After |delta|, the job should still be running.
+ task_runner_->FastForwardBy(delta);
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+void RemoteCommandsQueueTest::VerifyCommandIssuedTime(
+ RemoteCommandJob* job,
+ base::TimeTicks expected_issued_time) {
+ // Maximum possible error can be 1 millisecond due to truncating.
+ EXPECT_GE(expected_issued_time, job->issued_time());
+ EXPECT_LE(expected_issued_time - base::Milliseconds(1), job->issued_time());
+}
+
+TEST_F(RemoteCommandsQueueTest, SingleSucceedCommand) {
+ // Initialize a job expected to succeed after 5 seconds, from a protobuf with
+ // |kUniqueID|, |kPayload| and |test_start_time_| as command issued time.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(true, base::Seconds(5)));
+ InitializeJob(job.get(), kUniqueID, test_start_time_, kPayload);
+
+ AddJobAndVerifyRunningAfter(std::move(job), base::Seconds(4));
+
+ // After 6 seconds, the job is expected to be finished.
+ EXPECT_CALL(observer_,
+ OnJobFinished(AllOf(Property(&RemoteCommandJob::status,
+ RemoteCommandJob::SUCCEEDED),
+ Property(&RemoteCommandJob::GetResultPayload,
+ Pointee(StrEq(kPayload))))));
+ task_runner_->FastForwardBy(base::Seconds(2));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+TEST_F(RemoteCommandsQueueTest, SingleFailedCommand) {
+ // Initialize a job expected to fail after 10 seconds, from a protobuf with
+ // |kUniqueID|, |kPayload| and |test_start_time_| as command issued time.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(false, base::Seconds(10)));
+ InitializeJob(job.get(), kUniqueID, test_start_time_, kPayload);
+
+ AddJobAndVerifyRunningAfter(std::move(job), base::Seconds(9));
+
+ // After 11 seconds, the job is expected to be finished.
+ EXPECT_CALL(observer_,
+ OnJobFinished(AllOf(
+ Property(&RemoteCommandJob::status, RemoteCommandJob::FAILED),
+ Property(&RemoteCommandJob::GetResultPayload,
+ Pointee(StrEq(kPayload))))));
+ task_runner_->FastForwardBy(base::Seconds(2));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+TEST_F(RemoteCommandsQueueTest, SingleTerminatedCommand) {
+ // Initialize a job expected to fail after 600 seconds, from a protobuf with
+ // |kUniqueID|, |kPayload| and |test_start_time_| as command issued time.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(false, base::Seconds(600)));
+ InitializeJob(job.get(), kUniqueID, test_start_time_, kPayload);
+
+ AddJobAndVerifyRunningAfter(std::move(job), base::Seconds(599));
+
+ // After 601 seconds, the job is expected to be terminated (10 minutes is the
+ // timeout duration).
+ EXPECT_CALL(observer_, OnJobFinished(Property(&RemoteCommandJob::status,
+ RemoteCommandJob::TERMINATED)));
+ task_runner_->FastForwardBy(base::Seconds(2));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+TEST_F(RemoteCommandsQueueTest, SingleMalformedCommand) {
+ // Initialize a job expected to succeed after 10 seconds, from a protobuf with
+ // |kUniqueID|, |kMalformedCommandPayload| and |test_start_time_|.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(true, base::Seconds(10)));
+ // Should failed immediately.
+ FailInitializeJob(job.get(), kUniqueID, test_start_time_,
+ EchoRemoteCommandJob::kMalformedCommandPayload);
+}
+
+TEST_F(RemoteCommandsQueueTest, SingleExpiredCommand) {
+ // Initialize a job expected to succeed after 10 seconds, from a protobuf with
+ // |kUniqueID| and |test_start_time_ - 4 hours|.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(true, base::Seconds(10)));
+ InitializeJob(job.get(), kUniqueID, test_start_time_ - base::Hours(4),
+ std::string());
+
+ // Add the job to the queue. It should not be started.
+ EXPECT_CALL(observer_, OnJobFinished(Property(&RemoteCommandJob::status,
+ RemoteCommandJob::EXPIRED)));
+ queue_.AddJob(std::move(job));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+TEST_F(RemoteCommandsQueueTest, TwoCommands) {
+ InSequence sequence;
+
+ // Initialize a job expected to succeed after 5 seconds, from a protobuf with
+ // |kUniqueID|, |kPayload| and |test_start_time_| as command issued time.
+ std::unique_ptr<RemoteCommandJob> job(
+ new EchoRemoteCommandJob(true, base::Seconds(5)));
+ InitializeJob(job.get(), kUniqueID, test_start_time_, kPayload);
+
+ // Add the job to the queue, should start executing immediately. Pass the
+ // ownership of |job| as well.
+ EXPECT_CALL(
+ observer_,
+ OnJobStarted(AllOf(
+ Property(&RemoteCommandJob::unique_id, kUniqueID),
+ Property(&RemoteCommandJob::status, RemoteCommandJob::RUNNING))));
+ queue_.AddJob(std::move(job));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // Initialize another job expected to succeed after 5 seconds, from a protobuf
+ // with |kUniqueID2|, |kPayload2| and |test_start_time_ + 1s| as command
+ // issued time.
+ job = std::make_unique<EchoRemoteCommandJob>(true, base::Seconds(5));
+ InitializeJob(job.get(), kUniqueID2, test_start_time_ + base::Seconds(1),
+ kPayload2);
+
+ // After 2 seconds, add the second job. It should be queued and not start
+ // running immediately.
+ task_runner_->FastForwardBy(base::Seconds(2));
+ queue_.AddJob(std::move(job));
+
+ // After 4 seconds, nothing happens.
+ task_runner_->FastForwardBy(base::Seconds(2));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // After 6 seconds, the first job should finish running and the second one
+ // start immediately after that.
+ EXPECT_CALL(
+ observer_,
+ OnJobFinished(AllOf(
+ Property(&RemoteCommandJob::unique_id, kUniqueID),
+ Property(&RemoteCommandJob::status, RemoteCommandJob::SUCCEEDED),
+ Property(&RemoteCommandJob::GetResultPayload,
+ Pointee(StrEq(kPayload))))));
+ EXPECT_CALL(
+ observer_,
+ OnJobStarted(AllOf(
+ Property(&RemoteCommandJob::unique_id, kUniqueID2),
+ Property(&RemoteCommandJob::status, RemoteCommandJob::RUNNING))));
+ task_runner_->FastForwardBy(base::Seconds(2));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ // After 11 seconds, the second job should finish running as well.
+ EXPECT_CALL(
+ observer_,
+ OnJobFinished(AllOf(
+ Property(&RemoteCommandJob::unique_id, kUniqueID2),
+ Property(&RemoteCommandJob::status, RemoteCommandJob::SUCCEEDED),
+ Property(&RemoteCommandJob::GetResultPayload,
+ Pointee(StrEq(kPayload2))))));
+ task_runner_->FastForwardBy(base::Seconds(5));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc
new file mode 100644
index 00000000000..9e05b9920d5
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc
@@ -0,0 +1,418 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_commands_service.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/containers/contains.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
+#include "base/syslog_logging.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/cloud/enterprise_metrics.h"
+#include "components/policy/core/common/remote_commands/remote_commands_factory.h"
+
+namespace policy {
+
+namespace em = enterprise_management;
+
+namespace {
+
+RemoteCommandsService::MetricReceivedRemoteCommand RemoteCommandMetricFromType(
+ em::RemoteCommand_Type type) {
+ using Metric = RemoteCommandsService::MetricReceivedRemoteCommand;
+
+ switch (type) {
+ case em::RemoteCommand_Type_COMMAND_ECHO_TEST:
+ return Metric::kCommandEchoTest;
+ case em::RemoteCommand_Type_DEVICE_REBOOT:
+ return Metric::kDeviceReboot;
+ case em::RemoteCommand_Type_DEVICE_SCREENSHOT:
+ return Metric::kDeviceScreenshot;
+ case em::RemoteCommand_Type_DEVICE_SET_VOLUME:
+ return Metric::kDeviceSetVolume;
+ case em::RemoteCommand_Type_DEVICE_START_CRD_SESSION:
+ return Metric::kDeviceStartCrdSession;
+ case em::RemoteCommand_Type_DEVICE_FETCH_STATUS:
+ return Metric::kDeviceFetchStatus;
+ case em::RemoteCommand_Type_USER_ARC_COMMAND:
+ return Metric::kUserArcCommand;
+ case em::RemoteCommand_Type_DEVICE_WIPE_USERS:
+ return Metric::kDeviceWipeUsers;
+ case em::RemoteCommand_Type_DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE:
+ return Metric::kDeviceRefreshEnterpriseMachineCertificate;
+ case em::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH:
+ return Metric::kDeviceRemotePowerwash;
+ case em::RemoteCommand_Type_DEVICE_GET_AVAILABLE_DIAGNOSTIC_ROUTINES:
+ return Metric::kDeviceGetAvailableDiagnosticRoutines;
+ case em::RemoteCommand_Type_DEVICE_RUN_DIAGNOSTIC_ROUTINE:
+ return Metric::kDeviceRunDiagnosticRoutine;
+ case em::RemoteCommand_Type_DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE:
+ return Metric::kDeviceGetDiagnosticRoutineUpdate;
+ case em::RemoteCommand_Type_BROWSER_CLEAR_BROWSING_DATA:
+ return Metric::kBrowserClearBrowsingData;
+ case em::RemoteCommand_Type_DEVICE_RESET_EUICC:
+ return Metric::kDeviceResetEuicc;
+ case em::RemoteCommand_Type_BROWSER_ROTATE_ATTESTATION_CREDENTIAL:
+ return Metric::kBrowserRotateAttestationCredential;
+ }
+
+ // None of possible types matched. May indicate that there is new unhandled
+ // command type.
+ NOTREACHED() << "Unknown command type to record: " << type;
+ return Metric::kUnknownType;
+}
+
+const char* RemoteCommandTypeToString(em::RemoteCommand_Type type) {
+ switch (type) {
+ case em::RemoteCommand_Type_COMMAND_ECHO_TEST:
+ return "CommandEchoTest";
+ case em::RemoteCommand_Type_DEVICE_REBOOT:
+ return "DeviceReboot";
+ case em::RemoteCommand_Type_DEVICE_SCREENSHOT:
+ return "DeviceScreenshot";
+ case em::RemoteCommand_Type_DEVICE_SET_VOLUME:
+ return "DeviceSetVolume";
+ case em::RemoteCommand_Type_DEVICE_START_CRD_SESSION:
+ return "DeviceStartCrdSession";
+ case em::RemoteCommand_Type_DEVICE_FETCH_STATUS:
+ return "DeviceFetchStatus";
+ case em::RemoteCommand_Type_USER_ARC_COMMAND:
+ return "UserArcCommand";
+ case em::RemoteCommand_Type_DEVICE_WIPE_USERS:
+ return "DeviceWipeUsers";
+ case em::RemoteCommand_Type_DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE:
+ return "DeviceRefreshEnterpriseMachineCertificate";
+ case em::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH:
+ return "DeviceRemotePowerwash";
+ case em::RemoteCommand_Type_DEVICE_GET_AVAILABLE_DIAGNOSTIC_ROUTINES:
+ return "DeviceGetAvailableDiagnosticRoutines";
+ case em::RemoteCommand_Type_DEVICE_RUN_DIAGNOSTIC_ROUTINE:
+ return "DeviceRunDiagnosticRoutine";
+ case em::RemoteCommand_Type_DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE:
+ return "DeviceGetDiagnosticRoutineUpdate";
+ case em::RemoteCommand_Type_BROWSER_CLEAR_BROWSING_DATA:
+ return "BrowserClearBrowsingData";
+ case em::RemoteCommand_Type_DEVICE_RESET_EUICC:
+ return "DeviceResetEuicc";
+ case em::RemoteCommand_Type_BROWSER_ROTATE_ATTESTATION_CREDENTIAL:
+ return "BrowserRotateAttestationCredential";
+ }
+
+ NOTREACHED() << "Unknown command type: " << type;
+ return "";
+}
+
+em::RemoteCommandResult::ResultType CommandStatusToResultType(
+ RemoteCommandJob::Status status) {
+ switch (status) {
+ case RemoteCommandJob::SUCCEEDED:
+ return em::RemoteCommandResult_ResultType_RESULT_SUCCESS;
+ case RemoteCommandJob::FAILED:
+ return em::RemoteCommandResult_ResultType_RESULT_FAILURE;
+ case RemoteCommandJob::EXPIRED:
+ case RemoteCommandJob::INVALID:
+ return em::RemoteCommandResult_ResultType_RESULT_IGNORED;
+ case RemoteCommandJob::NOT_INITIALIZED:
+ case RemoteCommandJob::NOT_STARTED:
+ case RemoteCommandJob::RUNNING:
+ case RemoteCommandJob::TERMINATED:
+ case RemoteCommandJob::STATUS_TYPE_SIZE:
+ NOTREACHED();
+ return em::RemoteCommandResult_ResultType_RESULT_IGNORED;
+ }
+ NOTREACHED();
+ return em::RemoteCommandResult_ResultType_RESULT_IGNORED;
+}
+} // namespace
+
+// static
+const char* RemoteCommandsService::GetMetricNameReceivedRemoteCommand(
+ PolicyInvalidationScope scope) {
+ switch (scope) {
+ case PolicyInvalidationScope::kUser:
+ return kMetricUserRemoteCommandReceived;
+ case PolicyInvalidationScope::kDevice:
+ return kMetricDeviceRemoteCommandReceived;
+ case PolicyInvalidationScope::kCBCM:
+ return kMetricCBCMRemoteCommandReceived;
+ case PolicyInvalidationScope::kDeviceLocalAccount:
+ NOTREACHED() << "Unexpected instance of remote commands service with "
+ "device local account scope.";
+ return "";
+ }
+}
+
+// static
+std::string RemoteCommandsService::GetMetricNameExecutedRemoteCommand(
+ PolicyInvalidationScope scope,
+ em::RemoteCommand_Type command_type) {
+ const char* base_metric_name = nullptr;
+ switch (scope) {
+ case PolicyInvalidationScope::kUser:
+ base_metric_name = kMetricUserRemoteCommandExecutedTemplate;
+ break;
+ case PolicyInvalidationScope::kDevice:
+ base_metric_name = kMetricDeviceRemoteCommandExecutedTemplate;
+ break;
+ case PolicyInvalidationScope::kCBCM:
+ base_metric_name = kMetricCBCMRemoteCommandExecutedTemplate;
+ break;
+ case PolicyInvalidationScope::kDeviceLocalAccount:
+ NOTREACHED() << "Unexpected instance of remote commands service with "
+ "device local account scope.";
+ return "";
+ }
+
+ DCHECK(base_metric_name);
+ return base::StringPrintf(base_metric_name,
+ RemoteCommandTypeToString(command_type));
+}
+
+RemoteCommandsService::RemoteCommandsService(
+ std::unique_ptr<RemoteCommandsFactory> factory,
+ CloudPolicyClient* client,
+ CloudPolicyStore* store,
+ PolicyInvalidationScope scope)
+ : factory_(std::move(factory)),
+ client_(client),
+ store_(store),
+ scope_(scope) {
+ DCHECK(client_);
+ queue_.AddObserver(this);
+}
+
+RemoteCommandsService::~RemoteCommandsService() {
+ queue_.RemoveObserver(this);
+}
+
+bool RemoteCommandsService::FetchRemoteCommands() {
+ if (!client_->is_registered()) {
+ SYSLOG(WARNING) << "Client is not registered.";
+ return false;
+ }
+
+ if (command_fetch_in_progress_) {
+ has_enqueued_fetch_request_ = true;
+ return false;
+ }
+
+ command_fetch_in_progress_ = true;
+ has_enqueued_fetch_request_ = false;
+
+ std::vector<em::RemoteCommandResult> previous_results;
+ unsent_results_.swap(previous_results);
+
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> id_to_acknowledge;
+
+ if (has_finished_command_) {
+ // Acknowledges |lastest_finished_command_id_|, and removes it and every
+ // command before it from |fetched_command_ids_|.
+ id_to_acknowledge = std::make_unique<RemoteCommandJob::UniqueIDType>(
+ lastest_finished_command_id_);
+ // It's safe to remove these IDs from |fetched_command_ids_| here, since
+ // it is guaranteed that there is no earlier fetch request in progress
+ // anymore that could have returned these IDs.
+ while (!fetched_command_ids_.empty() &&
+ fetched_command_ids_.front() != lastest_finished_command_id_) {
+ fetched_command_ids_.pop_front();
+ }
+ }
+
+ client_->FetchRemoteCommands(
+ std::move(id_to_acknowledge), previous_results,
+ base::BindOnce(&RemoteCommandsService::OnRemoteCommandsFetched,
+ weak_factory_.GetWeakPtr()));
+
+ return true;
+}
+
+void RemoteCommandsService::SetClocksForTesting(
+ const base::Clock* clock,
+ const base::TickClock* tick_clock) {
+ queue_.SetClocksForTesting(clock, tick_clock);
+}
+
+void RemoteCommandsService::SetOnCommandAckedCallback(
+ base::OnceClosure callback) {
+ on_command_acked_callback_ = std::move(callback);
+}
+
+void RemoteCommandsService::VerifyAndEnqueueSignedCommand(
+ const em::SignedData& signed_command) {
+ const bool valid_signature = CloudPolicyValidatorBase::VerifySignature(
+ signed_command.data(), store_->policy_signature_public_key(),
+ signed_command.signature(),
+ CloudPolicyValidatorBase::SignatureType::SHA1);
+
+ auto ignore_result = base::BindOnce(
+ [](RemoteCommandsService* self, const char* error_msg,
+ MetricReceivedRemoteCommand metric) {
+ SYSLOG(ERROR) << error_msg;
+ em::RemoteCommandResult result;
+ result.set_result(em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+ result.set_command_id(-1);
+ self->unsent_results_.push_back(result);
+ self->RecordReceivedRemoteCommand(metric);
+ // Trigger another fetch so the results are uploaded.
+ self->FetchRemoteCommands();
+ },
+ base::Unretained(this));
+
+ if (!valid_signature) {
+ std::move(ignore_result)
+ .Run("Secure remote command signature verification failed",
+ MetricReceivedRemoteCommand::kInvalidSignature);
+ return;
+ }
+
+ em::PolicyData policy_data;
+ if (!policy_data.ParseFromString(signed_command.data()) ||
+ !policy_data.has_policy_type() ||
+ policy_data.policy_type() !=
+ dm_protocol::kChromeRemoteCommandPolicyType) {
+ std::move(ignore_result)
+ .Run("Secure remote command with wrong PolicyData type",
+ MetricReceivedRemoteCommand::kInvalid);
+ return;
+ }
+
+ em::RemoteCommand command;
+ if (!policy_data.has_policy_value() ||
+ !command.ParseFromString(policy_data.policy_value())) {
+ std::move(ignore_result)
+ .Run("Secure remote command invalid RemoteCommand data",
+ MetricReceivedRemoteCommand::kInvalid);
+ return;
+ }
+
+ const em::PolicyData* const policy = store_->policy();
+ if (!policy || policy->device_id() != command.target_device_id()) {
+ std::move(ignore_result)
+ .Run("Secure remote command wrong target device id",
+ MetricReceivedRemoteCommand::kInvalid);
+ return;
+ }
+
+ // Signature verification passed.
+ EnqueueCommand(command, signed_command);
+}
+
+void RemoteCommandsService::EnqueueCommand(
+ const em::RemoteCommand& command,
+ const em::SignedData& signed_command) {
+ if (!command.has_type() || !command.has_command_id()) {
+ SYSLOG(ERROR) << "Invalid remote command from server.";
+ const auto metric = !command.has_command_id()
+ ? MetricReceivedRemoteCommand::kInvalid
+ : MetricReceivedRemoteCommand::kUnknownType;
+ RecordReceivedRemoteCommand(metric);
+ return;
+ }
+
+ // If the command is already fetched, ignore it.
+ if (base::Contains(fetched_command_ids_, command.command_id())) {
+ RecordReceivedRemoteCommand(MetricReceivedRemoteCommand::kDuplicated);
+ return;
+ }
+
+ fetched_command_ids_.push_back(command.command_id());
+
+ std::unique_ptr<RemoteCommandJob> job =
+ factory_->BuildJobForType(command.type(), this);
+
+ if (!job || !job->Init(queue_.GetNowTicks(), command, signed_command)) {
+ SYSLOG(ERROR) << "Initialization of remote command type " << command.type()
+ << " with id " << command.command_id() << " failed.";
+ const auto metric = job == nullptr
+ ? MetricReceivedRemoteCommand::kInvalidScope
+ : MetricReceivedRemoteCommand::kInvalid;
+ RecordReceivedRemoteCommand(metric);
+ em::RemoteCommandResult ignored_result;
+ ignored_result.set_result(
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+ ignored_result.set_command_id(command.command_id());
+ unsent_results_.push_back(ignored_result);
+ return;
+ }
+
+ RecordReceivedRemoteCommand(RemoteCommandMetricFromType(command.type()));
+
+ queue_.AddJob(std::move(job));
+}
+
+void RemoteCommandsService::OnJobStarted(RemoteCommandJob* command) {}
+
+void RemoteCommandsService::OnJobFinished(RemoteCommandJob* command) {
+ has_finished_command_ = true;
+ lastest_finished_command_id_ = command->unique_id();
+ // TODO(binjin): Attempt to sync |lastest_finished_command_id_| to some
+ // persistent source, so that we can reload it later without relying solely on
+ // the server to keep our last acknowledged command ID.
+ // See http://crbug.com/466572.
+
+ em::RemoteCommandResult result;
+ result.set_command_id(command->unique_id());
+ result.set_timestamp(command->execution_started_time().ToJavaTime());
+ result.set_result(CommandStatusToResultType(command->status()));
+
+ std::unique_ptr<std::string> result_payload = command->GetResultPayload();
+ if (result_payload)
+ result.set_payload(std::move(*result_payload));
+
+ SYSLOG(INFO) << "Remote command " << command->unique_id()
+ << " finished with result " << result.result();
+
+ unsent_results_.push_back(result);
+
+ RecordExecutedRemoteCommand(*command);
+
+ FetchRemoteCommands();
+}
+
+void RemoteCommandsService::OnRemoteCommandsFetched(
+ DeviceManagementStatus status,
+ const std::vector<enterprise_management::SignedData>& commands) {
+ DCHECK(command_fetch_in_progress_);
+ command_fetch_in_progress_ = false;
+
+ if (!on_command_acked_callback_.is_null())
+ std::move(on_command_acked_callback_).Run();
+
+ // TODO(binjin): Add retrying on errors. See http://crbug.com/466572.
+ if (status == DM_STATUS_SUCCESS) {
+ for (const auto& command : commands)
+ VerifyAndEnqueueSignedCommand(command);
+ }
+
+ // Start another fetch request job immediately if there are unsent command
+ // results or enqueued fetch requests.
+ if (!unsent_results_.empty() || has_enqueued_fetch_request_)
+ FetchRemoteCommands();
+}
+
+void RemoteCommandsService::RecordReceivedRemoteCommand(
+ RemoteCommandsService::MetricReceivedRemoteCommand metric) const {
+ const char* metric_name = GetMetricNameReceivedRemoteCommand(scope_);
+ base::UmaHistogramEnumeration(metric_name, metric);
+}
+
+void RemoteCommandsService::RecordExecutedRemoteCommand(
+ const RemoteCommandJob& command) const {
+ const std::string metric_name =
+ GetMetricNameExecutedRemoteCommand(scope_, command.GetType());
+ base::UmaHistogramEnumeration(metric_name, command.status(),
+ RemoteCommandJob::STATUS_TYPE_SIZE);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_service.h b/chromium/components/policy/core/common/remote_commands/remote_commands_service.h
new file mode 100644
index 00000000000..5bb3e648bc4
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_service.h
@@ -0,0 +1,183 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_SERVICE_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/circular_deque.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/policy_invalidation_scope.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
+#include "components/policy/policy_export.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace base {
+class Clock;
+class TickClock;
+} // namespace base
+
+namespace policy {
+
+class CloudPolicyClient;
+class CloudPolicyStore;
+class RemoteCommandsFactory;
+
+// Service class which will connect to a CloudPolicyClient in order to fetch
+// remote commands from DMServer and send results for executed commands
+// back to the server.
+class POLICY_EXPORT RemoteCommandsService
+ : public RemoteCommandsQueue::Observer {
+ public:
+ // Represents received remote command status to be recorded.
+ // This enum is used to define the buckets for an enumerated UMA histogram.
+ // Hence,
+ // (a) existing enumerated constants should never be deleted or reordered
+ // (b) new constants should only be appended at the end of the enumeration
+ // (update RemoteCommandReceivedStatus in
+ // tools/metrics/histograms/enums.xml as well).
+ enum class MetricReceivedRemoteCommand {
+ // Invalid remote commands.
+ kInvalidSignature = 0,
+ kInvalid = 1,
+ kUnknownType = 2,
+ kDuplicated = 3,
+ kInvalidScope = 4,
+ // Remote commands type.
+ kCommandEchoTest = 5,
+ kDeviceReboot = 6,
+ kDeviceScreenshot = 7,
+ kDeviceSetVolume = 8,
+ kDeviceFetchStatus = 9,
+ kUserArcCommand = 10,
+ kDeviceWipeUsers = 11,
+ kDeviceStartCrdSession = 12,
+ kDeviceRemotePowerwash = 13,
+ kDeviceRefreshEnterpriseMachineCertificate = 14,
+ kDeviceGetAvailableDiagnosticRoutines = 15,
+ kDeviceRunDiagnosticRoutine = 16,
+ kDeviceGetDiagnosticRoutineUpdate = 17,
+ kBrowserClearBrowsingData = 18,
+ kDeviceResetEuicc = 19,
+ kBrowserRotateAttestationCredential = 20,
+ // Used by UMA histograms. Shall refer to the last enumeration.
+ kMaxValue = kBrowserRotateAttestationCredential
+ };
+
+ // Returns the metric name to report received commands.
+ static const char* GetMetricNameReceivedRemoteCommand(
+ PolicyInvalidationScope scope);
+ // Returns the metric name to report status of executed commands.
+ static std::string GetMetricNameExecutedRemoteCommand(
+ PolicyInvalidationScope scope,
+ enterprise_management::RemoteCommand_Type command_type);
+
+ RemoteCommandsService(std::unique_ptr<RemoteCommandsFactory> factory,
+ CloudPolicyClient* client,
+ CloudPolicyStore* store,
+ PolicyInvalidationScope scope);
+ RemoteCommandsService(const RemoteCommandsService&) = delete;
+ RemoteCommandsService& operator=(const RemoteCommandsService&) = delete;
+ ~RemoteCommandsService() override;
+
+ // Attempts to fetch remote commands, mainly supposed to be called by
+ // invalidation service. Note that there will be at most one ongoing fetch
+ // request and all other fetch request will be enqueued if another fetch
+ // request is in-progress. And in such a case, another request will be made
+ // immediately after the current ongoing request finishes.
+ // Returns true if the new request was started immediately. Returns false if
+ // another request was in progress already and the new request got enqueued.
+ bool FetchRemoteCommands();
+
+ // Returns whether a command fetch request is in progress or not.
+ bool IsCommandFetchInProgressForTesting() const {
+ return command_fetch_in_progress_;
+ }
+
+ // Set alternative clocks for testing.
+ void SetClocksForTesting(const base::Clock* clock,
+ const base::TickClock* tick_clock);
+
+ // Sets a callback that will be invoked the next time we receive a response
+ // from the server.
+ virtual void SetOnCommandAckedCallback(base::OnceClosure callback);
+
+ private:
+ // Helper functions to enqueue a command which we get from server.
+ // |VerifyAndEnqueueSignedCommand| is used for the case of secure remote
+ // commands; it verifies the command, decodes it, and passes it onto
+ // |EnqueueCommand|. The latter one does some additional checks and then
+ // creates the correct job for the particular remote command (it also takes
+ // the original |signed_command| so it can pass it to the job for caching in
+ // case the particular job needs to do additional signature verification).
+ void VerifyAndEnqueueSignedCommand(
+ const enterprise_management::SignedData& signed_command);
+ void EnqueueCommand(const enterprise_management::RemoteCommand& command,
+ const enterprise_management::SignedData& signed_command);
+
+ // RemoteCommandsQueue::Observer:
+ void OnJobStarted(RemoteCommandJob* command) override;
+ void OnJobFinished(RemoteCommandJob* command) override;
+
+ // Callback to handle commands we get from the server.
+ void OnRemoteCommandsFetched(
+ DeviceManagementStatus status,
+ const std::vector<enterprise_management::SignedData>& signed_commands);
+
+ // Records UMA metric of received remote command.
+ void RecordReceivedRemoteCommand(MetricReceivedRemoteCommand metric) const;
+ // Records UMA metric of executed remote command.
+ void RecordExecutedRemoteCommand(const RemoteCommandJob& command) const;
+
+ // Whether there is a command fetch on going or not.
+ bool command_fetch_in_progress_ = false;
+
+ // Whether there is an enqueued fetch request, which indicates there were
+ // additional FetchRemoteCommands() calls while a fetch request was ongoing.
+ bool has_enqueued_fetch_request_ = false;
+
+ // Command results that have not been sent back to the server yet.
+ std::vector<enterprise_management::RemoteCommandResult> unsent_results_;
+
+ // Whether at least one command has finished executing.
+ bool has_finished_command_ = false;
+
+ // ID of the latest command which has finished execution if
+ // |has_finished_command_| is true. We will acknowledge this ID to the
+ // server so that we can re-fetch commands that have not been executed yet
+ // after a crash or browser restart.
+ RemoteCommandJob::UniqueIDType lastest_finished_command_id_;
+
+ // Collects the IDs of all fetched commands. We need this since the command ID
+ // is opaque.
+ // IDs will be stored in the order that they are fetched from the server,
+ // and acknowledging a command will discard its ID from
+ // |fetched_command_ids_|, as well as the IDs of every command before it.
+ base::circular_deque<RemoteCommandJob::UniqueIDType> fetched_command_ids_;
+
+ RemoteCommandsQueue queue_;
+ std::unique_ptr<RemoteCommandsFactory> factory_;
+ const raw_ptr<CloudPolicyClient> client_;
+ const raw_ptr<CloudPolicyStore> store_;
+
+ // Callback which gets called after the last command got ACK'd to the server
+ // as executed.
+ base::OnceClosure on_command_acked_callback_;
+
+ // Represents remote commands scope covered by service.
+ const PolicyInvalidationScope scope_;
+
+ base::WeakPtrFactory<RemoteCommandsService> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_REMOTE_COMMANDS_SERVICE_H_
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc
new file mode 100644
index 00000000000..05ae7e2508b
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc
@@ -0,0 +1,864 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/remote_commands_service.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check_op.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
+#include "base/test/repeating_test_future.h"
+#include "base/test/test_future.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/core/common/remote_commands/remote_commands_factory.h"
+#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
+#include "components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h"
+#include "components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnNew;
+using testing::StrictMock;
+
+namespace policy {
+
+namespace {
+
+namespace em = enterprise_management;
+
+const char kDMToken[] = "dmtoken";
+const int kTestEchoCommandExecutionTimeInSeconds = 1;
+const char kDeviceId[] = "acme-device";
+
+#define EXPECT_NO_CALLS(args...) EXPECT_CALL(args).Times(0)
+
+// Builder class that constructs a |RemoteCommand|.
+class CommandBuilder {
+ public:
+ CommandBuilder() = default;
+ CommandBuilder(const CommandBuilder&) = delete;
+ CommandBuilder& operator=(const CommandBuilder&) = delete;
+ CommandBuilder(CommandBuilder&&) = default;
+ CommandBuilder& operator=(CommandBuilder&&) = default;
+ ~CommandBuilder() = default;
+
+ em::RemoteCommand Build() { return result_; }
+
+ CommandBuilder& WithId(int id) {
+ result_.set_command_id(id);
+ return *this;
+ }
+
+ CommandBuilder& WithoutId() {
+ result_.clear_command_id();
+ return *this;
+ }
+
+ CommandBuilder& WithType(em::RemoteCommand::Type type) {
+ result_.set_type(type);
+ return *this;
+ }
+
+ CommandBuilder& WithoutType() {
+ result_.clear_type();
+ return *this;
+ }
+
+ CommandBuilder& WithPayload(const std::string& payload) {
+ result_.set_payload(payload);
+ return *this;
+ }
+
+ CommandBuilder& WithTargetDeviceId(const std::string& value) {
+ result_.set_target_device_id(value);
+ return *this;
+ }
+
+ private:
+ em::RemoteCommand result_;
+};
+
+// Helper class that builds enterprise_management::SignedData.
+class SignedDataBuilder {
+ public:
+ SignedDataBuilder() = default;
+ SignedDataBuilder(const SignedDataBuilder&) = delete;
+ SignedDataBuilder& operator=(const SignedDataBuilder&) = delete;
+ SignedDataBuilder(SignedDataBuilder&&) = default;
+ SignedDataBuilder& operator=(SignedDataBuilder&&) = default;
+ ~SignedDataBuilder() = default;
+
+ em::SignedData Build() { return BuildSignedData(command_.Build()); }
+
+ SignedDataBuilder& WithCommandId(int id) {
+ command_.WithId(id);
+ return *this;
+ }
+
+ SignedDataBuilder& WithoutCommandId() {
+ command_.WithoutId();
+ return *this;
+ }
+
+ SignedDataBuilder& WithCommandType(em::RemoteCommand::Type type) {
+ command_.WithType(type);
+ return *this;
+ }
+
+ SignedDataBuilder& WithoutCommandType() {
+ command_.WithoutType();
+ return *this;
+ }
+
+ SignedDataBuilder& WithCommandPayload(const std::string& value) {
+ command_.WithPayload(value);
+ return *this;
+ }
+
+ SignedDataBuilder& WithTargetDeviceId(const std::string& value) {
+ command_.WithTargetDeviceId(value);
+ return *this;
+ }
+
+ SignedDataBuilder& WithSignedData(const std::string& value) {
+ signed_data.set_data(value);
+ return *this;
+ }
+
+ SignedDataBuilder& WithSignature(const std::string& value) {
+ signed_data.set_signature(value);
+ return *this;
+ }
+
+ SignedDataBuilder& WithPolicyType(const std::string& value) {
+ policy_data.set_policy_type(value);
+ return *this;
+ }
+
+ SignedDataBuilder& WithPolicyValue(const std::string& value) {
+ policy_data.set_policy_value(value);
+ return *this;
+ }
+
+ private:
+ // The signed data defaults to correctly signing the remote command,
+ // unless it was explicitly overwritten during this test.
+ em::SignedData BuildSignedData(const em::RemoteCommand& command) {
+ if (!policy_data.has_policy_type())
+ policy_data.set_policy_type("google/chromeos/remotecommand");
+
+ if (!policy_data.has_policy_value())
+ command.SerializeToString(policy_data.mutable_policy_value());
+
+ if (!signed_data.has_data())
+ policy_data.SerializeToString(signed_data.mutable_data());
+
+ if (!signed_data.has_signature())
+ signed_data.set_signature(SignDataWithTestKey(signed_data.data()));
+
+ return signed_data;
+ }
+
+ CommandBuilder command_;
+ em::PolicyData policy_data;
+ em::SignedData signed_data;
+};
+
+// Future value that waits for the remote command result that is sent to the
+// server.
+class ServerResponseFuture
+ : public base::test::TestFuture<em::RemoteCommandResult> {
+ public:
+ ServerResponseFuture() = default;
+ ServerResponseFuture(const ServerResponseFuture&) = delete;
+ ServerResponseFuture& operator=(const ServerResponseFuture&) = delete;
+ ~ServerResponseFuture() = default;
+
+ ResultReportedCallback GetCallback() {
+ return base::test::TestFuture<em::RemoteCommandResult>::GetCallback<
+ const em::RemoteCommandResult&>();
+ }
+};
+
+// A fake remote command job. It will not report completion until the test
+// calls any of the Finish*() methods.
+class FakeJob : public RemoteCommandJob {
+ public:
+ explicit FakeJob(enterprise_management::RemoteCommand_Type type)
+ : type_(type) {}
+ FakeJob(const FakeJob&) = delete;
+ FakeJob& operator=(const FakeJob&) = delete;
+ ~FakeJob() override = default;
+
+ // Returns the payload passed to |ParseCommandPayload|.
+ const std::string& GetPayload() const { return payload_; }
+
+ // Finish this job and report success.
+ void FinishWithSuccess(const std::string& payload) {
+ DCHECK(!succeed_callback_.is_null());
+ std::move(succeed_callback_).Run(std::make_unique<StringPayload>(payload));
+ }
+ // Finish this job and report an error.
+ void FinishWithFailure(const std::string& payload) {
+ DCHECK(!failed_callback_.is_null());
+ std::move(failed_callback_).Run(std::make_unique<StringPayload>(payload));
+ }
+
+ void Finish() { FinishWithSuccess(""); }
+
+ // RemoteCommandJob implementation:
+ enterprise_management::RemoteCommand_Type GetType() const override {
+ return type_;
+ }
+ bool ParseCommandPayload(const std::string& command_payload) override {
+ payload_ = command_payload;
+ return true;
+ }
+ bool IsExpired(base::TimeTicks now) override { return false; }
+ void RunImpl(CallbackWithResult succeed_callback,
+ CallbackWithResult failed_callback) override {
+ succeed_callback_ = std::move(succeed_callback);
+ failed_callback_ = std::move(failed_callback);
+ }
+
+ private:
+ class StringPayload : public RemoteCommandJob::ResultPayload {
+ public:
+ explicit StringPayload(const std::string& payload) : payload_(payload) {}
+ StringPayload(const StringPayload&) = delete;
+ StringPayload& operator=(const StringPayload&) = delete;
+ ~StringPayload() override = default;
+
+ // RemoteCommandJob::ResultPayload implementation:
+ std::unique_ptr<std::string> Serialize() override {
+ return std::make_unique<std::string>(payload_);
+ }
+
+ private:
+ const std::string payload_;
+ };
+
+ const enterprise_management::RemoteCommand_Type type_;
+
+ // The payload passed to ParseCommandPayload().
+ std::string payload_;
+
+ CallbackWithResult succeed_callback_;
+ CallbackWithResult failed_callback_;
+};
+
+// Fake RemoteCommand factory that creates FakeJob instances.
+// It also provides a WaitForJob() method that will block until a job is
+// created, and return the corresponding fake_job. It is the responsibility of
+// the test to finish the fake_job (by calling any of the Finish*() methods).
+class FakeJobFactory : public RemoteCommandsFactory {
+ public:
+ FakeJobFactory() = default;
+ FakeJobFactory(const FakeJobFactory&) = delete;
+ FakeJobFactory& operator=(const FakeJobFactory&) = delete;
+ ~FakeJobFactory() override { DCHECK(created_jobs_.IsEmpty()); }
+
+ // Wait until a new job is constructed.
+ FakeJob& WaitForJob() {
+ FakeJob* result = created_jobs_.Take();
+ DCHECK_NE(result, nullptr);
+ return *result;
+ }
+
+ private:
+ // RemoteCommandJobsFactory:
+ std::unique_ptr<RemoteCommandJob> BuildJobForType(
+ em::RemoteCommand_Type type,
+ RemoteCommandsService* service) override {
+ auto result = std::make_unique<FakeJob>(type);
+
+ created_jobs_.AddValue(result.get());
+ return result;
+ }
+
+ // List of all jobs created in BuildJobForType() and consumed in
+ // WaitForJob().
+ base::test::RepeatingTestFuture<FakeJob*> created_jobs_;
+};
+
+// Mocked RemoteCommand factory.
+// This will by default create remote command jobs that finish automatically,
+// but you can overwrite this by adding an EXPECT_CALL to your code:
+// EXPECT_CALL(job_factory, BuildJobForType).WillOnce(Return(<your job>));
+//
+// You can use this instead of |FakeJobFactory| if:
+// - You need to simulate a failure to create the job (in which case you
+// can return nullptr like this):
+// EXPECT_CALL(job_factory, BuildJobForType).WillOnce(Return(nullptr));
+// - You want the job to auto-finish without having to call one of the
+// Finish*() methods like you have to do with the |FakeJobFactory|.
+class MockJobFactory : public RemoteCommandsFactory {
+ public:
+ MockJobFactory() {
+ ON_CALL(*this, BuildJobForType(testing::_))
+ .WillByDefault(ReturnNew<EchoRemoteCommandJob>(
+ /*succeed=*/true,
+ /*execution_duration=*/base::Seconds(
+ kTestEchoCommandExecutionTimeInSeconds)));
+ }
+ MockJobFactory(const MockJobFactory&) = delete;
+ MockJobFactory& operator=(const MockJobFactory&) = delete;
+
+ MOCK_METHOD(RemoteCommandJob*,
+ BuildJobForType,
+ (em::RemoteCommand::Type type));
+
+ // ON_CALL(...).WillByDefault() does not support methods that return an
+ // unique_ptr, so we have to mock a version that returns a raw pointer, and
+ // wrap it here.
+ std::unique_ptr<RemoteCommandJob> BuildJobForType(
+ em::RemoteCommand::Type type,
+ RemoteCommandsService* service) override {
+ return base::WrapUnique<RemoteCommandJob>(BuildJobForType(type));
+ }
+};
+
+// A mocked CloudPolicyClient to interact with a TestingRemoteCommandsServer.
+class TestingCloudPolicyClientForRemoteCommands : public CloudPolicyClient {
+ public:
+ explicit TestingCloudPolicyClientForRemoteCommands(
+ TestingRemoteCommandsServer* server)
+ : CloudPolicyClient(nullptr /* service */,
+ nullptr /* url_loader_factory */,
+ CloudPolicyClient::DeviceDMTokenCallback()),
+ server_(server) {
+ dm_token_ = kDMToken;
+ }
+ TestingCloudPolicyClientForRemoteCommands(
+ const TestingCloudPolicyClientForRemoteCommands&) = delete;
+ TestingCloudPolicyClientForRemoteCommands& operator=(
+ const TestingCloudPolicyClientForRemoteCommands&) = delete;
+
+ ~TestingCloudPolicyClientForRemoteCommands() override = default;
+
+ private:
+ void FetchRemoteCommands(
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
+ const std::vector<em::RemoteCommandResult>& command_results,
+ RemoteCommandCallback callback) override {
+ std::vector<em::SignedData> commands =
+ server_->FetchCommands(std::move(last_command_id), command_results);
+
+ // Asynchronously send the response from the DMServer back to client.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), DM_STATUS_SUCCESS, commands));
+ }
+
+ raw_ptr<TestingRemoteCommandsServer> server_;
+};
+
+} // namespace
+
+// Base class for unit tests regarding remote commands service.
+class RemoteCommandsServiceTest
+ : public testing::TestWithParam<PolicyInvalidationScope> {
+ public:
+ RemoteCommandsServiceTest(const RemoteCommandsServiceTest&) = delete;
+ RemoteCommandsServiceTest& operator=(const RemoteCommandsServiceTest&) =
+ delete;
+
+ protected:
+ void SetUp() override {
+ // Set the public key and the device id.
+ std::vector<uint8_t> public_key = PolicyBuilder::GetPublicTestKey();
+ store_.policy_signature_public_key_.assign(public_key.begin(),
+ public_key.end());
+ auto policy_data = std::make_unique<em::PolicyData>();
+ policy_data->set_device_id("acme-device");
+ store_.set_policy_data_for_testing(std::move(policy_data));
+ }
+
+ RemoteCommandsServiceTest() {
+ server_.SetClock(mock_task_runner_->GetMockTickClock());
+ }
+
+ // Starts the RemoteCommandService using a job factory of the given type.
+ // Returns a reference to the job factory.
+ template <typename FactoryType>
+ FactoryType& StartServiceWith() {
+ auto factory = std::make_unique<FactoryType>();
+ auto* factory_ptr = factory.get();
+
+ remote_commands_service_ = std::make_unique<RemoteCommandsService>(
+ std::move(factory), &cloud_policy_client_, &store_, GetScope());
+ remote_commands_service_->SetClocksForTesting(
+ mock_task_runner_->GetMockClock(),
+ mock_task_runner_->GetMockTickClock());
+
+ return *factory_ptr;
+ }
+
+ void FetchRemoteCommands() {
+ // A return value of |true| means the fetch command was successfully issued.
+ EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
+ }
+
+ // Return a builder for a signed RemoteCommand, with the important fields set
+ // to valid defaults.
+ SignedDataBuilder Command() {
+ return std::move(
+ SignedDataBuilder{}
+ .WithCommandId(server_.GetNextCommandId())
+ .WithCommandType(em::RemoteCommand_Type_COMMAND_ECHO_TEST)
+ .WithTargetDeviceId(kDeviceId));
+ }
+
+ void FlushAllTasks() { mock_task_runner_->FastForwardUntilNoTasksRemain(); }
+
+ PolicyInvalidationScope GetScope() const { return GetParam(); }
+
+ const scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_ =
+ base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+ base::TestMockTimeTaskRunner::Type::kBoundToThread);
+
+ TestingRemoteCommandsServer server_;
+ TestingCloudPolicyClientForRemoteCommands cloud_policy_client_{&server_};
+ MockCloudPolicyStore store_;
+ std::unique_ptr<RemoteCommandsService> remote_commands_service_;
+};
+
+TEST_P(RemoteCommandsServiceTest,
+ ShouldCreateNoJobsIfServerHasNoRemoteCommands) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+
+ FetchRemoteCommands();
+ FlushAllTasks();
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldCreateJobWhenRemoteCommandIsFetched) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ server_.IssueCommand(
+ Command()
+ .WithCommandType(em::RemoteCommand_Type_DEVICE_FETCH_STATUS)
+ .WithCommandPayload("the payload")
+ .Build(),
+ {});
+
+ FetchRemoteCommands();
+
+ FakeJob& job = job_factory.WaitForJob();
+ EXPECT_EQ(job.GetType(), em::RemoteCommand_Type_DEVICE_FETCH_STATUS);
+ EXPECT_EQ(job.GetPayload(), "the payload");
+ job.Finish();
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldSendJobSuccessToRemoteServer) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+ ServerResponseFuture response_future;
+ server_.IssueCommand(Command().Build(), response_future.GetCallback());
+ FetchRemoteCommands();
+
+ job_factory.WaitForJob().FinishWithSuccess("<the-payload>");
+
+ // Now we wait until the result of the job is received by the server.
+ em::RemoteCommandResult result = response_future.Get();
+ EXPECT_EQ(result.result(), em::RemoteCommandResult_ResultType_RESULT_SUCCESS);
+ EXPECT_EQ(result.payload(), "<the-payload>");
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldSendJobFailureToRemoteServer) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ ServerResponseFuture response_future;
+ server_.IssueCommand(Command().Build(), response_future.GetCallback());
+ FetchRemoteCommands();
+
+ job_factory.WaitForJob().FinishWithFailure("<the-failure-payload>");
+
+ // Now we wait until the result of the job is received by the server.
+ em::RemoteCommandResult result = response_future.Get();
+ EXPECT_EQ(result.result(), em::RemoteCommandResult_ResultType_RESULT_FAILURE);
+ EXPECT_EQ(result.payload(), "<the-failure-payload>");
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldSendFailureToCreateJobToRemoteServer) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+
+ ServerResponseFuture response_future;
+ server_.IssueCommand(Command().Build(), response_future.GetCallback());
+ FetchRemoteCommands();
+
+ // Fail building of the job
+ EXPECT_CALL(job_factory, BuildJobForType).WillOnce(Return(nullptr));
+
+ // Now we wait until the result of the job is received by the server.
+ em::RemoteCommandResult result = response_future.Get();
+ EXPECT_EQ(result.result(), em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+ EXPECT_EQ(result.payload(), "");
+}
+
+TEST_P(RemoteCommandsServiceTest,
+ ShouldSupportMultipleRemoteCommandsSentTogether) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ // Send 2 remote commands
+ ServerResponseFuture first_future;
+ server_.IssueCommand(Command().WithCommandPayload("first").Build(),
+ first_future.GetCallback());
+ ServerResponseFuture second_future;
+ server_.IssueCommand(Command().WithCommandPayload("second").Build(),
+ second_future.GetCallback());
+ FetchRemoteCommands();
+
+ // Handle both jobs - in order.
+ FakeJob& first_job = job_factory.WaitForJob();
+ EXPECT_EQ(first_job.GetPayload(), "first");
+ first_job.FinishWithSuccess("first-result");
+
+ FakeJob& second_job = job_factory.WaitForJob();
+ EXPECT_EQ(second_job.GetPayload(), "second");
+ second_job.FinishWithSuccess("second-result");
+
+ // Expect results to both remote commands on the server.
+ em::RemoteCommandResult first_result = first_future.Get();
+ EXPECT_EQ(first_result.payload(), "first-result");
+
+ em::RemoteCommandResult second_result = second_future.Get();
+ EXPECT_EQ(second_result.payload(), "second-result");
+}
+
+TEST_P(RemoteCommandsServiceTest,
+ ShouldSupportMultipleRemoteCommandsSentBackToBack) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ // Send the first remote command.
+ ServerResponseFuture first_future;
+ server_.IssueCommand(Command().WithCommandPayload("first").Build(),
+ first_future.GetCallback());
+
+ FetchRemoteCommands();
+
+ // Send the second remote command after the first one is fetched.
+ ServerResponseFuture second_future;
+ server_.IssueCommand(Command().WithCommandPayload("second").Build(),
+ second_future.GetCallback());
+
+ // The system should now allow us to handle both jobs
+ // (by automatically fetching new remote commands when a job is finished).
+ FakeJob& first_job = job_factory.WaitForJob();
+ EXPECT_EQ(first_job.GetPayload(), "first");
+ first_job.FinishWithSuccess("first-result");
+
+ FakeJob& second_job = job_factory.WaitForJob();
+ EXPECT_EQ(second_job.GetPayload(), "second");
+ second_job.FinishWithSuccess("second-result");
+
+ // And the result of both jobs should be sent to the server.
+ em::RemoteCommandResult first_result = first_future.Get();
+ EXPECT_EQ(first_result.payload(), "first-result");
+
+ em::RemoteCommandResult second_result = second_future.Get();
+ EXPECT_EQ(second_result.payload(), "second-result");
+}
+
+TEST_P(RemoteCommandsServiceTest, NewCommandFollowingFetch) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ // Don't return anything on the first fetch.
+ server_.OnNextFetchCommandsCallReturnNothing();
+
+ // Add a command which will be issued after the first fetch.
+ server_.IssueCommand(
+ Command().WithCommandPayload("Command sent in the second fetch").Build(),
+ {});
+
+ // Attempt to fetch commands.
+ EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
+
+ // The command fetch should be in progress.
+ EXPECT_TRUE(remote_commands_service_->IsCommandFetchInProgressForTesting());
+
+ // And a following up fetch request should be enqueued.
+ // A return value of |false| means exactly that - another fetch request is in
+ // progress, but a follow up request has been enqueued.
+ EXPECT_FALSE(remote_commands_service_->FetchRemoteCommands());
+
+ FakeJob& job = job_factory.WaitForJob();
+ EXPECT_EQ(job.GetPayload(), "Command sent in the second fetch");
+ job.Finish();
+}
+
+// Tests that the 'acked callback' gets called after the next response from the
+// server.
+TEST_P(RemoteCommandsServiceTest, AckedCallback) {
+ auto& job_factory = StartServiceWith<FakeJobFactory>();
+
+ // Fetch the command.
+ ServerResponseFuture response_future;
+ server_.IssueCommand(Command().Build(), response_future.GetCallback());
+ FetchRemoteCommands();
+
+ // Wait for the job to be created. This means the fetch is completed.
+ FakeJob& job = job_factory.WaitForJob();
+
+ // Now we install the ack callback, which should be invoked only after the
+ // job finished and its result was uploaded to the server.
+ StrictMock<base::MockOnceCallback<void()>> ack_callback;
+ remote_commands_service_->SetOnCommandAckedCallback(ack_callback.Get());
+
+ job.Finish();
+
+ // Wait for the result to be uploaded to the server.
+ ASSERT_TRUE(response_future.Wait());
+
+ // Only now we should expect the ack callback to be called.
+ EXPECT_CALL(ack_callback, Run());
+
+ FlushAllTasks();
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldRejectCommandWithInvalidSignature) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ ServerResponseFuture response_future;
+
+ server_.IssueCommand(Command().WithSignature("random-signature").Build(),
+ response_future.GetCallback());
+ FetchRemoteCommands();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+ EXPECT_EQ(response_future.Get().result(),
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldRejectCommandWithInvalidSignedData) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ ServerResponseFuture response_future;
+
+ server_.IssueCommand(Command().WithSignedData("random-data").Build(),
+ response_future.GetCallback());
+ FetchRemoteCommands();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+ EXPECT_EQ(response_future.Get().result(),
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldRejectCommandWithInvalidPolicyType) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ ServerResponseFuture response_future;
+
+ server_.IssueCommand(Command().WithPolicyType("random-policy-type").Build(),
+ response_future.GetCallback());
+ FetchRemoteCommands();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+ EXPECT_EQ(response_future.Get().result(),
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+}
+
+TEST_P(RemoteCommandsServiceTest, ShouldRejectCommandWithInvalidPolicyValue) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ ServerResponseFuture response_future;
+
+ server_.IssueCommand(Command().WithPolicyValue("random-policy-value").Build(),
+ response_future.GetCallback());
+ FetchRemoteCommands();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+ EXPECT_EQ(response_future.Get().result(),
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+}
+
+TEST_P(RemoteCommandsServiceTest,
+ ShouldRejectCommandWithInvalidTargetDeviceId) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ ServerResponseFuture response_future;
+
+ server_.IssueCommand(Command().WithTargetDeviceId("wrong-device-id").Build(),
+ response_future.GetCallback());
+ FetchRemoteCommands();
+
+ EXPECT_NO_CALLS(job_factory, BuildJobForType);
+ EXPECT_EQ(response_future.Get().result(),
+ em::RemoteCommandResult_ResultType_RESULT_IGNORED);
+}
+
+class RemoteCommandsServiceHistogramTest : public RemoteCommandsServiceTest {
+ protected:
+ using MetricReceivedRemoteCommand =
+ RemoteCommandsService::MetricReceivedRemoteCommand;
+
+ RemoteCommandsServiceHistogramTest() {
+ StartServiceWith<NiceMock<MockJobFactory>>();
+ }
+
+ std::string GetMetricNameReceived() {
+ return RemoteCommandsService::GetMetricNameReceivedRemoteCommand(
+ GetScope());
+ }
+
+ std::string GetMetricNameExecuted() {
+ return RemoteCommandsService::GetMetricNameExecutedRemoteCommand(
+ GetScope(), em::RemoteCommand_Type_COMMAND_ECHO_TEST);
+ }
+
+ void ExpectReceivedCommandsMetrics(
+ const std::vector<MetricReceivedRemoteCommand>& metrics) {
+ for (const auto& metric : metrics) {
+ histogram_tester_.ExpectBucketCount(GetMetricNameReceived(), metric, 1);
+ }
+ histogram_tester_.ExpectTotalCount(GetMetricNameReceived(), metrics.size());
+ }
+
+ void ExpectExecutedCommandsMetrics(
+ const std::vector<RemoteCommandJob::Status>& metrics) {
+ for (const auto& metric : metrics) {
+ histogram_tester_.ExpectBucketCount(GetMetricNameExecuted(), metric, 1);
+ }
+ histogram_tester_.ExpectTotalCount(GetMetricNameExecuted(), metrics.size());
+ }
+
+ private:
+ base::HistogramTester histogram_tester_;
+};
+
+TEST_P(RemoteCommandsServiceHistogramTest, WhenNoCommandsNothingRecorded) {
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedCommandOfUnknownTypeRecordUnknownType) {
+ server_.IssueCommand(Command().WithoutCommandType().Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kUnknownType});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedCommandWithoutIdRecordInvalid) {
+ server_.IssueCommand(Command().WithoutCommandId().Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kInvalid});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedExistingCommandRecordDuplicated) {
+ server_.IssueCommand(Command().WithCommandId(222).Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ server_.IssueCommand(Command().WithCommandId(222).Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kCommandEchoTest,
+ MetricReceivedRemoteCommand::kDuplicated});
+ ExpectExecutedCommandsMetrics({RemoteCommandJob::SUCCEEDED});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenCannotBuildJobRecordInvalidScope) {
+ auto& job_factory = StartServiceWith<MockJobFactory>();
+ EXPECT_CALL(job_factory, BuildJobForType).WillOnce(Return(nullptr));
+
+ server_.IssueCommand(Command().Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kInvalidScope});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedInvalidSignatureRecordInvalidSignature) {
+ server_.IssueCommand(Command().WithSignature("wrong-signature").Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics(
+ {MetricReceivedRemoteCommand::kInvalidSignature});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedInvalidPolicyDataRecordInvalid) {
+ server_.IssueCommand(Command().WithPolicyType("random-policy-type").Build(),
+ {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kInvalid});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedInvalidTargetDeviceRecordInvalid) {
+ server_.IssueCommand(
+ Command().WithTargetDeviceId("invalid-device-id").Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kInvalid});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest,
+ WhenReceivedInvalidCommandRecordInvalid) {
+ server_.IssueCommand(Command().WithPolicyValue("wrong-value").Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics({MetricReceivedRemoteCommand::kInvalid});
+ ExpectExecutedCommandsMetrics({});
+}
+
+TEST_P(RemoteCommandsServiceHistogramTest, WhenReceivedValidCommandRecordType) {
+ server_.IssueCommand(Command().Build(), {});
+ FetchRemoteCommands();
+ FlushAllTasks();
+
+ ExpectReceivedCommandsMetrics(
+ {MetricReceivedRemoteCommand::kCommandEchoTest});
+ ExpectExecutedCommandsMetrics({RemoteCommandJob::SUCCEEDED});
+}
+
+INSTANTIATE_TEST_SUITE_P(RemoteCommandsServiceTestInstance,
+ RemoteCommandsServiceTest,
+ testing::Values(PolicyInvalidationScope::kUser,
+ PolicyInvalidationScope::kDevice));
+
+INSTANTIATE_TEST_SUITE_P(RemoteCommandsServiceHistogramTestInstance,
+ RemoteCommandsServiceHistogramTest,
+ testing::Values(PolicyInvalidationScope::kUser,
+ PolicyInvalidationScope::kDevice));
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.cc b/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.cc
new file mode 100644
index 00000000000..6e947488fff
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.cc
@@ -0,0 +1,79 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check_op.h"
+#include "base/location.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace policy {
+
+const int kCommandExpirationTimeInHours = 3;
+
+namespace em = enterprise_management;
+
+const char EchoRemoteCommandJob::kMalformedCommandPayload[] =
+ "_MALFORMED_COMMAND_PAYLOAD_";
+
+class EchoRemoteCommandJob::EchoPayload
+ : public RemoteCommandJob::ResultPayload {
+ public:
+ explicit EchoPayload(const std::string& payload) : payload_(payload) {}
+ EchoPayload(const EchoPayload&) = delete;
+ EchoPayload& operator=(const EchoPayload&) = delete;
+
+ // RemoteCommandJob::ResultPayload:
+ std::unique_ptr<std::string> Serialize() override;
+
+ private:
+ const std::string payload_;
+};
+
+std::unique_ptr<std::string> EchoRemoteCommandJob::EchoPayload::Serialize() {
+ return std::make_unique<std::string>(payload_);
+}
+
+EchoRemoteCommandJob::EchoRemoteCommandJob(bool succeed,
+ base::TimeDelta execution_duration)
+ : succeed_(succeed), execution_duration_(execution_duration) {
+ DCHECK_LT(base::Seconds(0), execution_duration_);
+}
+
+em::RemoteCommand_Type EchoRemoteCommandJob::GetType() const {
+ return em::RemoteCommand_Type_COMMAND_ECHO_TEST;
+}
+
+bool EchoRemoteCommandJob::ParseCommandPayload(
+ const std::string& command_payload) {
+ if (command_payload == kMalformedCommandPayload)
+ return false;
+ command_payload_ = command_payload;
+ return true;
+}
+
+bool EchoRemoteCommandJob::IsExpired(base::TimeTicks now) {
+ return !issued_time().is_null() &&
+ now > issued_time() + base::Hours(kCommandExpirationTimeInHours);
+}
+
+void EchoRemoteCommandJob::RunImpl(CallbackWithResult succeed_callback,
+ CallbackWithResult failed_callback) {
+ std::unique_ptr<ResultPayload> echo_payload(
+ new EchoPayload(command_payload_));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(
+ succeed_ ? std::move(succeed_callback) : std::move(failed_callback),
+ std::move(echo_payload)),
+ execution_duration_);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h b/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h
new file mode 100644
index 00000000000..2ffcab8541f
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/test_support/echo_remote_command_job.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_ECHO_REMOTE_COMMAND_JOB_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_ECHO_REMOTE_COMMAND_JOB_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+// Remote Command Job that simply echos its payload.
+// Used during unittest.
+class EchoRemoteCommandJob : public RemoteCommandJob {
+ public:
+ EchoRemoteCommandJob(bool succeed, base::TimeDelta execution_duration);
+ EchoRemoteCommandJob(const EchoRemoteCommandJob&) = delete;
+ EchoRemoteCommandJob& operator=(const EchoRemoteCommandJob&) = delete;
+
+ // RemoteCommandJob:
+ enterprise_management::RemoteCommand_Type GetType() const override;
+
+ static const char kMalformedCommandPayload[];
+
+ private:
+ class EchoPayload;
+
+ // RemoteCommandJob:
+ bool ParseCommandPayload(const std::string& command_payload) override;
+ bool IsExpired(base::TimeTicks now) override;
+ void RunImpl(CallbackWithResult succeed_callback,
+ CallbackWithResult failed_callback) override;
+
+ std::string command_payload_;
+
+ const bool succeed_;
+ const base::TimeDelta execution_duration_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_ECHO_REMOTE_COMMAND_JOB_H_
diff --git a/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.cc b/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.cc
new file mode 100644
index 00000000000..9619dca7c85
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.cc
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h"
+
+#include <iterator>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/hash/sha1.h"
+#include "base/location.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "crypto/signature_creator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+int GetCommandIdOrDefault(const em::SignedData& signed_command) {
+ em::PolicyData policy_data;
+ if (!signed_command.has_data() ||
+ !policy_data.ParseFromString(signed_command.data())) {
+ return -1;
+ }
+
+ em::RemoteCommand command;
+ if (!policy_data.has_policy_value() ||
+ !command.ParseFromString(policy_data.policy_value())) {
+ return -1;
+ }
+
+ return command.command_id();
+}
+
+} // namespace
+
+std::string SignDataWithTestKey(const std::string& data) {
+ std::unique_ptr<crypto::RSAPrivateKey> private_key =
+ PolicyBuilder::CreateTestSigningKey();
+ std::string sha1 = base::SHA1HashString(data);
+ std::vector<uint8_t> digest(sha1.begin(), sha1.end());
+ std::vector<uint8_t> result;
+ CHECK(crypto::SignatureCreator::Sign(private_key.get(),
+ crypto::SignatureCreator::SHA1,
+ digest.data(), digest.size(), &result));
+ return std::string(result.begin(), result.end());
+}
+
+struct TestingRemoteCommandsServer::RemoteCommandWithCallback {
+ RemoteCommandWithCallback(const em::SignedData& command_proto,
+ base::TimeTicks issued_time,
+ ResultReportedCallback reported_callback)
+ : command_id(GetCommandIdOrDefault(command_proto)),
+ command_proto(command_proto),
+ issued_time(issued_time),
+ reported_callback(std::move(reported_callback)) {}
+
+ RemoteCommandWithCallback(RemoteCommandWithCallback&& other) = default;
+ RemoteCommandWithCallback& operator=(RemoteCommandWithCallback&& other) =
+ default;
+
+ ~RemoteCommandWithCallback() = default;
+
+ int command_id;
+ em::SignedData command_proto;
+ base::TimeTicks issued_time;
+ ResultReportedCallback reported_callback;
+};
+
+TestingRemoteCommandsServer::TestingRemoteCommandsServer()
+ : clock_(base::DefaultTickClock::GetInstance()),
+ task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+ weak_ptr_to_this_ = weak_factory_.GetWeakPtr();
+}
+
+TestingRemoteCommandsServer::~TestingRemoteCommandsServer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Commands are removed from the queue when a result is reported. Only
+ // commands for which no result was expected should remain in the queue.
+ for (const auto& command_with_callback : commands_)
+ EXPECT_TRUE(command_with_callback.reported_callback.is_null());
+}
+
+void TestingRemoteCommandsServer::IssueCommand(
+ const em::SignedData& signed_data,
+ ResultReportedCallback reported_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::AutoLock auto_lock(lock_);
+
+ commands_.emplace_back(signed_data, clock_->NowTicks(),
+ std::move(reported_callback));
+}
+
+void TestingRemoteCommandsServer::OnNextFetchCommandsCallReturnNothing() {
+ return_nothing_for_next_fetch_ = true;
+}
+
+std::vector<em::SignedData> TestingRemoteCommandsServer::FetchCommands(
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
+ const RemoteCommandResults& previous_job_results) {
+ base::AutoLock auto_lock(lock_);
+
+ HandleRemoteCommandResults(previous_job_results);
+
+ if (return_nothing_for_next_fetch_) {
+ return_nothing_for_next_fetch_ = false;
+ return {};
+ }
+
+ std::vector<em::SignedData> result;
+ for (const auto& command_with_callback : commands_)
+ result.push_back(command_with_callback.command_proto);
+
+ return result;
+}
+
+void TestingRemoteCommandsServer::HandleRemoteCommandResults(
+ const RemoteCommandResults& results) {
+ for (const auto& job_result : results) {
+ EXPECT_TRUE(job_result.has_command_id());
+ EXPECT_TRUE(job_result.has_result());
+
+ bool found_command = false;
+ ResultReportedCallback reported_callback;
+
+ if (job_result.command_id() == -1) {
+ // The result can have command_id equal to -1 in case a signed command was
+ // rejected at the validation stage before it could be unpacked.
+ CHECK_EQ(commands_.size(), 1lu);
+ found_command = true;
+ reported_callback = std::move(commands_[0].reported_callback);
+ commands_.clear();
+ }
+
+ for (auto it = commands_.begin(); it != commands_.end(); ++it) {
+ if (it->command_id == job_result.command_id()) {
+ reported_callback = std::move(it->reported_callback);
+ commands_.erase(it);
+ found_command = true;
+ break;
+ }
+ }
+
+ // Verify that the command result is for an existing command actually
+ // expecting a result.
+ EXPECT_TRUE(found_command);
+
+ if (!reported_callback.is_null()) {
+ // Post task to the original thread which will report the result.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&TestingRemoteCommandsServer::ReportJobResult,
+ weak_ptr_to_this_, std::move(reported_callback),
+ job_result));
+ }
+ }
+}
+
+void TestingRemoteCommandsServer::SetClock(const base::TickClock* clock) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ clock_ = clock;
+}
+
+size_t TestingRemoteCommandsServer::NumberOfCommandsPendingResult() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return commands_.size();
+}
+
+void TestingRemoteCommandsServer::ReportJobResult(
+ ResultReportedCallback reported_callback,
+ const em::RemoteCommandResult& job_result) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ std::move(reported_callback).Run(job_result);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h b/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h
new file mode 100644
index 00000000000..96556d72e2f
--- /dev/null
+++ b/chromium/components/policy/core/common/remote_commands/test_support/testing_remote_commands_server.h
@@ -0,0 +1,143 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_TESTING_REMOTE_COMMANDS_SERVER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_TESTING_REMOTE_COMMANDS_SERVER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "components/policy/core/common/remote_commands/remote_command_job.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace base {
+class TickClock;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace policy {
+
+// Callback called when a command's result is reported back to the server.
+using ResultReportedCallback =
+ base::OnceCallback<void(const enterprise_management::RemoteCommandResult&)>;
+
+std::string SignDataWithTestKey(const std::string& data);
+
+// This class implements server-side logic for remote commands service tests. It
+// acts just like a queue, and there are mainly two exposed methods for this
+// purpose. Test authors are expected to call IssueCommand() to add commands to
+// the queue in their tests. FetchCommands() should be called when serving a
+// request to the server intercepted e.g. via a net::URLRequestInterceptor or a
+// mock CloudPolicyClient.
+// In addition, this class also supports verifying command result sent back to
+// server, and test authors should provide a callback for this purpose when
+// issuing a command.
+// All methods except FetchCommands() should be called from the same thread, and
+// FetchCommands() can be called from any thread.
+// Note that test author is responsible for ensuring that FetchCommands() is not
+// called from another thread after |this| has been destroyed.
+class TestingRemoteCommandsServer {
+ public:
+ TestingRemoteCommandsServer();
+ TestingRemoteCommandsServer(const TestingRemoteCommandsServer&) = delete;
+ TestingRemoteCommandsServer& operator=(const TestingRemoteCommandsServer&) =
+ delete;
+ virtual ~TestingRemoteCommandsServer();
+
+ using RemoteCommandResults =
+ std::vector<enterprise_management::RemoteCommandResult>;
+
+ using RemoteCommands = std::vector<enterprise_management::SignedData>;
+
+ // Add a command that will be returned on the next FetchCommands() call.
+ // |clock_| will be used to get the command issue time.
+ // |reported_callback| will be called from the same thread when a command
+ // result is reported back to the server.
+ void IssueCommand(const enterprise_management::SignedData& signed_data,
+ ResultReportedCallback reported_callback);
+
+ // Calling this will tell the server to return no commands on the next
+ // call to FetchCommands().
+ // All issued commands will instead be returned the second time
+ // FetchCommands() is called.
+ void OnNextFetchCommandsCallReturnNothing();
+
+ // Fetch commands, acknowledging all commands up to and including
+ // |last_command_id|, and provide |previous_job_results| as results for
+ // previous commands. |last_command_id| can be a nullptr which indicates that
+ // no commands will be acknowledged.
+ // See the protobuf definition for a definition of the protocol between server
+ // and client for remote command fetching.
+ // Unlike every other methods in the class, this method can be called from
+ // any thread.
+ RemoteCommands FetchCommands(
+ std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
+ const RemoteCommandResults& previous_job_results);
+
+ // Set alternative clock for obtaining the command issue time. The default
+ // clock uses the system clock.
+ void SetClock(const base::TickClock* clock);
+
+ // Get the number of commands for which no results have been reported yet.
+ // This number also includes commands which have not been fetched yet.
+ size_t NumberOfCommandsPendingResult() const;
+
+ // The latest command ID generated by server.
+ RemoteCommandJob::UniqueIDType last_command_id() const {
+ return last_generated_unique_id_;
+ }
+
+ RemoteCommandJob::UniqueIDType GetNextCommandId() {
+ return ++last_generated_unique_id_;
+ }
+
+ private:
+ struct RemoteCommandWithCallback;
+
+ void HandleRemoteCommandResults(const RemoteCommandResults& results);
+
+ void ReportJobResult(
+ ResultReportedCallback reported_callback,
+ const enterprise_management::RemoteCommandResult& job_result) const;
+
+ // If true no commands will be returned to the caller of the next
+ // FetchCommands() call.
+ bool return_nothing_for_next_fetch_ = false;
+
+ // The main command queue.
+ std::vector<RemoteCommandWithCallback> commands_;
+
+ // The latest command ID generated by server. This test server generates
+ // strictly monotonically increasing unique IDs.
+ RemoteCommandJob::UniqueIDType last_generated_unique_id_ = 0;
+
+ // Clock used to generate command issue time when IssueCommand() is called.
+ raw_ptr<const base::TickClock> clock_;
+
+ // A lock protecting the command queues, as well as generated and acknowledged
+ // IDs.
+ base::Lock lock_;
+
+ // The thread on which this test server is created, usually the UI thread.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // A weak pointer created early so that it can be accessed from other threads.
+ base::WeakPtr<TestingRemoteCommandsServer> weak_ptr_to_this_;
+
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<TestingRemoteCommandsServer> weak_factory_{this};
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_REMOTE_COMMANDS_TEST_SUPPORT_TESTING_REMOTE_COMMANDS_SERVER_H_
diff --git a/chromium/components/policy/core/common/schema.cc b/chromium/components/policy/core/common/schema.cc
new file mode 100644
index 00000000000..32f578aae54
--- /dev/null
+++ b/chromium/components/policy/core/common/schema.cc
@@ -0,0 +1,1655 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema.h"
+
+#include <limits.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <climits>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <utility>
+
+#include "base/check_op.h"
+#include "base/compiler_specific.h"
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/json_schema_constants.h"
+#include "components/policy/core/common/schema_internal.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace schema = json_schema_constants;
+
+namespace policy {
+
+using internal::PropertiesNode;
+using internal::PropertyNode;
+using internal::RestrictionNode;
+using internal::SchemaData;
+using internal::SchemaNode;
+
+namespace {
+
+struct ReferencesAndIDs {
+ // Maps schema "id" attributes to the corresponding SchemaNode index.
+ std::map<std::string, short> id_map;
+
+ // List of pairs of references to be assigned later. The string is the "id"
+ // whose corresponding index should be stored in the pointer, once all the IDs
+ // are available.
+ std::vector<std::pair<std::string, short*>> reference_list;
+};
+
+// Sizes for the storage arrays. These are calculated in advance so that the
+// arrays don't have to be resized during parsing, which would invalidate
+// pointers into their contents (i.e. string's c_str() and address of indices
+// for "$ref" attributes).
+struct StorageSizes {
+ StorageSizes()
+ : strings(0),
+ schema_nodes(0),
+ property_nodes(0),
+ properties_nodes(0),
+ restriction_nodes(0),
+ required_properties(0),
+ int_enums(0),
+ string_enums(0) {}
+ size_t strings;
+ size_t schema_nodes;
+ size_t property_nodes;
+ size_t properties_nodes;
+ size_t restriction_nodes;
+ size_t required_properties;
+ size_t int_enums;
+ size_t string_enums;
+};
+
+// |Schema::MaskSensitiveValues| will replace sensitive values with this string.
+// It should be consistent with the mask |NetworkConfigurationPolicyHandler|
+// uses for network credential fields.
+constexpr char kSensitiveValueMask[] = "********";
+
+// An invalid index, indicating that a node is not present; similar to a NULL
+// pointer.
+const short kInvalid = -1;
+
+// Maps a schema key to the corresponding base::Value::Type
+struct SchemaKeyToValueType {
+ const char* key;
+ base::Value::Type type;
+};
+
+// Allowed types and their base::Value::Type equivalent. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kSchemaTypesToValueTypes[] = {
+ {schema::kArray, base::Value::Type::LIST},
+ {schema::kBoolean, base::Value::Type::BOOLEAN},
+ {schema::kInteger, base::Value::Type::INTEGER},
+ {schema::kNumber, base::Value::Type::DOUBLE},
+ {schema::kObject, base::Value::Type::DICTIONARY},
+ {schema::kString, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kSchemaTypesToValueTypesEnd =
+ kSchemaTypesToValueTypes + std::size(kSchemaTypesToValueTypes);
+
+// Allowed attributes and types for type 'array'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForArray[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kItems, base::Value::Type::DICTIONARY},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForArrayEnd =
+ kAttributesAndTypesForArray + std::size(kAttributesAndTypesForArray);
+
+// Allowed attributes and types for type 'boolean'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForBoolean[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForBooleanEnd =
+ kAttributesAndTypesForBoolean + std::size(kAttributesAndTypesForBoolean);
+
+// Allowed attributes and types for type 'integer'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForInteger[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kEnum, base::Value::Type::LIST},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kMaximum, base::Value::Type::DOUBLE},
+ {schema::kMinimum, base::Value::Type::DOUBLE},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForIntegerEnd =
+ kAttributesAndTypesForInteger + std::size(kAttributesAndTypesForInteger);
+
+// Allowed attributes and types for type 'number'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForNumber[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForNumberEnd =
+ kAttributesAndTypesForNumber + std::size(kAttributesAndTypesForNumber);
+
+// Allowed attributes and types for type 'object'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForObject[] = {
+ {schema::kAdditionalProperties, base::Value::Type::DICTIONARY},
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kPatternProperties, base::Value::Type::DICTIONARY},
+ {schema::kProperties, base::Value::Type::DICTIONARY},
+ {schema::kRequired, base::Value::Type::LIST},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForObjectEnd =
+ kAttributesAndTypesForObject + std::size(kAttributesAndTypesForObject);
+
+// Allowed attributes and types for $ref. These are ordered alphabetically to
+// perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForRef[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kRef, base::Value::Type::STRING},
+ {schema::kTitle, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForRefEnd =
+ kAttributesAndTypesForRef + std::size(kAttributesAndTypesForRef);
+
+// Allowed attributes and types for type 'string'. These are ordered
+// alphabetically to perform binary search.
+const SchemaKeyToValueType kAttributesAndTypesForString[] = {
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kEnum, base::Value::Type::LIST},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kPattern, base::Value::Type::STRING},
+ {schema::kSensitiveValue, base::Value::Type::BOOLEAN},
+ {schema::kTitle, base::Value::Type::STRING},
+ {schema::kType, base::Value::Type::STRING},
+};
+const SchemaKeyToValueType* kAttributesAndTypesForStringEnd =
+ kAttributesAndTypesForString + std::size(kAttributesAndTypesForString);
+
+// Helper for std::lower_bound.
+bool CompareToString(const SchemaKeyToValueType& entry,
+ const std::string& key) {
+ return entry.key < key;
+}
+
+// Returns true if a SchemaKeyToValueType with key==|schema_key| can be found in
+// the array represented by |begin| and |end|. If so, |value_type| will be set
+// to the SchemaKeyToValueType value type.
+bool MapSchemaKeyToValueType(const std::string& schema_key,
+ const SchemaKeyToValueType* begin,
+ const SchemaKeyToValueType* end,
+ base::Value::Type* value_type) {
+ const SchemaKeyToValueType* entry =
+ std::lower_bound(begin, end, schema_key, CompareToString);
+ if (entry == end || entry->key != schema_key)
+ return false;
+ if (value_type)
+ *value_type = entry->type;
+ return true;
+}
+
+// Shorthand method for |SchemaTypeToValueType()| with
+// |kSchemaTypesToValueTypes|.
+bool SchemaTypeToValueType(const std::string& schema_type,
+ base::Value::Type* value_type) {
+ return MapSchemaKeyToValueType(schema_type, kSchemaTypesToValueTypes,
+ kSchemaTypesToValueTypesEnd, value_type);
+}
+
+bool StrategyAllowUnknown(SchemaOnErrorStrategy strategy) {
+ return strategy != SCHEMA_STRICT;
+}
+
+bool StrategyAllowInvalidListEntry(SchemaOnErrorStrategy strategy) {
+ return strategy == SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY;
+}
+
+void SchemaErrorFound(std::string* out_error_path,
+ std::string* out_error,
+ const std::string& msg) {
+ if (out_error_path)
+ *out_error_path = "";
+ if (out_error)
+ *out_error = msg;
+}
+
+void AddListIndexPrefixToPath(int index, std::string* path) {
+ if (path) {
+ if (path->empty())
+ *path = base::StringPrintf("items[%d]", index);
+ else
+ *path = base::StringPrintf("items[%d].", index) + *path;
+ }
+}
+
+void AddDictKeyPrefixToPath(const std::string& key, std::string* path) {
+ if (path) {
+ if (path->empty())
+ *path = key;
+ else
+ *path = key + "." + *path;
+ }
+}
+
+bool IgnoreUnknownAttributes(int options) {
+ return (options & kSchemaOptionsIgnoreUnknownAttributes);
+}
+
+// Check that the value's type and the expected type are equal. We also allow
+// integers when expecting doubles.
+bool CheckType(const base::Value* value, base::Value::Type expected_type) {
+ return value->type() == expected_type ||
+ (value->is_int() && expected_type == base::Value::Type::DOUBLE);
+}
+
+// Returns true if |type| is supported as schema's 'type' value.
+bool IsValidType(const std::string& type) {
+ return MapSchemaKeyToValueType(type, kSchemaTypesToValueTypes,
+ kSchemaTypesToValueTypesEnd, nullptr);
+}
+
+// Validate that |dict| only contains attributes that are allowed for the
+// corresponding value of 'type'. Also ensure that all of those attributes are
+// of the expected type. |options| can be used to ignore unknown attributes.
+bool ValidateAttributesAndTypes(const base::Value& dict,
+ const std::string& type,
+ int options,
+ std::string* error) {
+ const SchemaKeyToValueType* begin = nullptr;
+ const SchemaKeyToValueType* end = nullptr;
+ if (type == schema::kArray) {
+ begin = kAttributesAndTypesForArray;
+ end = kAttributesAndTypesForArrayEnd;
+ } else if (type == schema::kBoolean) {
+ begin = kAttributesAndTypesForBoolean;
+ end = kAttributesAndTypesForBooleanEnd;
+ } else if (type == schema::kInteger) {
+ begin = kAttributesAndTypesForInteger;
+ end = kAttributesAndTypesForIntegerEnd;
+ } else if (type == schema::kNumber) {
+ begin = kAttributesAndTypesForNumber;
+ end = kAttributesAndTypesForNumberEnd;
+ } else if (type == schema::kObject) {
+ begin = kAttributesAndTypesForObject;
+ end = kAttributesAndTypesForObjectEnd;
+ } else if (type == schema::kRef) {
+ begin = kAttributesAndTypesForRef;
+ end = kAttributesAndTypesForRefEnd;
+ } else if (type == schema::kString) {
+ begin = kAttributesAndTypesForString;
+ end = kAttributesAndTypesForStringEnd;
+ } else {
+ NOTREACHED() << "Type should be a valid schema type or '$ref'.";
+ }
+
+ base::Value::Type expected_type = base::Value::Type::NONE;
+ for (auto it : dict.DictItems()) {
+ if (MapSchemaKeyToValueType(it.first, begin, end, &expected_type)) {
+ if (!CheckType(&it.second, expected_type)) {
+ *error = base::StringPrintf("Invalid type for attribute '%s'",
+ it.first.c_str());
+ return false;
+ }
+ } else if (!IgnoreUnknownAttributes(options)) {
+ *error = base::StringPrintf("Unknown attribute '%s'", it.first.c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+// Validates that |enum_list| is a list and its items are all of type |type|.
+bool ValidateEnum(const base::Value* enum_list,
+ const std::string& type,
+ std::string* error) {
+ if (enum_list->type() != base::Value::Type::LIST ||
+ enum_list->GetListDeprecated().empty()) {
+ *error = "Attribute 'enum' must be a non-empty list.";
+ return false;
+ }
+ base::Value::Type expected_item_type = base::Value::Type::NONE;
+ MapSchemaKeyToValueType(type, kSchemaTypesToValueTypes,
+ kSchemaTypesToValueTypesEnd, &expected_item_type);
+ for (const base::Value& item : enum_list->GetListDeprecated()) {
+ if (item.type() != expected_item_type) {
+ *error = base::StringPrintf(
+ "Attribute 'enum' for type '%s' contains items with invalid types",
+ type.c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+// Forward declaration (used in ValidateProperties).
+bool IsValidSchema(const base::Value& dict, int options, std::string* error);
+
+// Validates that the values in the |properties| dict are valid schemas.
+bool ValidateProperties(const base::Value& properties,
+ int options,
+ std::string* error) {
+ for (auto dict_it : properties.DictItems()) {
+ if (dict_it.second.type() != base::Value::Type::DICTIONARY) {
+ *error = base::StringPrintf("Schema for property '%s' must be a dict.",
+ dict_it.first.c_str());
+ return false;
+ }
+ if (!IsValidSchema(dict_it.second, options, error))
+ return false;
+ }
+ return true;
+}
+
+// Checks whether the passed dict is a valid schema. See
+// |kAllowedAttributesAndTypes| for a list of supported types, supported
+// attributes and their expected types. Values for 'minimum' and 'maximum' for
+// type 'integer' can be of type int or double. Referenced IDs ($ref) are not
+// checked for existence and IDs are not checked for duplicates. The 'pattern'
+// attribute and keys for 'patternProperties' are not checked for valid regulax
+// expression syntax. Invalid regular expressions will cause a value validation
+// error.
+bool IsValidSchema(const base::Value& dict, int options, std::string* error) {
+ DCHECK(dict.is_dict());
+ // Validate '$ref'.
+ const base::Value* ref_id = dict.FindKey(schema::kRef);
+ if (ref_id)
+ return ValidateAttributesAndTypes(dict, schema::kRef, options, error);
+
+ // Validate 'type'.
+ const base::Value* type = dict.FindKey(schema::kType);
+ if (!type) {
+ *error = "Each schema must have a 'type' or '$ref'.";
+ return false;
+ }
+ if (type->type() != base::Value::Type::STRING) {
+ *error = "Attribute 'type' must be a string.";
+ return false;
+ }
+ const std::string type_string = type->GetString();
+ if (!IsValidType(type_string)) {
+ *error = base::StringPrintf("Unknown type '%s'.", type_string.c_str());
+ return false;
+ }
+
+ // Validate attributes and expected types.
+ if (!ValidateAttributesAndTypes(dict, type_string, options, error))
+ return false;
+
+ // Validate 'enum' attribute.
+ if (type_string == schema::kString || type_string == schema::kInteger) {
+ const base::Value* enum_list = dict.FindKey(schema::kEnum);
+ if (enum_list && !ValidateEnum(enum_list, type_string, error))
+ return false;
+ }
+
+ if (type_string == schema::kInteger) {
+ // Validate 'minimum' > 'maximum'.
+ const base::Value* minimum_value = dict.FindKey(schema::kMinimum);
+ const base::Value* maximum_value = dict.FindKey(schema::kMaximum);
+ if (minimum_value && maximum_value) {
+ double minimum = minimum_value->is_int() ? minimum_value->GetInt()
+ : minimum_value->GetDouble();
+ double maximum = maximum_value->is_int() ? maximum_value->GetInt()
+ : maximum_value->GetDouble();
+ if (minimum > maximum) {
+ *error = base::StringPrintf("Invalid range specified [%f;%f].", minimum,
+ maximum);
+ return false;
+ }
+ }
+ } else if (type_string == schema::kArray) {
+ // Validate type 'array'.
+ const base::Value* items = dict.FindKey(schema::kItems);
+ if (!items) {
+ *error = "Schema of type 'array' must have a schema in 'items'.";
+ return false;
+ }
+ if (!IsValidSchema(*items, options, error))
+ return false;
+ } else if (type_string == schema::kObject) {
+ // Validate type 'object'.
+ const base::Value* properties = dict.FindKey(schema::kProperties);
+ if (properties && !ValidateProperties(*properties, options, error))
+ return false;
+
+ const base::Value* pattern_properties =
+ dict.FindKey(schema::kPatternProperties);
+ if (pattern_properties &&
+ !ValidateProperties(*pattern_properties, options, error)) {
+ return false;
+ }
+
+ const base::Value* additional_properties =
+ dict.FindKey(schema::kAdditionalProperties);
+ if (additional_properties) {
+ if (!IsValidSchema(*additional_properties, options, error))
+ return false;
+ }
+
+ const base::Value* required = dict.FindKey(schema::kRequired);
+ if (required) {
+ for (const base::Value& item : required->GetListDeprecated()) {
+ if (!item.is_string()) {
+ *error = "Attribute 'required' may only contain strings.";
+ return false;
+ }
+ const std::string property_name = item.GetString();
+ if (!properties || !properties->FindKey(property_name)) {
+ *error = base::StringPrintf(
+ "Attribute 'required' contains unknown property '%s'.",
+ property_name.c_str());
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+// Contains the internal data representation of a Schema. This can either wrap
+// a SchemaData owned elsewhere (currently used to wrap the Chrome schema, which
+// is generated at compile time), or it can own its own SchemaData.
+class Schema::InternalStorage
+ : public base::RefCountedThreadSafe<InternalStorage> {
+ public:
+ InternalStorage(const InternalStorage&) = delete;
+ InternalStorage& operator=(const InternalStorage&) = delete;
+
+ static scoped_refptr<const InternalStorage> Wrap(const SchemaData* data);
+
+ static scoped_refptr<const InternalStorage> ParseSchema(
+ const base::Value& schema,
+ std::string* error);
+
+ const SchemaData* data() const { return &schema_data_; }
+
+ const SchemaNode* root_node() const { return schema(0); }
+
+ // Returns the validation_schema root node if one was generated, or nullptr.
+ const SchemaNode* validation_schema_root_node() const {
+ return schema_data_.validation_schema_root_index >= 0
+ ? schema(schema_data_.validation_schema_root_index)
+ : nullptr;
+ }
+
+ const SchemaNode* schema(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.schema_nodes + index;
+ }
+
+ const PropertiesNode* properties(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.properties_nodes + index;
+ }
+
+ const PropertyNode* property(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.property_nodes + index;
+ }
+
+ const RestrictionNode* restriction(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.restriction_nodes + index;
+ }
+
+ const char* const* required_property(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.required_properties + index;
+ }
+
+ const int* int_enums(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.int_enums + index;
+ }
+
+ const char* const* string_enums(int index) const {
+ DCHECK_GE(index, 0);
+ return schema_data_.string_enums + index;
+ }
+
+ // Compiles regular expression |pattern|. The result is cached and will be
+ // returned directly next time.
+ re2::RE2* CompileRegex(const std::string& pattern) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<InternalStorage>;
+
+ InternalStorage();
+ ~InternalStorage();
+
+ // Determines the expected |sizes| of the storage for the representation
+ // of |schema|.
+ static void DetermineStorageSizes(const base::Value& schema,
+ StorageSizes* sizes);
+
+ // Parses the JSON schema in |schema|.
+ //
+ // If |schema| has a "$ref" attribute then a pending reference is appended
+ // to the |reference_list|, and nothing else is done.
+ //
+ // Otherwise, |index| gets assigned the index of the corresponding SchemaNode
+ // in |schema_nodes_|. If the |schema| contains an "id" then that ID is mapped
+ // to the |index| in the |id_map|.
+ //
+ // If |schema| is invalid then |error| gets the error reason and false is
+ // returned. Otherwise returns true.
+ bool Parse(const base::Value& schema,
+ short* index,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error);
+
+ // Helper for Parse() that gets an already assigned |schema_node| instead of
+ // an |index| pointer.
+ bool ParseDictionary(const base::Value& schema,
+ SchemaNode* schema_node,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error);
+
+ // Helper for Parse() that gets an already assigned |schema_node| instead of
+ // an |index| pointer.
+ bool ParseList(const base::Value& schema,
+ SchemaNode* schema_node,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error);
+
+ bool ParseEnum(const base::Value& schema,
+ base::Value::Type type,
+ SchemaNode* schema_node,
+ std::string* error);
+
+ bool ParseRangedInt(const base::Value& schema,
+ SchemaNode* schema_node,
+ std::string* error);
+
+ bool ParseStringPattern(const base::Value& schema,
+ SchemaNode* schema_node,
+ std::string* error);
+
+ // Assigns the IDs in |id_map| to the pending references in the
+ // |reference_list|. If an ID is missing then |error| is set and false is
+ // returned; otherwise returns true.
+ static bool ResolveReferences(const ReferencesAndIDs& references_and_ids,
+ std::string* error);
+
+ // Sets |has_sensitive_children| for all |SchemaNode|s in |schema_nodes_|.
+ void FindSensitiveChildren();
+
+ // Returns true iff the node at |index| has sensitive child elements or
+ // contains a sensitive value itself.
+ bool FindSensitiveChildrenRecursive(int index,
+ std::set<int>* handled_schema_nodes);
+
+ // Cache for CompileRegex(), will memorize return value of every call to
+ // CompileRegex() and return results directly next time.
+ mutable std::map<std::string, std::unique_ptr<re2::RE2>> regex_cache_;
+
+ SchemaData schema_data_;
+ std::vector<std::string> strings_;
+ std::vector<SchemaNode> schema_nodes_;
+ std::vector<PropertyNode> property_nodes_;
+ std::vector<PropertiesNode> properties_nodes_;
+ std::vector<RestrictionNode> restriction_nodes_;
+ std::vector<const char*> required_properties_;
+ std::vector<int> int_enums_;
+ std::vector<const char*> string_enums_;
+};
+
+Schema::InternalStorage::InternalStorage() {}
+
+Schema::InternalStorage::~InternalStorage() {}
+
+// static
+scoped_refptr<const Schema::InternalStorage> Schema::InternalStorage::Wrap(
+ const SchemaData* data) {
+ InternalStorage* storage = new InternalStorage();
+ storage->schema_data_ = *data;
+ return storage;
+}
+
+// static
+scoped_refptr<const Schema::InternalStorage>
+Schema::InternalStorage::ParseSchema(const base::Value& schema,
+ std::string* error) {
+ // Determine the sizes of the storage arrays and reserve the capacity before
+ // starting to append nodes and strings. This is important to prevent the
+ // arrays from being reallocated, which would invalidate the c_str() pointers
+ // and the addresses of indices to fix.
+ StorageSizes sizes;
+ DetermineStorageSizes(schema, &sizes);
+
+ scoped_refptr<InternalStorage> storage = new InternalStorage();
+ storage->strings_.reserve(sizes.strings);
+ storage->schema_nodes_.reserve(sizes.schema_nodes);
+ storage->property_nodes_.reserve(sizes.property_nodes);
+ storage->properties_nodes_.reserve(sizes.properties_nodes);
+ storage->restriction_nodes_.reserve(sizes.restriction_nodes);
+ storage->required_properties_.reserve(sizes.required_properties);
+ storage->int_enums_.reserve(sizes.int_enums);
+ storage->string_enums_.reserve(sizes.string_enums);
+
+ short root_index = kInvalid;
+ ReferencesAndIDs references_and_ids;
+ if (!storage->Parse(schema, &root_index, &references_and_ids, error))
+ return nullptr;
+
+ if (root_index == kInvalid) {
+ *error = "The main schema can't have a $ref";
+ return nullptr;
+ }
+
+ // None of this should ever happen without having been already detected.
+ // But, if it does happen, then it will lead to corrupted memory; drop
+ // everything in that case.
+ if (root_index != 0 || sizes.strings != storage->strings_.size() ||
+ sizes.schema_nodes != storage->schema_nodes_.size() ||
+ sizes.property_nodes != storage->property_nodes_.size() ||
+ sizes.properties_nodes != storage->properties_nodes_.size() ||
+ sizes.restriction_nodes != storage->restriction_nodes_.size() ||
+ sizes.required_properties != storage->required_properties_.size() ||
+ sizes.int_enums != storage->int_enums_.size() ||
+ sizes.string_enums != storage->string_enums_.size()) {
+ *error =
+ "Failed to parse the schema due to a Chrome bug. Please file a "
+ "new issue at http://crbug.com";
+ return nullptr;
+ }
+
+ if (!ResolveReferences(references_and_ids, error))
+ return nullptr;
+
+ storage->FindSensitiveChildren();
+
+ SchemaData* data = &storage->schema_data_;
+ data->schema_nodes = storage->schema_nodes_.data();
+ data->property_nodes = storage->property_nodes_.data();
+ data->properties_nodes = storage->properties_nodes_.data();
+ data->restriction_nodes = storage->restriction_nodes_.data();
+ data->required_properties = storage->required_properties_.data();
+ data->int_enums = storage->int_enums_.data();
+ data->string_enums = storage->string_enums_.data();
+ data->validation_schema_root_index = -1;
+
+ return storage;
+}
+
+re2::RE2* Schema::InternalStorage::CompileRegex(
+ const std::string& pattern) const {
+ auto it = regex_cache_.find(pattern);
+ if (it == regex_cache_.end()) {
+ std::unique_ptr<re2::RE2> compiled(new re2::RE2(pattern));
+ re2::RE2* compiled_ptr = compiled.get();
+ regex_cache_.insert(std::make_pair(pattern, std::move(compiled)));
+ return compiled_ptr;
+ }
+ return it->second.get();
+}
+
+// static
+void Schema::InternalStorage::DetermineStorageSizes(const base::Value& schema,
+ StorageSizes* sizes) {
+ if (schema.FindStringKey(schema::kRef)) {
+ // Schemas with a "$ref" attribute don't take additional storage.
+ return;
+ }
+
+ base::Value::Type type = base::Value::Type::NONE;
+ const std::string* type_string = schema.FindStringKey(schema::kType);
+ if (!type_string || !SchemaTypeToValueType(*type_string, &type)) {
+ // This schema is invalid.
+ return;
+ }
+
+ sizes->schema_nodes++;
+
+ if (type == base::Value::Type::LIST) {
+ const base::Value* items = schema.FindDictKey(schema::kItems);
+ if (items)
+ DetermineStorageSizes(*items, sizes);
+ } else if (type == base::Value::Type::DICTIONARY) {
+ sizes->properties_nodes++;
+
+ const base::Value* additional_properties =
+ schema.FindDictKey(schema::kAdditionalProperties);
+ if (additional_properties)
+ DetermineStorageSizes(*additional_properties, sizes);
+
+ const base::Value* properties = schema.FindDictKey(schema::kProperties);
+ if (properties) {
+ for (auto property : properties->DictItems()) {
+ DetermineStorageSizes(property.second, sizes);
+ sizes->strings++;
+ sizes->property_nodes++;
+ }
+ }
+
+ const base::Value* pattern_properties =
+ schema.FindDictKey(schema::kPatternProperties);
+ if (pattern_properties) {
+ for (auto pattern_property : pattern_properties->DictItems()) {
+ DetermineStorageSizes(pattern_property.second, sizes);
+ sizes->strings++;
+ sizes->property_nodes++;
+ }
+ }
+
+ const base::Value* required_properties = schema.FindKey(schema::kRequired);
+ if (required_properties) {
+ sizes->strings += required_properties->GetListDeprecated().size();
+ sizes->required_properties +=
+ required_properties->GetListDeprecated().size();
+ }
+ } else if (schema.FindKey(schema::kEnum)) {
+ const base::Value* possible_values = schema.FindListKey(schema::kEnum);
+ if (possible_values) {
+ size_t num_possible_values = possible_values->GetListDeprecated().size();
+ if (type == base::Value::Type::INTEGER) {
+ sizes->int_enums += num_possible_values;
+ } else if (type == base::Value::Type::STRING) {
+ sizes->string_enums += num_possible_values;
+ sizes->strings += num_possible_values;
+ }
+ sizes->restriction_nodes++;
+ }
+ } else if (type == base::Value::Type::INTEGER) {
+ if (schema.FindKey(schema::kMinimum) || schema.FindKey(schema::kMaximum))
+ sizes->restriction_nodes++;
+ } else if (type == base::Value::Type::STRING) {
+ if (schema.FindKey(schema::kPattern)) {
+ sizes->strings++;
+ sizes->string_enums++;
+ sizes->restriction_nodes++;
+ }
+ }
+}
+
+bool Schema::InternalStorage::Parse(const base::Value& schema,
+ short* index,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error) {
+ const std::string* ref = schema.FindStringKey(schema::kRef);
+ if (ref) {
+ if (schema.FindStringKey(schema::kId)) {
+ *error = "Schemas with a $ref can't have an id";
+ return false;
+ }
+ references_and_ids->reference_list.emplace_back(*ref, index);
+ return true;
+ }
+
+ const std::string* type_string = schema.FindStringKey(schema::kType);
+ if (!type_string) {
+ *error = "The schema type must be declared.";
+ return false;
+ }
+
+ base::Value::Type type = base::Value::Type::NONE;
+ if (!SchemaTypeToValueType(*type_string, &type)) {
+ *error = "Type not supported: " + *type_string;
+ return false;
+ }
+
+ if (schema_nodes_.size() > std::numeric_limits<short>::max()) {
+ *error = "Can't have more than " +
+ std::to_string(std::numeric_limits<short>::max()) +
+ " schema nodes.";
+ return false;
+ }
+ *index = static_cast<short>(schema_nodes_.size());
+ schema_nodes_.push_back(SchemaNode());
+ SchemaNode* schema_node = &schema_nodes_.back();
+ schema_node->type = type;
+ schema_node->extra = kInvalid;
+ schema_node->is_sensitive_value = false;
+
+ absl::optional<bool> is_sensitive_value =
+ schema.FindBoolKey(schema::kSensitiveValue);
+ if (is_sensitive_value)
+ schema_node->is_sensitive_value = *is_sensitive_value;
+
+ if (type == base::Value::Type::DICTIONARY) {
+ if (!ParseDictionary(schema, schema_node, references_and_ids, error))
+ return false;
+ } else if (type == base::Value::Type::LIST) {
+ if (!ParseList(schema, schema_node, references_and_ids, error))
+ return false;
+ } else if (schema.FindKey(schema::kEnum)) {
+ if (!ParseEnum(schema, type, schema_node, error))
+ return false;
+ } else if (schema.FindKey(schema::kPattern)) {
+ if (!ParseStringPattern(schema, schema_node, error))
+ return false;
+ } else if (schema.FindKey(schema::kMinimum) ||
+ schema.FindKey(schema::kMaximum)) {
+ if (type != base::Value::Type::INTEGER) {
+ *error = "Only integers can have minimum and maximum";
+ return false;
+ }
+ if (!ParseRangedInt(schema, schema_node, error))
+ return false;
+ }
+ const std::string* id = schema.FindStringKey(schema::kId);
+ if (id) {
+ auto& id_map = references_and_ids->id_map;
+ if (base::Contains(id_map, *id)) {
+ *error = "Duplicated id: " + *id;
+ return false;
+ }
+ id_map[*id] = *index;
+ }
+
+ return true;
+}
+
+bool Schema::InternalStorage::ParseDictionary(
+ const base::Value& schema,
+ SchemaNode* schema_node,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error) {
+ int extra = static_cast<int>(properties_nodes_.size());
+ properties_nodes_.push_back(PropertiesNode());
+ properties_nodes_[extra].additional = kInvalid;
+ schema_node->extra = extra;
+
+ const base::Value* additional_properties =
+ schema.FindDictKey(schema::kAdditionalProperties);
+ if (additional_properties) {
+ if (!Parse(*additional_properties, &properties_nodes_[extra].additional,
+ references_and_ids, error)) {
+ return false;
+ }
+ }
+
+ properties_nodes_[extra].begin = static_cast<int>(property_nodes_.size());
+
+ const base::Value* properties = schema.FindDictKey(schema::kProperties);
+ if (properties) {
+ // This and below reserves nodes for all of the |properties|, and makes sure
+ // they are contiguous. Recursive calls to Parse() will append after these
+ // elements.
+ property_nodes_.resize(property_nodes_.size() + properties->DictSize());
+ }
+
+ properties_nodes_[extra].end = static_cast<int>(property_nodes_.size());
+
+ const base::Value* pattern_properties =
+ schema.FindDictKey(schema::kPatternProperties);
+ if (pattern_properties) {
+ property_nodes_.resize(property_nodes_.size() +
+ pattern_properties->DictSize());
+ }
+
+ properties_nodes_[extra].pattern_end =
+ static_cast<int>(property_nodes_.size());
+
+ if (properties != nullptr) {
+ int base_index = properties_nodes_[extra].begin;
+ int index = base_index;
+
+ for (auto property : properties->DictItems()) {
+ strings_.push_back(property.first);
+ property_nodes_[index].key = strings_.back().c_str();
+ if (!Parse(property.second, &property_nodes_[index].schema,
+ references_and_ids, error)) {
+ return false;
+ }
+ ++index;
+ }
+ CHECK_EQ(static_cast<int>(properties->DictSize()), index - base_index);
+ }
+
+ if (pattern_properties != nullptr) {
+ int base_index = properties_nodes_[extra].end;
+ int index = base_index;
+
+ for (auto pattern_property : pattern_properties->DictItems()) {
+ re2::RE2* compiled_regex = CompileRegex(pattern_property.first);
+ if (!compiled_regex->ok()) {
+ *error = "/" + pattern_property.first +
+ "/ is a invalid regex: " + compiled_regex->error();
+ return false;
+ }
+ strings_.push_back(pattern_property.first);
+ property_nodes_[index].key = strings_.back().c_str();
+ if (!Parse(pattern_property.second, &property_nodes_[index].schema,
+ references_and_ids, error)) {
+ return false;
+ }
+ ++index;
+ }
+ CHECK_EQ(static_cast<int>(pattern_properties->DictSize()),
+ index - base_index);
+ }
+
+ properties_nodes_[extra].required_begin = required_properties_.size();
+ const base::Value* required_properties = schema.FindKey(schema::kRequired);
+ if (required_properties) {
+ for (const base::Value& val : required_properties->GetListDeprecated()) {
+ strings_.push_back(val.GetString());
+ required_properties_.push_back(strings_.back().c_str());
+ }
+ }
+ properties_nodes_[extra].required_end = required_properties_.size();
+
+ if (properties_nodes_[extra].begin == properties_nodes_[extra].pattern_end) {
+ properties_nodes_[extra].begin = kInvalid;
+ properties_nodes_[extra].end = kInvalid;
+ properties_nodes_[extra].pattern_end = kInvalid;
+ properties_nodes_[extra].required_begin = kInvalid;
+ properties_nodes_[extra].required_end = kInvalid;
+ }
+
+ return true;
+}
+
+bool Schema::InternalStorage::ParseList(const base::Value& schema,
+ SchemaNode* schema_node,
+ ReferencesAndIDs* references_and_ids,
+ std::string* error) {
+ const base::Value* items = schema.FindDictKey(schema::kItems);
+ if (!items) {
+ *error = "Arrays must declare a single schema for their items.";
+ return false;
+ }
+ return Parse(*items, &schema_node->extra, references_and_ids, error);
+}
+
+bool Schema::InternalStorage::ParseEnum(const base::Value& schema,
+ base::Value::Type type,
+ SchemaNode* schema_node,
+ std::string* error) {
+ const base::Value* possible_values = schema.FindListKey(schema::kEnum);
+ if (!possible_values) {
+ *error = "Enum attribute must be a list value";
+ return false;
+ }
+ if (possible_values->GetListDeprecated().empty()) {
+ *error = "Enum attribute must be non-empty";
+ return false;
+ }
+ int offset_begin;
+ int offset_end;
+ if (type == base::Value::Type::INTEGER) {
+ offset_begin = static_cast<int>(int_enums_.size());
+ for (const auto& possible_value : possible_values->GetListDeprecated()) {
+ if (!possible_value.is_int()) {
+ *error = "Invalid enumeration member type";
+ return false;
+ }
+ int_enums_.push_back(possible_value.GetInt());
+ }
+ offset_end = static_cast<int>(int_enums_.size());
+ } else if (type == base::Value::Type::STRING) {
+ offset_begin = static_cast<int>(string_enums_.size());
+ for (const auto& possible_value : possible_values->GetListDeprecated()) {
+ if (!possible_value.is_string()) {
+ *error = "Invalid enumeration member type";
+ return false;
+ }
+ strings_.push_back(possible_value.GetString());
+ string_enums_.push_back(strings_.back().c_str());
+ }
+ offset_end = static_cast<int>(string_enums_.size());
+ } else {
+ *error = "Enumeration is only supported for integer and string.";
+ return false;
+ }
+ schema_node->extra = static_cast<int>(restriction_nodes_.size());
+ restriction_nodes_.push_back(RestrictionNode());
+ restriction_nodes_.back().enumeration_restriction.offset_begin = offset_begin;
+ restriction_nodes_.back().enumeration_restriction.offset_end = offset_end;
+ return true;
+}
+
+bool Schema::InternalStorage::ParseRangedInt(const base::Value& schema,
+ SchemaNode* schema_node,
+ std::string* error) {
+ int min_value = schema.FindIntKey(schema::kMinimum).value_or(INT_MIN);
+ int max_value = schema.FindIntKey(schema::kMaximum).value_or(INT_MAX);
+ if (min_value > max_value) {
+ *error = "Invalid range restriction for int type.";
+ return false;
+ }
+ schema_node->extra = static_cast<int>(restriction_nodes_.size());
+ restriction_nodes_.push_back(RestrictionNode());
+ restriction_nodes_.back().ranged_restriction.max_value = max_value;
+ restriction_nodes_.back().ranged_restriction.min_value = min_value;
+ return true;
+}
+
+bool Schema::InternalStorage::ParseStringPattern(const base::Value& schema,
+ SchemaNode* schema_node,
+ std::string* error) {
+ const std::string* pattern = schema.FindStringKey(schema::kPattern);
+ if (!pattern) {
+ *error = "Schema pattern must be a string.";
+ return false;
+ }
+ re2::RE2* compiled_regex = CompileRegex(*pattern);
+ if (!compiled_regex->ok()) {
+ *error = "/" + *pattern + "/ is invalid regex: " + compiled_regex->error();
+ return false;
+ }
+ int index = static_cast<int>(string_enums_.size());
+ strings_.push_back(*pattern);
+ string_enums_.push_back(strings_.back().c_str());
+ schema_node->extra = static_cast<int>(restriction_nodes_.size());
+ restriction_nodes_.push_back(RestrictionNode());
+ restriction_nodes_.back().string_pattern_restriction.pattern_index = index;
+ restriction_nodes_.back().string_pattern_restriction.pattern_index_backup =
+ index;
+ return true;
+}
+
+// static
+bool Schema::InternalStorage::ResolveReferences(
+ const ReferencesAndIDs& references_and_ids,
+ std::string* error) {
+ const auto& reference_list = references_and_ids.reference_list;
+ const auto& id_map = references_and_ids.id_map;
+ for (auto& ref : reference_list) {
+ auto id = id_map.find(ref.first);
+ if (id == id_map.end()) {
+ *error = "Invalid $ref: " + ref.first;
+ return false;
+ }
+ *ref.second = id->second;
+ }
+ return true;
+}
+
+void Schema::InternalStorage::FindSensitiveChildren() {
+ if (schema_nodes_.empty())
+ return;
+
+ std::set<int> handled_schema_nodes;
+ FindSensitiveChildrenRecursive(0, &handled_schema_nodes);
+}
+
+bool Schema::InternalStorage::FindSensitiveChildrenRecursive(
+ int index,
+ std::set<int>* handled_schema_nodes) {
+ DCHECK(static_cast<unsigned long>(index) < schema_nodes_.size());
+ SchemaNode& schema_node = schema_nodes_[index];
+ if (handled_schema_nodes->find(index) != handled_schema_nodes->end())
+ return schema_node.has_sensitive_children || schema_node.is_sensitive_value;
+
+ handled_schema_nodes->insert(index);
+ bool has_sensitive_children = false;
+ if (schema_node.type == base::Value::Type::DICTIONARY) {
+ const PropertiesNode& properties_node =
+ properties_nodes_[schema_node.extra];
+ // Iterate through properties and patternProperties.
+ for (int i = properties_node.begin; i < properties_node.pattern_end; ++i) {
+ int sub_index = property_nodes_[i].schema;
+ has_sensitive_children |=
+ FindSensitiveChildrenRecursive(sub_index, handled_schema_nodes);
+ }
+ if (properties_node.additional != kInvalid) {
+ has_sensitive_children |= FindSensitiveChildrenRecursive(
+ properties_node.additional, handled_schema_nodes);
+ }
+ } else if (schema_node.type == base::Value::Type::LIST) {
+ int sub_index = schema_node.extra;
+ has_sensitive_children |=
+ FindSensitiveChildrenRecursive(sub_index, handled_schema_nodes);
+ }
+ schema_node.has_sensitive_children = has_sensitive_children;
+
+ return schema_node.has_sensitive_children || schema_node.is_sensitive_value;
+}
+
+Schema::Iterator::Iterator(const scoped_refptr<const InternalStorage>& storage,
+ const PropertiesNode* node) {
+ if (node->begin == kInvalid || node->end == kInvalid) {
+ it_ = nullptr;
+ end_ = nullptr;
+ } else {
+ storage_ = storage;
+ it_ = storage->property(node->begin);
+ end_ = storage->property(node->end);
+ }
+}
+
+Schema::Iterator::Iterator(const Iterator& iterator)
+ : storage_(iterator.storage_), it_(iterator.it_), end_(iterator.end_) {}
+
+Schema::Iterator::~Iterator() {}
+
+Schema::Iterator& Schema::Iterator::operator=(const Iterator& iterator) {
+ storage_ = iterator.storage_;
+ it_ = iterator.it_;
+ end_ = iterator.end_;
+ return *this;
+}
+
+bool Schema::Iterator::IsAtEnd() const {
+ return it_ == end_;
+}
+
+void Schema::Iterator::Advance() {
+ DCHECK(it_);
+ ++it_;
+}
+
+const char* Schema::Iterator::key() const {
+ return it_->key;
+}
+
+Schema Schema::Iterator::schema() const {
+ return Schema(storage_, storage_->schema(it_->schema));
+}
+
+Schema::Schema() : node_(nullptr) {}
+
+Schema::Schema(const scoped_refptr<const InternalStorage>& storage,
+ const SchemaNode* node)
+ : storage_(storage), node_(node) {}
+
+Schema::Schema(const Schema& schema)
+ : storage_(schema.storage_), node_(schema.node_) {}
+
+Schema::~Schema() {}
+
+Schema& Schema::operator=(const Schema& schema) {
+ storage_ = schema.storage_;
+ node_ = schema.node_;
+ return *this;
+}
+
+// static
+Schema Schema::Wrap(const SchemaData* data) {
+ scoped_refptr<const InternalStorage> storage = InternalStorage::Wrap(data);
+ return Schema(storage, storage->root_node());
+}
+
+bool Schema::Validate(const base::Value& value,
+ SchemaOnErrorStrategy strategy,
+ std::string* out_error_path,
+ std::string* out_error) const {
+ if (!valid()) {
+ SchemaErrorFound(out_error_path, out_error, "The schema is invalid.");
+ return false;
+ }
+
+ if (value.type() != type()) {
+ // Allow the integer to double promotion. Note that range restriction on
+ // double is not supported now.
+ if (value.is_int() && type() == base::Value::Type::DOUBLE) {
+ return true;
+ }
+
+ SchemaErrorFound(out_error_path, out_error,
+ "The value type doesn't match the schema type.");
+ return false;
+ }
+
+ if (value.is_dict()) {
+ base::flat_set<std::string> present_properties;
+ for (auto dict_item : value.DictItems()) {
+ SchemaList schema_list = GetMatchingProperties(dict_item.first);
+ if (schema_list.empty()) {
+ // Unknown property was detected.
+ SchemaErrorFound(out_error_path, out_error,
+ "Unknown property: " + dict_item.first);
+ if (!StrategyAllowUnknown(strategy))
+ return false;
+ } else {
+ for (const auto& subschema : schema_list) {
+ std::string new_error;
+ const bool validation_result = subschema.Validate(
+ dict_item.second, strategy, out_error_path, &new_error);
+ if (!new_error.empty()) {
+ AddDictKeyPrefixToPath(dict_item.first, out_error_path);
+ if (out_error)
+ *out_error = std::move(new_error);
+ }
+ if (!validation_result) {
+ // Invalid property was detected.
+ return false;
+ }
+ }
+ present_properties.insert(dict_item.first);
+ }
+ }
+
+ for (const auto& required_property : GetRequiredProperties()) {
+ if (base::Contains(present_properties, required_property))
+ continue;
+
+ SchemaErrorFound(
+ out_error_path, out_error,
+ "Missing or invalid required property: " + required_property);
+ return false;
+ }
+ } else if (value.is_list()) {
+ for (size_t index = 0; index < value.GetListDeprecated().size(); ++index) {
+ const base::Value& list_item = value.GetListDeprecated()[index];
+ std::string new_error;
+ const bool validation_result =
+ GetItems().Validate(list_item, strategy, out_error_path, &new_error);
+ if (!new_error.empty()) {
+ AddListIndexPrefixToPath(index, out_error_path);
+ if (out_error)
+ *out_error = std::move(new_error);
+ }
+ if (!validation_result && !StrategyAllowInvalidListEntry(strategy))
+ return false; // Invalid list item was detected.
+ }
+ } else if (value.is_int()) {
+ if (node_->extra != kInvalid &&
+ !ValidateIntegerRestriction(node_->extra, value.GetInt())) {
+ SchemaErrorFound(out_error_path, out_error, "Invalid value for integer");
+ return false;
+ }
+ } else if (value.is_string()) {
+ if (node_->extra != kInvalid &&
+ !ValidateStringRestriction(node_->extra, value.GetString().c_str())) {
+ SchemaErrorFound(out_error_path, out_error, "Invalid value for string");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Schema::Normalize(base::Value* value,
+ SchemaOnErrorStrategy strategy,
+ std::string* out_error_path,
+ std::string* out_error,
+ bool* out_changed) const {
+ if (!valid()) {
+ SchemaErrorFound(out_error_path, out_error, "The schema is invalid.");
+ return false;
+ }
+
+ if (value->type() != type()) {
+ // Allow the integer to double promotion. Note that range restriction on
+ // double is not supported now.
+ if (value->is_int() && type() == base::Value::Type::DOUBLE) {
+ return true;
+ }
+
+ SchemaErrorFound(out_error_path, out_error,
+ "The value type doesn't match the schema type.");
+ return false;
+ }
+
+ if (value->is_dict()) {
+ base::flat_set<std::string> present_properties;
+ std::vector<std::string> drop_list; // Contains the keys to drop.
+ for (auto dict_item : value->DictItems()) {
+ SchemaList schema_list = GetMatchingProperties(dict_item.first);
+ if (schema_list.empty()) {
+ // Unknown property was detected.
+ SchemaErrorFound(out_error_path, out_error,
+ "Unknown property: " + dict_item.first);
+ if (!StrategyAllowUnknown(strategy))
+ return false;
+ drop_list.push_back(dict_item.first);
+ } else {
+ for (const auto& subschema : schema_list) {
+ std::string new_error;
+ const bool normalization_result =
+ subschema.Normalize(&dict_item.second, strategy, out_error_path,
+ &new_error, out_changed);
+ if (!new_error.empty()) {
+ AddDictKeyPrefixToPath(dict_item.first, out_error_path);
+ if (out_error)
+ *out_error = std::move(new_error);
+ }
+ if (!normalization_result) {
+ // Invalid property was detected.
+ return false;
+ }
+ }
+ present_properties.insert(dict_item.first);
+ }
+ }
+
+ for (const auto& required_property : GetRequiredProperties()) {
+ if (base::Contains(present_properties, required_property))
+ continue;
+
+ SchemaErrorFound(
+ out_error_path, out_error,
+ "Missing or invalid required property: " + required_property);
+ return false;
+ }
+
+ if (out_changed && !drop_list.empty())
+ *out_changed = true;
+ for (const auto& drop_key : drop_list)
+ value->RemoveKey(drop_key);
+ return true;
+ } else if (value->is_list()) {
+ base::Value::ListStorage list = std::move(*value).TakeListDeprecated();
+ // Instead of removing invalid list items afterwards, we push valid items
+ // forward in the list by overriding invalid items. The next free position
+ // is indicated by |write_index|, which gets increased for every valid item.
+ // At the end |list| is resized to |write_index|'s size.
+ size_t write_index = 0;
+ for (size_t index = 0; index < list.size(); ++index) {
+ base::Value& list_item = list[index];
+ std::string new_error;
+ const bool normalization_result = GetItems().Normalize(
+ &list_item, strategy, out_error_path, &new_error, out_changed);
+ if (!new_error.empty()) {
+ AddListIndexPrefixToPath(index, out_error_path);
+ if (out_error)
+ *out_error = new_error;
+ }
+ if (!normalization_result) {
+ // Invalid list item was detected.
+ if (!StrategyAllowInvalidListEntry(strategy))
+ return false;
+ } else {
+ if (write_index != index)
+ list[write_index] = std::move(list_item);
+ ++write_index;
+ }
+ }
+ if (out_changed && write_index < list.size())
+ *out_changed = true;
+ list.resize(write_index);
+ *value = base::Value(std::move(list));
+ return true;
+ }
+
+ return Validate(*value, strategy, out_error_path, out_error);
+}
+
+void Schema::MaskSensitiveValues(base::Value* value) const {
+ if (!valid())
+ return;
+
+ MaskSensitiveValuesRecursive(value);
+}
+
+// static
+Schema Schema::Parse(const std::string& content, std::string* error) {
+ // Validate as a generic JSON schema, and ignore unknown attributes; they
+ // may become used in a future version of the schema format.
+ absl::optional<base::Value> dict = Schema::ParseToDictAndValidate(
+ content, kSchemaOptionsIgnoreUnknownAttributes, error);
+ if (!dict.has_value())
+ return Schema();
+
+ // Validate the main type.
+ const std::string* type = dict->FindStringKey(schema::kType);
+ if (!type || *type != schema::kObject) {
+ *error =
+ "The main schema must have a type attribute with \"object\" value.";
+ return Schema();
+ }
+
+ // Checks for invalid attributes at the top-level.
+ if (dict.value().FindKey(schema::kAdditionalProperties) ||
+ dict.value().FindKey(schema::kPatternProperties)) {
+ *error =
+ "\"additionalProperties\" and \"patternProperties\" are not "
+ "supported at the main schema.";
+ return Schema();
+ }
+
+ scoped_refptr<const InternalStorage> storage =
+ InternalStorage::ParseSchema(dict.value(), error);
+ if (!storage)
+ return Schema();
+ return Schema(storage, storage->root_node());
+}
+
+// static
+absl::optional<base::Value> Schema::ParseToDictAndValidate(
+ const std::string& schema,
+ int validator_options,
+ std::string* error) {
+ base::JSONReader::ValueWithError value_with_error =
+ base::JSONReader::ReadAndReturnValueWithError(
+ schema, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+ *error = value_with_error.error_message;
+
+ if (!value_with_error.value)
+ return absl::nullopt;
+ base::Value json = std::move(value_with_error.value.value());
+ if (!json.is_dict()) {
+ *error = "Schema must be a JSON object";
+ return absl::nullopt;
+ }
+ if (!IsValidSchema(json, validator_options, error))
+ return absl::nullopt;
+ return json;
+}
+
+base::Value::Type Schema::type() const {
+ CHECK(valid());
+ return node_->type;
+}
+
+Schema::Iterator Schema::GetPropertiesIterator() const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::DICTIONARY, type());
+ return Iterator(storage_, storage_->properties(node_->extra));
+}
+
+namespace {
+
+bool CompareKeys(const PropertyNode& node, const std::string& key) {
+ return node.key < key;
+}
+
+} // namespace
+
+Schema Schema::GetKnownProperty(const std::string& key) const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::DICTIONARY, type());
+ const PropertiesNode* node = storage_->properties(node_->extra);
+ if (node->begin == kInvalid || node->end == kInvalid)
+ return Schema();
+ const PropertyNode* begin = storage_->property(node->begin);
+ const PropertyNode* end = storage_->property(node->end);
+ const PropertyNode* it = std::lower_bound(begin, end, key, CompareKeys);
+ if (it != end && it->key == key)
+ return Schema(storage_, storage_->schema(it->schema));
+ return Schema();
+}
+
+Schema Schema::GetAdditionalProperties() const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::DICTIONARY, type());
+ const PropertiesNode* node = storage_->properties(node_->extra);
+ if (node->additional == kInvalid)
+ return Schema();
+ return Schema(storage_, storage_->schema(node->additional));
+}
+
+SchemaList Schema::GetPatternProperties(const std::string& key) const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::DICTIONARY, type());
+ const PropertiesNode* node = storage_->properties(node_->extra);
+ if (node->end == kInvalid || node->pattern_end == kInvalid)
+ return {};
+ const PropertyNode* begin = storage_->property(node->end);
+ const PropertyNode* end = storage_->property(node->pattern_end);
+ SchemaList matching_properties;
+ for (const PropertyNode* it = begin; it != end; ++it) {
+ if (re2::RE2::PartialMatch(key, *storage_->CompileRegex(it->key))) {
+ matching_properties.push_back(
+ Schema(storage_, storage_->schema(it->schema)));
+ }
+ }
+ return matching_properties;
+}
+
+std::vector<std::string> Schema::GetRequiredProperties() const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::DICTIONARY, type());
+ const PropertiesNode* node = storage_->properties(node_->extra);
+ if (node->required_begin == kInvalid || node->required_end == kInvalid)
+ return {};
+ const size_t begin = node->required_begin;
+ const size_t end = node->required_end;
+
+ return std::vector<std::string>(storage_->required_property(begin),
+ storage_->required_property(end));
+}
+
+Schema Schema::GetProperty(const std::string& key) const {
+ Schema schema = GetKnownProperty(key);
+ if (schema.valid())
+ return schema;
+ return GetAdditionalProperties();
+}
+
+SchemaList Schema::GetMatchingProperties(const std::string& key) const {
+ SchemaList schema_list;
+
+ Schema known_property = GetKnownProperty(key);
+ if (known_property.valid())
+ schema_list.push_back(known_property);
+
+ SchemaList pattern_properties = GetPatternProperties(key);
+ schema_list.insert(schema_list.end(), pattern_properties.begin(),
+ pattern_properties.end());
+
+ if (schema_list.empty()) {
+ Schema additional_property = GetAdditionalProperties();
+ if (additional_property.valid())
+ schema_list.push_back(additional_property);
+ }
+
+ return schema_list;
+}
+
+Schema Schema::GetItems() const {
+ CHECK(valid());
+ CHECK_EQ(base::Value::Type::LIST, type());
+ if (node_->extra == kInvalid)
+ return Schema();
+ return Schema(storage_, storage_->schema(node_->extra));
+}
+
+bool Schema::ValidateIntegerRestriction(int index, int value) const {
+ const RestrictionNode* rnode = storage_->restriction(index);
+ if (rnode->ranged_restriction.min_value <=
+ rnode->ranged_restriction.max_value) {
+ return rnode->ranged_restriction.min_value <= value &&
+ rnode->ranged_restriction.max_value >= value;
+ } else {
+ for (int i = rnode->enumeration_restriction.offset_begin;
+ i < rnode->enumeration_restriction.offset_end; ++i) {
+ if (*storage_->int_enums(i) == value)
+ return true;
+ }
+ return false;
+ }
+}
+
+bool Schema::ValidateStringRestriction(int index, const char* str) const {
+ const RestrictionNode* rnode = storage_->restriction(index);
+ if (rnode->enumeration_restriction.offset_begin <
+ rnode->enumeration_restriction.offset_end) {
+ for (int i = rnode->enumeration_restriction.offset_begin;
+ i < rnode->enumeration_restriction.offset_end; ++i) {
+ if (strcmp(*storage_->string_enums(i), str) == 0)
+ return true;
+ }
+ return false;
+ } else {
+ int pattern_index = rnode->string_pattern_restriction.pattern_index;
+ DCHECK(pattern_index ==
+ rnode->string_pattern_restriction.pattern_index_backup);
+ re2::RE2* regex =
+ storage_->CompileRegex(*storage_->string_enums(pattern_index));
+ return re2::RE2::PartialMatch(str, *regex);
+ }
+}
+
+void Schema::MaskSensitiveValuesRecursive(base::Value* value) const {
+ if (IsSensitiveValue()) {
+ *value = base::Value(kSensitiveValueMask);
+ return;
+ }
+ if (!HasSensitiveChildren())
+ return;
+ if (value->type() != type())
+ return;
+
+ if (value->is_dict()) {
+ for (auto dict_item : value->DictItems()) {
+ auto& sub_value = dict_item.second;
+ SchemaList schema_list = GetMatchingProperties(dict_item.first);
+ for (const auto& schema_item : schema_list)
+ schema_item.MaskSensitiveValuesRecursive(&sub_value);
+ }
+ } else if (value->is_list()) {
+ for (auto& list_elem : value->GetListDeprecated())
+ GetItems().MaskSensitiveValuesRecursive(&list_elem);
+ }
+}
+
+Schema Schema::GetValidationSchema() const {
+ CHECK(valid());
+ const SchemaNode* validation_schema_root_node =
+ storage_->validation_schema_root_node();
+ if (!validation_schema_root_node)
+ return Schema();
+ return Schema(storage_, validation_schema_root_node);
+}
+
+bool Schema::IsSensitiveValue() const {
+ CHECK(valid());
+
+ // This is safe because |node_| is guaranteed to have been returned from
+ // |storage_| and |storage_->root_node()| always returns to the |SchemaNode|
+ // with index 0.
+ int index = node_ - storage_->root_node();
+ const SchemaNode* schema_node = storage_->schema(index);
+ if (!schema_node)
+ return false;
+ return schema_node->is_sensitive_value;
+}
+
+bool Schema::HasSensitiveChildren() const {
+ CHECK(valid());
+
+ // This is safe because |node_| is guaranteed to have been returned from
+ // |storage_| and |storage_->root_node()| always returns to the |SchemaNode|
+ // with index 0.
+ int index = node_ - storage_->root_node();
+ const SchemaNode* schema_node = storage_->schema(index);
+ if (!schema_node)
+ return false;
+ return schema_node->has_sensitive_children;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema.h b/chromium/components/policy/core/common/schema.h
new file mode 100644
index 00000000000..d07629f1078
--- /dev/null
+++ b/chromium/components/policy/core/common/schema.h
@@ -0,0 +1,259 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/policy/policy_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+namespace internal {
+
+struct POLICY_EXPORT SchemaData;
+struct POLICY_EXPORT SchemaNode;
+struct POLICY_EXPORT PropertyNode;
+struct POLICY_EXPORT PropertiesNode;
+
+} // namespace internal
+
+// Option flags passed to Schema::Validate() and Schema::Normalize(), describing
+// the strategy to handle unknown properties or invalid values for dict type.
+// Note that in Schema::Normalize() allowed errors will be dropped and thus
+// ignored.
+// Unknown error indicates that some value in a dictionary (may or may not be
+// the one in root) have unknown property name according to schema.
+// Invalid error indicates a validation failure against the schema. As
+// validation is done recursively, a validation failure of dict properties or
+// list items might be ignored (or dropped in Normalize()) or trigger whole
+// dictionary/list validation failure.
+enum SchemaOnErrorStrategy {
+ // No errors will be allowed. This should not be used for policies, since it
+ // basically prevents future changes to the policy (Server sends newField, but
+ // clients running older versions of Chrome reject the policy because they
+ // don't know newField). Prefer to use |SCHEMA_ALLOW_UNKNOWN| or
+ // |SCHEMA_ALLOW_UNKOWN_AND_INVALID_LIST_ENTRY| for policies
+ // instead.
+ SCHEMA_STRICT = 0,
+ // Unknown properties in any dictionary will be ignored.
+ SCHEMA_ALLOW_UNKNOWN,
+ // In addition to the previous, invalid list entries will be ignored for all
+ // lists in the schema. Should only be used in cases where dropping list items
+ // is safe. For example, can't be used if an empty list has a special meaning,
+ // like allowing everything.
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY,
+};
+
+// Schema validation options for Schema::ParseToDictAndValidate().
+constexpr int kSchemaOptionsNone = 0;
+constexpr int kSchemaOptionsIgnoreUnknownAttributes = 1 << 0;
+
+class Schema;
+
+typedef std::vector<Schema> SchemaList;
+
+// Describes the expected type of one policy. Also recursively describes the
+// types of inner elements, for structured types.
+// Objects of this class refer to external, immutable data and are cheap to
+// copy.
+//
+// See components/policy/core/common/json_schema_constants.h for a list of
+// supported features and data types. Only these features and data-types are
+// supported and enforced. For the full schema proposal see
+// https://json-schema.org/understanding-json-schema/index.html.
+//
+// There are also these departures from the proposal:
+// - "additionalProperties": false is not supported. The value of
+// "additionalProperties" has to be a schema if present. Otherwise, the
+// behavior for unknown attributes is controlled by |SchemaOnErrorStrategy|.
+// - "sensitiveValue" (bool) marks a value to be sensitive. This is used to
+// mask those values in the UI by calling |MaskSensitiveValues()|.
+class POLICY_EXPORT Schema {
+ public:
+ // Used internally to store shared data.
+ class InternalStorage;
+
+ // Builds an empty, invalid schema.
+ Schema();
+
+ // Makes a copy of |schema| that shares the same internal storage.
+ Schema(const Schema& schema);
+
+ ~Schema();
+
+ Schema& operator=(const Schema& schema);
+
+ // Returns a Schema that references static data. This can be used by
+ // the embedder to pass structures generated at compile time, which can then
+ // be quickly loaded at runtime.
+ static Schema Wrap(const internal::SchemaData* data);
+
+ // Parses the JSON schema in |schema| and returns a Schema that owns
+ // the internal representation. If |schema| is invalid then an invalid Schema
+ // is returned and |error| contains a reason for the failure.
+ static Schema Parse(const std::string& schema, std::string* error);
+
+ // Verifies if |schema| is a valid JSON v3 schema. When this validation passes
+ // then |schema| is valid JSON that can be parsed into a Value, and that Value
+ // can be used to build a |Schema|. Returns the parsed Value when |schema|
+ // validated, otherwise returns nullopt. In that case, |error| contains an
+ // error description. For performance reasons, currently IsValidSchema() won't
+ // check the correctness of regular expressions used in "pattern" and
+ // "patternProperties" and in Validate() invalid regular expression don't
+ // accept any strings.
+ // |options| is a bitwise-OR combination of the options above (see
+ // |kSchemaOptions*| above).
+ static absl::optional<base::Value> ParseToDictAndValidate(
+ const std::string& schema,
+ int options,
+ std::string* error);
+
+ // Returns true if this Schema is valid. Schemas returned by the methods below
+ // may be invalid, and in those cases the other methods must not be used.
+ bool valid() const { return !!node_; }
+
+ base::Value::Type type() const;
+
+ // Validate |value| against current schema, |strategy| is the strategy to
+ // handle unknown properties or invalid values. Allowed errors will be
+ // ignored. |out_error_path| and |out_error| will contain the last error
+ // location and detailed message if |value| doesn't strictly conform to the
+ // schema. If |value| doesn't conform to the schema even within the allowance
+ // of |strategy|, false will be returned and |out_error_path| and |out_error|
+ // will contain the corresponding error that caused the failure.
+ // |out_error_path| and |out_error| can be nullptr and in that case no value
+ // will be returned.
+ bool Validate(const base::Value& value,
+ SchemaOnErrorStrategy strategy,
+ std::string* out_error_path,
+ std::string* out_error) const;
+
+ // Similar to Validate() but drop values with errors instead of ignoring them.
+ // |out_changed| is a pointer to a boolean value, and indicate whether |value|
+ // is changed or not (probably dropped properties or items). Be sure to set
+ // the bool that |out_changed| pointed to false before calling Normalize().
+ // |out_error_path|, |out_error| and |out_changed| can be nullptr and in that
+ // case no value will be set. This function will also take the ownership of
+ // dropped base::Value and destroy them.
+ bool Normalize(base::Value* value,
+ SchemaOnErrorStrategy strategy,
+ std::string* out_error_path,
+ std::string* out_error,
+ bool* out_changed) const;
+
+ // Modifies |value| in place - masks values that have been marked as sensitive
+ // ("sensitiveValue": true) in this Schema. Note that |value| may not be
+ // schema-valid according to this Schema after this function returns - the
+ // masking is performed by replacing values with string values, so the value
+ // types may not correspond to this Schema anymore.
+ void MaskSensitiveValues(base::Value* value) const;
+
+ // Used to iterate over the known properties of Type::DICTIONARY schemas.
+ class POLICY_EXPORT Iterator {
+ public:
+ Iterator(const scoped_refptr<const InternalStorage>& storage,
+ const internal::PropertiesNode* node);
+ Iterator(const Iterator& iterator);
+ ~Iterator();
+
+ Iterator& operator=(const Iterator& iterator);
+
+ // The other methods must not be called if the iterator is at the end.
+ bool IsAtEnd() const;
+
+ // Advances the iterator to the next property.
+ void Advance();
+
+ // Returns the name of the current property.
+ const char* key() const;
+
+ // Returns the Schema for the current property. This Schema is always valid.
+ Schema schema() const;
+
+ private:
+ scoped_refptr<const InternalStorage> storage_;
+ raw_ptr<const internal::PropertyNode> it_;
+ raw_ptr<const internal::PropertyNode> end_;
+ };
+
+ // These methods should be called only if type() == Type::DICTIONARY,
+ // otherwise invalid memory will be read. A CHECK is currently enforcing this.
+
+ // Returns an iterator that goes over the named properties of this schema.
+ // The returned iterator is at the beginning.
+ Iterator GetPropertiesIterator() const;
+
+ // Returns the Schema for the property named |key|. If |key| is not a known
+ // property name then the returned Schema is not valid.
+ Schema GetKnownProperty(const std::string& key) const;
+
+ // Returns all Schemas from pattern properties that match |key|. May be empty.
+ SchemaList GetPatternProperties(const std::string& key) const;
+
+ // Returns this Schema's required properties. May be empty if the Schema has
+ // no required properties.
+ std::vector<std::string> GetRequiredProperties() const;
+
+ // Returns the Schema for additional properties. If additional properties are
+ // not allowed for this Schema then the Schema returned is not valid.
+ Schema GetAdditionalProperties() const;
+
+ // Returns the Schema for |key| if it is a known property, otherwise returns
+ // the Schema for additional properties.
+ // DEPRECATED: This function didn't consider patternProperties, use
+ // GetMatchingProperties() instead.
+ // TODO(binjin): Replace calls to this function with GetKnownProperty() or
+ // GetMatchingProperties() and remove this later.
+ Schema GetProperty(const std::string& key) const;
+
+ // Returns all Schemas that are supposed to be validated against for |key|.
+ // May be empty.
+ SchemaList GetMatchingProperties(const std::string& key) const;
+
+ // Returns the Schema for items of an array.
+ // This method should be called only if type() == Type::LIST,
+ // otherwise invalid memory will be read. A CHECK is currently enforcing this.
+ Schema GetItems() const;
+
+ // Gets the validation schema associated with this |schema| - or if there
+ // isn't one, returns an empty invalid schema. There are a few policies that
+ // contain embedded JSON - these policies have a schema for validating that
+ // JSON that is more complicated than the regular schema. For other policies
+ // it is not defined. To get the validation schema for a policy, call
+ // |chrome_schema.GetValidationSchema().GetKnownProperty(policy_name)|, where
+ // |chrome_schema| is the root schema that has all policies as children.
+ Schema GetValidationSchema() const;
+
+ // If this returns true, the value described by this schema should not be
+ // displayed on the UI.
+ bool IsSensitiveValue() const;
+
+ // If this returns true, the schema contains child elements that contain
+ // sensitive values.
+ bool HasSensitiveChildren() const;
+
+ private:
+ // Builds a schema pointing to the inner structure of |storage|,
+ // rooted at |node|.
+ Schema(const scoped_refptr<const InternalStorage>& storage,
+ const internal::SchemaNode* node);
+
+ bool ValidateIntegerRestriction(int index, int value) const;
+ bool ValidateStringRestriction(int index, const char* str) const;
+
+ void MaskSensitiveValuesRecursive(base::Value* value) const;
+
+ scoped_refptr<const InternalStorage> storage_;
+ const internal::SchemaNode* node_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_H_
diff --git a/chromium/components/policy/core/common/schema_fuzzer.cc b/chromium/components/policy/core/common/schema_fuzzer.cc
new file mode 100644
index 00000000000..a236f8870d7
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_fuzzer.cc
@@ -0,0 +1,85 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+namespace {
+
+// Holds the state and performs initialization that's shared across fuzzer runs.
+struct Environment {
+ Environment() {
+ chrome_policy_schema = Schema::Wrap(GetChromeSchemaData());
+ CHECK(chrome_policy_schema.valid());
+ }
+
+ Schema chrome_policy_schema;
+};
+
+// Test schema parsing.
+void TestParsing(const Environment& env, const std::string& data) {
+ std::string error;
+ Schema::Parse(data, &error);
+}
+
+// Test validation and normalization against the Chrome policy schema.
+void TestValidation(const Environment& env, const base::Value& parsed_json) {
+ // Exercise with every possible strategy.
+ for (auto strategy : {SCHEMA_STRICT, SCHEMA_ALLOW_UNKNOWN,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY}) {
+ env.chrome_policy_schema.Validate(parsed_json, strategy,
+ /*out_error_path=*/nullptr,
+ /*out_error=*/nullptr);
+
+ base::Value copy = parsed_json.Clone();
+ if (env.chrome_policy_schema.Normalize(&copy, strategy,
+ /*out_error_path=*/nullptr,
+ /*out_error=*/nullptr,
+ /*out_changed=*/nullptr)) {
+ // If normalization succeeded, the validation of the result should succeed
+ // too.
+ CHECK(env.chrome_policy_schema.Validate(copy, strategy,
+ /*out_error_path=*/nullptr,
+ /*out_error=*/nullptr));
+ }
+ }
+}
+
+// Test masking sensitive values.
+void TestMasking(const Environment& env, const base::Value& parsed_json) {
+ base::Value copy = parsed_json.Clone();
+ env.chrome_policy_schema.MaskSensitiveValues(&copy);
+}
+
+} // namespace
+
+// Fuzzer for methods of the `Schema` class.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static Environment env;
+
+ const std::string data_string(reinterpret_cast<const char*>(data), size);
+
+ TestParsing(env, data_string);
+
+ absl::optional<base::Value> parsed_json = base::JSONReader::Read(data_string);
+ if (parsed_json) {
+ TestValidation(env, *parsed_json);
+ TestMasking(env, *parsed_json);
+ }
+
+ return 0;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_internal.h b/chromium/components/policy/core/common/schema_internal.h
new file mode 100644
index 00000000000..fcac8caf047
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_internal.h
@@ -0,0 +1,150 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/values.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+namespace internal {
+
+// These types are used internally by the SchemaOwner parser, and by the
+// compile-time code generator. They shouldn't be used directly.
+
+// Represents the type of one policy, or an item of a list policy, or a
+// property of a map policy.
+struct POLICY_EXPORT SchemaNode {
+ // The policy type.
+ base::Value::Type type;
+
+ // If |type| is Type::DICTIONARY then |extra| is an offset into
+ // SchemaData::properties_nodes that indexes the PropertiesNode describing
+ // the entries of this dictionary.
+ //
+ // If |type| is Type::LIST then |extra| is an offset into
+ // SchemaData::schema_nodes that indexes the SchemaNode describing the items
+ // of this list.
+ //
+ // If |type| is Type::INTEGER or Type::STRING, and contains corresponding
+ // restriction (enumeration of possible values, or range for integer), then
+ // |extra| is an offset into SchemaData::restriction_nodes that indexes the
+ // RestrictionNode describing the restriction on the value.
+ //
+ // Otherwise extra is -1 and is invalid.
+ short extra;
+
+ // True if this value is sensitive and should be masked before displaying it
+ // to the user.
+ bool is_sensitive_value;
+
+ // True if any of its children has |is_sensitive_value|==true.
+ bool has_sensitive_children;
+};
+
+// Represents an entry of a map policy.
+struct POLICY_EXPORT PropertyNode {
+ // The entry key.
+ const char* key;
+
+ // An offset into SchemaData::schema_nodes that indexes the SchemaNode
+ // describing the structure of this key.
+ short schema;
+};
+
+// Represents the list of keys of a map policy.
+struct POLICY_EXPORT PropertiesNode {
+ // An offset into SchemaData::property_nodes that indexes the PropertyNode
+ // describing the first known property of this map policy.
+ short begin;
+
+ // An offset into SchemaData::property_nodes that indexes the PropertyNode
+ // right beyond the last known property of this map policy.
+ //
+ // If |begin == end| then the map policy that this PropertiesNode corresponds
+ // to does not have known properties.
+ //
+ // Note that the range [begin, end) is sorted by PropertyNode::key, so that
+ // properties can be looked up by binary searching in the range.
+ short end;
+
+ // An offset into SchemaData::property_nodes that indexes the PropertyNode
+ // right beyond the last known pattern property.
+ //
+ // [end, pattern_end) is the range that covers all pattern properties
+ // defined. It's not required to be sorted.
+ short pattern_end;
+
+ // An offset into SchemaData::required_properties that indexes the first
+ // required property of this map policy.
+ short required_begin;
+
+ // An offset into SchemaData::required_properties that indexes the property
+ // right beyond the last required property.
+ //
+ // If |required_begin == required_end|, then the map policy that this
+ // PropertiesNode corresponds to does not have any required properties.
+ //
+ // Note that the range [required_begin, required_end) is not sorted.
+ short required_end;
+
+ // If this map policy supports keys with any value (besides the well-known
+ // values described in the range [begin, end)) then |additional| is an offset
+ // into SchemaData::schema_nodes that indexes the SchemaNode describing the
+ // structure of the values for those keys. Otherwise |additional| is -1 and
+ // is invalid.
+ short additional;
+};
+
+// Represents the restriction on Type::INTEGER or Type::STRING instance of
+// base::Value.
+union POLICY_EXPORT RestrictionNode {
+ // Offsets into SchemaData::int_enums or SchemaData::string_enums, the
+ // entry of which describes the enumeration of all possible values of
+ // corresponding integer or string value. |offset_begin| being strictly less
+ // than |offset_end| is assumed.
+ struct EnumerationRestriction {
+ int offset_begin;
+ int offset_end;
+ } enumeration_restriction;
+
+ // For integer type only, represents that all values between |min_value|
+ // and |max_value| can be choosen. Note that integer type in base::Value
+ // is bounded, so this can also be used if only one of |min_value| and
+ // |max_value| is stated. |max_value| being greater or equal to |min_value|
+ // is assumed.
+ struct RangedRestriction {
+ int max_value;
+ int min_value;
+ } ranged_restriction;
+
+ // For string type only, requires |pattern_index| and |pattern_index_backup|
+ // to be exactly the same. And it's an offset into SchemaData::string_enums
+ // which contains the regular expression that the target string must follow.
+ struct StringPatternRestriction {
+ int pattern_index;
+ int pattern_index_backup;
+ } string_pattern_restriction;
+};
+
+// Contains arrays of related nodes. All of the offsets in these nodes reference
+// other nodes in these arrays.
+struct POLICY_EXPORT SchemaData {
+ raw_ptr<const SchemaNode> schema_nodes;
+ raw_ptr<const PropertyNode> property_nodes;
+ raw_ptr<const PropertiesNode> properties_nodes;
+ raw_ptr<const RestrictionNode> restriction_nodes;
+ raw_ptr<const char* const> required_properties;
+
+ raw_ptr<const int> int_enums;
+ raw_ptr<const char* const> string_enums;
+ int validation_schema_root_index;
+};
+
+} // namespace internal
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_INTERNAL_H_
diff --git a/chromium/components/policy/core/common/schema_map.cc b/chromium/components/policy/core/common/schema_map.cc
new file mode 100644
index 00000000000..66841fbf15c
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_map.cc
@@ -0,0 +1,129 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+
+#include <utility>
+
+#include "base/values.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace policy {
+
+SchemaMap::SchemaMap() {}
+
+SchemaMap::SchemaMap(DomainMap map) : map_(std::move(map)) {}
+
+SchemaMap::~SchemaMap() {}
+
+const DomainMap& SchemaMap::GetDomains() const {
+ return map_;
+}
+
+const ComponentMap* SchemaMap::GetComponents(PolicyDomain domain) const {
+ const auto it = map_.find(domain);
+ return it == map_.end() ? nullptr : &it->second;
+}
+
+const Schema* SchemaMap::GetSchema(const PolicyNamespace& ns) const {
+ const ComponentMap* map = GetComponents(ns.domain);
+ if (!map)
+ return nullptr;
+ const auto it = map->find(ns.component_id);
+ return it == map->end() ? nullptr : &it->second;
+}
+
+void SchemaMap::FilterBundle(PolicyBundle* bundle,
+ bool drop_invalid_component_policies) const {
+ for (auto& bundle_item : *bundle) {
+ const PolicyNamespace& ns = bundle_item.first;
+ PolicyMap& policy_map = bundle_item.second;
+
+ // Chrome policies are not filtered, so that typos appear in about:policy.
+ if (ns.domain == POLICY_DOMAIN_CHROME)
+ continue;
+
+ const Schema* schema = GetSchema(ns);
+
+ if (!schema) {
+ policy_map.Clear();
+ continue;
+ }
+
+ if (!schema->valid()) {
+ // Don't serve unknown policies.
+ if (drop_invalid_component_policies) {
+ policy_map.Clear();
+ } else {
+ policy_map.SetAllInvalid();
+ }
+ continue;
+ }
+
+ for (auto it_map = policy_map.begin(); it_map != policy_map.end();) {
+ const std::string& policy_name = it_map->first;
+ PolicyMap::Entry& entry = it_map->second;
+ const Schema policy_schema = schema->GetProperty(policy_name);
+
+ const bool has_value = entry.value_unsafe();
+ const bool is_valid =
+ has_value &&
+ policy_schema.Normalize(entry.value_unsafe(), SCHEMA_ALLOW_UNKNOWN,
+ /* out_error_path=*/nullptr,
+ /* out_error=*/nullptr,
+ /* out_changed=*/nullptr);
+ if (drop_invalid_component_policies && (!has_value || !is_valid)) {
+ it_map = policy_map.EraseIt(it_map);
+ continue;
+ }
+
+ ++it_map;
+
+ if (!has_value) {
+ entry.SetIgnored();
+ continue;
+ }
+
+ if (!is_valid)
+ entry.SetInvalid();
+ }
+ }
+}
+
+bool SchemaMap::HasComponents() const {
+ for (const auto& item : map_) {
+ const PolicyDomain& domain = item.first;
+ const ComponentMap& component_map = item.second;
+ if (domain == POLICY_DOMAIN_CHROME)
+ continue;
+ if (!component_map.empty())
+ return true;
+ }
+ return false;
+}
+
+void SchemaMap::GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const {
+ GetNamespacesNotInOther(older.get(), added);
+ older->GetNamespacesNotInOther(this, removed);
+}
+
+void SchemaMap::GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const {
+ list->clear();
+ for (const auto& item : map_) {
+ const PolicyDomain& domain = item.first;
+ const ComponentMap& component_map = item.second;
+ for (const auto& comp : component_map) {
+ const std::string& component_id = comp.first;
+ const PolicyNamespace ns(domain, component_id);
+ if (!other->GetSchema(ns))
+ list->push_back(ns);
+ }
+ }
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_map.h b/chromium/components/policy/core/common/schema_map.h
new file mode 100644
index 00000000000..97cdb46a783
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_map.h
@@ -0,0 +1,72 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyBundle;
+
+// Maps component id (e.g. extension id) to schema.
+typedef std::map<std::string, Schema> ComponentMap;
+typedef std::map<PolicyDomain, ComponentMap> DomainMap;
+
+// Contains a mapping of policy namespaces (domain + component ID) to its
+// corresponding Schema.
+// This class is thread-safe.
+class POLICY_EXPORT SchemaMap : public base::RefCountedThreadSafe<SchemaMap> {
+ public:
+ SchemaMap();
+ explicit SchemaMap(DomainMap map);
+ SchemaMap(const SchemaMap&) = delete;
+ SchemaMap& operator=(const SchemaMap&) = delete;
+
+ const DomainMap& GetDomains() const;
+
+ const ComponentMap* GetComponents(PolicyDomain domain) const;
+
+ const Schema* GetSchema(const PolicyNamespace& ns) const;
+
+ // Removes all the policies in |bundle| that don't match the known schemas.
+ // Unknown components are also dropped. Unknown fields in component policies
+ // are removed.
+ // If |drop_invalid_component_policies| is true, invalid policies are removed.
+ // If |drop_invalid_component_policies| is false, they will merely be marked
+ // invalid. They will still be filtered when accessing them via
+ // PolicyMap::Get() or PolicyMap::GetValue(), but will be surfaced in
+ // about:policy with an attached error.
+ void FilterBundle(PolicyBundle* bundle,
+ bool drop_invalid_component_policies) const;
+
+ // Returns true if this map contains at least one component of a domain other
+ // than POLICY_DOMAIN_CHROME.
+ bool HasComponents() const;
+
+ void GetChanges(const scoped_refptr<SchemaMap>& older,
+ PolicyNamespaceList* removed,
+ PolicyNamespaceList* added) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<SchemaMap>;
+
+ void GetNamespacesNotInOther(const SchemaMap* other,
+ PolicyNamespaceList* list) const;
+
+ ~SchemaMap();
+
+ DomainMap map_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_MAP_H_
diff --git a/chromium/components/policy/core/common/schema_map_unittest.cc b/chromium/components/policy/core/common/schema_map_unittest.cc
new file mode 100644
index 00000000000..96b34e07fc3
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_map_unittest.cc
@@ -0,0 +1,385 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_map.h"
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/policy/core/common/external_data_fetcher.h"
+#include "components/policy/core/common/external_data_manager.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ R"({
+ "type": "object",
+ "properties": {
+ "string": { "type": "string" },
+ "integer": { "type": "integer" },
+ "boolean": { "type": "boolean" },
+ "double": { "type": "number" },
+ "list": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "object": {
+ "type": "object",
+ "properties": {
+ "a": { "type": "string" },
+ "b": { "type": "integer" }
+ }
+ }
+ }
+ })";
+
+} // namespace
+
+class SchemaMapTest : public testing::Test {
+ protected:
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ if (!schema.valid())
+ ADD_FAILURE() << error;
+ return schema;
+ }
+
+ scoped_refptr<SchemaMap> CreateTestMap() {
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map["extension-1"] = schema;
+ component_map["extension-2"] = schema;
+ component_map["legacy-extension"] = Schema();
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+
+ return new SchemaMap(std::move(domain_map));
+ }
+};
+
+TEST_F(SchemaMapTest, Empty) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_TRUE(map->GetDomains().empty());
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_CHROME));
+ EXPECT_FALSE(map->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_FALSE(map->GetSchema(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, HasComponents) {
+ scoped_refptr<SchemaMap> map = new SchemaMap();
+ EXPECT_FALSE(map->HasComponents());
+
+ // The Chrome schema does not count as a component.
+ Schema schema = CreateTestSchema();
+ ComponentMap component_map;
+ component_map[""] = schema;
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_CHROME] = component_map;
+ map = new SchemaMap(std::move(domain_map));
+ EXPECT_FALSE(map->HasComponents());
+
+ // An extension schema does.
+ domain_map.clear();
+ domain_map[POLICY_DOMAIN_EXTENSIONS] = component_map;
+ map = new SchemaMap(std::move(domain_map));
+ EXPECT_TRUE(map->HasComponents());
+}
+
+TEST_F(SchemaMapTest, Lookups) {
+ scoped_refptr<SchemaMap> map = CreateTestMap();
+ ASSERT_TRUE(map.get());
+ EXPECT_TRUE(map->HasComponents());
+
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "extension-1")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, "legacy-extension")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "")));
+ EXPECT_FALSE(map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-3")));
+
+ const Schema* schema =
+ map->GetSchema(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "extension-1"));
+ ASSERT_TRUE(schema);
+ EXPECT_TRUE(schema->valid());
+
+ schema = map->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "legacy-extension"));
+ ASSERT_TRUE(schema);
+ EXPECT_FALSE(schema->valid());
+}
+
+// Tests FilterBundle when |drop_invalid_component_policies| is set to true.
+TEST_F(SchemaMapTest, FilterBundle) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["abc"] = schema;
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(std::move(domain_map));
+
+ PolicyBundle bundle;
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/true);
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+
+ // The Chrome namespace isn't filtered.
+ PolicyBundle expected_bundle;
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ expected_bundle.Get(chrome_ns).Set("ChromePolicy", POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("value"), nullptr);
+ bundle.CopyFrom(expected_bundle);
+
+ // Unknown components are filtered out.
+ PolicyNamespace another_extension_ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
+ bundle.Get(another_extension_ns)
+ .Set("AnotherExtensionPolicy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/true);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "abc");
+ PolicyMap& map = expected_bundle.Get(extension_ns);
+ base::ListValue list;
+ list.Append("a");
+ list.Append("b");
+ map.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, list.Clone(), nullptr);
+ map.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
+ map.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1), nullptr);
+ map.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(1.2), nullptr);
+ base::DictionaryValue dict;
+ dict.SetStringKey("a", "b");
+ dict.SetIntKey("b", 2);
+ map.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, dict.Clone(), nullptr);
+ map.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value"), nullptr);
+
+ bundle.MergeFrom(expected_bundle);
+ bundle.Get(extension_ns)
+ .Set("Unexpected", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("to-be-removed"), nullptr);
+
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/true);
+ // Merged twice so this causes a conflict.
+ expected_bundle.Get(chrome_ns)
+ .GetMutable("ChromePolicy")
+ ->AddConflictingPolicy(
+ expected_bundle.Get(chrome_ns).Get("ChromePolicy")->DeepCopy());
+ expected_bundle.Get(chrome_ns)
+ .GetMutable("ChromePolicy")
+ ->AddMessage(PolicyMap::MessageType::kInfo,
+ IDS_POLICY_CONFLICT_SAME_VALUE);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+
+ // Mismatched types are also removed.
+ bundle.Clear();
+ PolicyMap& badmap = bundle.Get(extension_ns);
+ badmap.Set("list", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ badmap.Set("boolean", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(0), nullptr);
+ badmap.Set("integer", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ badmap.Set("null", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ badmap.Set("double", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ badmap.Set("object", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
+ badmap.Set("string", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, absl::nullopt,
+ std::make_unique<ExternalDataFetcher>(nullptr, std::string()));
+
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/true);
+ EXPECT_TRUE(bundle.Equals(empty_bundle));
+}
+
+// Tests FilterBundle when |drop_invalid_component_policies| is set to true.
+TEST_F(SchemaMapTest, LegacyComponents) {
+ std::string error;
+ Schema schema = Schema::Parse(
+ R"({
+ "type": "object",
+ "properties": {
+ "String": { "type": "string" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(std::move(domain_map));
+
+ // |bundle| contains policies loaded by a policy provider.
+ PolicyBundle bundle;
+
+ // Known components with schemas are filtered.
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
+ bundle.Get(extension_ns)
+ .Set("String", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 1"), nullptr);
+
+ // The Chrome namespace isn't filtered.
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("ChromePolicy", POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("value 3"), nullptr);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.MergeFrom(bundle);
+
+ // Known components without a schema are filtered out completely.
+ PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
+ bundle.Get(without_schema_ns)
+ .Set("Schemaless", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 2"), nullptr);
+
+ // Unknown policies of known components with a schema are removed when
+ // |drop_invalid_component_policies| is true in the FilterBundle call.
+ bundle.Get(extension_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 4"), nullptr);
+
+ // Unknown components are removed.
+ PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
+ bundle.Get(unknown_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 5"), nullptr);
+
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/true);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+}
+
+// Tests FilterBundle when |drop_invalid_component_policies| is set to false.
+TEST_F(SchemaMapTest, FilterBundleInvalidatesPolicies) {
+ std::string error;
+ Schema schema = Schema::Parse(
+ R"({
+ "type": "object",
+ "properties": {
+ "String": { "type": "string" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ DomainMap domain_map;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["with-schema"] = schema;
+ domain_map[POLICY_DOMAIN_EXTENSIONS]["without-schema"] = Schema();
+ scoped_refptr<SchemaMap> schema_map = new SchemaMap(std::move(domain_map));
+
+ // |bundle| contains policies loaded by a policy provider.
+ PolicyBundle bundle;
+
+ // Known components with schemas are filtered.
+ PolicyNamespace extension_ns(POLICY_DOMAIN_EXTENSIONS, "with-schema");
+ bundle.Get(extension_ns)
+ .Set("String", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 1"), nullptr);
+
+ // The Chrome namespace isn't filtered.
+ PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("ChromePolicy", POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+ base::Value("value 3"), nullptr);
+
+ // Unknown policies of known components with a schema are invalidated when
+ // |drop_invalid_component_policies| is false in the FilterBundle call.
+ bundle.Get(extension_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 4"), nullptr);
+
+ // Known components without a schema are also invalidated.
+ PolicyNamespace without_schema_ns(POLICY_DOMAIN_EXTENSIONS, "without-schema");
+ bundle.Get(without_schema_ns)
+ .Set("Schemaless", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 2"), nullptr);
+
+ PolicyBundle expected_bundle;
+ expected_bundle.MergeFrom(bundle);
+ // Two policies will be invalidated.
+ expected_bundle.Get(extension_ns).GetMutable("Surprise")->SetInvalid();
+ expected_bundle.Get(without_schema_ns).GetMutable("Schemaless")->SetInvalid();
+
+ // Unknown components are removed.
+ PolicyNamespace unknown_ns(POLICY_DOMAIN_EXTENSIONS, "unknown");
+ bundle.Get(unknown_ns)
+ .Set("Surprise", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("value 5"), nullptr);
+
+ // Get a reference to the policies that will be invalidated now, since it can
+ // no longer be accessed with Get() after being invalidated.
+ const PolicyMap::Entry* invalid_policy_entry_1 =
+ bundle.Get(extension_ns).Get("Surprise");
+ ASSERT_TRUE(invalid_policy_entry_1);
+ const PolicyMap::Entry* invalid_policy_entry_2 =
+ bundle.Get(without_schema_ns).Get("Schemaless");
+ ASSERT_TRUE(invalid_policy_entry_2);
+
+ schema_map->FilterBundle(&bundle, /*drop_invalid_component_policies=*/false);
+ EXPECT_TRUE(bundle.Equals(expected_bundle));
+ EXPECT_TRUE(invalid_policy_entry_1->ignored());
+ EXPECT_TRUE(invalid_policy_entry_2->ignored());
+}
+
+TEST_F(SchemaMapTest, GetChanges) {
+ DomainMap map;
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> older = new SchemaMap(std::move(map));
+ map.clear();
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ scoped_refptr<SchemaMap> newer = new SchemaMap(std::move(map));
+
+ PolicyNamespaceList removed;
+ PolicyNamespaceList added;
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ EXPECT_TRUE(added.empty());
+
+ map.clear();
+ map[POLICY_DOMAIN_CHROME][""] = Schema();
+ map[POLICY_DOMAIN_EXTENSIONS]["xyz"] = Schema();
+ newer = new SchemaMap(std::move(map));
+ newer->GetChanges(older, &removed, &added);
+ EXPECT_TRUE(removed.empty());
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), added[0]);
+
+ older = newer;
+ map.clear();
+ map[POLICY_DOMAIN_EXTENSIONS]["abc"] = Schema();
+ newer = new SchemaMap(std::move(map));
+ newer->GetChanges(older, &removed, &added);
+ ASSERT_EQ(2u, removed.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), removed[0]);
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), removed[1]);
+ ASSERT_EQ(1u, added.size());
+ EXPECT_EQ(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), added[0]);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_registry.cc b/chromium/components/policy/core/common/schema_registry.cc
new file mode 100644
index 00000000000..3891636d268
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry.cc
@@ -0,0 +1,279 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include "base/check_op.h"
+#include "base/notreached.h"
+#include "base/observer_list.h"
+#include "extensions/buildflags/buildflags.h"
+
+namespace policy {
+
+SchemaRegistry::Observer::~Observer() {}
+
+SchemaRegistry::InternalObserver::~InternalObserver() {}
+
+SchemaRegistry::SchemaRegistry() : schema_map_(new SchemaMap) {
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ domains_ready_[i] = false;
+#if !BUILDFLAG(ENABLE_EXTENSIONS)
+ SetExtensionsDomainsReady();
+#endif
+}
+
+SchemaRegistry::~SchemaRegistry() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto& observer : internal_observers_)
+ observer.OnSchemaRegistryShuttingDown(this);
+}
+
+void SchemaRegistry::RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ComponentMap map;
+ map[ns.component_id] = schema;
+ RegisterComponents(ns.domain, map);
+}
+
+void SchemaRegistry::RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Don't issue notifications if nothing is being registered.
+ if (components.empty())
+ return;
+ // Assume that a schema was updated if the namespace was already registered
+ // before.
+ DomainMap map(schema_map_->GetDomains());
+ for (auto it = components.begin(); it != components.end(); ++it)
+ map[domain][it->first] = it->second;
+ schema_map_ = new SchemaMap(std::move(map));
+ Notify(true);
+}
+
+void SchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ schema_map_ = new SchemaMap(std::move(map));
+ Notify(false);
+ } else {
+ // Extension might be uninstalled before install so the associated policies
+ // are unregistered before registered. For example, a policy forced
+ // extension is removed from forced list during launch due to policy update.
+ DCHECK(ns.domain != POLICY_DOMAIN_CHROME);
+ }
+}
+
+bool SchemaRegistry::IsReady() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i) {
+ if (!domains_ready_[i])
+ return false;
+ }
+ return true;
+}
+
+void SchemaRegistry::SetDomainReady(PolicyDomain domain) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (domains_ready_[domain])
+ return;
+ domains_ready_[domain] = true;
+ if (IsReady()) {
+ for (auto& observer : observers_)
+ observer.OnSchemaRegistryReady();
+ }
+}
+
+void SchemaRegistry::SetAllDomainsReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
+ SetDomainReady(static_cast<PolicyDomain>(i));
+}
+
+void SchemaRegistry::SetExtensionsDomainsReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ SetDomainReady(POLICY_DOMAIN_EXTENSIONS);
+ SetDomainReady(POLICY_DOMAIN_SIGNIN_EXTENSIONS);
+}
+
+void SchemaRegistry::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+}
+
+void SchemaRegistry::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+void SchemaRegistry::AddInternalObserver(InternalObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ internal_observers_.AddObserver(observer);
+}
+
+void SchemaRegistry::RemoveInternalObserver(InternalObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ internal_observers_.RemoveObserver(observer);
+}
+
+void SchemaRegistry::Notify(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto& observer : observers_)
+ observer.OnSchemaRegistryUpdated(has_new_schemas);
+}
+
+CombinedSchemaRegistry::CombinedSchemaRegistry()
+ : own_schema_map_(new SchemaMap) {
+ // The combined registry is always ready, since it can always start tracking
+ // another registry that is not ready yet and going from "ready" to "not
+ // ready" is not allowed.
+ SetAllDomainsReady();
+}
+
+CombinedSchemaRegistry::~CombinedSchemaRegistry() {}
+
+void CombinedSchemaRegistry::Track(SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ registries_.insert(registry);
+ registry->AddObserver(this);
+ registry->AddInternalObserver(this);
+ // Recombine the maps only if the |registry| has any components other than
+ // POLICY_DOMAIN_CHROME.
+ if (registry->schema_map()->HasComponents())
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::RegisterComponents(
+ PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(own_schema_map_->GetDomains());
+ for (auto it = components.begin(); it != components.end(); ++it)
+ map[domain][it->first] = it->second;
+ own_schema_map_ = new SchemaMap(std::move(map));
+ Combine(true);
+}
+
+void CombinedSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DomainMap map(own_schema_map_->GetDomains());
+ if (map[ns.domain].erase(ns.component_id) != 0) {
+ own_schema_map_ = new SchemaMap(std::move(map));
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ Combine(has_new_schemas);
+}
+
+void CombinedSchemaRegistry::OnSchemaRegistryShuttingDown(
+ SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ registry->RemoveObserver(this);
+ registry->RemoveInternalObserver(this);
+ if (registries_.erase(registry) != 0) {
+ if (registry->schema_map()->HasComponents())
+ Combine(false);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void CombinedSchemaRegistry::Combine(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // If two registries publish a Schema for the same component then it's
+ // undefined which version gets in the combined registry.
+ //
+ // The common case is that both registries want policy for the same component,
+ // and the Schemas should be the same; in that case this makes no difference.
+ //
+ // But if the Schemas are different then one of the components is out of date.
+ // In that case the policy loaded will be valid only for one of them, until
+ // the outdated components are updated. This is a known limitation of the
+ // way policies are loaded currently, but isn't a problem worth fixing for
+ // the time being.
+ DomainMap map(own_schema_map_->GetDomains());
+ for (auto reg_it = registries_.begin(); reg_it != registries_.end();
+ ++reg_it) {
+ const DomainMap& reg_domain_map = (*reg_it)->schema_map()->GetDomains();
+ for (auto domain_it = reg_domain_map.begin();
+ domain_it != reg_domain_map.end(); ++domain_it) {
+ const ComponentMap& reg_component_map = domain_it->second;
+ for (auto comp_it = reg_component_map.begin();
+ comp_it != reg_component_map.end(); ++comp_it) {
+ map[domain_it->first][comp_it->first] = comp_it->second;
+ }
+ }
+ }
+ schema_map_ = new SchemaMap(std::move(map));
+ Notify(has_new_schemas);
+}
+
+ForwardingSchemaRegistry::ForwardingSchemaRegistry(SchemaRegistry* wrapped)
+ : wrapped_(wrapped) {
+ schema_map_ = wrapped_->schema_map();
+ wrapped_->AddObserver(this);
+ wrapped_->AddInternalObserver(this);
+ UpdateReadiness();
+}
+
+ForwardingSchemaRegistry::~ForwardingSchemaRegistry() {
+ if (wrapped_) {
+ wrapped_->RemoveObserver(this);
+ wrapped_->RemoveInternalObserver(this);
+ }
+}
+
+void ForwardingSchemaRegistry::RegisterComponents(
+ PolicyDomain domain,
+ const ComponentMap& components) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // POLICY_DOMAIN_CHROME is skipped to avoid spurious updates when a new
+ // Profile is created. If the ForwardingSchemaRegistry is used outside
+ // device-level accounts then this should become configurable.
+ if (wrapped_ && domain != POLICY_DOMAIN_CHROME)
+ wrapped_->RegisterComponents(domain, components);
+ // Ignore otherwise.
+}
+
+void ForwardingSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (wrapped_)
+ wrapped_->UnregisterComponent(ns);
+ // Ignore otherwise.
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ schema_map_ = wrapped_->schema_map();
+ Notify(has_new_schemas);
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryReady() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UpdateReadiness();
+}
+
+void ForwardingSchemaRegistry::OnSchemaRegistryShuttingDown(
+ SchemaRegistry* registry) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(wrapped_, registry);
+ wrapped_->RemoveObserver(this);
+ wrapped_->RemoveInternalObserver(this);
+ wrapped_ = nullptr;
+ // Keep serving the same |schema_map_|.
+}
+
+void ForwardingSchemaRegistry::UpdateReadiness() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (wrapped_->IsReady())
+ SetAllDomainsReady();
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_registry.h b/chromium/components/policy/core/common/schema_registry.h
new file mode 100644
index 00000000000..41dab48057d
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry.h
@@ -0,0 +1,170 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
+
+#include <set>
+
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class SchemaMap;
+
+// Holds the main reference to the current SchemaMap, and allows a list of
+// observers to get notified whenever it is updated.
+// This object is not thread safe and must be used from the owner's thread,
+// usually UI.
+class POLICY_EXPORT SchemaRegistry {
+ public:
+ class POLICY_EXPORT Observer {
+ public:
+ // Invoked whenever schemas are registered or unregistered.
+ // |has_new_schemas| is true if a new component has been registered since
+ // the last update; this allows observers to ignore updates when
+ // components are unregistered but still get a handle to the current map
+ // (e.g. for periodic reloads).
+ virtual void OnSchemaRegistryUpdated(bool has_new_schemas) = 0;
+
+ // Invoked when all policy domains become ready.
+ virtual void OnSchemaRegistryReady() {}
+
+ protected:
+ virtual ~Observer();
+ };
+
+ // This observer is only meant to be used by subclasses.
+ class POLICY_EXPORT InternalObserver {
+ public:
+ // Invoked when |registry| is about to be destroyed.
+ virtual void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) = 0;
+
+ protected:
+ virtual ~InternalObserver();
+ };
+
+ SchemaRegistry();
+ SchemaRegistry(const SchemaRegistry&) = delete;
+ SchemaRegistry& operator=(const SchemaRegistry&) = delete;
+ virtual ~SchemaRegistry();
+
+ const scoped_refptr<SchemaMap>& schema_map() const { return schema_map_; }
+
+ // Register a single component.
+ void RegisterComponent(const PolicyNamespace& ns,
+ const Schema& schema);
+
+ // Register a list of components for a given domain.
+ virtual void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components);
+
+ virtual void UnregisterComponent(const PolicyNamespace& ns);
+
+ // Returns true if all domains have registered the initial components.
+ bool IsReady() const;
+
+ // This indicates that the initial components for |domain| have all been
+ // registered. It must be invoked at least once for each policy domain;
+ // subsequent calls for the same domain are ignored.
+ void SetDomainReady(PolicyDomain domain);
+ // This is equivalent to calling |SetDomainReady| with each of the policy
+ // domains.
+ void SetAllDomainsReady();
+ // This is equivalent to calling |SetDomainReady| with each of the domains
+ // that correspond to policy for extensions.
+ void SetExtensionsDomainsReady();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void AddInternalObserver(InternalObserver* observer);
+ void RemoveInternalObserver(InternalObserver* observer);
+
+ protected:
+ void Notify(bool has_new_schemas);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ scoped_refptr<SchemaMap> schema_map_;
+
+ private:
+ base::ObserverList<Observer, true>::Unchecked observers_;
+ base::ObserverList<InternalObserver, true>::Unchecked internal_observers_;
+ bool domains_ready_[POLICY_DOMAIN_SIZE];
+};
+
+// A registry that combines the maps of other registries.
+class POLICY_EXPORT CombinedSchemaRegistry
+ : public SchemaRegistry,
+ public SchemaRegistry::Observer,
+ public SchemaRegistry::InternalObserver {
+ public:
+ CombinedSchemaRegistry();
+ CombinedSchemaRegistry(const CombinedSchemaRegistry&) = delete;
+ CombinedSchemaRegistry& operator=(const CombinedSchemaRegistry&) = delete;
+ ~CombinedSchemaRegistry() override;
+
+ void Track(SchemaRegistry* registry);
+
+ // SchemaRegistry:
+ void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) override;
+ void UnregisterComponent(const PolicyNamespace& ns) override;
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
+ // SchemaRegistry::InternalObserver:
+ void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) override;
+
+ private:
+ void Combine(bool has_new_schemas);
+
+ std::set<SchemaRegistry*> registries_;
+ scoped_refptr<SchemaMap> own_schema_map_;
+};
+
+// A registry that wraps another schema registry.
+class POLICY_EXPORT ForwardingSchemaRegistry
+ : public SchemaRegistry,
+ public SchemaRegistry::Observer,
+ public SchemaRegistry::InternalObserver {
+ public:
+ // This registry will stop updating its SchemaMap when |wrapped| is
+ // destroyed.
+ explicit ForwardingSchemaRegistry(SchemaRegistry* wrapped);
+ ForwardingSchemaRegistry(const ForwardingSchemaRegistry&) = delete;
+ ForwardingSchemaRegistry& operator=(const ForwardingSchemaRegistry&) = delete;
+ ~ForwardingSchemaRegistry() override;
+
+ // SchemaRegistry:
+ void RegisterComponents(PolicyDomain domain,
+ const ComponentMap& components) override;
+ void UnregisterComponent(const PolicyNamespace& ns) override;
+
+ // SchemaRegistry::Observer:
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+ void OnSchemaRegistryReady() override;
+
+ // SchemaRegistry::InternalObserver:
+ void OnSchemaRegistryShuttingDown(SchemaRegistry* registry) override;
+
+ private:
+ void UpdateReadiness();
+
+ raw_ptr<SchemaRegistry> wrapped_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_H_
diff --git a/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.cc b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.cc
new file mode 100644
index 00000000000..9165039679a
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.cc
@@ -0,0 +1,108 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry_tracking_policy_provider.h"
+
+#include <utility>
+
+#include "components/policy/core/common/schema_map.h"
+#include "components/policy/core/common/schema_registry.h"
+
+namespace policy {
+
+SchemaRegistryTrackingPolicyProvider::SchemaRegistryTrackingPolicyProvider(
+ ConfigurationPolicyProvider* delegate)
+ : delegate_(delegate), state_(WAITING_FOR_REGISTRY_READY) {
+ delegate_->AddObserver(this);
+ // Serve the initial |delegate_| policies.
+ OnUpdatePolicy(delegate_);
+}
+
+SchemaRegistryTrackingPolicyProvider::~SchemaRegistryTrackingPolicyProvider() {
+ delegate_->RemoveObserver(this);
+}
+
+void SchemaRegistryTrackingPolicyProvider::Init(SchemaRegistry* registry) {
+ ConfigurationPolicyProvider::Init(registry);
+ if (registry->IsReady())
+ OnSchemaRegistryReady();
+}
+
+bool SchemaRegistryTrackingPolicyProvider::IsInitializationComplete(
+ PolicyDomain domain) const {
+ if (domain == POLICY_DOMAIN_CHROME)
+ return delegate_->IsInitializationComplete(domain);
+ // This provider keeps its own state for all the other domains.
+ return state_ == READY;
+}
+
+bool SchemaRegistryTrackingPolicyProvider::IsFirstPolicyLoadComplete(
+ PolicyDomain domain) const {
+ if (domain == POLICY_DOMAIN_CHROME)
+ return delegate_->IsFirstPolicyLoadComplete(domain);
+ // This provider keeps its own state for all the other domains.
+ return state_ == READY;
+}
+
+void SchemaRegistryTrackingPolicyProvider::RefreshPolicies() {
+ delegate_->RefreshPolicies();
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnSchemaRegistryReady() {
+ DCHECK_EQ(WAITING_FOR_REGISTRY_READY, state_);
+ // This provider's registry is ready, meaning that it has all the initial
+ // components schemas; the delegate's registry should also see them now,
+ // since it's tracking the former.
+ // Asking the delegate to RefreshPolicies now means that the next
+ // OnUpdatePolicy from the delegate will have the initial policy for
+ // components.
+ if (!schema_map()->HasComponents()) {
+ // If there are no component registered for this provider then there's no
+ // need to reload.
+ state_ = READY;
+ OnUpdatePolicy(delegate_);
+ return;
+ }
+
+ state_ = WAITING_FOR_REFRESH;
+ RefreshPolicies();
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnSchemaRegistryUpdated(
+ bool has_new_schemas) {
+ if (state_ != READY)
+ return;
+ if (has_new_schemas) {
+ RefreshPolicies();
+ } else {
+ // Remove the policies that were being served for the component that have
+ // been removed. This is important so that update notifications are also
+ // sent in case those component are reinstalled during the current session.
+ OnUpdatePolicy(delegate_);
+ }
+}
+
+void SchemaRegistryTrackingPolicyProvider::OnUpdatePolicy(
+ ConfigurationPolicyProvider* provider) {
+ DCHECK_EQ(delegate_, provider);
+
+ if (state_ == WAITING_FOR_REFRESH)
+ state_ = READY;
+
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle());
+ if (state_ == READY) {
+ bundle->CopyFrom(delegate_->policies());
+ schema_map()->FilterBundle(bundle.get(),
+ /*drop_invalid_component_policies=*/true);
+ } else {
+ // Always pass on the Chrome policy, even if the components are not ready
+ // yet.
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle->Get(chrome_ns) = delegate_->policies().Get(chrome_ns).Clone();
+ }
+
+ UpdatePolicy(std::move(bundle));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.h b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.h
new file mode 100644
index 00000000000..0687e737a02
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider.h
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr.h"
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// A policy provider that relies on a delegate provider to obtain policy
+// settings, but uses a different SchemaRegistry to determine which policy
+// namespaces to request from the delegate provider.
+//
+// This provider tracks the SchemaRegistry's state, and becomes ready after
+// making sure the delegate provider has refreshed its policies with an updated
+// view of the complete schema. It is expected that the delegate's
+// SchemaRegistry is a CombinedSchemaRegistry tracking the
+// SchemaRegistryTrackingPolicyProvider's registry.
+//
+// This policy provider implementation is used to wrap the platform policy
+// provider for use with individual profiles, which may have different
+// SchemaRegistries. The SchemaRegistryTrackingPolicyProvider ensures that
+// initialization completion is only signaled for non-Chrome PolicyDomains after
+// the SchemaRegistry is fully initialized. This is important to avoid flapping
+// on startup due to asynchronous SchemaRegistry initialization while the
+// underlying policy provider has already completed initialization.
+//
+// A concrete example of this is POLICY_DOMAIN_EXTENSIONS, which registers
+// the PolicyNamespaces for the different extensions it's interested in based
+// on what extensions are installed in a Profile. Before that happens, the
+// underlying policy providers will not load the corresponding policy, so at
+// startup there would be a window during which the policy appears to be not
+// present. This is avoided by only flagging POLICY_DOMAIN_EXTENSIONS ready
+// once the corresponding SchemaRegistry has been fully initialized with the
+// list of installed extensions.
+class POLICY_EXPORT SchemaRegistryTrackingPolicyProvider
+ : public ConfigurationPolicyProvider,
+ public ConfigurationPolicyProvider::Observer {
+ public:
+ // The |delegate| must outlive this provider.
+ explicit SchemaRegistryTrackingPolicyProvider(
+ ConfigurationPolicyProvider* delegate);
+ SchemaRegistryTrackingPolicyProvider(
+ const SchemaRegistryTrackingPolicyProvider&) = delete;
+ SchemaRegistryTrackingPolicyProvider& operator=(
+ const SchemaRegistryTrackingPolicyProvider&) = delete;
+ ~SchemaRegistryTrackingPolicyProvider() override;
+
+ // ConfigurationPolicyProvider:
+ //
+ // Note that Init() and Shutdown() are not forwarded to the |delegate_|, since
+ // this provider does not own it and its up to the |delegate_|'s owner to
+ // initialize it and shut it down.
+ //
+ // Note also that this provider may have a SchemaRegistry passed in Init()
+ // that doesn't match the |delegate_|'s; therefore OnSchemaRegistryUpdated()
+ // and OnSchemaRegistryReady() are not forwarded either. It is assumed that
+ // the |delegate_|'s SchemaRegistry contains a superset of this provider's
+ // SchemaRegistry though (i.e. it's a CombinedSchemaRegistry that contains
+ // this provider's SchemaRegistry).
+ //
+ // This provider manages its own initialization state for all policy domains
+ // except POLICY_DOMAIN_CHROME, whose status is always queried from the
+ // |delegate_|. RefreshPolicies() calls are also forwarded, since this
+ // provider doesn't have a "real" policy source of its own.
+ void Init(SchemaRegistry* registry) override;
+ bool IsInitializationComplete(PolicyDomain domain) const override;
+ bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+ void RefreshPolicies() override;
+ void OnSchemaRegistryReady() override;
+ void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
+ // ConfigurationPolicyProvider::Observer:
+ void OnUpdatePolicy(ConfigurationPolicyProvider* provider) override;
+
+ private:
+ enum InitializationState {
+ WAITING_FOR_REGISTRY_READY,
+ WAITING_FOR_REFRESH,
+ READY,
+ };
+
+ raw_ptr<ConfigurationPolicyProvider> delegate_;
+ InitializationState state_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_SCHEMA_REGISTRY_TRACKING_POLICY_PROVIDER_H_
diff --git a/chromium/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc
new file mode 100644
index 00000000000..aca1393c241
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry_tracking_policy_provider_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry_tracking_policy_provider.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/values.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/core/common/schema_registry.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"foo\": { \"type\": \"string\" }"
+ " }"
+ "}";
+
+} // namespace
+
+class SchemaRegistryTrackingPolicyProviderTest : public testing::Test {
+ protected:
+ SchemaRegistryTrackingPolicyProviderTest()
+ : schema_registry_tracking_provider_(&mock_provider_) {
+ mock_provider_.Init();
+ schema_registry_tracking_provider_.Init(&schema_registry_);
+ schema_registry_tracking_provider_.AddObserver(&observer_);
+ }
+
+ ~SchemaRegistryTrackingPolicyProviderTest() override {
+ schema_registry_tracking_provider_.RemoveObserver(&observer_);
+ schema_registry_tracking_provider_.Shutdown();
+ mock_provider_.Shutdown();
+ }
+
+ Schema CreateTestSchema() {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ if (!schema.valid())
+ ADD_FAILURE() << error;
+ return schema;
+ }
+
+ SchemaRegistry schema_registry_;
+ MockConfigurationPolicyObserver observer_;
+ MockConfigurationPolicyProvider mock_provider_;
+ SchemaRegistryTrackingPolicyProvider schema_registry_tracking_provider_;
+};
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, Empty) {
+ EXPECT_FALSE(schema_registry_.IsReady());
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_EXTENSIONS));
+
+ EXPECT_CALL(mock_provider_, IsInitializationComplete(POLICY_DOMAIN_CHROME))
+ .WillOnce(Return(false));
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_CHROME));
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ const PolicyBundle empty_bundle;
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(empty_bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, PassOnChromePolicy) {
+ PolicyBundle bundle;
+ const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
+ bundle.Get(chrome_ns).Set("policy", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("visible"),
+ nullptr);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ std::unique_ptr<PolicyBundle> delegate_bundle(new PolicyBundle);
+ delegate_bundle->CopyFrom(bundle);
+ delegate_bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"))
+ .Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("not visible"), nullptr);
+ mock_provider_.UpdatePolicy(std::move(delegate_bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ POLICY_DOMAIN_EXTENSIONS));
+ EXPECT_TRUE(schema_registry_tracking_provider_.policies().Equals(bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, RefreshPolicies) {
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_tracking_provider_.RefreshPolicies();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, SchemaReady) {
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ schema_registry_.SetAllDomainsReady();
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, SchemaReadyWithComponents) {
+ PolicyMap policy_map;
+ policy_map.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("omg"), nullptr);
+ std::unique_ptr<PolicyBundle> bundle(new PolicyBundle);
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, "")) = policy_map.Clone();
+ bundle->Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz")) =
+ policy_map.Clone();
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ mock_provider_.UpdatePolicy(std::move(bundle));
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies()).Times(0);
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), CreateTestSchema());
+ schema_registry_.SetExtensionsDomainsReady();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+ PolicyBundle expected_bundle;
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, "")) =
+ policy_map.Clone();
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(expected_bundle));
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ schema_registry_tracking_provider_.OnUpdatePolicy(&mock_provider_);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+ expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz")) =
+ policy_map.Clone();
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(expected_bundle));
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, DelegateUpdates) {
+ schema_registry_.RegisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"), CreateTestSchema());
+ EXPECT_FALSE(schema_registry_.IsReady());
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ PolicyMap policy_map;
+ policy_map.Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("omg"), nullptr);
+ // Chrome policy updates are visible even if the components aren't ready.
+ EXPECT_CALL(observer_, OnUpdatePolicy(&schema_registry_tracking_provider_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.SetAllDomainsReady();
+ EXPECT_TRUE(schema_registry_.IsReady());
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+ EXPECT_FALSE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ // The provider becomes ready after this refresh completes, and policy updates
+ // are visible after that.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+
+ EXPECT_TRUE(schema_registry_tracking_provider_.IsInitializationComplete(
+ policy::POLICY_DOMAIN_EXTENSIONS));
+
+ // Updates continue to be visible.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdateChromePolicy(policy_map);
+ Mock::VerifyAndClearExpectations(&observer_);
+}
+
+TEST_F(SchemaRegistryTrackingPolicyProviderTest, RemoveAndAddComponent) {
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ const PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, "xyz");
+ schema_registry_.RegisterComponent(ns, CreateTestSchema());
+ schema_registry_.SetAllDomainsReady();
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ // Serve policy for |ns|.
+ PolicyBundle platform_policy;
+ platform_policy.Get(ns).Set("foo", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+ POLICY_SOURCE_CLOUD, base::Value("omg"), nullptr);
+ std::unique_ptr<PolicyBundle> copy(new PolicyBundle);
+ copy->CopyFrom(platform_policy);
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ mock_provider_.UpdatePolicy(std::move(copy));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(platform_policy));
+
+ // Now remove that component.
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ schema_registry_.UnregisterComponent(ns);
+ Mock::VerifyAndClearExpectations(&observer_);
+ const PolicyBundle empty;
+ EXPECT_TRUE(schema_registry_tracking_provider_.policies().Equals(empty));
+
+ // Adding it back should serve the current policies again, even though they
+ // haven't changed on the platform provider.
+ EXPECT_CALL(mock_provider_, RefreshPolicies());
+ schema_registry_.RegisterComponent(ns, CreateTestSchema());
+ Mock::VerifyAndClearExpectations(&mock_provider_);
+
+ EXPECT_CALL(observer_, OnUpdatePolicy(_));
+ copy = std::make_unique<PolicyBundle>();
+ copy->CopyFrom(platform_policy);
+ mock_provider_.UpdatePolicy(std::move(copy));
+ Mock::VerifyAndClearExpectations(&observer_);
+ EXPECT_TRUE(
+ schema_registry_tracking_provider_.policies().Equals(platform_policy));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_registry_unittest.cc b/chromium/components/policy/core/common/schema_registry_unittest.cc
new file mode 100644
index 00000000000..298b583035d
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_registry_unittest.cc
@@ -0,0 +1,342 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema_registry.h"
+
+#include <memory>
+
+#include "base/test/gtest_util.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/schema.h"
+#include "extensions/buildflags/buildflags.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Mock;
+using ::testing::_;
+
+namespace policy {
+
+namespace {
+
+const char kTestSchema[] =
+ R"({
+ "type": "object",
+ "properties": {
+ "string": { "type": "string" },
+ "integer": { "type": "integer" },
+ "boolean": { "type": "boolean" },
+ "double": { "type": "number" },
+ "list": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "object": {
+ "type": "object",
+ "properties": {
+ "a": { "type": "string" },
+ "b": { "type": "integer" }
+ }
+ }
+ }
+ })";
+
+class MockSchemaRegistryObserver : public SchemaRegistry::Observer {
+ public:
+ MockSchemaRegistryObserver() {}
+ ~MockSchemaRegistryObserver() override {}
+
+ MOCK_METHOD1(OnSchemaRegistryUpdated, void(bool));
+ MOCK_METHOD0(OnSchemaRegistryReady, void());
+};
+
+bool SchemaMapEquals(const scoped_refptr<SchemaMap>& schema_map1,
+ const scoped_refptr<SchemaMap>& schema_map2) {
+ PolicyNamespaceList added;
+ PolicyNamespaceList removed;
+ schema_map1->GetChanges(schema_map2, &removed, &added);
+ return added.empty() && removed.empty();
+}
+
+} // namespace
+
+TEST(SchemaRegistryTest, Notifications) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ SchemaRegistry registry;
+ registry.AddObserver(&observer);
+
+ ASSERT_TRUE(registry.schema_map().get());
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Re-register also triggers notifications, because the Schema might have
+ // changed.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_FALSE(registry.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ // Registering multiple components at once issues only one notification.
+ ComponentMap components;
+ components["abc"] = schema;
+ components["def"] = schema;
+ components["xyz"] = schema;
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry.RegisterComponents(POLICY_DOMAIN_EXTENSIONS, components);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ registry.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, IsReady) {
+ SchemaRegistry registry;
+ MockSchemaRegistryObserver observer;
+ registry.AddObserver(&observer);
+
+ EXPECT_FALSE(registry.IsReady());
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetExtensionsDomainsReady();
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_FALSE(registry.IsReady());
+#endif
+ EXPECT_CALL(observer, OnSchemaRegistryReady());
+ registry.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+ EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0);
+ registry.SetDomainReady(POLICY_DOMAIN_CHROME);
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(registry.IsReady());
+
+ CombinedSchemaRegistry combined;
+ EXPECT_TRUE(combined.IsReady());
+
+ registry.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, Combined) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ MockSchemaRegistryObserver observer;
+ std::unique_ptr<SchemaRegistry> registry1(new SchemaRegistry);
+ std::unique_ptr<SchemaRegistry> registry2(new SchemaRegistry);
+ CombinedSchemaRegistry combined;
+ combined.AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Starting to track a registry issues notifications when it comes with new
+ // schemas.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.Track(registry1.get());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding a new empty registry does not trigger notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ combined.Track(registry2.get());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding the same component to the combined registry itself triggers
+ // notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ combined.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Adding components to the sub-registries triggers notifications.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // If the same component is published in 2 sub-registries then the combined
+ // registry publishes one of them.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ ASSERT_EQ(1u, combined.schema_map()->GetDomains().size());
+ ASSERT_TRUE(combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS));
+ ASSERT_EQ(
+ 2u,
+ combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS)->size());
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at the combined registry.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ combined.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry1->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Still registered at registry2.
+ EXPECT_TRUE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry2->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"));
+ Mock::VerifyAndClearExpectations(&observer);
+ // Now it's gone.
+ EXPECT_FALSE(combined.schema_map()->GetSchema(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)).Times(2);
+ registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""),
+ schema);
+ registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "hij"),
+ schema);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Untracking |registry1| doesn't trigger an update notification, because it
+ // doesn't contain any components.
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0);
+ registry1.reset();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry2.reset();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ combined.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, ForwardingSchemaRegistry) {
+ std::unique_ptr<SchemaRegistry> registry(new SchemaRegistry);
+ ForwardingSchemaRegistry forwarding(registry.get());
+ MockSchemaRegistryObserver observer;
+ forwarding.AddObserver(&observer);
+
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+ // They always have the same SchemaMap.
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(true));
+ registry->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"),
+ Schema());
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ EXPECT_CALL(observer, OnSchemaRegistryUpdated(false));
+ registry->UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"));
+ Mock::VerifyAndClearExpectations(&observer);
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+
+ // No notifications expected for these calls.
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+
+ registry->SetExtensionsDomainsReady();
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding.IsReady());
+
+ EXPECT_CALL(observer, OnSchemaRegistryReady());
+ registry->SetDomainReady(POLICY_DOMAIN_CHROME);
+ EXPECT_TRUE(registry->IsReady());
+ EXPECT_TRUE(forwarding.IsReady());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map()));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ forwarding.SetExtensionsDomainsReady();
+ forwarding.SetDomainReady(POLICY_DOMAIN_CHROME);
+ EXPECT_TRUE(forwarding.IsReady());
+
+ // Keep the same SchemaMap when the original registry is gone.
+ // No notifications are expected in this case either.
+ scoped_refptr<SchemaMap> schema_map = registry->schema_map();
+ registry.reset();
+ EXPECT_TRUE(SchemaMapEquals(schema_map, forwarding.schema_map()));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ forwarding.RemoveObserver(&observer);
+}
+
+TEST(SchemaRegistryTest, ForwardingSchemaRegistryReadiness) {
+ std::unique_ptr<SchemaRegistry> registry(new SchemaRegistry);
+
+ ForwardingSchemaRegistry forwarding_1(registry.get());
+ EXPECT_FALSE(registry->IsReady());
+ EXPECT_FALSE(forwarding_1.IsReady());
+
+ // Once the wrapped registry gets ready, the forwarding schema registry
+ // becomes ready too.
+ registry->SetAllDomainsReady();
+ EXPECT_TRUE(registry->IsReady());
+ EXPECT_TRUE(forwarding_1.IsReady());
+
+ // The wrapped registry was ready at the time when the forwarding registry was
+ // constructed, so the forwarding registry is immediately ready too.
+ ForwardingSchemaRegistry forwarding_2(registry.get());
+ EXPECT_TRUE(forwarding_2.IsReady());
+
+ // Destruction of the wrapped registry doesn't change the readiness of the
+ // forwarding registry.
+ registry.reset();
+ EXPECT_TRUE(forwarding_1.IsReady());
+ EXPECT_TRUE(forwarding_2.IsReady());
+}
+
+// Extension policy unregister before register shouldn't cause DCHECK failure.
+// However, Chrome policy should always register first.
+TEST(SchemaRegistryTest, UnregisterBeforeRegister) {
+ SchemaRegistry registry;
+ ASSERT_NO_FATAL_FAILURE(registry.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "")));
+ ASSERT_NO_FATAL_FAILURE(registry.UnregisterComponent(
+ PolicyNamespace(POLICY_DOMAIN_SIGNIN_EXTENSIONS, "")));
+
+ ASSERT_DCHECK_DEATH(
+ registry.UnregisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, "")));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/schema_unittest.cc b/chromium/components/policy/core/common/schema_unittest.cc
new file mode 100644
index 00000000000..ff5bf4bb5ef
--- /dev/null
+++ b/chromium/components/policy/core/common/schema_unittest.cc
@@ -0,0 +1,1592 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/schema.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "components/policy/core/common/schema_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+#define TestSchemaValidation(a, b, c, d) \
+ TestSchemaValidationHelper( \
+ base::StringPrintf("%s:%i", __FILE__, __LINE__), a, b, c, d)
+
+const char kTestSchema[] = R"({
+ "type": "object",
+ "properties": {
+ "Boolean": { "type": "boolean" },
+ "Integer": { "type": "integer" },
+ "Number": { "type": "number" },
+ "String": { "type": "string" },
+ "Array": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "ArrayOfObjects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "string" },
+ "two": { "type": "integer" }
+ }
+ }
+ },
+ "ArrayOfArray": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "Object": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "boolean" },
+ "two": { "type": "integer" }
+ },
+ "additionalProperties": { "type": "string" }
+ },
+ "ObjectOfObject": {
+ "type": "object",
+ "properties": {
+ "Object": {
+ "type": "object",
+ "properties": {
+ "one": { "type": "string" },
+ "two": { "type": "integer" }
+ }
+ }
+ }
+ },
+ "IntegerWithEnums": {
+ "type": "integer",
+ "enum": [1, 2, 3]
+ },
+ "IntegerWithEnumsGaps": {
+ "type": "integer",
+ "enum": [10, 20, 30]
+ },
+ "StringWithEnums": {
+ "type": "string",
+ "enum": ["one", "two", "three"]
+ },
+ "IntegerWithRange": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 3
+ },
+ "ObjectOfArray": {
+ "type": "object",
+ "properties": {
+ "List": {
+ "type": "array",
+ "items": { "type": "integer" }
+ }
+ }
+ },
+ "ArrayOfObjectOfArray": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "List": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ }
+ },
+ "StringWithPattern": {
+ "type": "string",
+ "pattern": "^foo+$"
+ },
+ "ObjectWithPatternProperties": {
+ "type": "object",
+ "patternProperties": {
+ "^foo+$": { "type": "integer" },
+ "^bar+$": {
+ "type": "string",
+ "enum": ["one", "two"]
+ }
+ },
+ "properties": {
+ "bar": {
+ "type": "string",
+ "enum": ["one", "three"]
+ }
+ }
+ },
+ "ObjectWithRequiredProperties": {
+ "type": "object",
+ "properties": {
+ "Integer": {
+ "type": "integer",
+ "enum": [1, 2]
+ },
+ "String": { "type": "string" },
+ "Number": { "type": "number" }
+ },
+ "patternProperties": {
+ "^Integer": {
+ "type": "integer",
+ "enum": [1, 3]
+ }
+ },
+ "required": [ "Integer", "String" ]
+ }
+ }
+})";
+
+bool ParseFails(const std::string& content) {
+ std::string error;
+ Schema schema = Schema::Parse(content, &error);
+ if (schema.valid())
+ return false;
+ EXPECT_FALSE(error.empty());
+ return true;
+}
+
+void TestSchemaValidationHelper(const std::string& source,
+ Schema schema,
+ const base::Value& value,
+ SchemaOnErrorStrategy strategy,
+ bool expected_return_value) {
+ std::string error;
+ static const char kNoErrorReturned[] = "No error returned.";
+
+ // Test that Schema::Validate() works as expected.
+ error = kNoErrorReturned;
+ bool returned = schema.Validate(value, strategy, nullptr, &error);
+ ASSERT_EQ(expected_return_value, returned) << source << ": " << error;
+
+ // Test that Schema::Normalize() will return the same value as
+ // Schema::Validate().
+ error = kNoErrorReturned;
+ base::Value cloned_value(value.Clone());
+ bool touched = false;
+ returned =
+ schema.Normalize(&cloned_value, strategy, nullptr, &error, &touched);
+ EXPECT_EQ(expected_return_value, returned) << source << ": " << error;
+
+ bool strictly_valid = schema.Validate(value, SCHEMA_STRICT, nullptr, &error);
+ EXPECT_EQ(touched, !strictly_valid && returned) << source;
+
+ // Test that Schema::Normalize() have actually dropped invalid and unknown
+ // properties.
+ if (expected_return_value) {
+ EXPECT_TRUE(schema.Validate(cloned_value, SCHEMA_STRICT, nullptr, &error))
+ << source;
+ EXPECT_TRUE(schema.Normalize(&cloned_value, SCHEMA_STRICT, nullptr, &error,
+ nullptr))
+ << source;
+ }
+}
+
+void TestSchemaValidationWithPath(Schema schema,
+ const base::Value& value,
+ const std::string& expected_failure_path) {
+ std::string error_path = "NOT_SET";
+ std::string error;
+
+ bool returned = schema.Validate(value, SCHEMA_STRICT, &error_path, &error);
+ ASSERT_FALSE(returned) << error_path;
+ EXPECT_EQ(error_path, expected_failure_path);
+}
+
+std::string SchemaObjectWrapper(const std::string& subschema) {
+ return "{"
+ " \"type\": \"object\","
+ " \"properties\": {"
+ " \"SomePropertyName\":" + subschema +
+ " }"
+ "}";
+}
+
+} // namespace
+
+TEST(SchemaTest, MinimalSchema) {
+ EXPECT_FALSE(ParseFails(R"({ "type": "object" })"));
+}
+
+TEST(SchemaTest, InvalidSchemas) {
+ EXPECT_TRUE(ParseFails(""));
+ EXPECT_TRUE(ParseFails("omg"));
+ EXPECT_TRUE(ParseFails("\"omg\""));
+ EXPECT_TRUE(ParseFails("123"));
+ EXPECT_TRUE(ParseFails("[]"));
+ EXPECT_TRUE(ParseFails("null"));
+ EXPECT_TRUE(ParseFails("{}"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "additionalProperties": { "type":"object" }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "patternProperties": { "a+b*": { "type": "object" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": "bogus" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": ["string", "number"] } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": { "type": "any" } }
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": { "Policy": 123 }
+ })"));
+
+ EXPECT_FALSE(ParseFails(R"({
+ "type": "object",
+ "unknown attribute": "is ignored"
+ })"));
+}
+
+TEST(SchemaTest, Ownership) {
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "sub": {
+ "type": "object",
+ "properties": {
+ "subsub": { "type": "string" }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ schema = schema.GetKnownProperty("sub");
+ ASSERT_TRUE(schema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ {
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ("subsub", it.key());
+
+ schema = it.schema();
+ it.Advance();
+ EXPECT_TRUE(it.IsAtEnd());
+ }
+
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::STRING, schema.type());
+
+ // This test shouldn't leak nor use invalid memory.
+}
+
+TEST(SchemaTest, ValidSchema) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+ EXPECT_FALSE(schema.GetProperty("invalid").valid());
+
+ Schema sub = schema.GetProperty("Boolean");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, sub.type());
+
+ sub = schema.GetProperty("Integer");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("Number");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::DOUBLE, sub.type());
+
+ sub = schema.GetProperty("String");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("Array");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("ArrayOfObjects");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, sub.type());
+ Schema subsub = sub.GetProperty("one");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subsub.type());
+ subsub = sub.GetProperty("two");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
+ subsub = sub.GetProperty("invalid");
+ EXPECT_FALSE(subsub.valid());
+
+ sub = schema.GetProperty("ArrayOfArray");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ sub = sub.GetItems();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("Object");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+ subsub = sub.GetProperty("one");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, subsub.type());
+ subsub = sub.GetProperty("two");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
+ subsub = sub.GetProperty("undeclared");
+ ASSERT_TRUE(subsub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, subsub.type());
+
+ sub = schema.GetProperty("IntegerWithEnums");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("IntegerWithEnumsGaps");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("StringWithEnums");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("IntegerWithRange");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
+
+ sub = schema.GetProperty("StringWithPattern");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, sub.type());
+
+ sub = schema.GetProperty("ObjectWithPatternProperties");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+
+ sub = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, sub.type());
+
+ struct {
+ const char* expected_key;
+ base::Value::Type expected_type;
+ } kExpectedProperties[] = {
+ { "Array", base::Value::Type::LIST },
+ { "ArrayOfArray", base::Value::Type::LIST },
+ { "ArrayOfObjectOfArray", base::Value::Type::LIST },
+ { "ArrayOfObjects", base::Value::Type::LIST },
+ { "Boolean", base::Value::Type::BOOLEAN },
+ { "Integer", base::Value::Type::INTEGER },
+ { "IntegerWithEnums", base::Value::Type::INTEGER },
+ { "IntegerWithEnumsGaps", base::Value::Type::INTEGER },
+ { "IntegerWithRange", base::Value::Type::INTEGER },
+ { "Number", base::Value::Type::DOUBLE },
+ { "Object", base::Value::Type::DICTIONARY },
+ { "ObjectOfArray", base::Value::Type::DICTIONARY },
+ { "ObjectOfObject", base::Value::Type::DICTIONARY },
+ { "ObjectWithPatternProperties", base::Value::Type::DICTIONARY },
+ { "ObjectWithRequiredProperties", base::Value::Type::DICTIONARY },
+ { "String", base::Value::Type::STRING },
+ { "StringWithEnums", base::Value::Type::STRING },
+ { "StringWithPattern", base::Value::Type::STRING },
+ };
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ for (size_t i = 0; i < std::size(kExpectedProperties); ++i) {
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ(kExpectedProperties[i].expected_key, it.key());
+ ASSERT_TRUE(it.schema().valid());
+ EXPECT_EQ(kExpectedProperties[i].expected_type, it.schema().type());
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+}
+
+TEST(SchemaTest, Lookups) {
+ std::string error;
+
+ Schema schema = Schema::Parse(R"({ "type": "object" })", &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ // This empty schema should never find named properties.
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetRequiredProperties().empty());
+ EXPECT_TRUE(schema.GetPatternProperties("").empty());
+ EXPECT_FALSE(schema.GetAdditionalProperties().valid());
+ EXPECT_TRUE(schema.GetPropertiesIterator().IsAtEnd());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "Boolean": { "type": "boolean" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetKnownProperty("Boolean").valid());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "aa" : { "type": "boolean" },
+ "abab" : { "type": "string" },
+ "ab" : { "type": "number" },
+ "aba" : { "type": "integer" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+
+ struct {
+ const char* expected_key;
+ base::Value::Type expected_type;
+ } kExpectedKeys[] = {
+ { "aa", base::Value::Type::BOOLEAN },
+ { "ab", base::Value::Type::DOUBLE },
+ { "aba", base::Value::Type::INTEGER },
+ { "abab", base::Value::Type::STRING },
+ };
+ for (size_t i = 0; i < std::size(kExpectedKeys); ++i) {
+ Schema sub = schema.GetKnownProperty(kExpectedKeys[i].expected_key);
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(kExpectedKeys[i].expected_type, sub.type());
+ }
+
+ schema = Schema::Parse(R"(
+ {
+ "type": "object",
+ "properties": {
+ "String": { "type": "string" },
+ "Object": {
+ "type": "object",
+ "properties": {"Integer": {"type": "integer"}},
+ "required": [ "Integer" ]
+ },
+ "Number": { "type": "number" }
+ },
+ "required": [ "String", "Object"]
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String", "Object"}),
+ schema.GetRequiredProperties());
+
+ schema = schema.GetKnownProperty("Object");
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"Integer"}),
+ schema.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Wrap) {
+ const internal::SchemaNode kSchemas[] = {
+ { base::Value::Type::DICTIONARY, 0 }, // 0: root node
+ { base::Value::Type::BOOLEAN, -1 }, // 1
+ { base::Value::Type::INTEGER, -1 }, // 2
+ { base::Value::Type::DOUBLE, -1 }, // 3
+ { base::Value::Type::STRING, -1 }, // 4
+ { base::Value::Type::LIST, 4 }, // 5: list of strings.
+ { base::Value::Type::LIST, 5 }, // 6: list of lists of strings.
+ { base::Value::Type::INTEGER, 0 }, // 7: integer enumerations.
+ { base::Value::Type::INTEGER, 1 }, // 8: ranged integers.
+ { base::Value::Type::STRING, 2 }, // 9: string enumerations.
+ { base::Value::Type::STRING, 3 }, // 10: string with pattern.
+ { base::Value::Type::DICTIONARY, 1 }, // 11: dictionary with required
+ // properties
+ };
+
+ const internal::PropertyNode kPropertyNodes[] = {
+ { "Boolean", 1}, // 0
+ { "DictRequired", 11}, // 1
+ { "Integer", 2}, // 2
+ { "List", 5}, // 3
+ { "Number", 3}, // 4
+ { "String", 4}, // 5
+ { "IntEnum", 7}, // 6
+ { "RangedInt", 8}, // 7
+ { "StrEnum", 9}, // 8
+ { "StrPat", 10}, // 9
+ { "bar+$", 4}, // 10
+ { "String", 4}, // 11
+ { "Number", 3}, // 12
+ };
+
+ const internal::PropertiesNode kProperties[] = {
+ // 0 to 10 (exclusive) are the known properties in kPropertyNodes, 9 is
+ // patternProperties and 6 is the additionalProperties node.
+ { 0, 10, 11, 0, 0, 6 },
+ // 11 to 13 (exclusive) are the known properties in kPropertyNodes. 0 to
+ // 1 (exclusive) are the required properties in kRequired. -1 indicates
+ // no additionalProperties.
+ { 11, 13, 13, 0, 1, -1 },
+ };
+
+ const internal::RestrictionNode kRestriction[] = {
+ {{0, 3}}, // 0: [1, 2, 3]
+ {{5, 1}}, // 1: minimum = 1, maximum = 5
+ {{0, 3}}, // 2: ["one", "two", "three"]
+ {{3, 3}}, // 3: pattern "foo+"
+ };
+
+ const char* kRequired[] = {"String"};
+
+ const int kIntEnums[] = {1, 2, 3};
+
+ const char* kStringEnums[] = {
+ "one", // 0
+ "two", // 1
+ "three", // 2
+ "foo+", // 3
+ };
+
+ const internal::SchemaData kData = {
+ kSchemas, kPropertyNodes, kProperties, kRestriction,
+ kRequired, kIntEnums, kStringEnums,
+ -1 // validation_schema_root_index
+ };
+
+ Schema schema = Schema::Wrap(&kData);
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ // Wrapped schemas have no sensitive values.
+ EXPECT_FALSE(schema.IsSensitiveValue());
+
+ struct {
+ const char* key;
+ base::Value::Type type;
+ } kExpectedProperties[] = {
+ { "Boolean", base::Value::Type::BOOLEAN },
+ { "DictRequired", base::Value::Type::DICTIONARY },
+ { "Integer", base::Value::Type::INTEGER },
+ { "List", base::Value::Type::LIST },
+ { "Number", base::Value::Type::DOUBLE },
+ { "String", base::Value::Type::STRING },
+ { "IntEnum", base::Value::Type::INTEGER },
+ { "RangedInt", base::Value::Type::INTEGER },
+ { "StrEnum", base::Value::Type::STRING },
+ { "StrPat", base::Value::Type::STRING },
+ };
+
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ for (size_t i = 0; i < std::size(kExpectedProperties); ++i) {
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ(kExpectedProperties[i].key, it.key());
+ Schema sub = it.schema();
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(kExpectedProperties[i].type, sub.type());
+
+ if (sub.type() == base::Value::Type::LIST) {
+ Schema items = sub.GetItems();
+ ASSERT_TRUE(items.valid());
+ EXPECT_EQ(base::Value::Type::STRING, items.type());
+ }
+
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+
+ Schema sub = schema.GetAdditionalProperties();
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, sub.type());
+ Schema subsub = sub.GetItems();
+ ASSERT_TRUE(subsub.valid());
+ ASSERT_EQ(base::Value::Type::LIST, subsub.type());
+ Schema subsubsub = subsub.GetItems();
+ ASSERT_TRUE(subsubsub.valid());
+ ASSERT_EQ(base::Value::Type::STRING, subsubsub.type());
+
+ SchemaList schema_list = schema.GetPatternProperties("barr");
+ ASSERT_EQ(1u, schema_list.size());
+ sub = schema_list[0];
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sub.type());
+
+ EXPECT_TRUE(schema.GetPatternProperties("ba").empty());
+ EXPECT_TRUE(schema.GetPatternProperties("bar+$").empty());
+
+ Schema dict = schema.GetKnownProperty("DictRequired");
+ ASSERT_TRUE(dict.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, dict.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String"}), dict.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Validate) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ base::DictionaryValue bundle;
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ // Wrong type, expected integer.
+ bundle.SetBoolKey("Integer", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // Wrong type, expected list of strings.
+ {
+ bundle.DictClear();
+ base::ListValue list;
+ list.Append(1);
+ bundle.SetKey("Array", std::move(list));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Wrong type in a sub-object.
+ {
+ bundle.DictClear();
+ base::DictionaryValue dict;
+ dict.SetStringKey("one", "one");
+ bundle.SetKey("Object", std::move(dict));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Unknown name.
+ bundle.DictClear();
+ bundle.SetBoolKey("Unknown", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // All of these will be valid.
+ bundle.DictClear();
+ bundle.SetBoolKey("Boolean", true);
+ bundle.SetIntKey("Integer", 123);
+ bundle.SetDoubleKey("Number", 3.14);
+ bundle.SetStringKey("String", "omg");
+
+ {
+ base::ListValue list;
+ list.Append("a string");
+ list.Append("another string");
+ bundle.SetKey("Array", std::move(list));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetStringKey("one", "string");
+ dict.SetIntKey("two", 2);
+ base::ListValue list;
+ list.Append(dict.Clone());
+ list.Append(std::move(dict));
+ bundle.SetKey("ArrayOfObjects", std::move(list));
+ }
+
+ {
+ base::ListValue list;
+ list.Append("a string");
+ list.Append("another string");
+ base::ListValue listlist;
+ listlist.Append(list.Clone());
+ listlist.Append(std::move(list));
+ bundle.SetKey("ArrayOfArray", std::move(listlist));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetBoolKey("one", true);
+ dict.SetIntKey("two", 2);
+ dict.SetStringKey("additionally", "a string");
+ dict.SetStringKey("and also", "another string");
+ bundle.SetKey("Object", std::move(dict));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetIntKey("Integer", 1);
+ dict.SetStringKey("String", "a string");
+ dict.SetDoubleKey("Number", 3.14);
+ bundle.SetKey("ObjectWithRequiredProperties", std::move(dict));
+ }
+
+ bundle.SetIntKey("IntegerWithEnums", 1);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 20);
+ bundle.SetStringKey("StringWithEnums", "two");
+ bundle.SetIntKey("IntegerWithRange", 3);
+
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ bundle.SetIntKey("IntegerWithEnums", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnums", 1);
+
+ bundle.SetIntKey("IntegerWithEnumsGaps", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 9);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 10);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 11);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 19);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 21);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 29);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 30);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 31);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 100);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithEnumsGaps", 20);
+
+ bundle.SetStringKey("StringWithEnums", "FOUR");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetStringKey("StringWithEnums", "two");
+
+ bundle.SetIntKey("IntegerWithRange", 4);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetIntKey("IntegerWithRange", 3);
+
+ // Unknown top level property.
+ bundle.SetStringKey("boom", "bang");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(schema, bundle,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(schema, bundle, "");
+ bundle.RemoveKey("boom");
+
+ // Invalid top level property.
+ bundle.SetIntKey("Boolean", 12345);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(schema, bundle,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+ TestSchemaValidationWithPath(schema, bundle, "Boolean");
+ bundle.SetBoolKey("Boolean", true);
+
+ // Tests on ObjectOfObject.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfObject");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Unknown property.
+ root.SetBoolPath("Object.three", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(subschema, root, "Object");
+ root.RemovePath("Object.three");
+
+ // Invalid property.
+ root.SetIntPath("Object.one", 12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+ TestSchemaValidationWithPath(subschema, root, "Object.one");
+ root.RemovePath("Object.one");
+ }
+
+ // Tests on ArrayOfObjects.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjects");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+ base::Value::ListView root_view = root.GetListDeprecated();
+
+ // Unknown property.
+ base::Value dict_value(base::Value::Type::DICTIONARY);
+ dict_value.SetBoolKey("three", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0]");
+ root.EraseListIter(root_view.begin() + (root_view.size() - 1));
+
+ // Invalid property.
+ dict_value = base::Value(base::Value::Type::DICTIONARY);
+ dict_value.SetBoolKey("two", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].two");
+ root.EraseListIter(root_view.begin() + (root_view.size() - 1));
+ }
+
+ // Tests on ObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ base::ListValue* list_value =
+ root.SetList("List", std::make_unique<base::ListValue>());
+
+ // Test that there are not errors here.
+ list_value->Append(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+
+ // Invalid list item.
+ list_value->Append("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(subschema, root, "List.items[1]");
+ }
+
+ // Tests on ArrayOfObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+
+ auto dict_value = std::make_unique<base::DictionaryValue>();
+ base::ListValue* list_value =
+ dict_value->SetList("List", std::make_unique<base::ListValue>());
+ root.Append(std::move(dict_value));
+
+ // Test that there are not errors here.
+ list_value->Append("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+
+ // Invalid list item.
+ list_value->Append(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].List.items[1]");
+ }
+
+ // Tests on StringWithPattern.
+ {
+ Schema subschema = schema.GetProperty("StringWithPattern");
+ ASSERT_TRUE(subschema.valid());
+
+ TestSchemaValidation(subschema, base::Value("foobar"), SCHEMA_STRICT,
+ false);
+ TestSchemaValidation(subschema, base::Value("foo"), SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, base::Value("fo"), SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, base::Value("fooo"), SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, base::Value("^foo+$"), SCHEMA_STRICT,
+ false);
+ }
+
+ // Tests on ObjectWithPatternProperties.
+ {
+ Schema subschema = schema.GetProperty("ObjectWithPatternProperties");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ ASSERT_EQ(1u, subschema.GetPatternProperties("fooo").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("foo").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("barr").size());
+ ASSERT_EQ(1u, subschema.GetPatternProperties("bar").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("fooo").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("foo").size());
+ ASSERT_EQ(1u, subschema.GetMatchingProperties("barr").size());
+ ASSERT_EQ(2u, subschema.GetMatchingProperties("bar").size());
+ ASSERT_TRUE(subschema.GetPatternProperties("foobar").empty());
+
+ root.SetIntKey("fooo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolKey("fooo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.RemoveKey("fooo");
+
+ root.SetIntKey("foo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolKey("foo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.RemoveKey("foo");
+
+ root.SetStringKey("barr", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetStringKey("barr", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetBoolKey("barr", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.RemoveKey("barr");
+
+ root.SetStringKey("bar", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetStringKey("bar", "two");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetStringKey("bar", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.RemoveKey("bar");
+
+ root.SetIntKey("foobar", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
+ root.RemoveKey("foobar");
+ }
+
+ // Tests on ObjectWithRequiredProperties
+ {
+ Schema subschema = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Required property missing.
+ root.SetIntKey("Integer", 1);
+ root.SetDoubleKey("Number", 3.14);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+
+ // Invalid required property.
+ root.SetIntKey("String", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+ root.SetStringKey("String", "a string");
+
+ // Invalid subschema of required property with multiple subschemas.
+ //
+ // The "Integer" property has two subschemas, one in "properties" and one
+ // in "patternProperties". The first test generates a valid schema for the
+ // first subschema and the second test generates a valid schema for the
+ // second subschema. In both cases validation should fail because one of the
+ // required properties is invalid.
+ root.SetIntKey("Integer", 2);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+
+ root.SetIntKey("Integer", 3);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root,
+ SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
+ }
+
+ // Test that integer to double promotion is allowed.
+ bundle.SetIntKey("Number", 31415);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+}
+
+TEST(SchemaTest, InvalidReferences) {
+ // References to undeclared schemas fail.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": { "$ref": "undeclared" }
+ }
+ })"));
+
+ // Can't refer to self.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": {
+ "id": "self",
+ "$ref": "self"
+ }
+ }
+ })"));
+
+ // Duplicated IDs are invalid.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "properties": {
+ "name": {
+ "id": "x",
+ "type": "string"
+ },
+ "another": {
+ "id": "x",
+ "type": "string"
+ }
+ }
+ })"));
+
+ // Main object can't be a reference.
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "id": "main",
+ "$ref": "main"
+ })"));
+
+ EXPECT_TRUE(ParseFails(R"({
+ "type": "object",
+ "$ref": "main"
+ })"));
+}
+
+TEST(SchemaTest, RecursiveReferences) {
+ // Verifies that references can go to a parent schema, to define a
+ // recursive type.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "bookmarks": {
+ "type": "array",
+ "id": "ListOfBookmarks",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "url": { "type": "string" },
+ "children": { "$ref": "ListOfBookmarks" }
+ }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema parent = schema.GetKnownProperty("bookmarks");
+ ASSERT_TRUE(parent.valid());
+ ASSERT_EQ(base::Value::Type::LIST, parent.type());
+
+ // Check the recursive type a number of times.
+ for (int i = 0; i < 10; ++i) {
+ Schema items = parent.GetItems();
+ ASSERT_TRUE(items.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, items.type());
+
+ Schema prop = items.GetKnownProperty("name");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::STRING, prop.type());
+
+ prop = items.GetKnownProperty("url");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::STRING, prop.type());
+
+ prop = items.GetKnownProperty("children");
+ ASSERT_TRUE(prop.valid());
+ ASSERT_EQ(base::Value::Type::LIST, prop.type());
+
+ parent = prop;
+ }
+}
+
+TEST(SchemaTest, UnorderedReferences) {
+ // Verifies that references and IDs can come in any order.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "a": { "$ref": "shared" },
+ "b": { "$ref": "shared" },
+ "c": { "$ref": "shared" },
+ "d": { "$ref": "shared" },
+ "e": {
+ "type": "boolean",
+ "id": "shared"
+ },
+ "f": { "$ref": "shared" },
+ "g": { "$ref": "shared" },
+ "h": { "$ref": "shared" },
+ "i": { "$ref": "shared" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ for (char c = 'a'; c <= 'i'; ++c) {
+ Schema sub = schema.GetKnownProperty(std::string(1, c));
+ ASSERT_TRUE(sub.valid()) << c;
+ ASSERT_EQ(base::Value::Type::BOOLEAN, sub.type()) << c;
+ }
+}
+
+TEST(SchemaTest, AdditionalPropertiesReference) {
+ // Verifies that "additionalProperties" can be a reference.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "policy": {
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ }
+ },
+ "additionalProperties": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema policy = schema.GetKnownProperty("policy");
+ ASSERT_TRUE(policy.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, policy.type());
+
+ Schema foo = policy.GetKnownProperty("foo");
+ ASSERT_TRUE(foo.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
+
+ Schema additional = policy.GetAdditionalProperties();
+ ASSERT_TRUE(additional.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, additional.type());
+
+ Schema x = policy.GetProperty("x");
+ ASSERT_TRUE(x.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, x.type());
+}
+
+TEST(SchemaTest, ItemsReference) {
+ // Verifies that "items" can be a reference.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ },
+ "list": {
+ "type": "array",
+ "items": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema foo = schema.GetKnownProperty("foo");
+ ASSERT_TRUE(foo.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
+
+ Schema list = schema.GetKnownProperty("list");
+ ASSERT_TRUE(list.valid());
+ ASSERT_EQ(base::Value::Type::LIST, list.type());
+
+ Schema items = list.GetItems();
+ ASSERT_TRUE(items.valid());
+ ASSERT_EQ(base::Value::Type::BOOLEAN, items.type());
+}
+
+TEST(SchemaTest, SchemaNodeSensitiveValues) {
+ std::string error;
+
+ const std::string kNormalBooleanSchema = "normal_boolean";
+ const std::string kSensitiveBooleanSchema = "sensitive_boolean";
+ const std::string kSensitiveStringSchema = "sensitive_string";
+ const std::string kSensitiveObjectSchema = "sensitive_object";
+ const std::string kSensitiveArraySchema = "sensitive_array";
+ const std::string kSensitiveIntegerSchema = "sensitive_integer";
+ const std::string kSensitiveNumberSchema = "sensitive_number";
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "normal_boolean": {
+ "type": "boolean"
+ },
+ "sensitive_boolean": {
+ "type": "boolean",
+ "sensitiveValue": true
+ },
+ "sensitive_string": {
+ "type": "string",
+ "sensitiveValue": true
+ },
+ "sensitive_object": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "boolean"
+ },
+ "sensitiveValue": true
+ },
+ "sensitive_array": {
+ "type": "array",
+ "items": {
+ "type": "boolean"
+ },
+ "sensitiveValue": true
+ },
+ "sensitive_integer": {
+ "type": "integer",
+ "sensitiveValue": true
+ },
+ "sensitive_number": {
+ "type": "number",
+ "sensitiveValue": true
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+ EXPECT_FALSE(schema.IsSensitiveValue());
+ EXPECT_TRUE(schema.HasSensitiveChildren());
+
+ Schema normal_boolean = schema.GetKnownProperty(kNormalBooleanSchema);
+ ASSERT_TRUE(normal_boolean.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, normal_boolean.type());
+ EXPECT_FALSE(normal_boolean.IsSensitiveValue());
+ EXPECT_FALSE(normal_boolean.HasSensitiveChildren());
+
+ Schema sensitive_boolean = schema.GetKnownProperty(kSensitiveBooleanSchema);
+ ASSERT_TRUE(sensitive_boolean.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, sensitive_boolean.type());
+ EXPECT_TRUE(sensitive_boolean.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_boolean.HasSensitiveChildren());
+
+ Schema sensitive_string = schema.GetKnownProperty(kSensitiveStringSchema);
+ ASSERT_TRUE(sensitive_string.valid());
+ EXPECT_EQ(base::Value::Type::STRING, sensitive_string.type());
+ EXPECT_TRUE(sensitive_string.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_string.HasSensitiveChildren());
+
+ Schema sensitive_object = schema.GetKnownProperty(kSensitiveObjectSchema);
+ ASSERT_TRUE(sensitive_object.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, sensitive_object.type());
+ EXPECT_TRUE(sensitive_object.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_object.HasSensitiveChildren());
+
+ Schema sensitive_array = schema.GetKnownProperty(kSensitiveArraySchema);
+ ASSERT_TRUE(sensitive_array.valid());
+ EXPECT_EQ(base::Value::Type::LIST, sensitive_array.type());
+ EXPECT_TRUE(sensitive_array.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_array.HasSensitiveChildren());
+
+ Schema sensitive_integer = schema.GetKnownProperty(kSensitiveIntegerSchema);
+ ASSERT_TRUE(sensitive_integer.valid());
+ EXPECT_EQ(base::Value::Type::INTEGER, sensitive_integer.type());
+ EXPECT_TRUE(sensitive_integer.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_integer.HasSensitiveChildren());
+
+ Schema sensitive_number = schema.GetKnownProperty(kSensitiveNumberSchema);
+ ASSERT_TRUE(sensitive_number.valid());
+ EXPECT_EQ(base::Value::Type::DOUBLE, sensitive_number.type());
+ EXPECT_TRUE(sensitive_number.IsSensitiveValue());
+ EXPECT_FALSE(sensitive_number.HasSensitiveChildren());
+
+ // Run |MaskSensitiveValues| on the top-level schema
+ base::DictionaryValue object;
+ object.SetKey("objectProperty", base::Value(true));
+ base::ListValue array;
+ array.Append(base::Value(true));
+
+ base::Value value(base::Value::Type::DICTIONARY);
+ value.SetKey(kNormalBooleanSchema, base::Value(true));
+ value.SetKey(kSensitiveBooleanSchema, base::Value(true));
+ value.SetKey(kSensitiveStringSchema, base::Value("testvalue"));
+ value.SetKey(kSensitiveObjectSchema, std::move(object));
+ value.SetKey(kSensitiveArraySchema, std::move(array));
+ value.SetKey(kSensitiveIntegerSchema, base::Value(42));
+ value.SetKey(kSensitiveNumberSchema, base::Value(3.141));
+ schema.MaskSensitiveValues(&value);
+
+ base::Value value_masked("********");
+ base::Value value_expected(base::Value::Type::DICTIONARY);
+ value_expected.SetKey(kNormalBooleanSchema, base::Value(true));
+ value_expected.SetKey(kSensitiveBooleanSchema, value_masked.Clone());
+ value_expected.SetKey(kSensitiveStringSchema, value_masked.Clone());
+ value_expected.SetKey(kSensitiveObjectSchema, value_masked.Clone());
+ value_expected.SetKey(kSensitiveArraySchema, value_masked.Clone());
+ value_expected.SetKey(kSensitiveIntegerSchema, value_masked.Clone());
+ value_expected.SetKey(kSensitiveNumberSchema, value_masked.Clone());
+ EXPECT_EQ(value_expected, value);
+
+ // Run |MaskSensitiveValues| on a sub-schema
+ base::Value string_value("testvalue");
+ sensitive_string.MaskSensitiveValues(&string_value);
+ EXPECT_EQ(value_masked.Clone(), string_value);
+}
+
+TEST(SchemaTest, SchemaNodeNoSensitiveValues) {
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean"
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+ EXPECT_FALSE(schema.IsSensitiveValue());
+
+ Schema foo = schema.GetKnownProperty("foo");
+ ASSERT_TRUE(foo.valid());
+ EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
+ EXPECT_FALSE(foo.IsSensitiveValue());
+
+ base::Value value(base::Value::Type::DICTIONARY);
+ value.SetKey("foo", base::Value(true));
+
+ base::Value expected_value = value.Clone();
+ schema.MaskSensitiveValues(&value);
+ EXPECT_EQ(expected_value, value);
+}
+
+TEST(SchemaTest, EnumerationRestriction) {
+ // Enum attribute is a list.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": 12
+ })")));
+
+ // Empty enum attributes is not allowed.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "enum": []
+ })")));
+
+ // Enum elements type should be same as stated.
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": [1, 2, 3]
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "enum": [1, 2, 3]
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "string",
+ "enum": ["1", "2", "3"]
+ })")));
+}
+
+TEST(SchemaTest, RangedRestriction) {
+ EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "minimum": 10,
+ "maximum": 5
+ })")));
+
+ EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
+ "type": "integer",
+ "minimum": 10,
+ "maximum": 20
+ })")));
+}
+
+TEST(SchemaTest, ParseToDictAndValidate) {
+ std::string error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate("", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate("\0", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(
+ Schema::ParseToDictAndValidate("string", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(
+ Schema::ParseToDictAndValidate(R"("string")", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate("[]", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate("{}", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(R"({ "type": 123 })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(R"({ "type": "invalid" })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": []
+ })", // Invalid properties type.
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "string",
+ "enum": [ {} ]
+ })", // "enum" dict values must contain "name".
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "string",
+ "enum": [ { "name": {} } ]
+ })", // "enum" name must be a simple value.
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "array",
+ "items": [ 123 ],
+ })", // "items" must contain a schema or schemas.
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_TRUE(Schema::ParseToDictAndValidate(
+ R"({ "type": "object" })", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({ "type": ["object", "array"] })", kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "array",
+ "items": [
+ { "type": "string" },
+ { "type": "integer" }
+ ]
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_TRUE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": {
+ "string-property": {
+ "type": "string",
+ "title": "The String Policy",
+ "description": "This policy controls the String widget."
+ },
+ "integer-property": {
+ "type": "number"
+ },
+ "enum-property": {
+ "type": "integer",
+ "enum": [0, 1, 10, 100]
+ },
+ "items-property": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_TRUE(Schema::ParseToDictAndValidate(
+ R"#({
+ "type": "object",
+ "patternProperties": {
+ ".": { "type": "boolean" },
+ "foo": { "type": "boolean" },
+ "^foo$": { "type": "boolean" },
+ "foo+": { "type": "boolean" },
+ "foo?": { "type": "boolean" },
+ "fo{2,4}": { "type": "boolean" },
+ "(left)|(right)": { "type": "boolean" }
+ }
+ })#",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_TRUE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "unknown attribute": "that should just be ignored"
+ })",
+ kSchemaOptionsIgnoreUnknownAttributes, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "unknown attribute": "that will cause a failure"
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": 123
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": [ 123 ]
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": ["bar"]
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_FALSE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "required": ["bar"]
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+ EXPECT_TRUE(Schema::ParseToDictAndValidate(
+ R"({
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": ["foo"]
+ })",
+ kSchemaOptionsNone, &error))
+ << error;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/values_util.cc b/chromium/components/policy/core/common/values_util.cc
new file mode 100644
index 00000000000..c2b269f38d8
--- /dev/null
+++ b/chromium/components/policy/core/common/values_util.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/values_util.h"
+
+#include <vector>
+
+namespace policy {
+
+base::flat_set<std::string> ValueToStringSet(const base::Value* value) {
+ if (!value || !value->is_list())
+ return base::flat_set<std::string>();
+
+ const auto& items = value->GetListDeprecated();
+
+ std::vector<std::string> item_vector;
+ item_vector.reserve(items.size());
+
+ for (const auto& item : items) {
+ if (item.is_string())
+ item_vector.emplace_back(item.GetString());
+ }
+
+ return base::flat_set<std::string>(std::move(item_vector));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/core/common/values_util.h b/chromium/components/policy/core/common/values_util.h
new file mode 100644
index 00000000000..2134f00f306
--- /dev/null
+++ b/chromium/components/policy/core/common/values_util.h
@@ -0,0 +1,23 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_VALUES_UTIL_H_
+#define COMPONENTS_POLICY_CORE_COMMON_VALUES_UTIL_H_
+
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "base/values.h"
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+// Converts a list of string value to string flat set. Returns empty
+// set if |value| is not set. Non-string items will be ignored.
+POLICY_EXPORT base::flat_set<std::string> ValueToStringSet(
+ const base::Value* value);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_CORE_COMMON_VALUES_UTIL_H_
diff --git a/chromium/components/policy/core/common/values_util_unittest.cc b/chromium/components/policy/core/common/values_util_unittest.cc
new file mode 100644
index 00000000000..58f9696221a
--- /dev/null
+++ b/chromium/components/policy/core/common/values_util_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/values_util.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+TEST(PolicyValuesToStringSetTest, Convert) {
+ base::Value::ListStorage items;
+ items.push_back(base::Value("1"));
+ items.push_back(base::Value("2"));
+ items.push_back(base::Value("3"));
+ base::Value value(items);
+ base::flat_set<std::string> expected_set = {"1", "2", "3"};
+ EXPECT_EQ(expected_set, ValueToStringSet(&value));
+}
+
+TEST(PolicyValuesToStringSetTest, SkipInvalidItem) {
+ base::Value::ListStorage items;
+ items.push_back(base::Value("1"));
+ items.push_back(base::Value());
+ items.push_back(base::Value(0));
+ items.push_back(base::Value(true));
+ items.push_back(base::Value(base::Value::Type::BINARY));
+ items.push_back(base::Value(base::Value::Type::LIST));
+ items.push_back(base::Value(base::Value::Type::DICTIONARY));
+ items.push_back(base::Value("2"));
+ items.push_back(base::Value("3"));
+ items.push_back(base::Value(""));
+ base::Value value(items);
+ base::flat_set<std::string> expected_set = {"1", "2", "3", ""};
+ EXPECT_EQ(expected_set, ValueToStringSet(&value));
+}
+
+TEST(PolicyValuesToStringSetTest, InvalidValues) {
+ std::unique_ptr<base::Value> values[] = {
+ nullptr,
+ std::make_unique<base::Value>(),
+ std::make_unique<base::Value>(0),
+ std::make_unique<base::Value>(true),
+ std::make_unique<base::Value>(base::Value::Type::BINARY),
+ std::make_unique<base::Value>(base::Value::Type::LIST),
+ std::make_unique<base::Value>(base::Value::Type::DICTIONARY),
+ std::make_unique<base::Value>(""),
+ std::make_unique<base::Value>("a"),
+ std::make_unique<base::Value>("0"),
+ };
+ for (const auto& value : values)
+ EXPECT_EQ(base::flat_set<std::string>(), ValueToStringSet(value.get()));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/policy_export.h b/chromium/components/policy/policy_export.h
new file mode 100644
index 00000000000..b6030aa1787
--- /dev/null
+++ b/chromium/components/policy/policy_export.h
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_POLICY_EXPORT_H_
+#define COMPONENTS_POLICY_POLICY_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(POLICY_COMPONENT_IMPLEMENTATION)
+#define POLICY_EXPORT __declspec(dllexport)
+#else
+#define POLICY_EXPORT __declspec(dllimport)
+#endif // defined(POLICY_COMPONENT_IMPLEMENTATION)
+
+#else // defined(WIN32)
+
+#if defined(POLICY_COMPONENT_IMPLEMENTATION)
+#define POLICY_EXPORT __attribute__((visibility("default")))
+#else
+#define POLICY_EXPORT
+#endif // defined(POLICY_COMPONENT_IMPLEMENTATION)
+
+#endif // defined(WIN32)
+
+#else // defined(COMPONENT_BUILD)
+
+#define POLICY_EXPORT
+
+#endif // defined(COMPONENT_BUILD)
+
+#endif // COMPONENTS_POLICY_POLICY_EXPORT_H_
diff --git a/chromium/components/policy/proto/README.md b/chromium/components/policy/proto/README.md
new file mode 100644
index 00000000000..c6f1c7c2539
--- /dev/null
+++ b/chromium/components/policy/proto/README.md
@@ -0,0 +1,36 @@
+# About //components/policy/proto
+
+This directory contains proto definitions for communication with the device management server.
+
+## User policies
+
+There are two protocol buffers defining the messages for user policies - `chrome_settings.proto` and `cloud_policy.proto`. Both files are auto-generated by the [generate_policy_source.py](https://source.chromium.org/chromium/chromium/src/+/main:components/policy/tools/generate_policy_source.py) script based on [policy_templates.json](https://source.chromium.org/chromium/chromium/src/+/main:components/policy/resources/policy_templates.json) as part of [building Chrome](https://source.chromium.org/chromium/chromium/src/+/main:components/policy/BUILD.gn;drc=326a2697fbe91fe9a953873763ad0a2695883d73;l=102).
+
+The reason there are two files is a compromise between readability and performance.
+
+* `chrome_settings.proto`
+
+ This file lists all non-device policies including comments containing their detailed descriptions. Additionally every policy in this file has a distinct message type. For example, this is the message for the `HomepageLocation` policy:
+
+
+ ```
+ message HomepageLocationProto {
+ optional PolicyOptions policy_options = 1;
+ optional string HomepageLocation = 2;
+ }
+ ```
+
+* `cloud_policy.proto`
+
+ This file is generated for each target platform and it therefore contains only the policy messages that a certain platform supports. Additionally each field uses a generic type defined in [policy_common_definitions.proto](https://source.chromium.org/chromium/chromium/src/+/main:components/policy/proto/policy_common_definitions.proto). For example this is the message for any string policy:
+
+ ```
+ message StringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string value = 2;
+ }
+ ```
+
+The client code for each platform uses the more compact `cloud_policy.proto` to parse the policy blobs it receives from the device management server. On the other hand, the device management server needs to know of all the policies that exist for all the platforms, therefore `chrome_settings.proto` is what the server code uses.
+
+The two files are compatible and when the messages are serialized their binary content is equivalent. [CloudPolicyProtoTest.VerifyProtobufEquivalence](https://source.chromium.org/chromium/chromium/src/+/2d4ccc5062a5314b89973a2d53159f431a0ecfd3:chrome/browser/policy/cloud/cloud_policy_browsertest.cc;l=530) browser test makes sure that no regressions are introduced here. \ No newline at end of file
diff --git a/chromium/components/policy/proto/chrome_device_policy.proto b/chromium/components/policy/proto/chrome_device_policy.proto
new file mode 100644
index 00000000000..826b585c5c3
--- /dev/null
+++ b/chromium/components/policy/proto/chrome_device_policy.proto
@@ -0,0 +1,1873 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+import "policy_common_definitions.proto";
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+// Everything below this comment will be synchronized between client and server
+// repos ( go/cros-proto-sync ).
+
+message DevicePolicyRefreshRateProto {
+ // In milliseconds.
+ optional int64 device_policy_refresh_rate = 1;
+}
+
+message UserWhitelistProto {
+ // If a UserWhitelistProto is included in the ChromeDeviceSettingsProto but
+ // the user_whitelist field is empty then no user can sign-in.
+ repeated string user_whitelist = 1;
+}
+
+message UserAllowlistProto {
+ // If a UserAllowlistProto is included in the ChromeDeviceSettingsProto but
+ // the user_whitelist field is empty then no user can sign-in.
+ repeated string user_allowlist = 1;
+}
+
+message AllowNewUsersProto {
+ // Determines whether we allow arbitrary users to log into the device.
+ // This interacts with the UserAllowlistProto as follows:
+ // allow_new_users | user_allowlist | anyone can log in
+ //-----------------+--------------------+------------------
+ // present, true | not present | Yes
+ //-----------------+--------------------+------------------
+ // present, true | present | Yes
+ //-----------------+--------------------+------------------
+ // present, false | not present | (Broken) Yes
+ //-----------------+--------------------+------------------
+ // present, false | present | No, W/L enforced
+ //-----------------+--------------------+------------------
+ // not present | not present | Yes
+ //-----------------+--------------------+------------------
+ // not present | present, empty | Yes
+ //-----------------+--------------------+------------------
+ // not present | present, non-empty | No, W/L enforced
+ //-----------------+--------------------+------------------
+ optional bool allow_new_users = 1 [default = true];
+}
+
+message GuestModeEnabledProto {
+ // Determines if guests are allowed to log in to the device.
+ optional bool guest_mode_enabled = 1 [default = true];
+}
+
+message ShowUserNamesOnSigninProto {
+ // Determines if we show pods for existing users on the sign in screen.
+ optional bool show_user_names = 1 [default = true];
+}
+
+message DataRoamingEnabledProto {
+ // Determines if cellular data roaming is enabled.
+ optional bool data_roaming_enabled = 1 [default = false];
+}
+
+message OBSOLETE_DeviceProxySettingsProto {
+ // One of "direct", "auto_detect", "pac_script", "fixed_servers", "system"
+ optional string OBSOLETE_proxy_mode = 1 [deprecated = true];
+ optional string OBSOLETE_proxy_server = 2 [deprecated = true];
+ optional string OBSOLETE_proxy_pac_url = 3 [deprecated = true];
+ optional string OBSOLETE_proxy_bypass_list = 4 [deprecated = true];
+}
+
+// This is used by chromeos, make sure to do cleanup there before marking it as
+// obsolette.
+message CameraEnabledProto {
+ optional bool camera_enabled = 1;
+}
+
+message MetricsEnabledProto {
+ optional bool metrics_enabled = 1;
+}
+
+message ReleaseChannelProto {
+ // One of "stable-channel", "beta-channel", or "dev-channel"
+ optional string release_channel = 1;
+
+ // The user can select the channel if |release_channel_delegated| is true.
+ // The value of |release_channel| is only taken into account if
+ // |release_channel_delegated| is set to false.
+ optional bool release_channel_delegated = 2;
+
+ // |release_lts_tag| is forwarded as the "ltshint" attribute to Omaha.
+ optional string release_lts_tag = 3;
+}
+
+message DeviceOpenNetworkConfigurationProto {
+ // The network configuration blob. This is a JSON string as specified by ONC.
+ optional string open_network_configuration = 1;
+}
+
+message NetworkHostnameProto {
+ // The device hostname template. It might contain following
+ // patterns that would be substituted by the device:
+ // ASSET_ID, SERIAL_NUM, MAC_ADDR, and string after substitution should
+ // be a valid hostname.
+ optional string device_hostname_template = 1;
+}
+
+message HostnameUserConfigurableProto {
+ // Determines if user is allowed to configure the device hostname
+ optional bool device_hostname_user_configurable = 1 [default = false];
+}
+
+// Policies to turn on portions of the device status reports.
+// If changed, the default values have to be updated in
+// chrome/browser/ash/policy/status_collector/device_status_collector.cc
+// and
+// chrome/browser/ash/policy/status_collector/child_status_collector.cc.
+message DeviceReportingProto {
+ optional bool report_version_info = 1 [default = true];
+ optional bool report_activity_times = 2 [default = true];
+ optional bool report_boot_mode = 3 [default = true];
+ optional bool report_location = 4 [default = false];
+ optional bool report_network_interfaces = 5 [default = true];
+ optional bool report_users = 6 [default = true];
+ optional bool report_hardware_status = 7 [default = true];
+ optional bool report_session_status = 8 [default = true];
+ optional bool report_os_update_status = 10 [default = false];
+ optional bool report_running_kiosk_app = 11 [default = false];
+ optional bool report_power_status = 12 [default = false];
+ optional bool report_storage_status = 13 [default = false];
+ optional bool report_board_status = 14 [default = false];
+ optional bool report_cpu_info = 15 [default = false];
+ optional bool report_graphics_status = 16 [default = false];
+ optional bool report_crash_report_info = 17 [default = false];
+ optional bool report_timezone_info = 18 [default = false];
+ optional bool report_memory_info = 19 [default = false];
+ optional bool report_backlight_info = 20 [default = false];
+ optional bool report_app_info = 21 [default = false];
+ optional bool report_bluetooth_info = 22 [default = false];
+ optional bool report_fan_info = 23 [default = false];
+ optional bool report_vpd_info = 24 [default = false];
+ optional bool report_system_info = 25 [default = false];
+ optional bool report_print_jobs = 26 [default = false];
+ optional bool report_login_logout = 27 [default = false];
+ optional bool report_audio_status = 28 [default = true];
+ optional bool report_network_configuration = 29 [default = true];
+ optional bool report_network_status = 30 [default = true];
+ optional bool report_security_status = 31 [default = false];
+ optional bool report_crd_sessions = 36 [default = false];
+ optional bool report_peripherals = 37 [default = false];
+
+ // Frequency to report device status, default to 3 hours.
+ // If changed, the default value has to be updated in
+ // chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc.
+ optional int64 device_status_frequency = 9 [default = 10800000];
+
+ // This is a internal flag that will be used to control whether enable
+ // granular device reporting is enabled
+ optional bool enable_granular_reporting = 32 [default = true];
+
+ // Network telemetry policies.
+ optional int64 report_network_telemetry_collection_rate_ms = 33
+ [default = 3600000];
+ optional int64 report_network_telemetry_event_checking_rate_ms = 34
+ [default = 600000];
+
+ // Audio telemetry policy
+ optional int64 report_device_audio_status_checking_rate_ms = 35
+ [default = 600000];
+}
+
+message EphemeralUsersEnabledProto {
+ // Determines whether users should be treated as ephemeral. In ephemeral users
+ // mode, no cryptohome is created for the user, but a tmpfs mount is used
+ // instead such that upon logout all user state is discarded.
+ optional bool ephemeral_users_enabled = 1;
+}
+
+message DeviceKeylockerForStorageEncryptionEnabledProto {
+ // Determines whether cryptohome uses Keylocker for storage encryption ciphers
+ // when supported.
+ optional bool enabled = 1;
+}
+
+// Details of an extension to install as part of the AppPack.
+message OBSOLETE_AppPackEntryProto {
+ optional string OBSOLETE_extension_id = 1 [deprecated = true];
+ optional string OBSOLETE_update_url = 2 [deprecated = true];
+
+ // This field was added but never used and there are no plans to support it
+ // eventually either.
+ optional bool OBSOLETE_online_only = 3 [deprecated = true];
+}
+
+message OBSOLETE_AppPackProto {
+ // List of extensions to install as part of the AppPack.
+ repeated OBSOLETE_AppPackEntryProto app_pack = 1 [deprecated = true];
+}
+
+// This is a special policy for kiosk/retail mode that specifies what apps
+// should be pinned to the launcher. For regular accounts, pinned apps are
+// controlled through user policy.
+message OBSOLETE_PinnedAppsProto {
+ // App IDs for the apps to pin.
+ repeated string OBSOLETE_app_id = 1 [deprecated = true];
+}
+
+message OBSOLETE_ForcedLogoutTimeoutsProto {
+ // All timeouts are specified in milliseconds.
+
+ // Specifies the timeout before an idle user session is terminated.
+ // If this field is omitted or set to 0, no logout on idle will be performed.
+ optional int64 OBSOLETE_idle_logout_timeout = 1 [deprecated = true];
+
+ // Specifies the duration of a warning countdown before the user is logged out
+ // because of idleness as specified by the |idle_logout_timeout| value.
+ // This field is only used if |idle_logout_timeout| != 0 is specified.
+ optional int64 OBSOLETE_idle_logout_warning_duration = 2 [deprecated = true];
+}
+
+message OBSOLETE_ScreenSaverProto {
+ // Specifies the extension ID which is to be used as a screen saver on the
+ // login screen if no user activity is present. Only respected if the device
+ // is in RETAIL mode.
+ optional string OBSOLETE_screen_saver_extension_id = 1 [deprecated = true];
+
+ // Specifies the timeout before the screen saver is activated. If this field
+ // is omitted or set to 0, no screen-saver will be started.
+ // Measured in milliseconds.
+ optional int64 OBSOLETE_screen_saver_timeout = 2 [deprecated = true];
+}
+
+// Enterprise controls for auto-update behavior of Chrome OS.
+message AutoUpdateSettingsProto {
+ reserved 13;
+
+ // True if we don't want the device to auto-update (target_version_prefix is
+ // ignored in this case).
+ optional bool update_disabled = 1;
+
+ // Specifies the prefix of the target version we want the device to
+ // update to, if it's on an older version. If the device is already on
+ // a version with the given prefix, then there's no effect. If the device is
+ // on a higher version, the behavior depends on |rollback_to_target_version|.
+ // The format of this version can be one of the following:
+ // ---------------------------------------------------------------------
+ // "" (or not set at all): update to latest version available.
+ // 1412.: update to any minor version of 1412 (e.g. 1412.24.34 or 1412.60.2)
+ // 1412.2.: update to any minor version of 1412.2 (e.g. 1412.2.34 or 1412.2.2)
+ // 1412.24.34: update to this specific version only
+ // ---------------------------------------------------------------------
+ optional string target_version_prefix = 2;
+
+ // The Chrome browser version (e.g. "17.*") corresponding to the
+ // target_version_prefix above. The target_version_prefix is the internal OS
+ // version that external users normally are not aware of. This display_name
+ // can be used by the devices to display a message to end-users about the auto
+ // update setting.
+ optional string target_version_display_name = 3;
+
+ // Specifies the number of seconds up to which a device may randomly
+ // delay its download of an update from the time the update was first pushed
+ // out to the server. The device may wait a portion of this time in terms
+ // of wall-clock-time and the remaining portion in terms of the number of
+ // update checks. In any case, the scatter is upper bounded by a constant
+ // amount of time so that a device does not ever get stuck waiting to download
+ // an update forever.
+ optional int64 scatter_factor_in_seconds = 4;
+
+ // Enumerates network connection types.
+ enum ConnectionType {
+ reserved 2;
+
+ CONNECTION_TYPE_ETHERNET = 0;
+ CONNECTION_TYPE_WIFI = 1;
+ CONNECTION_TYPE_BLUETOOTH = 3;
+ CONNECTION_TYPE_CELLULAR = 4;
+ }
+
+ // The types of connections that are OK to use for OS updates. OS updates
+ // potentially put heavy strain on the connection due to their size and may
+ // incur additional cost. Therefore, they are by default not enabled for
+ // connection types that are considered expensive (currently only Cellular).
+ repeated ConnectionType allowed_connection_types = 5;
+
+ // This has been replaced by |reboot_after_update| below.
+ optional bool OBSOLETE_reboot_after_update = 6 [deprecated = true];
+
+ // True if AU payloads can be downloaded via HTTP. False otherwise.
+ optional bool http_downloads_enabled = 7 [default = false];
+
+ // True if the device should reboot automatically when an update has been
+ // applied and a reboot is required to complete the update process.
+ //
+ // Note: Currently, automatic reboots are only enabled while the login screen
+ // is being shown or a kiosk app session is in progress. This will change in
+ // the future and the policy will always apply, regardless of whether a
+ // session of any particular type is in progress or not.
+ optional bool reboot_after_update = 8;
+
+ // True if AU payloads may be shared with and consumed from other devices
+ // on the LAN, using p2p. False otherwise.
+ optional bool p2p_enabled = 9 [default = false];
+
+ // The possible types of rollback.
+ enum RollbackToTargetVersion {
+ // No value set. Default is ROLLBACK_DISABLED.
+ ROLLBACK_UNSPECIFIED = 0;
+ // No rollback should happen if |target_version_prefix| specifies an older
+ // version than the currently installed Chrome OS version. If this is the
+ // case, the device will still respect |target_version_prefix|, so it will
+ // not update Chrome OS.
+ ROLLBACK_DISABLED = 1;
+ // If |target_version_prefix| specifies an older version than the currently
+ // installed Chrome OS version, the device should roll back to a Chrome OS
+ // version starting with |target_version_prefix|. The device does a full
+ // powerwash during the rollback, including TPM reset.
+ ROLLBACK_AND_POWERWASH = 2;
+ // If |target_version_prefix| specifies an older version than the currently
+ // installed Chrome OS version, the device should roll back to a Chrome OS
+ // version starting with |target_version_prefix|.
+ // If possible, the device tries to carry over device-level configuration
+ // including network credentials during the rollback process.
+ // If that is not possible, rolls back with a full powerwash.
+ ROLLBACK_AND_RESTORE_IF_POSSIBLE = 3;
+ }
+
+ // Specifies what should happen if |target_version_prefix| specifies an older
+ // version than the currently installed Chrome OS version.
+ optional RollbackToTargetVersion rollback_to_target_version = 10
+ [default = ROLLBACK_DISABLED];
+
+ // Specifies the number of Chrome milestones rollback should be allowed,
+ // starting from the stable version at any time. Setting this policy prevents
+ // firmware and kernel rollback protection to apply for at least this number
+ // of milestones.
+ optional int32 rollback_allowed_milestones = 11 [default = 0];
+
+ // Specifies the time intervals during which the device is not allowed to do
+ // automatic update checks. This is a JSON string, for details see
+ // "DeviceAutoUpdateTimeRestrictions" in policy_templates.json.
+ optional string disallowed_time_intervals = 12;
+
+ // Specifies how much of the fleet to update per day as a json
+ // string that contains a list of pairs <day, percentage>. For more
+ // details and examples, see "DeviceUpdateStagingSchedule" in
+ // policy_templates.json.
+ optional string staging_schedule = 14;
+
+ // This token is forwarded to omaha by update_engine. If it is set, omaha may
+ // serve a quick fix build identified by the token.
+ //
+ // This field is primarily used for quick fixes, but it is also used by the
+ // Hotrod team to subdivide the Stable channel into cohorts.
+ optional string device_quick_fix_build_token = 15;
+
+ // Types of channel downgrade behavior.
+ enum ChannelDowngradeBehavior {
+ // Channel downgrade behavior unspecified. Default is
+ // WAIT_FOR_VERSION_CATCH_UP.
+ CHANNEL_DOWNGRADE_BEHAVIOR_UNSPECIFIED = 0;
+ // On a channel downgrade, e.g. beta to stable, wait for the device's
+ // version to become available on the new channel. No updates happen until
+ // then. This is the default.
+ WAIT_FOR_VERSION_CATCH_UP = 1;
+ // Roll back and reset the device on a channel downgrade. This does a full
+ // powerwash and tries to preserve wifi and enrollment.
+ ROLLBACK = 2;
+ // Allow the user to decide whether to wait or roll back and reset on a
+ // user-initiated channel downgrade.
+ ALLOW_USER_TO_CONFIGURE = 3;
+ }
+
+ // Specifies what should happen if the device channel is downgraded.
+ optional ChannelDowngradeBehavior channel_downgrade_behavior = 16
+ [default = WAIT_FOR_VERSION_CATCH_UP];
+
+ // |target_version_selector| is forwarded as the "targetversionselector"
+ // attribute to Omaha and is used by it if for minor version pinning. The
+ // field is not and shall not be processed by the client.
+ optional string target_version_selector = 17;
+}
+
+message OBSOLETE_StartUpUrlsProto {
+ // Specifies the URLs to be loaded on login to the anonymous account used if
+ // the device is in RETAIL mode.
+ repeated string OBSOLETE_start_up_urls = 1 [deprecated = true];
+}
+
+message SystemTimezoneProto {
+ // Specifies an owner-determined timezone that applies to the login screen and
+ // all users. Valid values are listed in "timezone_settings.cc". Additionally,
+ // timezones from the "IANA Time Zone Database" (e.g. listed on wikipedia)
+ // that are equivalent to one of the timezones in "timezone_settings.cc" are
+ // valid. In case of an invalid value, the setting is still activated with a
+ // fallback timezone (currently "GMT"). In case of an empty string or if no
+ // value is provided, the timezone device setting is inactive. In that case,
+ // the currently active timezone will remain in use however users can change
+ // the timezone and the change is persistent. Thus a change by one user
+ // affects the login-screen and all other users.
+ optional string timezone = 1;
+
+ // This allows domain administrators to control the timezone settings for
+ // their devices.
+ enum AutomaticTimezoneDetectionType {
+ USERS_DECIDE = 0;
+ DISABLED = 1;
+ IP_ONLY = 2;
+ SEND_WIFI_ACCESS_POINTS = 3;
+ SEND_ALL_LOCATION_INFO = 4;
+ }
+
+ optional AutomaticTimezoneDetectionType timezone_detection_type = 2;
+}
+
+message SystemUse24HourClockProto {
+ // Specifies an owner-determined clock format that applies to the login
+ // screen and is used as a default for all user sessions. Users can still
+ // override the format to use for their account.
+ //
+ // True and false select a 24 and 12 hour clock format, respectively. The
+ // default format for the case the setting is not present is 24 hour clock.
+ optional bool use_24hour_clock = 1;
+}
+
+// Parameters for Kiosk App device-local accounts.
+message KioskAppInfoProto {
+ // Indicates the Kiosk App for the corresponding device-local account. The
+ // string value should be a valid 32-character Chrome App identifier and
+ // specifies the Kiosk App to download and run.
+ optional string app_id = 1;
+
+ // Optional extension update URL to download the Kiosk App package from. If
+ // not specified, the app will be downloaded from the standard Chrome Web
+ // Store update URL.
+ optional string update_url = 2;
+}
+
+// Describes which Android application is to be launched.
+message AndroidKioskAppInfoProto {
+ // Package name (must be present).
+ // In the event this is the only field that is specified, runtime may use
+ // PackageManager.getLaunchIntentForPackage() to start the app. See
+ // https://developer.android.com/reference/android/content/pm/PackageManager.html
+ // Example of the package name: "com.android.camera". Do not include "app:"
+ // prefix in the package name.
+ optional string package_name = 1;
+
+ // Class name (optional). If present, class name is to be combined with
+ // package name to form a ComponentName. See
+ // https://developer.android.com/reference/android/content/ComponentName.html
+ optional string class_name = 2;
+
+ // Action (optional). The third parameter required for creating an Intent.
+ // If omitted, runtime may choose a reasonable default action
+ // (e.g. android.intent.action.MAIN).
+ // If package and action are specified, but not the class name, runtime may
+ // use PackageManager.queryIntentActivity() to find out the class name.
+ optional string action = 3;
+
+ // Display name (optional).
+ // User-friendly app name that should be used in Chrome UI where kiosk app
+ // name is shown. Chrome side could override the string with an updated
+ // value that it will get from Google Play when the app will be installed.
+ optional string display_name = 4;
+}
+
+// Parameters for Web App-based device local accounts.
+message WebKioskAppInfoProto {
+ // Install url (must be present).
+ // In case it is the only field provided, title and icon will be deduced
+ // during first app launch.
+ optional string url = 1;
+
+ // Title (optional).
+ // User-friendly app name that should be used in Chrome UI where kiosk app
+ // name is shown. Chrome side could override the string with an updated
+ // value that it will get during actual app launch.
+ optional string title = 2;
+
+ // Icon url (optional).
+ // Is not used in the current Implementation. Will be used instead of the
+ // placeholder icon that is displayed before the first successful app
+ // launch.
+ optional string icon_url = 3;
+}
+
+// Describes a single device-local account.
+message DeviceLocalAccountInfoProto {
+ // Deprecated: Account identifier for a public session device-local account.
+ // Old code didn't have the |type| field, so it can't handle new types of
+ // device-local accounts gracefully (i.e. ignoring unsupported types). New
+ // code should instead set type to ACCOUNT_TYPE_PUBLIC_SESSION and write the
+ // identifier to the |account_id| field below. If the |type| field is present,
+ // |deprecated_public_session_id| will be ignored.
+ optional string deprecated_public_session_id = 1;
+
+ // Identifier for the device-local account. This is an opaque identifier that
+ // is used to distinguish different device-local accounts configured. All
+ // configured accounts on a device must have unique identifiers.
+ optional string account_id = 2;
+
+ // LINT.IfChange
+ // Indicates the type of device-local account.
+ enum AccountType {
+ // A login-less, policy-configured browsing session.
+ ACCOUNT_TYPE_PUBLIC_SESSION = 0;
+ // An account that serves as a container for a single full-screen
+ // Chrome app.
+ ACCOUNT_TYPE_KIOSK_APP = 1;
+ // An account that serves as a container for a single full-screen
+ // Android app.
+ ACCOUNT_TYPE_KIOSK_ANDROID_APP = 2;
+ // SAML public session account
+ ACCOUNT_TYPE_SAML_PUBLIC_SESSION = 3;
+ // Web App.
+ ACCOUNT_TYPE_WEB_KIOSK_APP = 4;
+ }
+ // Should keep ChromeServletUtil.toDimensionAccountType logic in sync with
+ // AccountType enum.
+ // LINT.ThenChange(//depot/google3/java/com/google/chrome/cros/dmserver/chrome/ChromeServletUtil.java)
+
+ // The account type.
+ optional AccountType type = 3;
+
+ // Kiosk App parameters, relevant if |type| is ACCOUNT_TYPE_KIOSK_APP.
+ optional KioskAppInfoProto kiosk_app = 4;
+
+ // Kiosk App parameters, relevant if |type| is ACCOUNT_TYPE_KIOSK_ANDROID_APP
+ optional AndroidKioskAppInfoProto android_kiosk_app = 5;
+
+ // Web Kiosk App parameters, relevant if |type| is ACCOUNT_TYPE_WEB_KIOSK_APP
+ optional WebKioskAppInfoProto web_kiosk_app = 6;
+}
+
+message DeviceLocalAccountsProto {
+ // The list of device-local accounts (i.e. accounts without an associated
+ // cloud-backed profile) that are available on the device.
+ repeated DeviceLocalAccountInfoProto account = 1;
+
+ // The identifier of the device-local account to which the device
+ // should be logged in automatically. Should be equal to one of the
+ // ids in DeviceLocalAccountInfoProto.
+ optional string auto_login_id = 2;
+
+ // The amount of time, in milliseconds, that should elapse at the signin
+ // screen without user interaction before automatically logging in.
+ optional int64 auto_login_delay = 3;
+
+ // Whether the keyboard shortcut to prevent zero-delay auto-login should be
+ // enabled or not. By default, the user has 3 seconds to press a shortcut
+ // to prevent auto-login, which is useful to sign-in to a regular user session
+ // and configure the machine. If this policy is set to false then this
+ // shortcut is disabled and there is no way to skip auto-login.
+ optional bool enable_auto_login_bailout = 4 [default = true];
+
+ // Whether network configuration should be offered or not when the device
+ // does not have access to the Internet. If the policy is omitted or set to
+ // true, the network configuration will be offered. Otherwise, only an error
+ // message is displayed.
+ // Note: If both this policy and enable_auto_login_bailout policy above is
+ // set to false, there are chances that the device might become totally
+ // unusable when there is no Internet access and has to go through the
+ // recovery process.
+ // If the device is offline at startup then the network configuration screen
+ // is always shown, before auto-login kicks in.
+ optional bool prompt_for_network_when_offline = 5 [default = true];
+}
+
+message ManagedGuestSessionPrivacyWarningsProto {
+ // Enable the privacy warnings on both; the login screen of the managed-guest
+ // session & inside the auto-launched managed-guest sessions.
+ // If this policy is set to false, all the privacy warnings are deactivated.
+ // If it's set to true or not set, then the privacy warnings will be shown by
+ // default.
+ optional bool enabled = 1 [default = true];
+}
+
+message AllowRedeemChromeOsRegistrationOffersProto {
+ // Chrome OS Registration service provides way for chromeos device users
+ // to redeem electronic offers provided by service provider.
+ // This value determines if users are allowed to redeem offers through
+ // Chrome OS Registration service.
+ optional bool allow_redeem_offers = 1 [default = true];
+}
+
+message FeatureFlagsProto {
+ // Specifies switches that should be passed to Google Chrome when it starts.
+ // The specified switches are applied on the login screen only. Switches set
+ // via this policy do not propagate into user sessions.
+ // This is deprecated because it turned out that storing raw switches is
+ // problematic since Chrome can't easily tie switches back to feature flags to
+ // validate them. The |feature_flags| field below works in terms of feature
+ // flag names (i.e. chrome://flags items) instead and supersedes |switches|.
+ repeated string switches = 1 [deprecated = true];
+
+ // Specifies feature flags (i.e. chrome://flags items) that should be enabled
+ // when Chrome starts. The format of the individual entries matches the format
+ // chrome://flags uses for internal bookkeeping, i.e. either the flag name as
+ // listed on chrome://flags (for flags that only have a single choice besides
+ // the default) or the flag name followed by the index of the chosen option,
+ // separated by an '@' character (for flags with multiple choices). The
+ // specified feature flags are applied on the login screen only and don't
+ // propagate into the user session.
+ repeated string feature_flags = 2;
+}
+
+message UptimeLimitProto {
+ // This has been replaced by |uptime_limit| below.
+ optional int64 OBSOLETE_uptime_limit = 1 [deprecated = true];
+
+ // Sets the length of device uptime after which an automatic reboot is
+ // scheduled. An automatic reboot is scheduled at the selected time but may be
+ // delayed on the device by up to 24 hours, e.g. if a user is currently using
+ // the device or an app/extension has requested reboots to be inhibited
+ // temporarily. The policy value should be specified in seconds.
+ //
+ // Note: Currently, automatic reboots are only enabled while the login screen
+ // is being shown or a kiosk app session is in progress. This will change in
+ // the future and the policy will always apply, regardless of whether a
+ // session of any particular type is in progress or not.
+ optional int64 uptime_limit = 2;
+}
+
+message VariationsParameterProto {
+ // The string for the restrict parameter to be appended to the Variations URL
+ // when pinging the Variations server.
+ optional string parameter = 1;
+}
+
+message AttestationSettingsProto {
+ // Attestation involves proving that a cryptographic key is protected by a
+ // legitimate Chrome OS TPM and reporting the operating mode of the platform.
+ // This setting enables enterprise attestation features at a device level. If
+ // this is enabled a machine key will be generated and certified by the Chrome
+ // OS CA. If this setting is disabled, even users with attestation settings
+ // enabled will not be able to use those features on the device.
+ optional bool attestation_enabled = 1 [default = false];
+
+ // Chrome OS devices can use remote attestation (Verified Access) to get a
+ // certificate issued by the Chrome OS CA that asserts the device is eligible
+ // to play protected content. This process involves sending hardware
+ // endorsement information to the Chrome OS CA which uniquely identifies the
+ // device. This setting allows this feature to be disabled for the device
+ // regardless of any user-specific settings.
+ optional bool content_protection_enabled = 2 [default = true];
+}
+
+message AccessibilitySettingsProto {
+ // Sets the default state of the following accessibility features on the login
+ // screen:
+ //
+ // 1) Large cursor: login_screen_default_large_cursor_enabled
+ // 2) Spoken feedback: login_screen_default_spoken_feedback_enabled
+ // 3) High contrast: login_screen_default_high_contrast_enabled
+ // 4) Screen magnifier: login_screen_default_screen_magnifier_type
+ // 5) Virtual keyboard: login_screen_default_virtual_keyboard_enabled
+ //
+ // Each acts as follows: If the corresponding policy is set to true, the
+ // associated accessibility feature will be enabled when the login screen is
+ // shown. If this policy is set to false, the accessibility feature will be
+ // disabled when the login screen is shown. Users can temporarily override
+ // this setting by enabling or disabling the corresponding accessibiilty
+ // feature. However, the user's choice is not persistent and the default is
+ // restored whenever the login screen is shown anew or the user remains idle
+ // on the login screen for a minute. If this policy is left unset, the
+ // corresponding accessibiilty feature is disabled when the login screen is
+ // first shown. Users can enable or disable the corresponding accessibiilty
+ // feature anytime and its status on the login screen is persisted between
+ // users.
+
+ optional bool login_screen_default_large_cursor_enabled = 1;
+ optional bool login_screen_default_spoken_feedback_enabled = 2;
+ optional bool login_screen_default_high_contrast_enabled = 3;
+
+ // Enumerates the screen magnifier types.
+ enum ScreenMagnifierType {
+ // Screen magnifier disabled.
+ SCREEN_MAGNIFIER_TYPE_NONE = 0;
+ // Full-screen magnifier enabled.
+ SCREEN_MAGNIFIER_TYPE_FULL = 1;
+ }
+ optional ScreenMagnifierType login_screen_default_screen_magnifier_type = 4;
+
+ optional bool login_screen_default_virtual_keyboard_enabled = 5;
+
+ // Sets the mandatory or default state, depending on the PolicyOptions, of the
+ // following accessibility features on the login screen:
+ //
+ // 1) Large cursor: login_screen_large_cursor_enabled
+ // PolicyOptions: login_screen_large_cursor_enabled_options
+ // 2) Spoken feedback: login_screen_spoken_feedback_enabled
+ // PolicyOptions: login_screen_spoken_feedback_enabled_options
+ // 3) High contrast: login_screen_high_contrast_enabled
+ // PolicyOptions: login_screen_high_contrast_enabled_options
+ // 4) Virtual keyboard: login_screen_virtual_keyboard_enabled
+ // PolicyOptions: login_screen_virtual_keyboard_enabled_options
+ // 5) Dictation: login_screen_dictation_enabled
+ // PolicyOptions: login_screen_dictation_enabled_options
+ // 6) Select to speak: login_screen_select_to_speak_enabled
+ // PolicyOptions: login_screen_select_to_speak_enabled_options
+ // 7) Cursor highlight: login_screen_cursor_highlight_enabled
+ // PolicyOptions: login_screen_cursor_highlight_enabled_options
+ // 8) Caret highlight: login_screen_caret_highlight_enabled
+ // PolicyOptions: login_screen_caret_highlight_enabled_options
+ // 9) Mono audio: login_screen_mono_audio_enabled
+ // PolicyOptions: login_screen_mono_audio_enabled_options
+ // 10) Autoclick: login_screen_autoclick_enabled
+ // PolicyOptions: login_screen_autoclick_enabled_options
+ // 11) Sticky keys: login_screen_sticky_keys_enabled
+ // PolicyOptions: login_screen_sticky_keys_enabled_options
+ // 12) Keyboard focus highlight: login_screen_keyboard_focus_highlight_enabled
+ // PolicyOptions: login_screen_keyboard_focus_highlight_enabled_options
+ // 13) Screen magnifier: login_screen_screen_magnifier_type
+ // PolicyOptions: login_screen_screen_magnifier_type_options
+ // 14) Show options in system tray menu:
+ // login_screen_show_options_in_system_tray_menu_enabled
+ // PolicyOptions: login_screen_sticky_keys_enabled_options
+ // 15) Accessibility shortcuts: login_screen_shortcuts_enabled
+ // PolicyOptions: login_screen_shortcuts_enabled_options
+ //
+ // For all the aforementioned accessibility policies:
+ // - If this policy is set to true, the accessibility feature will be enabled
+ // when the login screen is shown.
+ // - If the policy is set to false, the accessibility feature will be
+ // disabled when the login screen is shown.
+ // - If the policy is left unset, the accessibility feature is disabled when
+ // the login screen is first shown.
+ // - If the PolicyOptions is set to mandatory, the user won't be able
+ // to change these settings.
+ // - If the PolicyOptions is set to recommended, the user can temporarily
+ // override this setting by enabling or disabling the accessibility feature.
+ // However, the user's choice is not persistent and the default is restored
+ // whenever the login screen is shown anew or the user remains idle on the
+ // login screen for a minute. Users can enable or disable the accessibility
+ // feature anytime and its status on the login screen is persisted between
+ // users.
+
+ optional bool login_screen_large_cursor_enabled = 6;
+ optional PolicyOptions login_screen_large_cursor_enabled_options = 7;
+
+ optional bool login_screen_spoken_feedback_enabled = 8;
+ optional PolicyOptions login_screen_spoken_feedback_enabled_options = 9;
+
+ optional bool login_screen_high_contrast_enabled = 10;
+ optional PolicyOptions login_screen_high_contrast_enabled_options = 11;
+
+ optional bool login_screen_virtual_keyboard_enabled = 12;
+ optional PolicyOptions login_screen_virtual_keyboard_enabled_options = 13;
+
+ optional bool login_screen_dictation_enabled = 14;
+ optional PolicyOptions login_screen_dictation_enabled_options = 15;
+
+ optional bool login_screen_select_to_speak_enabled = 16;
+ optional PolicyOptions login_screen_select_to_speak_enabled_options = 17;
+
+ optional bool login_screen_cursor_highlight_enabled = 18;
+ optional PolicyOptions login_screen_cursor_highlight_enabled_options = 19;
+
+ optional bool login_screen_caret_highlight_enabled = 20;
+ optional PolicyOptions login_screen_caret_highlight_enabled_options = 21;
+
+ optional bool login_screen_mono_audio_enabled = 22;
+ optional PolicyOptions login_screen_mono_audio_enabled_options = 23;
+
+ optional bool login_screen_autoclick_enabled = 24;
+ optional PolicyOptions login_screen_autoclick_enabled_options = 25;
+
+ optional bool login_screen_sticky_keys_enabled = 26;
+ optional PolicyOptions login_screen_sticky_keys_enabled_options = 27;
+
+ optional bool login_screen_keyboard_focus_highlight_enabled = 28;
+ optional PolicyOptions login_screen_keyboard_focus_highlight_enabled_options =
+ 29;
+
+ optional int64 login_screen_screen_magnifier_type = 30;
+ optional PolicyOptions login_screen_screen_magnifier_type_options = 31;
+
+ optional bool login_screen_show_options_in_system_tray_menu_enabled = 32;
+ optional PolicyOptions
+ login_screen_show_options_in_system_tray_menu_enabled_options = 33;
+
+ optional bool login_screen_shortcuts_enabled = 34;
+ optional PolicyOptions login_screen_shortcuts_enabled_options = 35;
+}
+
+message OBSOLETE_SupervisedUsersSettingsProto {
+ // Defines whether supervised users can be created on the device.
+ optional bool OBSOLETE_supervised_users_enabled = 1 [deprecated = true];
+}
+
+message LoginScreenPowerManagementProto {
+ // Configures power management on the login screen. The policy should be
+ // specified as a string that expresses the individual settings in JSON
+ // format, conforming to the following schema:
+ // {
+ // "type": "object",
+ // "properties": {
+ // "AC": {
+ // "description": "Power management settings applicable only when
+ // running on AC power",
+ // "type": "object",
+ // "properties": {
+ // "Delays": {
+ // "type": "object",
+ // "properties": {
+ // "ScreenDim": {
+ // "description": "The length of time without user input after
+ // which the screen is dimmed, in milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // },
+ // "ScreenOff": {
+ // "description": "The length of time without user input after
+ // which the screen is turned off, in
+ // milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // },
+ // "Idle": {
+ // "description": "The length of time without user input after
+ // which the idle action is taken, in
+ // milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // }
+ // }
+ // },
+ // "IdleAction": {
+ // "description": "Action to take when the idle delay is reached",
+ // "enum": [ "Suspend", "Shutdown", "DoNothing" ]
+ // }
+ // }
+ // },
+ // "Battery": {
+ // "description": "Power management settings applicable only when
+ // running on battery power",
+ // "type": "object",
+ // "properties": {
+ // "Delays": {
+ // "type": "object",
+ // "properties": {
+ // "ScreenDim": {
+ // "description": "The length of time without user input after
+ // which the screen is dimmed, in milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // },
+ // "ScreenOff": {
+ // "description": "The length of time without user input after
+ // which the screen is turned off, in
+ // milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // },
+ // "Idle": {
+ // "description": "The length of time without user input after
+ // which the idle action is taken, in
+ // milliseconds",
+ // "type": "integer",
+ // "minimum": 0
+ // }
+ // }
+ // },
+ // "IdleAction": {
+ // "description": "Action to take when the idle delay is reached",
+ // "enum": [ "Suspend", "Shutdown", "DoNothing" ]
+ // }
+ // }
+ // },
+ // "LidCloseAction": {
+ // "description": "Action to take when the lid is closed",
+ // "enum": [ "Suspend", "Shutdown", "DoNothing" ]
+ // },
+ // "UserActivityScreenDimDelayScale": {
+ // "description": "Percentage by which the screen dim delay is scaled
+ // when user activity is observed while the screen is
+ // dimmed or soon after the screen has been turned off",
+ // "type": "integer",
+ // "minimum": 0
+ // }
+ // }
+ // }
+ optional string login_screen_power_management = 1;
+}
+
+message AutoCleanupSettigsProto {
+ // Deprecated. There is only one disk-full cleanup strategy: LRU.
+ optional string clean_up_strategy = 1;
+}
+
+// Settings that control low-level functions of the system.
+message SystemSettingsProto {
+ // Whether developer mode is allowed on the device. If the device owner sets
+ // this flag to true, the system will refuse to boot and show an error screen
+ // when the developer switch is turned on.
+ optional bool block_devmode = 1;
+}
+
+// Settings that control login for SAML users.
+message SAMLSettingsProto {
+ // Whether cookies set by a SAML IdP should be transferred to users' profiles
+ // every time a user authenticates via SAML during login. If false, cookies
+ // are transferred during each user's first login only.
+ optional bool transfer_saml_cookies = 1;
+}
+
+message RebootOnShutdownProto {
+ // Determines whether the device automatically reboots whenever the user shuts
+ // it down. If this flag is set to true, shutdown is forbidden and UI elements
+ // trigger a device reboot instead of a power off. This policy affects
+ // shutdowns triggered from the UI only. If the user shuts down the device
+ // using the power button, it will not automatically reboot, even if the
+ // policy is enabled.
+ optional bool reboot_on_shutdown = 1 [default = false];
+}
+
+// Settings that control whether a device would send heartbeat messages to GCM,
+// and how frequently to send these.
+message DeviceHeartbeatSettingsProto {
+ // Whether the device should send heartbeat messages. The default is false.
+ optional bool heartbeat_enabled = 1 [default = false];
+
+ // How frequently devices send heartbeats back to server. The unit is in
+ // milliseconds. The default is 2 minutes.
+ optional int64 heartbeat_frequency = 2 [default = 120000];
+}
+
+message ExtensionCacheSizeProto {
+ // Specifies the maximum extension cache size, in bytes. The default is 256
+ // MiB. The minimum allowed value is 1 MiB, smaller values will get ignored.
+ optional int64 extension_cache_size = 1;
+}
+
+message LoginScreenDomainAutoCompleteProto {
+ // If this policy is not configured or set to a blank string,
+ // no autocomplete option during user sign-in flow will be shown.
+ // If this policy is set to a string representing a domain name, an
+ // autocomplete option during user sign-in will be shown allowing the user
+ // to type in only their user name without the domain name extension. The user
+ // will be able to overwrite this domain name extension.
+ optional string login_screen_domain_auto_complete = 1;
+}
+
+// Settings that control whether a device would send system logs to the server.
+message DeviceLogUploadSettingsProto {
+ // Whether the device should send system logs. The default is false.
+ optional bool system_log_upload_enabled = 1 [default = false];
+}
+
+// This setting is controlled by the device policy DisplayRotationDefault.
+// If the policy is set and therefore display_rotation_default contains a value,
+// all displays will be rotated clockwise to the specified orientation at
+// reboot, when first connected, or when the setting is changed.
+// If the optional field |Rotation display_rotation_default = 1| is not present,
+// no changes are done to the rotation.
+message DisplayRotationDefaultProto {
+ // This enum corresponds to gfx::Display::Rotation in ui/gfx/display.h.
+ enum Rotation {
+ ROTATE_0 = 0;
+ ROTATE_90 = 1;
+ ROTATE_180 = 2;
+ ROTATE_270 = 3;
+ }
+ optional Rotation display_rotation_default = 1;
+}
+
+// This setting is controlled by the device policy
+// DeviceLoginScreenPrivacyScreenEnabled.
+message DeviceLoginScreenPrivacyScreenEnabledProto {
+ optional bool enabled = 1 [default = false];
+}
+
+// This setting is configured by the device policy DeviceDisplayResolution.
+// If |device_display_resolution| contains a value, then it's treated as a JSON
+// object that uses the schema defined for DeviceDisplayResolution policy.
+// Example of the policy value:
+// {
+// "external_width": 1920,
+// "external_height": 1080,
+// "external_scale_percentage": 50,
+// "internal_scale_percentage": 150,
+// "recommended": true
+// }
+// It sets a 1920x1080 display mode for any external displays and
+// scales them to 50%, also scales the built-in display to 150%.
+// If "recommended" flag is set to true, user is able to override
+// any settings via the settings page.
+message DeviceDisplayResolutionProto {
+ optional string device_display_resolution = 1;
+}
+
+// Settings that control whether to allow Chrome to be pinned to a specific
+// version according to the auto-launched kiosk app’s requirement.
+message AllowKioskAppControlChromeVersionProto {
+ optional bool allow_kiosk_app_control_chrome_version = 1 [default = false];
+}
+
+// Settings that control the flow of the login authentication to be either via
+// GAIA (default), or via an interstitial screen that can redirect to a SAML IdP
+// endpoint or return back to the default GAIA flow.
+message LoginAuthenticationBehaviorProto {
+ enum LoginBehavior {
+ GAIA = 0;
+ SAML_INTERSTITIAL = 1;
+ }
+
+ optional LoginBehavior login_authentication_behavior = 1 [default = GAIA];
+}
+
+// Identifiers of a USB device or device family.
+message UsbDeviceIdProto {
+ // USB Vendor Identifier (aka idVendor).
+ optional int32 vendor_id = 1;
+ // USB Product Identifier (aka idProduct).
+ optional int32 product_id = 2;
+}
+
+// This setting contains the list of USB devices to detach from the kernel
+// drivers in order to use them in web applications.
+// The list is used by the permission_broker daemon.
+message UsbDetachableWhitelistProto {
+ repeated UsbDeviceIdProto id = 1;
+}
+
+// Identifiers of a USB device or device family.
+message UsbDeviceIdInclusiveProto {
+ // USB Vendor Identifier (aka idVendor).
+ optional int32 vendor_id = 1;
+ // USB Product Identifier (aka idProduct).
+ optional int32 product_id = 2;
+}
+
+// This setting contains the list of USB devices to detach from the kernel
+// drivers in order to use them in web applications.
+// The list is used by the permission_broker daemon.
+message UsbDetachableAllowlistProto {
+ repeated UsbDeviceIdInclusiveProto id = 1;
+}
+
+message AllowBluetoothProto {
+ // Policy which controls whether Bluetooth is available.
+ optional bool allow_bluetooth = 1 [default = true];
+}
+
+message DeviceWiFiAllowedProto {
+ // Policy which controls the ability to connect to wireless networks.
+ optional bool device_wifi_allowed = 1 [default = true];
+}
+
+// Settings that control whether a device can download hardware configuration
+// files from the Quirks Server.
+message DeviceQuirksDownloadEnabledProto {
+ optional bool quirks_download_enabled = 1;
+}
+
+// A list of security origins for SAML login pages that are allowed to
+// access the webcam. No login pages will be allowed to access the
+// webcam if the list is empty.
+message LoginVideoCaptureAllowedUrlsProto {
+ repeated string urls = 1;
+}
+
+// Settings that control whether a device can connect to a 802.11r enabled
+// WiFi network.
+message DeviceWiFiFastTransitionEnabledProto {
+ optional bool device_wifi_fast_transition_enabled = 1;
+}
+
+message NetworkThrottlingEnabledProto {
+ optional bool enabled = 1 [default = false];
+ optional int32 upload_rate_kbits = 2 [default = 0];
+ optional int32 download_rate_kbits = 3 [default = 0];
+}
+
+// A list of apps or extensions to install from the webstore on the login page.
+// It is a list of strings, each string contains an extension ID and an update
+// URL, delimited by a semicolon.
+message DeviceLoginScreenExtensionsProto {
+ repeated string device_login_screen_extensions = 1;
+}
+
+// A list of allowed locales on the login screen.
+message LoginScreenLocalesProto {
+ repeated string login_screen_locales = 1;
+}
+
+// A list of allowed input methods on the login screen.
+message LoginScreenInputMethodsProto {
+ repeated string login_screen_input_methods = 1;
+}
+
+// The url and hash specified in JSON format that can be used to set the
+// device-level wallpaper on the login screen before any user logs in.
+message DeviceWallpaperImageProto {
+ optional string device_wallpaper_image = 1;
+}
+
+// Migration strategy for the case when ARC(N+) needs the ext4 encryption while
+// the device used ecryptfs in the past.
+message DeviceEcryptfsMigrationStrategyProto {
+ enum MigrationStrategy {
+ // Default value, unspecified.
+ UNSET = 0;
+ // ARC is not allowed, no data migration needed.
+ DISALLOW_ARC = 1;
+ // The data migration is allowed, opening the possibility to use ARC.
+ ALLOW_MIGRATION = 2;
+ }
+
+ optional MigrationStrategy migration_strategy = 1;
+}
+
+// This setting controls how the on-board secure element hardware can be used
+// to provide a second-factor authentication in addition to the TPM
+// functionality.
+message DeviceSecondFactorAuthenticationProto {
+ enum U2fMode {
+ // Default value, unspecified.
+ UNSET = 0;
+ // Feature disabled.
+ DISABLED = 1;
+ // U2F as defined by the FIDO Alliance specification:
+ // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915.zip
+ U2F = 2;
+ // U2F plus extensions for individual attestation certificate.
+ U2F_EXTENDED = 3;
+ }
+
+ optional U2fMode mode = 1;
+}
+
+message CastReceiverNameProto {
+ // The name advertised as a Google Cast destination by the device,
+ // up to 24 characters. If the name is empty, the device name will
+ // be used.
+ optional string name = 1;
+}
+
+// Day of the week and time in milliseconds since the start of the day.
+message WeeklyTimeProto {
+ enum DayOfWeek {
+ DAY_OF_WEEK_UNSPECIFIED = 0;
+ MONDAY = 1;
+ TUESDAY = 2;
+ WEDNESDAY = 3;
+ THURSDAY = 4;
+ FRIDAY = 5;
+ SATURDAY = 6;
+ SUNDAY = 7;
+ }
+ // Day of week.
+ optional DayOfWeek day_of_week = 1;
+ // Time of day in milliseconds from beginning of the day.
+ optional int32 time = 2;
+}
+
+// Start and end of an interval represented by WeeklyTimes
+message WeeklyTimeIntervalProto {
+ optional WeeklyTimeProto start = 1;
+ optional WeeklyTimeProto end = 2;
+}
+
+// Allow less restricted using of Chromebooks that are managed by school,
+// while the device is not at school ("OffHours").
+message DeviceOffHoursProto {
+ // List of intervals when ignored policies are not applied. These intervals
+ // are in the timezone specified by the timezone string.
+ repeated WeeklyTimeIntervalProto intervals = 1;
+ // Timezone in the same format as SystemTimezoneProto.timezone.
+ optional string timezone = 2;
+ // List of policy proto tags which settings are ignored during OffHours
+ // mode. List contains policy proto tags from ChromeDeviceSettingsProto
+ // (i.e. proto tag = 1 means device_policy_refresh_rate policy).
+ // Proto tags are used because they are consistent identifiers.
+ // During OffHoursMode default settings of ignored policies are used.
+ repeated int32 ignored_policy_proto_tags = 3;
+}
+
+// The url and hash specified in JSON format that can be used to retrieve
+// the device-level printers configuration file.
+message DeviceNativePrintersProto {
+ // External policy blob encoded as JSON.
+ optional string external_policy = 1;
+}
+
+// The policy which determines the type of access restriction that is applied to
+// the devicel-level printers list.
+message DeviceNativePrintersAccessModeProto {
+ enum AccessMode {
+ ACCESS_MODE_BLACKLIST = 0; // Use NatvePrintersBlacklistProto.
+ ACCESS_MODE_WHITELIST = 1; // Use NaviePrintersWhitelistProto.
+ ACCESS_MODE_ALL = 2; // Allow access to all specified printers.
+ }
+
+ // The type of access which is applied to the device-level printer list.
+ optional AccessMode access_mode = 1;
+}
+
+// A collection of ids defining the printers which are explicitly disallowed for
+// the device.
+message DeviceNativePrintersBlacklistProto {
+ // A collection of ids for which are explicitly disallowed.
+ repeated string blacklist = 1;
+}
+
+// A collection of ids defining the printers which are explicitly allowed for
+// the device.
+message DeviceNativePrintersWhitelistProto {
+ // A collection of ids for the list of printers which are accessible.
+ repeated string whitelist = 1;
+}
+
+// The url and hash specified in JSON format that can be used to retrieve
+// the device-level printers configuration file.
+message DevicePrintersProto {
+ // External policy blob encoded as JSON.
+ optional string external_policy = 1;
+}
+
+// A collection of ids for the list of print servers which are accessible.
+// The policy which determines the type of access restriction that is applied to
+// the devicel-level printers list.
+message DevicePrintersAccessModeProto {
+ enum AccessMode {
+ ACCESS_MODE_BLOCKLIST = 0; // Use DevicePrintersBlocklistProto.
+ ACCESS_MODE_ALLOWLIST = 1; // Use DevicePrintersAllowlistProto.
+ ACCESS_MODE_ALL = 2; // Allow access to all specified printers.
+ }
+
+ // The type of access which is applied to the device-level printer list.
+ optional AccessMode access_mode = 1;
+}
+
+// A collection of ids defining the printers which are explicitly disallowed for
+// the device.
+message DevicePrintersBlocklistProto {
+ // A collection of ids for which are explicitly disallowed.
+ repeated string blocklist = 1;
+}
+
+// A collection of ids defining the printers which are explicitly allowed for
+// the device.
+message DevicePrintersAllowlistProto {
+ // A collection of ids for the list of printers which are accessible.
+ repeated string allowlist = 1;
+}
+
+// The url and hash specified in JSON format that can be used to retrieve
+// the device-level external print servers configuration file.
+message DeviceExternalPrintServersProto {
+ // External policy blob encoded as JSON.
+ optional string external_policy = 1;
+}
+
+// A collection of ids defining the external print servers which are explicitly
+// allowed for the device.
+message DeviceExternalPrintServersAllowlistProto {
+ // A collection of ids for the list of print servers which are accessible.
+ repeated string allowlist = 1;
+}
+
+// Settings to control the behavior of the TPM firmware update functionality.
+message TPMFirmwareUpdateSettingsProto {
+ // Whether the user is allowed to invoke the update via powerwash. This flow
+ // performs a powerwash operation (which implies a TPM clear), followed by
+ // installation of the TPM firmware update. As a result of the powerwash, all
+ // writable data on the device will be cleared.
+ optional bool allow_user_initiated_powerwash = 1;
+
+ // Whether the user is allowed to invoke a variant of the update flow that
+ // clears the TPM to install the firmware update, but preserves device-wide
+ // state (including enrollment). User data will not be preserved in this flow.
+ optional bool allow_user_initiated_preserve_device_state = 2;
+
+ enum AutoUpdateMode {
+ // No value set. Default is NEVER.
+ AUTO_UPDATE_MODE_UNSPECIFIED = 0;
+ // Don't auto update TPM firmware.
+ NEVER = 1;
+ // Update firmware at the next reboot after user acknowledges the update.
+ USER_ACKNOWLEDGMENT = 2;
+ // Update firmware at the next reboot.
+ WITHOUT_ACKNOWLEDGMENT = 3;
+ // Update firmware after enrollment.
+ ENROLLMENT = 4;
+ }
+
+ // Controls how automatic firmware updates are enforced for vulnerable
+ // firmware. All flows preserve local device state.
+ optional AutoUpdateMode auto_update_mode = 3 [default = NEVER];
+}
+
+// Settings to control the minimum version that is allowed to sign in / stay
+// in session. This is now deprecated from M82 onwards.
+message OBSOLETE_MinimumRequiredVersionProto {
+ // Value is chrome_version string, e.g. 61.0.3163.120
+ // The client will use prefix matching to compare its version against the
+ // value of this field.
+ optional string OBSOLETE_chrome_version = 1 [deprecated = true];
+}
+
+// Specifies a list of rules to automatically select certificates on SAML IdP
+// pages on the sign-in screen.
+message DeviceLoginScreenAutoSelectCertificateForUrls {
+ // Each entry is one rule, which must be a stringified JSON dictionary.
+ // Each dictionary must have the form { "pattern": "$URL_PATTERN", "filter" :
+ // $FILTER }. $FILTER restricts from which client certificates the browser
+ // will automatically select. Independent of the filter, only certificates
+ // will be selected that match the server's certificate request. If $FILTER
+ // has the form { "ISSUER": { "CN": "$ISSUER_CN" } }, additionally only client
+ // certificates are selected that are issued by a certificate with the
+ // CommonName $ISSUER_CN. If $FILTER is the empty dictionary {}, the selection
+ // of client certificates is not additionally restricted.
+ repeated string login_screen_auto_select_certificate_rules = 1;
+}
+
+// Setting that controls whether unaffiliated users are allowed to use ARC
+// (true by default)
+message UnaffiliatedArcAllowedProto {
+ optional bool unaffiliated_arc_allowed = 1;
+}
+
+// Allowed encryption types for requesting Kerberos tickets from Active
+// Directory servers. Applies to Active Directory management mode only.
+message DeviceKerberosEncryptionTypesProto {
+ enum Types {
+ ENC_TYPES_ALL = 0; // AES + RC4_HMAC.
+ ENC_TYPES_STRONG = 1; // AES only.
+ ENC_TYPES_LEGACY = 2; // RC4_HMAC only.
+ // Next ID to use: 3
+ }
+
+ optional Types types = 1 [default = ENC_TYPES_STRONG];
+}
+
+// Specifies how user policy from device GPOs interacts with user policy from
+// user GPOs. In 'MERGE' mode, device GPOs take preference in case of conflicts.
+// Applies to Active Directory management mode only.
+message DeviceUserPolicyLoopbackProcessingModeProto {
+ enum Mode {
+ USER_POLICY_MODE_DEFAULT = 0; // Only take user policy from user GPOs.
+ USER_POLICY_MODE_MERGE = 1; // Merge device GPOs on top of user GPOs.
+ USER_POLICY_MODE_REPLACE = 2; // Only take user policy from device GPOs.
+ // Next ID to use: 3
+ }
+
+ optional Mode mode = 1 [default = USER_POLICY_MODE_DEFAULT];
+}
+
+// Specifies a list of origins. Each of the specified origins will run in its
+// own process on the sign-in screen.
+message OBSOLETE_DeviceLoginScreenIsolateOriginsProto {
+ // A comma-separated list of the origins to be run in a separate process on
+ // the sign-in screen.
+ // If the value of this policy does not match the value of the user policy
+ // IsolateOrigins, the chrome process will be restarted on user sign-in to
+ // apply the value specified by the user policy.
+ optional string OBSOLETE_isolate_origins = 1 [deprecated = true];
+}
+
+// Specifies if each site should run in its own process on the sign-in screen.
+message OBSOLETE_DeviceLoginScreenSitePerProcessProto {
+ // If true, each site will run in its own process on the sign-in screen.
+ // If the value of this policy does not match the value of the user policy
+ // SitePerProcess, the chrome process will be restarted on user sign-in to
+ // apply the value specified by the user policy.
+ optional bool OBSOLETE_site_per_process = 1 [deprecated = true];
+}
+
+// Setting to control if running virtual machines on Chrome OS is allowed.
+message VirtualMachinesAllowedProto {
+ optional bool virtual_machines_allowed = 1;
+}
+
+// Specifies if and how often Active Directory machine (computer) account
+// passwords are changed in the AuthPolicy daemon in Chrome OS.
+// Applies to Active Directory management mode only.
+message DeviceMachinePasswordChangeRateProto {
+ optional int32 rate_days = 1;
+}
+
+// Specifies how long cached Active Directory Group Policy Objects (GPOs) may be
+// reused until they are re-downloaded (a version change also forces a
+// re-download).
+// Applies to Active Directory management mode only.
+message DeviceGpoCacheLifetimeProto {
+ optional int32 lifetime_hours = 1;
+}
+
+// Specifies how long cached Active Directory authentication data may be reused
+// until it is refreshed. This can significantly speed up user authentication.
+// Applies to Active Directory management mode only.
+message DeviceAuthDataCacheLifetimeProto {
+ optional int32 lifetime_hours = 1;
+}
+
+// Setting to control the authentication type for newly added users which log in
+// via SAML.
+message SamlLoginAuthenticationTypeProto {
+ enum Type {
+ TYPE_DEFAULT = 0; // Implementation-defined default config.
+ TYPE_CLIENT_CERTIFICATE = 1; // Client certificate authentication.
+ // Next ID to use: 2
+ }
+
+ optional Type saml_login_authentication_type = 1 [default = TYPE_DEFAULT];
+}
+
+// Setting that controls whether unaffiliated users are allowed to run Crostini
+// (true by default)
+message DeviceUnaffiliatedCrostiniAllowedProto {
+ optional bool device_unaffiliated_crostini_allowed = 1;
+}
+
+// Setting that controls whether PluginVm is allowed to run on this device.
+message PluginVmAllowedProto {
+ optional bool plugin_vm_allowed = 1;
+}
+
+// Setting that specifies PluginVm license key for this device.
+message PluginVmLicenseKeyProto {
+ optional string plugin_vm_license_key = 1;
+}
+
+// Setting that controls whether the device should reboot when user sign out.
+message DeviceRebootOnUserSignoutProto {
+ enum RebootOnSignoutMode {
+ // No value set. Default is NEVER.
+ REBOOT_ON_SIGNOUT_MODE_UNSPECIFIED = 0;
+ // Do not reboot on signout.
+ NEVER = 1;
+ // Reboot on signout if an ARC session was active during the user session.
+ ARC_SESSION = 2;
+ // Always reboot on signout.
+ ALWAYS = 3;
+ // Reboot on signout if an ARC session was active or a VM was started
+ // during the user session.
+ VM_STARTED_OR_ARC_SESSION = 4;
+ }
+
+ optional RebootOnSignoutMode reboot_on_signout_mode = 1 [default = NEVER];
+}
+
+// Setting that controls whether wilco diagnostics and telemetry controller is
+// allowed on this device.
+message DeviceWilcoDtcAllowedProto {
+ optional bool device_wilco_dtc_allowed = 1;
+}
+
+// Setting that specifies wilco diagnostics and telemetry controller
+// configuration for this device.
+message DeviceWilcoDtcConfigurationProto {
+ optional string device_wilco_dtc_configuration = 1;
+}
+
+// Settings that control power peak shift policy.
+message DevicePowerPeakShiftProto {
+ // Setting that controls whether power peak shift is enabled on this device.
+ // For details see "DevicePowerPeakShiftEnabled" in policy_templates.json.
+ optional bool enabled = 1;
+
+ // Setting that controls power peak shift battery threshold on this device.
+ // For details see "DevicePowerPeakShiftBatteryThreshold" in
+ // policy_templates.json.
+ optional int32 battery_threshold = 2;
+
+ // Setting that controls power peak shift day configs on this device.
+ // This is a JSON string, for details see "DevicePowerPeakShiftDayConfig" in
+ // policy_templates.json.
+ optional string day_configs = 3;
+}
+
+// Settings that control boot on AC policy.
+message DeviceBootOnAcProto {
+ // Setting that controls whether boot on AC is enabled on this device.
+ optional bool enabled = 1;
+}
+
+// Settings that control device's dock MAC address source.
+message DeviceDockMacAddressSourceProto {
+ enum Source {
+ SOURCE_UNSPECIFIED = 0;
+ // Device's designated dock MAC address.
+ DEVICE_DOCK_MAC_ADDRESS = 1;
+ // Device's built-in NIC MAC address.
+ DEVICE_NIC_MAC_ADDRESS = 2;
+ // Dock's built-in NIC MAC address.
+ DOCK_NIC_MAC_ADDRESS = 3;
+ }
+ optional Source source = 1;
+}
+
+// Settings that control advanced battery charge mode policy.
+message DeviceAdvancedBatteryChargeModeProto {
+ // Setting that controls whether advanced battery charge mode is enabled on
+ // this device.
+ // For details see "DeviceAdvancedBatteryChargeModeEnabled" in
+ // policy_templates.json.
+ optional bool enabled = 1;
+
+ // Setting that controls advanced battery charge mode day config on this
+ // device.
+ // This is a JSON string, for details see
+ // "DeviceAdvancedBatteryChargeModeDayConfig" in policy_templates.json.
+ optional string day_configs = 2;
+}
+
+// Settings that control battery charge mode policy.
+message DeviceBatteryChargeModeProto {
+ enum BatteryChargeMode {
+ MODE_UNSPECIFIED = 0;
+ STANDARD = 1;
+ EXPRESS_CHARGE = 2;
+ PRIMARILY_AC_USE = 3;
+ ADAPTIVE = 4;
+ CUSTOM = 5;
+ }
+
+ // Setting that controls battery charge mode on this device.
+ // For details see "DeviceBatteryChargeMode" in policy_templates.json.
+ optional BatteryChargeMode battery_charge_mode = 1;
+
+ // Percent at which charging starts when using CUSTOM.
+ // For details see "DeviceBatteryChargeCustomStartCharging" in
+ // policy_templates.json.
+ optional int32 custom_charge_start = 2;
+
+ // Percent at which charging stops when using CUSTOM.
+ // For details see "DeviceBatteryChargeCustomStopCharging" in
+ // policy_templates.json.
+ optional int32 custom_charge_stop = 3;
+}
+
+// Settings that control USB power share policy.
+message DeviceUsbPowerShareProto {
+ // Setting that controls whether USB power share is enabled on this device.
+ optional bool enabled = 1;
+}
+
+// Settings that control when a device will wake up and check for updates. These
+// checks are recurring. In order to disable a set schedule the policy must be
+// removed.
+message DeviceScheduledUpdateCheckProto {
+ // This is a JSON string, for details see "DeviceScheduledUpdateCheck" in
+ // policy_templates.json.
+ optional string device_scheduled_update_check_settings = 1;
+}
+
+// Settings that control if the device is allowed to powerwash.
+message DevicePowerwashAllowedProto {
+ // Determines if powerwash is allowed on the device.
+ optional bool device_powerwash_allowed = 1;
+}
+
+// Settings that controls which devices are whitelisted for certain urls to be
+// used via the WebUSB API on the login screen.
+message DeviceLoginScreenWebUsbAllowDevicesForUrlsProto {
+ // This is a JSON string, for details see
+ // "DeviceLoginScreenWebUsbAllowDevicesForUrls" in policy_templates.json.
+ optional string device_login_screen_webusb_allow_devices_for_urls = 1;
+}
+
+// Settings that control the availability of System-proxy service and the web
+// proxy credentials for system services connecting through System-proxy.
+message SystemProxySettingsProto {
+ // This is a JSON string, for details see "SystemProxySettings" in
+ // policy_templates.json.
+ optional string system_proxy_settings = 1;
+}
+
+// Settings that control what certificates should be privisioned via DM server.
+message RequiredClientCertificateForDeviceProto {
+ // This is a JSON string, for details see
+ // "RequiredClientCertificateForDevice" in policy_templates.json.
+ optional string required_client_certificate_for_device = 1;
+}
+
+// Setting that controls whether ARC ADB sideloading is allowed for the device.
+message DeviceCrostiniArcAdbSideloadingAllowedProto {
+ enum AllowanceMode {
+ DISALLOW = 0;
+ DISALLOW_WITH_POWERWASH = 1;
+ ALLOW_FOR_AFFILIATED_USERS = 2;
+ // Next ID to use: 3
+ }
+
+ optional AllowanceMode mode = 1 [default = DISALLOW];
+}
+
+message DeviceShowLowDiskSpaceNotificationProto {
+ optional bool device_show_low_disk_space_notification = 1;
+}
+
+// Setting that controls whether all Family Link accounts are allowed on the
+// device additionally to the accounts listed in UserAllowlistProto.
+message DeviceFamilyLinkAccountsAllowedProto {
+ optional bool family_link_accounts_allowed = 1 [default = false];
+}
+
+// Setting that controls whether ARC data snapshotting is enabled for the device
+// and time intervals of updating ARC data snapshot.
+message DeviceArcDataSnapshotHoursProto {
+ // This is a JSON string, for details see
+ // "DeviceArcDataSnapshotHours" in policy_template.json
+ optional string arc_data_snapshot_hours = 1;
+}
+
+// Setting that controls whether system-wide trace collection using the Perfetto
+// system tracing service is allowed.
+message DeviceSystemWideTracingEnabledProto {
+ optional bool enabled = 1 [default = false];
+}
+
+// Setting that controls whether data access is enabled for Thunderbolt/USB4
+// peripherals. This proto is no longer being used, please use
+// DevicePciPeripheralDataAccessEnabledProtoV2.
+message DevicePciPeripheralDataAccessEnabledProto {
+ optional bool enabled = 1 [default = false];
+}
+
+// Setting that controls whether data access is enabled for Thunderbolt/USB4
+// peripherals. This replaces DevicePciPeripheralDataAccessEnabledProto. Used
+// only for the associated CrosSetting.
+message DevicePciPeripheralDataAccessEnabledProtoV2 {
+ optional bool enabled = 1;
+}
+
+// Setting that controls whether Borealis will be allowed on the device.
+message DeviceBorealisAllowedProto {
+ optional bool allowed = 1 [default = true];
+}
+
+message DeviceAllowedBluetoothServicesProto {
+ // Policy which controls which service UUID is available.
+ repeated string allowlist = 1;
+}
+
+// Policy that controls whether packet captures will be allowed on the device.
+message DeviceDebugPacketCaptureAllowedProto {
+ optional bool allowed = 1;
+}
+
+// Settings that control when a device will reboot. The reboots are
+// recurring. In order to disable scheduled reboots the policy must be
+// removed.
+message DeviceScheduledRebootProto {
+ // This is a JSON string, for details see "DeviceScheduledReboot" in
+ // policy_templates.json.
+ optional string device_scheduled_reboot_settings = 1;
+}
+
+// Setting that controls whether restricted managed guest session is enabled on
+// the device.
+message DeviceRestrictedManagedGuestSessionEnabledProto {
+ // If this policy is set to true, it will forcefully override certain
+ // policies. If it's set to false or not set, then no policies will be
+ // overridden.
+ optional bool enabled = 1 [default = false];
+}
+
+// Setting that controls whether keyboard shortcuts mapping are consistent
+// across all international keyboard layouts.
+message DeviceI18nShortcutsEnabledProto {
+ optional bool enabled = 1 [default = true];
+}
+
+// reven board collects hardware data of the device to provide relevant updates.
+// Setting that controls whether device hardware data can be also used for other
+// purposes.
+message RevenDeviceHWDataUsageEnabledProto {
+ optional bool hardware_data_usage_enabled = 1 [default = false];
+}
+
+// Controls whether Login WebUI will be explicitly loaded on start.
+// Overrides EnableLazyLoginWebUILoading feature.
+message DeviceLoginScreenWebUILazyLoadingProto {
+ optional bool enabled = 1 [default = false];
+}
+
+// Setting that controls different configurations for the Encrypted Reporting
+// Pipeline.
+message EncryptedReportingPipelineConfigurationProto {
+ // Controls overall functioning of Encrypted Reporting Pipeline: setting it to
+ // false would disable the pipeline on a device
+ optional bool enabled = 1 [default = true];
+}
+
+message ChromeDeviceSettingsProto {
+ reserved 61, 90;
+ optional DevicePolicyRefreshRateProto device_policy_refresh_rate = 1;
+ optional UserWhitelistProto user_whitelist = 2;
+ optional GuestModeEnabledProto guest_mode_enabled = 3;
+ optional OBSOLETE_DeviceProxySettingsProto device_proxy_settings = 4
+ [deprecated = true];
+ optional CameraEnabledProto camera_enabled = 5;
+ optional ShowUserNamesOnSigninProto show_user_names = 6;
+ optional DataRoamingEnabledProto data_roaming_enabled = 7;
+ optional AllowNewUsersProto allow_new_users = 8;
+ optional MetricsEnabledProto metrics_enabled = 9;
+ optional ReleaseChannelProto release_channel = 10;
+ optional DeviceOpenNetworkConfigurationProto open_network_configuration = 11;
+ optional DeviceReportingProto device_reporting = 12;
+ optional EphemeralUsersEnabledProto ephemeral_users_enabled = 13;
+ optional OBSOLETE_AppPackProto app_pack = 14 [deprecated = true];
+ optional OBSOLETE_ForcedLogoutTimeoutsProto forced_logout_timeouts = 15
+ [deprecated = true];
+ optional OBSOLETE_ScreenSaverProto login_screen_saver = 16
+ [deprecated = true];
+ optional AutoUpdateSettingsProto auto_update_settings = 17;
+ optional OBSOLETE_StartUpUrlsProto start_up_urls = 18 [deprecated = true];
+ optional OBSOLETE_PinnedAppsProto pinned_apps = 19 [deprecated = true];
+ optional SystemTimezoneProto system_timezone = 20;
+ optional DeviceLocalAccountsProto device_local_accounts = 21;
+ optional AllowRedeemChromeOsRegistrationOffersProto allow_redeem_offers = 22;
+ optional FeatureFlagsProto feature_flags = 23;
+ optional UptimeLimitProto uptime_limit = 24;
+ optional VariationsParameterProto variations_parameter = 25;
+ optional AttestationSettingsProto attestation_settings = 26;
+ optional AccessibilitySettingsProto accessibility_settings = 27;
+ optional OBSOLETE_SupervisedUsersSettingsProto supervised_users_settings = 28
+ [deprecated = true];
+ optional LoginScreenPowerManagementProto login_screen_power_management = 29;
+ optional SystemUse24HourClockProto use_24hour_clock = 30;
+ optional AutoCleanupSettigsProto auto_clean_up_settings = 31;
+ optional SystemSettingsProto system_settings = 32;
+ optional SAMLSettingsProto saml_settings = 33;
+ optional RebootOnShutdownProto reboot_on_shutdown = 34;
+ optional DeviceHeartbeatSettingsProto device_heartbeat_settings = 35;
+ optional ExtensionCacheSizeProto extension_cache_size = 36;
+ optional LoginScreenDomainAutoCompleteProto
+ login_screen_domain_auto_complete = 37;
+ optional DeviceLogUploadSettingsProto device_log_upload_settings = 38;
+ optional DisplayRotationDefaultProto display_rotation_default = 39;
+ optional AllowKioskAppControlChromeVersionProto
+ allow_kiosk_app_control_chrome_version = 40;
+ optional LoginAuthenticationBehaviorProto login_authentication_behavior = 41;
+ optional UsbDetachableWhitelistProto usb_detachable_whitelist = 42;
+ optional AllowBluetoothProto allow_bluetooth = 43;
+ optional DeviceQuirksDownloadEnabledProto quirks_download_enabled = 44;
+ optional LoginVideoCaptureAllowedUrlsProto login_video_capture_allowed_urls =
+ 45;
+ optional DeviceLoginScreenExtensionsProto device_login_screen_extensions = 46;
+ optional NetworkThrottlingEnabledProto network_throttling = 47;
+ optional DeviceWallpaperImageProto device_wallpaper_image = 48;
+ optional LoginScreenLocalesProto login_screen_locales = 49;
+ optional LoginScreenInputMethodsProto login_screen_input_methods = 50;
+ optional DeviceEcryptfsMigrationStrategyProto
+ device_ecryptfs_migration_strategy = 51 [deprecated = true];
+ optional DeviceSecondFactorAuthenticationProto
+ device_second_factor_authentication = 52;
+ optional CastReceiverNameProto cast_receiver_name = 53;
+ optional DeviceOffHoursProto device_off_hours = 54;
+ optional DeviceNativePrintersProto native_device_printers = 55;
+ optional DeviceNativePrintersAccessModeProto
+ native_device_printers_access_mode = 56;
+ optional DeviceNativePrintersBlacklistProto native_device_printers_blacklist =
+ 57;
+ optional DeviceNativePrintersWhitelistProto native_device_printers_whitelist =
+ 58;
+ optional TPMFirmwareUpdateSettingsProto tpm_firmware_update_settings = 59;
+ optional OBSOLETE_MinimumRequiredVersionProto minimum_required_version = 60
+ [deprecated = true];
+ optional DeviceLoginScreenAutoSelectCertificateForUrls
+ device_login_screen_auto_select_certificate_for_urls = 62;
+ optional UnaffiliatedArcAllowedProto unaffiliated_arc_allowed = 63;
+ optional NetworkHostnameProto network_hostname = 64;
+ optional DeviceKerberosEncryptionTypesProto device_kerberos_encryption_types =
+ 65;
+ optional DeviceUserPolicyLoopbackProcessingModeProto
+ device_user_policy_loopback_processing_mode = 66;
+ optional OBSOLETE_DeviceLoginScreenIsolateOriginsProto
+ device_login_screen_isolate_origins = 67 [deprecated = true];
+ optional OBSOLETE_DeviceLoginScreenSitePerProcessProto
+ device_login_screen_site_per_process = 68 [deprecated = true];
+ optional VirtualMachinesAllowedProto virtual_machines_allowed = 69;
+ optional DeviceMachinePasswordChangeRateProto
+ device_machine_password_change_rate = 70;
+ optional SamlLoginAuthenticationTypeProto saml_login_authentication_type = 71;
+ optional DeviceUnaffiliatedCrostiniAllowedProto
+ device_unaffiliated_crostini_allowed = 72;
+ optional DeviceWiFiFastTransitionEnabledProto
+ device_wifi_fast_transition_enabled = 73;
+ optional DeviceDisplayResolutionProto device_display_resolution = 74;
+ optional PluginVmAllowedProto plugin_vm_allowed = 75;
+ optional DeviceGpoCacheLifetimeProto device_gpo_cache_lifetime = 76;
+ optional DeviceAuthDataCacheLifetimeProto device_auth_data_cache_lifetime =
+ 77;
+ optional PluginVmLicenseKeyProto plugin_vm_license_key = 78;
+ optional DeviceRebootOnUserSignoutProto device_reboot_on_user_signout = 79;
+ optional DeviceWilcoDtcAllowedProto device_wilco_dtc_allowed = 80;
+ optional DeviceWilcoDtcConfigurationProto device_wilco_dtc_configuration = 81;
+ optional DeviceWiFiAllowedProto device_wifi_allowed = 82;
+ optional DevicePowerPeakShiftProto device_power_peak_shift = 83;
+ optional DeviceBootOnAcProto device_boot_on_ac = 84;
+ optional DeviceDockMacAddressSourceProto device_dock_mac_address_source = 85;
+ optional DeviceAdvancedBatteryChargeModeProto
+ device_advanced_battery_charge_mode = 86;
+ optional DeviceBatteryChargeModeProto device_battery_charge_mode = 87;
+ optional DeviceUsbPowerShareProto device_usb_power_share = 88;
+ optional DeviceScheduledUpdateCheckProto device_scheduled_update_check = 89;
+ optional DevicePowerwashAllowedProto device_powerwash_allowed = 91;
+ optional DeviceLoginScreenWebUsbAllowDevicesForUrlsProto
+ device_login_screen_webusb_allow_devices_for_urls = 92;
+ optional BooleanPolicyProto device_login_screen_system_info_enforced = 93;
+ optional StringListPolicyProto device_web_based_attestation_allowed_urls = 94;
+ optional BooleanPolicyProto device_show_numeric_keyboard_for_password = 95;
+ optional BooleanPolicyProto login_screen_primary_mouse_button_switch = 96;
+ optional StringPolicyProto device_minimum_version = 97;
+ optional SystemProxySettingsProto system_proxy_settings = 98;
+ optional IntegerPolicyProto device_chrome_variations_type = 99;
+ optional DeviceLoginScreenPrivacyScreenEnabledProto
+ device_login_screen_privacy_screen_enabled = 100;
+ optional RequiredClientCertificateForDeviceProto
+ required_client_certificate_for_device = 101;
+ optional DeviceCrostiniArcAdbSideloadingAllowedProto
+ device_crostini_arc_adb_sideloading_allowed = 102;
+ optional StringPolicyProto device_minimum_version_aue_message = 103;
+ optional ManagedGuestSessionPrivacyWarningsProto
+ managed_guest_session_privacy_warnings = 104;
+ optional DeviceExternalPrintServersProto external_print_servers = 105;
+ optional DeviceExternalPrintServersAllowlistProto
+ external_print_servers_allowlist = 106;
+ optional DevicePrintersAccessModeProto device_printers_access_mode = 107;
+ optional DevicePrintersBlocklistProto device_printers_blocklist = 108;
+ optional DevicePrintersAllowlistProto device_printers_allowlist = 109;
+ optional DevicePrintersProto device_printers = 110;
+ optional DeviceShowLowDiskSpaceNotificationProto
+ device_show_low_disk_space_notification = 111;
+ optional UserAllowlistProto user_allowlist = 112;
+ optional UsbDetachableAllowlistProto usb_detachable_allowlist = 113;
+ optional DeviceFamilyLinkAccountsAllowedProto family_link_accounts_allowed =
+ 114;
+ optional DeviceArcDataSnapshotHoursProto arc_data_snapshot_hours = 115;
+ optional BooleanPolicyProto device_allow_mgs_to_store_display_properties =
+ 116;
+ optional DeviceSystemWideTracingEnabledProto
+ device_system_wide_tracing_enabled = 117;
+ optional DevicePciPeripheralDataAccessEnabledProto
+ device_pci_peripheral_data_access_enabled = 118;
+ optional DeviceBorealisAllowedProto device_borealis_allowed = 119;
+ optional DeviceAllowedBluetoothServicesProto
+ device_allowed_bluetooth_services = 120;
+ optional DeviceDebugPacketCaptureAllowedProto
+ device_debug_packet_capture_allowed = 121;
+ optional DeviceScheduledRebootProto device_scheduled_reboot = 122;
+ optional DevicePciPeripheralDataAccessEnabledProtoV2
+ device_pci_peripheral_data_access_enabled_v2 = 123;
+ optional DeviceRestrictedManagedGuestSessionEnabledProto
+ device_restricted_managed_guest_session_enabled = 124;
+ optional HostnameUserConfigurableProto hostname_user_configurable = 125;
+ optional BooleanPolicyProto
+ login_screen_prompt_on_multiple_matching_certificates = 126;
+ optional BooleanPolicyProto kiosk_crx_manifest_update_url_ignored = 127;
+ optional DeviceI18nShortcutsEnabledProto device_i18n_shortcuts_enabled = 128;
+ optional BooleanPolicyProto chromad_to_cloud_migration_enabled = 129;
+ optional RevenDeviceHWDataUsageEnabledProto hardware_data_usage_enabled = 130;
+ optional DeviceLoginScreenWebUILazyLoadingProto login_web_ui_lazy_loading =
+ 131;
+ optional DeviceKeylockerForStorageEncryptionEnabledProto
+ keylocker_for_storage_encryption_enabled = 132;
+ optional BooleanPolicyProto device_run_automatic_cleanup_on_login = 133;
+ optional EncryptedReportingPipelineConfigurationProto
+ device_encrypted_reporting_pipeline_enabled = 134;
+}
diff --git a/chromium/components/policy/proto/chrome_extension_policy.proto b/chromium/components/policy/proto/chrome_extension_policy.proto
new file mode 100644
index 00000000000..35767514ca1
--- /dev/null
+++ b/chromium/components/policy/proto/chrome_extension_policy.proto
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+// Describes how to retrieve policy data for a particular extension. The
+// extension ID is in the |settings_entity_id| field of the PolicyData message
+// that contains the ExternalPolicyData in its |policy_value| field.
+message ExternalPolicyData {
+ reserved 3;
+
+ // A URL where the policy data can be downloaded from.
+ optional string download_url = 1;
+
+ // SHA-256 hash of the data at |download_url|. This is used to verify the
+ // integrity of the data, and to detect updates on the client side: the client
+ // downloads the data when its local hash does not match |secure_hash|.
+ optional bytes secure_hash = 2;
+}
diff --git a/chromium/components/policy/proto/device_management_backend.proto b/chromium/components/policy/proto/device_management_backend.proto
new file mode 100644
index 00000000000..717377cf63d
--- /dev/null
+++ b/chromium/components/policy/proto/device_management_backend.proto
@@ -0,0 +1,4394 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+import "private_membership_rlwe.proto";
+
+// Everything below this comment will be synchronized between client and server
+// repos ( go/cros-proto-sync ).
+
+// This enum needs to be shared between DeviceRegisterRequest and
+// LicenseAvailability protos. With java_api_version 1, this means that enum
+// needs to be wrapped into a message.
+message LicenseType {
+ // Enumerates different license types.
+ enum LicenseTypeEnum {
+ // Unknown/undefined
+ UNDEFINED = 0;
+ // Chrome Device Management Perpetual, unused as 1 & 2 are in the same
+ // license pool.
+ CDM_PERPETUAL = 1;
+ // Chrome Device Management Annual, unused
+ CDM_ANNUAL = 2;
+ // Chrome Kiosk
+ KIOSK = 3;
+ }
+
+ optional LicenseTypeEnum license_type = 1;
+}
+
+// Data along with a cryptographic signature verifying their authenticity.
+message SignedData {
+ // The data to be signed.
+ optional bytes data = 1;
+ // The signature of the data field.
+ optional bytes signature = 2;
+ // How many bytes were added to the end of original data before signature
+ // (e.g. a nonce to avoid proxy attacks of the signing service).
+ optional int32 extra_data_bytes = 3;
+}
+
+// Request from device to server to check user account type for enrollment.
+message CheckUserAccountRequest {
+ // Email address of a user.
+ // The user may not exist in GAIA.
+ optional string user_email = 1;
+}
+
+// Request from device to server to register a device, user or browser.
+message DeviceRegisterRequest {
+ reserved 5, 10;
+
+ // Reregister device without erasing server state. It can be used
+ // to refresh dmtoken etc. Client MUST set this value to true if it
+ // reuses an existing device id.
+ optional bool reregister = 1;
+
+ // Register type. This field does not exist for TT release.
+ // When a client requests for policies, server should verify the
+ // client has been registered properly. For example, a client must
+ // register with type DEVICE in order to retrieve device policies.
+ enum Type {
+ reserved 5;
+
+ TT = 0; // Register for TT release.
+ USER = 1; // Register for Chrome OS user polices.
+ DEVICE = 2; // Register for Chrome OS device policies.
+ BROWSER = 3; // Register for desktop Chrome browser user policies.
+ ANDROID_BROWSER = 4; // Register for Android Chrome browser user policies.
+ }
+ // NOTE: we also use this field to detect client version. If this
+ // field is missing, then the request comes from TT. We will remove
+ // Chrome OS TT support once it is over.
+ optional Type type = 2 [default = TT];
+
+ // Machine hardware id, such as serial number.
+ // This field is required if register type == DEVICE.
+ optional string machine_id = 3;
+
+ // Machine model name, such as "ZGA", "Cr-48", "Nexus One". If the
+ // model name is not available, client SHOULD send generic name like
+ // "Android", or "Chrome OS".
+ optional string machine_model = 4;
+
+ // Indicates a requisition of the registering entity that the server can act
+ // upon. This allows clients to pass hints e.g. at device enrollment time
+ // about the intended use of the device.
+ optional string requisition = 6;
+
+ // The current server-backed state key for the client, if applicable. This can
+ // be used by the server to link the registration request to an existing
+ // device record for re-enrollment.
+ optional bytes server_backed_state_key = 7;
+
+ // Enumerates different flavors of registration.
+ enum Flavor {
+ reserved 12;
+
+ // User manually enrolls a device for device management.
+ FLAVOR_ENROLLMENT_MANUAL = 0;
+ // User re-starts enrollment manually to recover from loss of policy.
+ FLAVOR_ENROLLMENT_MANUAL_RENEW = 1;
+ // Device enrollment forced by local device configuration, such as OEM
+ // partition flags to force enrollment.
+ FLAVOR_ENROLLMENT_LOCAL_FORCED = 2;
+ // Enrollment advertised by local device configuration, such as OEM
+ // partition flags indicating to prompt for enrollment, but allowing the
+ // user to skip.
+ FLAVOR_ENROLLMENT_LOCAL_ADVERTISED = 3;
+ // Device state downloaded from the server during OOBE indicates that
+ // re-enrollment is mandatory.
+ FLAVOR_ENROLLMENT_SERVER_FORCED = 4;
+ // Device state downloaded from the server during OOBE indicates that the
+ // device should prompt for (re-)enrollment, but the user is allowed to
+ // skip.
+ FLAVOR_ENROLLMENT_SERVER_ADVERTISED = 5;
+ // Device detected in steady state that it is supposed to be enrolled, but
+ // the policy is missing.
+ FLAVOR_ENROLLMENT_RECOVERY = 6;
+ // User policy registration for a logged-in user.
+ FLAVOR_USER_REGISTRATION = 7;
+ // Attestation-based with the option to use a different authentication
+ // mechanism.
+ FLAVOR_ENROLLMENT_ATTESTATION = 8;
+ // Forced attestation-based enrollment (cannot fallback to another flavor).
+ FLAVOR_ENROLLMENT_ATTESTATION_LOCAL_FORCED = 9;
+ // Device state downloaded from the server during OOBE indicates that
+ // re-enrollment is mandatory and should be attestation-based.
+ FLAVOR_ENROLLMENT_ATTESTATION_SERVER_FORCED = 10;
+ // Device state downloaded from the server indicated that re-enrollment is
+ // mandatory, but it failed and we are doing a fallback to manual
+ // enrollment.
+ FLAVOR_ENROLLMENT_ATTESTATION_MANUAL_FALLBACK = 11;
+ // Device state downloaded from the server during OOBE indicates that
+ // initial enrollment is mandatory.
+ FLAVOR_ENROLLMENT_INITIAL_SERVER_FORCED = 13;
+ // Device state downloaded from the server during OOBE indicates that
+ // initial enrollment is mandatory and should be attestation-based.
+ FLAVOR_ENROLLMENT_ATTESTATION_INITIAL_SERVER_FORCED = 14;
+ // Device state downloaded from the server indicated that initial enrollment
+ // is mandatory, but it failed and we are doing a fallback to manual
+ // enrollment.
+ FLAVOR_ENROLLMENT_ATTESTATION_INITIAL_MANUAL_FALLBACK = 15;
+ }
+
+ // Indicates the registration flavor. This is passed to the server FYI when
+ // registering for policy so the server can distinguish registration triggers.
+ optional Flavor flavor = 8;
+
+ // If specified, represents the license type selected by user on the device.
+ optional LicenseType license_type = 9;
+
+ // Enumerates different expected lifetimes of registration.
+ enum Lifetime {
+ // Default case.
+ LIFETIME_UNDEFINED = 0;
+ // No expiration, most of the registrations have this lifetime.
+ LIFETIME_INDEFINITE = 1;
+ // Lifetime for ephemeral user policy registration.
+ LIFETIME_EPHEMERAL_USER = 2;
+ }
+
+ // Indicates the expected lifetime of registration.
+ optional Lifetime lifetime = 11 [default = LIFETIME_INDEFINITE];
+
+ // The 4-character brand code of the device.
+ optional string brand_code = 12;
+
+ // Previous DMToken that should be reused for re-registration.
+ optional string reregistration_dm_token = 13;
+
+ // MAC address for onboard network (ethernet) interface.
+ // The format is twelve (12) hexadecimal digits without any delimiter
+ // (uppercase letters).
+ // This field might be set only if register type == DEVICE.
+ optional string ethernet_mac_address = 14;
+
+ // Built-in MAC address for the docking station that the device can be
+ // connected to.
+ // The format is twelve (12) hexadecimal digits without any delimiter
+ // (uppercase letters).
+ // This field might be set only if register type == DEVICE.
+ optional string dock_mac_address = 15;
+
+ // The date the device was manufactured in yyyy-mm-dd format.
+ // This field might be set only if register type == DEVICE.
+ optional string manufacture_date = 16;
+
+ // Currently using in token enrollment to ensure domain in request matches
+ // domain from token.
+ optional string expected_enrollment_domain = 17;
+
+ // Identification of the device that is not already available.
+ optional DeviceRegisterIdentification device_register_identification = 18;
+
+ // Indicates all possible PSM (Private Set Membership) protocol final results
+ // without specifying the root cause in case of an error.
+ enum PsmExecutionResult {
+ // PSM protocol started and it neither finished successfully nor
+ // terminated due to a protocol's error.
+ PSM_RESULT_UNKNOWN = 0;
+
+ // PSM finished successfully and there was server-backed state for the
+ // device.
+ PSM_RESULT_SUCCESSFUL_WITH_STATE = 1;
+
+ // PSM finished successfully and there was no server-backed state for the
+ // device.
+ PSM_RESULT_SUCCESSFUL_WITHOUT_STATE = 2;
+
+ // PSM terminated due to an error.
+ PSM_RESULT_ERROR = 3;
+ }
+ optional PsmExecutionResult psm_execution_result = 19;
+
+ // Timestamp of PSM retrieving the device's determination successfully in
+ // milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 psm_determination_timestamp_ms = 20;
+
+ // Next id: 21.
+}
+
+// Identification of a device used during its registration.
+message DeviceRegisterIdentification {
+ // The attested device ID for devices using Zero-Touch (see go/zt-sn).
+ optional string attested_device_id = 1;
+}
+
+// Response from server to device
+message CheckUserAccountResponse {
+ // Enum listing the possible user account status.
+ enum UserAccountType {
+ UNKNOWN_USER_ACCOUNT_TYPE = 0;
+
+ // There is no GAIA user exist mapping to the specific user email.
+ NOT_EXIST = 1;
+
+ // The GAIA user mapping to the specific user email is not a dasher user.
+ CONSUMER = 2;
+ // The GAIA user is a dasher user. See http://go/is-dasher-user
+ DASHER = 3;
+ }
+
+ // The domain abstracted from the specific email has been verified by dasher.
+ optional bool domain_verified = 1;
+
+ // The account type mapping from the specific user email.
+ optional UserAccountType user_account_type = 2;
+}
+
+// Response from server to device register request.
+message DeviceRegisterResponse {
+ // Device management token for this registration. This token MUST be
+ // part of HTTP Authorization header for all future requests from
+ // device to server.
+ required string device_management_token = 1;
+
+ // Device display name. By default, server generates the name in
+ // the format of "Machine Model - Machine Id". However, domain
+ // admin can update it using Admin console, so do NOT treat it as constant.
+ optional string machine_name = 2;
+
+ // Enum listing the possible modes the device should be locked into when the
+ // registration is finished.
+ enum DeviceMode {
+ // In ENTERPRISE mode the device has no local owner and device settings are
+ // controlled through the cloud policy infrastructure. Auto-enrollment is
+ // supported in that mode.
+ ENTERPRISE = 0;
+ // DEPRECATED: Devices in RETAIL mode also have no local owner and get their
+ // device settings from the cloud, but additionally this mode enables the
+ // demo account on the device.
+ RETAIL_DEPRECATED = 1;
+ // Devices in CHROME_AD mode are in enterprises with AD. Device settings
+ // are controlled through the AD policy infrastructure.
+ CHROME_AD = 2;
+ // Devices in DEMO mode have no local owner and get their device settings
+ // from the cloud. They are controlled by demo mode domain and provide
+ // customized demo experience to the users.
+ DEMO = 3;
+ }
+ optional DeviceMode enrollment_type = 3 [default = ENTERPRISE];
+
+ // An opaque configuration string for devices that require it. CHROME_AD
+ // devices, for example, may use this string for AD discovery. Must be at
+ // most a few kBytes.
+ optional string configuration_seed = 4;
+
+ // List of user affiliation IDs. The list is used to define if the user
+ // registering for policy is affiliated on the device.
+ // Only sent if DeviceRegisterRequest.Type == USER
+ repeated string user_affiliation_ids = 5;
+
+ // The unique directory api ID of the device which was generated on the
+ // server-side.
+ optional string directory_api_id = 6;
+}
+
+// Request from device to server to unregister device.
+// GoogleDMToken MUST be in HTTP Authorization header.
+message DeviceUnregisterRequest {}
+
+// Response from server to device for unregister request.
+message DeviceUnregisterResponse {}
+
+// Request from device to server to upload a device certificate or an enrollment
+// identifier.
+// GoogleDMToken MUST be in HTTP Authorization header.
+message DeviceCertUploadRequest {
+ enum CertificateType {
+ // Default value for when a type is not specified.
+ CERTIFICATE_TYPE_UNSPECIFIED = 0;
+ // Enterprise machine certificate used for remote attestation.
+ ENTERPRISE_MACHINE_CERTIFICATE = 1;
+ // Enrollment certificate used to obtain an enrollment identifier.
+ ENTERPRISE_ENROLLMENT_CERTIFICATE = 2;
+ }
+
+ // Certificate in X.509 format.
+ optional bytes device_certificate = 1;
+ // Type of certificate. If omitted, will be guessed from the other fields.
+ optional CertificateType certificate_type = 2;
+ // Enrollment identifier if provided.
+ optional bytes enrollment_id = 3;
+}
+
+// Response from server to device for cert upload request.
+message DeviceCertUploadResponse {}
+
+// Request to access a Google service with the given scope.
+message DeviceServiceApiAccessRequest {
+ // The list of auth scopes the device requests from DMServer.
+ repeated string auth_scopes = 1;
+
+ // OAuth2 client ID to which the returned authorization code is bound.
+ optional string oauth2_client_id = 2;
+
+ // Enumerates different flavors of registration.
+ enum DeviceType {
+ // Authcode will be used by Chrome OS
+ // (this is typically requested during device enrollment)
+ CHROME_OS = 0;
+ // Authcode will be used by Android (ARC) subsystem
+ // (this is typically requested during ARC Kiosk session setup)
+ ANDROID_OS = 1;
+ // Authcode will be used by Chrome OS Demo Mode. This auth code can be used
+ // to access Google Docs.
+ // Please see go/cros-demo-mode and go/demo-mode-account-brainstorm.
+ CHROME_OS_DEMO_MODE = 2;
+ // Authcode will be used by the enterprise-managed Chrome Browser to
+ // register for policy invalidations. This is requested during enrollment.
+ CHROME_BROWSER = 3;
+ }
+
+ // Device type indicates the intended use of the auth code.
+ optional DeviceType device_type = 3;
+}
+
+// Response from server to API access request.
+message DeviceServiceApiAccessResponse {
+ // The OAuth2 authorization code for the requested scope(s).
+ // This can be exchanged for a refresh token.
+ optional string auth_code = 1;
+}
+
+// Device Identifier for non-Chrome OS platform.
+message BrowserDeviceIdentifier {
+ reserved 3;
+
+ // Name of the computer.
+ optional string computer_name = 1;
+ // Device serial number (definition depending on the platform).
+ optional string serial_number = 2;
+}
+
+message PolicyFetchRequest {
+ reserved 5;
+
+ // This is the policy type, which maps to D3 policy type internally.
+ // By convention, we use "/" as separator to create policy namespace.
+ // The policy type names are case insensitive.
+ //
+ // Possible values for Chrome OS are:
+ // google/chromeos/device => ChromeDeviceSettingsProto
+ // google/chromeos/user => ChromeSettingsProto
+ // google/chromeos/publicaccount => ChromeSettingsProto
+ // google/chrome/machine-level-user => ChromeSettingsProto
+ // google/chrome/machine-level-user-android => ChromeSettingsProto
+ // google/chrome/machine-level-user-ios => ChromeSettingsProto
+ // google/chrome/extension => ExternalPolicyData
+ // google/chrome/machine-level-extension => ExternalPolicyData
+ // google/chromeos/signinextension => ExternalPolicyData
+ // google/android/user => ChromeSettingsProto
+ // google/chromeos/remotecommand => RemoteCommand (*)
+ //
+ // Types marked with an (*) are not policies, but data signed with the policy
+ // key. It is illegal to try to fetch policies with those types.
+ optional string policy_type = 1;
+
+ // This is the last policy timestamp that client received from server. The
+ // expectation is that this field is filled by the value of
+ // PolicyData.timestamp from the last policy received by the client.
+ optional int64 timestamp = 2;
+
+ // Tell server what kind of security signature is required.
+ // TODO(b/147782972): Move to toplevel in sync with Chrome OS client code.
+ enum SignatureType {
+ NONE = 0;
+ SHA1_RSA = 1;
+ SHA256_RSA = 2;
+ }
+ optional SignatureType signature_type = 3 [default = NONE];
+
+ // The version number of the public key that is currently stored
+ // on the client. This should be the last number the server had
+ // supplied as new_public_key_version in PolicyData.
+ // This field is unspecified if the client does not yet have a
+ // public key.
+ optional int32 public_key_version = 4;
+
+ // This field is used for devices to send the additional ID to fetch settings.
+ // Retrieving some settings requires more than just device or user ID.
+ // For example, to retrieve public account, devices need to pass in
+ // public account ID in addition to device ID. To retrieve extension or
+ // plug-in settings, devices need to pass in extension/plug-in ID in
+ // addition to user ID.
+ // policy_type represents the type of settings (e.g. public account,
+ // extension) devices request to fetch.
+ optional string settings_entity_id = 6;
+
+ // If this fetch is due to a policy invalidation, this field contains the
+ // version provided with the invalidation. The server interprets this value
+ // and the value of invalidation_payload to fetch the up-to-date policy.
+ optional int64 invalidation_version = 7;
+
+ // If this fetch is due to a policy invalidation, this field contains the
+ // payload delivered with the invalidation. The server interprets this value
+ // and the value of invalidation_version to fetch the up-to-date policy.
+ optional bytes invalidation_payload = 8;
+
+ // Hash string for the chrome policy verification public key which is embedded
+ // into Chrome binary. Matching private key will be used by the server
+ // to sign per-domain policy keys during key rotation. If server does not
+ // have the key which matches this hash string, that could indicate malicious
+ // or out-of-date Chrome client.
+ optional string verification_key_hash = 9;
+
+ // Encoded information from a policy invalidation notification. This is opaque
+ // to the client and should be forwarded from the invalidation notification.
+ optional string policy_invalidation_info = 10;
+
+ // Whether or not the client only supports the new PolicyData invalidation
+ // topics. If true, only the policy_invalidation_topic and
+ // command_invalidation_topic fields will be set in the PolicyData response.
+ optional bool invalidation_topics_only = 11;
+
+ // If this is an affiliated user, this is the device's DMToken.
+ optional string device_dm_token = 12;
+
+ // Device identifier for helping identify non-Chrome OS devices.
+ optional BrowserDeviceIdentifier browser_device_identifier = 13;
+}
+
+// This message customizes how the device behaves when it is disabled by its
+// owner. The message will be sent as part of the DeviceState fetched during
+// normal operation and as part of the DeviceStateRetrievalResponse fetched when
+// the device is wiped/reinstalled.
+message DisabledState {
+ // A message to the finder/thief that should be shown on the screen.
+ optional string message = 1;
+}
+
+message DeviceState {
+ // Modes of operation that the device can be in.
+ enum DeviceMode {
+ // The device is operating normally. Sessions can be started and the device
+ // can be used.
+ DEVICE_MODE_NORMAL = 0;
+ // The device has been disabled by its owner. The device will show a warning
+ // screen and will not allow any sessions to be started.
+ DEVICE_MODE_DISABLED = 1;
+ }
+ // The mode of operation that the device should be in.
+ optional DeviceMode device_mode = 1 [default = DEVICE_MODE_NORMAL];
+
+ // State that is relevant only when the |device_mode| is
+ // |DEVICE_MODE_DISABLED|.
+ optional DisabledState disabled_state = 2;
+}
+
+message CustomerLogo {
+ // The SCS url for the logo set by the admin for a particular OU.
+ // This is in the form https://admin.googleusercontent.com/<scs_url_key>.
+ optional string logo_url = 1;
+}
+
+// This message is included in serialized form in PolicyFetchResponse below. It
+// may also be signed, with the signature being created for the serialized form.
+message PolicyData {
+ reserved 10, 13, 14, 18, 19;
+ reserved "command_invalidation_name";
+ reserved "command_invalidation_source";
+ reserved "invalidation_name";
+ reserved "invalidation_source";
+ reserved "valid_serial_number_missing";
+
+ // See PolicyFetchRequest.policy_type.
+ optional string policy_type = 1;
+
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time). It is
+ // included here so that the time at which the server issued this response
+ // cannot be faked (as protection against replay attacks). It is the timestamp
+ // generated by DMServer, NOT the time admin last updated the policy or
+ // anything like that.
+ optional int64 timestamp = 2;
+
+ // The DM token that was used by the client in the HTTP POST header for
+ // authenticating the request. It is included here again so that the client
+ // can verify that the response is meant for them (and not issued by a replay
+ // or man-in-the-middle attack).
+ // Note that the existence or non-existence of the DM token is not the correct
+ // way to determine whether the device is managed. Cf. |management_mode| below
+ // for details.
+ optional string request_token = 3;
+
+ // The serialized value of the actual policy protobuf. This can be
+ // deserialized to an instance of, for example, ChromeSettingsProto,
+ // ChromeDeviceSettingsProto, or ExternalPolicyData.
+ optional bytes policy_value = 4;
+
+ // The device display name assigned by the server. It is only
+ // filled if the display name is available.
+ //
+ // The display name of the machine as generated by the server or set
+ // by the Administrator in the Admin console GUI. This is the same thing as
+ // |machine_name| in DeviceRegisterResponse but it might have
+ // changed since then.
+ optional string machine_name = 5;
+
+ // Version number of the server's current public key. (The key that
+ // was used to sign this response. Numbering should start at 1 and be
+ // increased by 1 at each key rotation.)
+ optional int32 public_key_version = 6;
+
+ // The user this policy is intended for. In case of device policy, the name
+ // of the owner (who registered the device).
+ optional string username = 7;
+
+ // In this field the DMServer should echo back the "deviceid" HTTP parameter
+ // from the request. This is also used for user and device local accounts ids,
+ // see client_id in code.
+ optional string device_id = 8;
+
+ // Indicates which state this association with DMServer is in. This can be
+ // used to tell the client that it is not receiving policy even though the
+ // registration with the server is kept active.
+ enum AssociationState {
+ // Association is active and policy is pushed.
+ ACTIVE = 0;
+ // Association is alive, but the corresponding domain is not managed.
+ UNMANAGED = 1;
+ // The device has been deprovisioned by the administrator and is no longer
+ // managed.
+ DEPROVISIONED = 2;
+ }
+ optional AssociationState state = 9 [default = ACTIVE];
+
+ // Indicates which public account or extension/plug-in this policy data is
+ // for. See PolicyFetchRequest.settings_entity_id for more details.
+ optional string settings_entity_id = 11;
+
+ // Indicates the identity the device service account is associated with.
+ // This is only sent as part of device policy fetch.
+ optional string service_account_identity = 12;
+
+ // Server-provided identifier of the fetched policy. This is to be used
+ // by the client when requesting Policy Posture assertion through an API
+ // call or SAML flow. For details, see http://go/chrome-nac-server-design.
+ optional string policy_token = 15;
+
+ // Indicates the management mode of the device. Note that old policies do not
+ // have this field. If this field is not set but request_token is set, assume
+ // the management mode is ENTERPRISE_MANAGED. If both this field and
+ // request_token are not set, assume the management mode is LOCAL_OWNER.
+ enum ManagementMode {
+ // The device is owned locally. The policies are set by the local owner of
+ // the device.
+ LOCAL_OWNER = 0;
+ // The device is enterprise-managed (either via DM server or through Active
+ // Directory). See the comment above for backward compatibility.
+ ENTERPRISE_MANAGED = 1;
+ // Obsolete. Don't use.
+ OBSOLETE_CONSUMER_MANAGED = 2;
+ }
+ optional ManagementMode management_mode = 16;
+
+ // Indicates the state that the device should be in.
+ optional DeviceState device_state = 17;
+
+ // The free-text location info the admin enters to associate the device
+ // with a location.
+ optional string annotated_location = 20;
+
+ // The free-text asset identifier the admin enters to associate the device
+ // with a user-generated identifier.
+ optional string annotated_asset_id = 21;
+
+ // The unique directory api ID of the device which was generated on the
+ // server-side.
+ optional string directory_api_id = 22;
+
+ // List of device affiliation IDs. If there exists an overlap between user
+ // affiliation IDs and device affiliation IDs, we consider that the user is
+ // affiliated on the device. Otherwise the user is not affiliated on the
+ // device. Should be fetched with device policy. Ignored if fetched with
+ // other polices.
+ repeated string device_affiliation_ids = 23;
+
+ // List of user affiliation IDs. The list is used to define if current user
+ // is affiliated on the device. See device_affiliation_ids for details.
+ // Should be fetched with user policy. Ignored if fetched with other polices.
+ repeated string user_affiliation_ids = 24;
+
+ // Used as the display domain when the primary domain gets renamed. This field
+ // is present only for device policies.
+ optional string display_domain = 25;
+
+ // Invalidation topic for devices. Clients register for FCM messages using
+ // this topic in order to receive notifications for device policy changes.
+ optional string policy_invalidation_topic = 26;
+
+ // Invalidation topic for commands. Clients register for FCM messages using
+ // this topic in order to receive notifications that one or more commands are
+ // available for execution.
+ optional string command_invalidation_topic = 27;
+
+ // Whether the device needs to upload an enrollment identifier to the cloud.
+ // TODO(b/136188860) migrates to enrollment_certificate_needed under
+ // client_action_required.
+ optional bool enrollment_id_needed = 28;
+
+ // Gaia id of the user the policy is intended for.
+ // Should be fetched with user policy.
+ optional string gaia_id = 29;
+
+ // Indicate this device's market segment. The MarketSegment enum in
+ // cloud_policy_constants.h (http://shortn/_3iFWcdjy0P) must be kept in sync
+ // with this enum.
+ enum MarketSegment {
+ MARKET_SEGMENT_UNSPECIFIED = 0;
+ ENROLLED_EDUCATION = 1;
+ ENROLLED_ENTERPRISE = 2;
+ }
+
+ // This field should only be set for Device Policy response.
+ // See go/cros-rlz-segments
+ optional MarketSegment market_segment = 30;
+
+ // This field is currently only set for Device Policy response.
+ // This represents the logo set by the admin for the OU that the device
+ // belongs to. This is domain metadata included in a device policy response,
+ // but it is not an explicit device policy.
+ optional CustomerLogo customer_logo = 31;
+
+ // b/129771193
+ // This setting is from SingleSignOnSettingsProto#change_password_uri
+ // http://google3/ccc/hosted/policies/services/common/sso_settings.proto?l=48&rcl=241246111
+ // This field is currently only set for User Policy response.
+ optional string change_password_uri = 32;
+
+ // This field is used for asking client to perform some actions. For instance,
+ // server asks client to re-upload enrollment certificate. In long term, new
+ // added field which asks client to perform an action in policy data should be
+ // put in ClientActionRequired message.
+ optional ClientActionRequired client_action_required = 33;
+
+ // Obfuscated customerId the device is enrolled into.
+ // Only set for device policy.
+ optional string obfuscated_customer_id = 34;
+
+ // The different types of user segments for metrics logging. If any values are
+ // added to this enum, the corresponding enum in
+ // UserTypeByDeviceTypeMetricsProvider::UserSegment
+ // (http://shortn/_uK3ZM4pC0a) should be updated.
+ enum MetricsLogSegment {
+ UNSPECIFIED = 0;
+ K12 = 1;
+ UNIVERSITY = 2;
+ NONPROFIT = 3;
+ ENTERPRISE = 4;
+ }
+
+ // Indicates the segment the user's metrics should be logged under,
+ // UNSPECIFIED if not relevant.
+ // This field should only be set for User Policy response.
+ optional MetricsLogSegment metrics_log_segment = 35;
+
+ // This field will be populated with primary domain name for domain verified
+ // customer, and primary admin email for domainless customer. The client side
+ // will use this field to display who manages this device/browser/user.
+ optional string managed_by = 36;
+
+ // An identifier (e.g. "inboundSamlSsoProfiles/0abcdefg1234567") for the
+ // device's managing OU's SSO profile. Currently, this points to the OU's
+ // SAML settings. May support OIDC in the future.
+ optional string sso_profile = 37;
+}
+
+message ClientActionRequired {
+ // Whether device needs to upload an enterprise enrollment certificate to
+ // cloud.
+ optional bool enrollment_certificate_needed = 1;
+}
+
+message PolicyFetchResponse {
+ // Since a single policy request may ask for multiple policies, DM server
+ // provides separate error codes (making use of standard HTTP Status Codes)
+ // for each individual policy fetch.
+ optional int32 error_code = 1;
+
+ // Human readable error message for customer support purpose.
+ optional string error_message = 2;
+
+ // This is a serialized |PolicyData| protobuf (defined above).
+ optional bytes policy_data = 3;
+
+ // Signature of the policy data above.
+ optional bytes policy_data_signature = 4;
+
+ // If the public key has been rotated on the server, the new public
+ // key is sent here. It is already used for |policy_data_signature|
+ // above, whereas |new_public_key_signature| is created using the
+ // old key (so the client can trust the new key). If this is the
+ // first time when the client requests policies (so it doesn't have
+ // on old public key), then |new_public_key_signature| is empty.
+ optional bytes new_public_key = 5;
+ optional bytes new_public_key_signature = 6;
+
+ // DEPRECATED: Exists only to support older clients. This signature is similar
+ // to new_public_key_verification_data_signature, but is computed over
+ // DEPRECATEDPolicyPublicKeyAndDomain (which is equivalent to
+ // PublicKeyVerificationData proto with version field unset).
+ optional bytes new_public_key_verification_signature_deprecated = 7
+ [deprecated = true];
+
+ // This is a serialized |PublicKeyVerificationData| protobuf (defined
+ // below). See comments for |new_public_key_verification_data_signature| field
+ // for details on how this data is signed.
+ // Please note that |new_public_key| is also included inside this data
+ // field. Thus we have new public key signed with old version of private key
+ // (if client indicated to us that it has old key version), and
+ // new public key data signed by primary verification key (if client told
+ // us that it has public verification key - see |verification_key_id| field
+ // of |PolicyFetchRequest|). In most cases, both signatures will be provided.
+ // However, client might not have old policy signing key - for example, when
+ // new profile is being set up. In this case, only verification signature
+ // is supplied.
+ // Or, client might not have verification public key (legacy Chrome build
+ // before verification key was introduced, or outdated build which has
+ // old/compromised verification key). In that case, verification signature
+ // cannot be provided.
+ // If client is missing both public keys (old signing key and verification
+ // key), then we are unable to produce any valid signature and client must
+ // drop such PolicyFetchResponse.
+ optional bytes new_public_key_verification_data = 8;
+
+ // If new_public_key is specified, this field contains the signature of a
+ // PublicKeyVerificationData protobuf, signed using a key only available to
+ // DMServer. The public key portion of this well-known key is embedded into
+ // the Chrome binary. The hash of that embedded key is passed to DMServer as
+ // verification_key_hash field in PolicyFetchRequest. DMServer picks a private
+ // key on the server which matches the hash (matches public key on the
+ // client). If DMServer is unable to find matching key, it returns an error
+ // instead of policy data. In case a hash was not specified, DMServer leaves
+ // the verification signature field empty (legacy behavior).
+ // This signature is provided to better protect first key delivery (since the
+ // browser does not possess the previous signing key, DMServer cannot compute
+ // new_public_key_signature).
+ // See http://go/chrome-nac-server-design for more information.
+ optional bytes new_public_key_verification_data_signature = 9;
+
+ // DEPRECATED! Client-side should verify and rely on the policy_type inside
+ // the signed policy_data.
+ optional string policy_type = 10 [deprecated = true];
+
+ // The type of signature used to generate policy_data_signature.
+ optional PolicyFetchRequest.SignatureType policy_data_signature_type = 11;
+}
+
+// DEPRECATED: Protobuf used to generate the deprecated
+// new_public_key_verification_signature field.
+message DEPRECATEDPolicyPublicKeyAndDomain {
+ // The public key to sign (taken from the |new_public_key| field in
+ // PolicyFetchResponse).
+ optional bytes new_public_key = 1;
+
+ // The domain associated with this key (should match the domain portion of the
+ // username field of the policy).
+ optional string domain = 2;
+}
+
+// This message contains the information which is signed by the verification key
+// during policy key rotation. It is included in serialized form in
+// PolicyFetchResponse above. A signature of the serialized form is included in
+// the new_public_key_verification_data_signature field.
+message PublicKeyVerificationData {
+ // The new public policy key after a key rotation.
+ optional bytes new_public_key = 1;
+
+ // The domain of the device/user.
+ optional string domain = 2;
+
+ // The version number of the new_public_key. This must be monotonically
+ // increasing (within a domain).
+ optional int32 new_public_key_version = 3;
+}
+
+// Request from device to server for reading policies.
+message DevicePolicyRequest {
+ // The policy fetch requests. If this field exists, the requests must come
+ // from a non-TT client. The repeated field allows clients to request
+ // multiple policies for better performance.
+ repeated PolicyFetchRequest requests = 3;
+}
+
+// Response from server to device for reading policies.
+message DevicePolicyResponse {
+ // The policy fetch responses.
+ repeated PolicyFetchResponse responses = 3;
+}
+
+message TimePeriod {
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 start_timestamp = 1;
+ optional int64 end_timestamp = 2;
+}
+
+message ActiveTimePeriod {
+ optional TimePeriod time_period = 1;
+
+ // The active duration during the above time period.
+ // The unit is milli-second.
+ optional int32 active_duration = 2;
+
+ // Email address of the active user. Present only if the user type is managed
+ // and affiliated.
+ optional string user_email = 3;
+
+ enum SessionType {
+ SESSION_UNKNOWN = 0;
+ SESSION_AFFILIATED_USER = 1;
+ SESSION_MANAGED_GUEST = 2;
+ SESSION_KIOSK = 3;
+ SESSION_ARC_KIOSK = 4;
+ SESSION_WEB_KIOSK = 5;
+ }
+
+ optional SessionType session_type = 4;
+}
+
+// Details about a network interface.
+message NetworkInterface {
+ // Indicates the type of network device.
+ enum NetworkDeviceType {
+ reserved 2;
+
+ TYPE_ETHERNET = 0;
+ TYPE_WIFI = 1;
+ TYPE_BLUETOOTH = 3;
+ TYPE_CELLULAR = 4;
+ }
+
+ // Network device type.
+ optional NetworkDeviceType type = 1;
+
+ // MAC address (if applicable) of the corresponding network device. This is
+ // formatted as an ASCII string with 12 hex digits. Example: A0B1C2D3E4F5.
+ optional string mac_address = 2;
+
+ // MEID (if applicable) of the corresponding network device. Formatted as
+ // ASCII string composed of 14 hex digits. Example: A10000009296F2.
+ optional string meid = 3;
+
+ // IMEI (if applicable) of the corresponding network device. 15-16 decimal
+ // digits encoded as ASCII string. Example: 355402040158759.
+ optional string imei = 4;
+
+ // The device path associated with this network interface.
+ optional string device_path = 5;
+
+ // The integrated circuit card ID associated with the device's sim card.
+ optional string iccid = 6;
+
+ // The mobile directory number associated with the device's sim card.
+ optional string mdn = 7;
+
+ // List of EID (EUICC Identifier) of all cellular EUICCs
+ // (Embedded Universal Integrated Circuit Cards) on the device.
+ // 32 decimal digits encoded as ASCII string. e.g.
+ repeated string eids = 8;
+}
+
+// Information about configured/visible networks - this is separate from
+// NetworkInterface because a configured network may not be associated with
+// any specific interface, or may be visible across multiple interfaces.
+message NetworkState {
+ // The current state of this network.
+ // CARRIER (1), DISCONNECT (8) and ACTIVATION_FAILURE (10) are not used by the
+ // client.
+ enum ConnectionState {
+ IDLE = 0;
+ CARRIER = 1;
+ ASSOCIATION = 2;
+ CONFIGURATION = 3;
+ READY = 4;
+ PORTAL = 5;
+ OFFLINE = 6;
+ ONLINE = 7;
+ DISCONNECT = 8;
+ FAILURE = 9;
+ ACTIVATION_FAILURE = 10;
+ UNKNOWN = 11;
+ }
+
+ // For networks associated with a device, the path of the device.
+ optional string device_path = 1;
+
+ // Current state of this connection as reported by shill.
+ optional ConnectionState connection_state = 2;
+
+ // For wireless networks, the signal_strength in dBm.
+ optional int32 signal_strength = 3;
+
+ // The IP address this interface is bound to, if any.
+ optional string ip_address = 4;
+
+ // The gateway IP for this interface, if any.
+ optional string gateway = 5;
+}
+
+// Details about a device user.
+message DeviceUser {
+ // Types of device users which can be reported.
+ enum UserType {
+ // A user managed by the same domain as the device.
+ USER_TYPE_MANAGED = 0;
+
+ // A user not managed by the same domain as the device.
+ USER_TYPE_UNMANAGED = 1;
+ }
+
+ // The type of the user.
+ required UserType type = 1;
+
+ // Email address of the user. Present only if the user type is managed.
+ optional string email = 2;
+}
+
+// Information about a single disk volume.
+message VolumeInfo {
+ optional string volume_id = 1;
+
+ // The unit is bytes.
+ optional int64 storage_total = 2;
+ optional int64 storage_free = 3;
+}
+
+// Information about a single CPU utilization.
+message CpuUtilizationInfo {
+ // CPU utilization (0-100).
+ optional int32 cpu_utilization_pct = 1;
+ // The timestamp representing time at which the information was collected.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 timestamp = 2;
+}
+
+// Information about a single free RAM.
+message SystemFreeRamInfo {
+ // Free RAM [in bytes] (unreliable due to GC).
+ optional int64 size_in_bytes = 1;
+ // The timestamp representing time at which the information was collected.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 timestamp = 2;
+}
+
+// Information about a single CPU temperature channel.
+message CPUTempInfo {
+ // Temperature channel label.
+ optional string cpu_label = 1;
+ // CPU temperature in Celsius.
+ optional int32 cpu_temp = 2;
+ // Unix timestamp.
+ optional int64 timestamp = 3;
+}
+
+// Contains the Stateful Partition Information for user data storage in the
+// device.
+message StatefulPartitionInfo {
+ // Available space for user data storage in the device in bytes.
+ optional uint64 available_space = 1;
+ // Total space for user data storage in the device in bytes.
+ optional uint64 total_space = 2;
+ // File system on stateful partition. e.g. ext4.
+ optional string filesystem = 3;
+ // Source of stateful partition. e.g. /dev/mmcblk0p1.
+ optional string mount_source = 4;
+}
+
+// Chrome release channel, shared for different reports.
+enum Channel {
+ CHANNEL_UNKNOWN = 0;
+ CHANNEL_CANARY = 1;
+ CHANNEL_DEV = 2;
+ CHANNEL_BETA = 3;
+ CHANNEL_STABLE = 4;
+}
+
+// Frequently changing data for battery.
+message BatterySample {
+ optional int64 timestamp = 1;
+ // Battery voltage
+ optional int64 voltage = 2;
+ // Battery remaining capacity (mA-hours)
+ optional int64 remaining_capacity = 3;
+ // Temperature in Celsius.
+ optional int32 temperature = 4;
+ // The battery discharge rate measured in mW. Positive if the battery is being
+ // discharged, negative if it's being charged.
+ optional int32 discharge_rate = 5;
+ // Battery charge percentage
+ optional int32 charge_rate = 6;
+ // Battery current (mA)
+ optional int64 current = 7;
+ // Battery status read from sysfs
+ optional string status = 8;
+}
+
+// Status of the single battery
+message BatteryInfo {
+ optional string serial = 1;
+ optional string manufacturer = 2;
+ optional string battery_health = 3;
+ // Design capacity (mA-hours)
+ optional int64 design_capacity = 4;
+ // Full charge capacity (mA-hours)
+ optional int64 full_charge_capacity = 5;
+ optional int32 cycle_count = 6;
+ // Last sampling data.
+ repeated BatterySample samples = 7;
+ // Designed minimum output voltage (mV)
+ optional int32 design_min_voltage = 9;
+ // The date the battery was manufactured in yyyy-mm-dd format.
+ optional string manufacture_date = 10;
+ // Technology of the battery.
+ optional string technology = 11;
+}
+
+// Status of the power subsystem
+message PowerStatus {
+ enum PowerSource {
+ POWER_UNKNOWN = 0;
+ POWER_AC = 1;
+ POWER_BATTERY = 2;
+ }
+ optional PowerSource power_source = 1;
+ repeated BatteryInfo batteries = 2;
+}
+
+// LifeTime estimation for eMMC devices
+message DiskLifetimeEstimation {
+ // Lifetime estimations for SLC and MLC areas of eMMC.
+ // Values range from 00h to 0Bh -- indicating the percentage of device
+ // lifetime used.
+ optional int32 slc = 1;
+ optional int32 mlc = 2;
+}
+
+// Status of the single storage device
+// Next id: 27
+message DiskInfo {
+ optional string serial = 1;
+ optional string manufacturer = 2;
+ optional string model = 3;
+ // Size in bytes
+ optional int64 size = 4;
+ // eMMC / NVMe / ATA / SCSI.
+ optional string type = 5;
+ optional string health = 6;
+ // volume_id for volumes on this disk.
+ repeated string volumes = 7;
+ // Read/write statistics for this disk.
+ optional uint64 bytes_read_since_last_boot = 8;
+ optional uint64 bytes_written_since_last_boot = 9;
+ optional uint64 read_time_seconds_since_last_boot = 10;
+ optional uint64 write_time_seconds_since_last_boot = 11;
+ // Counts the time the disk and queue were busy, so unlike the fields above,
+ // parallel requests are not counted multiple times.
+ optional uint64 io_time_seconds_since_last_boot = 12;
+ // Time spent discarding since last boot. Discarding is writing to clear
+ // blocks which are no longer in use. Supported on kernels 4.18+.
+ optional uint64 discard_time_seconds_since_last_boot = 13;
+
+ // The manufacturer of the block device.
+ oneof vendor_id {
+ // NVME vendors:
+ // https://pcisig.com/membership/member-companies
+ uint32 nvme_subsystem_vendor = 14;
+ // EMMC oemids
+ // https://screenshot.googleplex.com/eZWNnV8qGnc
+ uint32 emmc_oemid = 15;
+ uint32 other_vendor = 16;
+ }
+
+ // The manufacturer-specific product identifier.
+ oneof product_id {
+ uint32 nvme_subsystem_device = 17;
+ uint32 emmc_pnm = 18;
+ uint32 other_product = 19;
+ }
+
+ // The revision of the device's hardware.
+ oneof hardware_revision {
+ uint32 nvme_hardware_rev = 20;
+ uint32 emmc_hardware_rev = 21;
+ uint32 other_hardware_rev = 22;
+ }
+
+ // The revision of the device's firmware.
+ oneof firmware_revision {
+ uint64 nvme_firmware_rev = 23;
+ uint64 emmc_firmware_rev = 24;
+ uint32 other_firmware_rev = 25;
+ }
+
+ // The purpose of the device on the system.
+ enum DevicePurpose {
+ PURPOSE_UNKNOWN = 0;
+ PURPOSE_BOOT = 1;
+ PURPOSE_SWAP = 2;
+ }
+ optional DevicePurpose purpose = 26;
+}
+
+// Status of the storage subsystem.
+message StorageStatus {
+ repeated DiskInfo disks = 1;
+ optional DiskLifetimeEstimation lifetime_estimation = 2;
+}
+
+// Sampling for single temperature measurements
+message ThermalSample {
+ optional int64 timestamp = 1;
+ optional int32 temperature = 2;
+}
+
+// Temperature measurement series for thermal point.
+message ThermalInfo {
+ reserved 2;
+ optional string label = 1;
+ repeated ThermalSample samples = 3;
+}
+
+// Status for various on-board components
+message BoardStatus {
+ repeated ThermalInfo thermal_infos = 1;
+}
+
+// Status about a system's various elements.
+message SystemStatus {
+ // The product SKU (stock keeping unit) number.
+ optional string vpd_sku_number = 1;
+ // The date the device was first activated.
+ // Format: YYYY-WW.
+ optional string first_power_date = 2;
+ // The date the device was manufactured (finalized in factory).
+ // Format: YYYY-MM-DD.
+ optional string manufacture_date = 3;
+ // Contents of CrosConfig in /arc/build-properties/marketing-name. E.g. "HP
+ // Chromebook x360 14"
+ optional string marketing_name = 4;
+ // The BIOS version. E.g. "Google_Sarien.12200.58.0"
+ optional string bios_version = 5;
+ // The product name of the motherboard. E.g. "Sarien"
+ optional string board_name = 6;
+ // The version of the motherboard. E.g. "rev16"
+ optional string board_version = 7;
+ // The chassis type of the device. The values reported by chassis type are
+ // mapped in
+ // www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf.
+ // E.g. "9"
+ optional uint64 chassis_type = 8;
+ // The product name (model) of the system. E.g. "Sarien"
+ optional string product_name = 9;
+ // The product serial number.
+ optional string vpd_serial_number = 10;
+}
+
+// Status of a single C-state. C-states are various modes the CPU can transition
+// to in order to use more or less power.
+message CpuCStateInfo {
+ // Name of the state.
+ optional string name = 1;
+ // Time spent in the state since the last reboot, in microseconds.
+ optional uint64 time_in_state_since_last_boot_us = 2;
+}
+
+// Status of a single logical CPU.
+message LogicalCpuInfo {
+ // Maximum frequency the CPU is allowed to run at, by policy.
+ optional uint32 scaling_max_frequency_khz = 1;
+ // Current frequency the CPU is running at.
+ optional uint32 scaling_current_frequency_khz = 2;
+ // Idle time since last boot.
+ optional uint64 idle_time_seconds = 3;
+ // Information about the logical CPU's time in various C-states.
+ repeated CpuCStateInfo c_states = 4;
+}
+
+// Status of a single physical CPU on the device.
+message CpuInfo {
+ // The CPU model name.
+ optional string model_name = 1;
+
+ // The CPU architecture.
+ enum Architecture {
+ ARCHITECTURE_UNSPECIFIED = 0;
+ X86_64 = 1;
+ AARCH64 = 2;
+ ARMV7L = 3;
+ }
+ optional Architecture architecture = 2;
+
+ // The max CPU clock speed in kHz.
+ optional uint32 max_clock_speed_khz = 3;
+
+ repeated LogicalCpuInfo logical_cpus = 4;
+}
+
+// Overall CPU information for the device.
+message GlobalCpuInfo {
+ // Total number of threads on the device.
+ optional uint32 num_total_threads = 1;
+}
+
+// Status for a single display. A display screen with resolution 1920x1080
+// would have resolution_width: 1920 and resolution_height: 1080.
+message DisplayInfo {
+ // Resolution width
+ optional uint32 resolution_width = 1;
+ // Resolution height
+ optional uint32 resolution_height = 2;
+ // Refresh rate (Hz)
+ optional uint32 refresh_rate = 3;
+ // Set to true if display is internal, otherwise set to false.
+ optional bool is_internal = 4;
+}
+
+// Status of a single graphics adapter (GPU).
+message GraphicsAdapterInfo {
+ // Adapter name. Example: Mesa DRI Intel(R) UHD Graphics 620 (Kabylake GT2)
+ optional string name = 1;
+ // Driver version
+ optional string driver_version = 2;
+ // Represents the graphics card device id
+ optional uint64 device_id = 3;
+ // GPU consumption of system RAM (bytes)
+ optional uint64 system_ram_usage = 4;
+}
+
+// Status of the graphics subsystem.
+message GraphicsStatus {
+ optional GraphicsAdapterInfo adapter = 1;
+ repeated DisplayInfo displays = 2;
+}
+
+// Status of a crash report.
+message CrashReportInfo {
+ // The status options should align with crash_reporter::ReportUploadState.
+ enum CrashReportUploadStatus {
+ UPLOAD_STATUS_UNKNOWN = 0;
+ UPLOAD_STATUS_NOT_UPLOADED = 1;
+ UPLOAD_STATUS_PENDING = 2;
+ UPLOAD_STATUS_PENDING_USER_REQUESTED = 3;
+ UPLOAD_STATUS_UPLOADED = 4;
+ }
+
+ // ID as provided by chrome://crashes.
+ optional string remote_id = 1;
+
+ // The timestamp when the crash is captured.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 capture_timestamp = 2;
+
+ // Human readable string that identifies what caused the crash.
+ optional string cause = 3;
+
+ // The upload status of crash report.
+ optional CrashReportUploadStatus upload_status = 4;
+}
+
+// Timezone information for the device. This reflects what set timezone of the
+// device, not necessarily the actual location of the device.
+message TimezoneInfo {
+ // The timezone of the device in POSIX standard. (MST7MDT,M3.2.0,M11.1.0)
+ optional string posix = 1;
+ // The timezone region of the device in the Olsen format (America/Denver).
+ optional string region = 2;
+}
+
+// Memory information for the device.
+message MemoryInfo {
+ // Total memory, in KiB.
+ optional uint32 total_memory_kib = 1;
+ // Free memory, in KiB.
+ optional uint32 free_memory_kib = 2;
+ // Available memory, in KiB.
+ optional uint32 available_memory_kib = 3;
+ // Number of page faults since the last boot.
+ optional uint64 page_faults_since_last_boot = 4;
+}
+
+// Information about the device's backlights.
+message BacklightInfo {
+ // Path to this backlight on the system. Useful if the caller needs to
+ // correlate with other information.
+ optional string path = 1;
+ // Maximum brightness for the backlight.
+ optional uint32 max_brightness = 2;
+ // Current brightness of the backlight, between 0 and max_brightness.
+ optional uint32 brightness = 3;
+}
+
+// Information about the device's fan.
+message FanInfo {
+ // Fan speed in RPM.
+ optional uint32 speed_rpm = 1;
+}
+
+// Information about a device's Bluetooth adapter, which is used to detect and
+// connect to Bluetooth devices.
+message BluetoothAdapterInfo {
+ // The name of the adapter.
+ optional string name = 1;
+ // The MAC address of the adapter.
+ optional string address = 2;
+ // Indicates whether the adapter is on or off.
+ optional bool powered = 3;
+ // The number of devices connected to this adapter.
+ optional uint32 num_connected_devices = 4;
+}
+
+// Information from the device's SMBIOS. This is used to determine
+// info such as the device's vendor and product name/version.
+message SmbiosInfo {
+ optional string sys_vendor = 1;
+ optional string product_name = 2;
+ optional string product_version = 3;
+ optional string bios_version = 4;
+}
+
+// Information about the parameters passed to the kernel.
+message KernelParameters {
+ // True if cros_efi was passed to the kernel.
+ // Used to know if the device was booted via EFI.
+ optional bool cros_efi = 1;
+}
+
+// Information about the device's EFI Variables from efivarfs.
+message EFIVars {
+ // True if Secure Boot is enabled on the device.
+ optional bool secure_boot = 1;
+}
+
+// Information about how the OS was booted. This is information
+// such as boot method and other bios settings
+message BootInfo {
+ enum BootMethod {
+ UNKNOWN = 0;
+ CROS_SECURE = 1;
+ // This field maps to kCrosEfi from cros_healthd.
+ CROS_UEFI = 2;
+ CROS_LEGACY = 3;
+ CROS_EFI_SECURE = 4;
+ }
+ optional BootMethod boot_method = 1;
+
+ // DEPRECATED: secure_boot is deprecated in favor of the
+ // CROS_EFI_SECURE BootMethod value.
+ optional bool secure_boot = 2 [deprecated = true];
+}
+
+// Hardware component bus device classes.
+// Maps to BusDeviceClass from cros_healthd.
+enum BusDeviceClass {
+ DEVICE_CLASS_UNSPECIFIED = 0;
+ DISPLAY_CONTROLLER = 1;
+ ETHERNET_CONTROLLER = 2;
+ WIRELESS_CONTROLLER = 3;
+ BLUETOOTH_ADAPTER = 4;
+ THUNDERBOLT_CONTROLLER = 5;
+}
+
+// Hardware component buses.
+// Maps to BusInfo types from cros_healthd.
+enum BusType {
+ BUS_TYPE_UNSPECIFIED = 0;
+ PCI_BUS = 1;
+ USB_BUS = 2;
+ THUNDERBOLT_BUS = 3;
+}
+
+// Information about a device's network hardware.
+message NetworkAdapterInfo {
+ optional BusDeviceClass device_class = 1;
+ optional BusType bus_type = 2;
+ optional int32 vendor_id = 3;
+ optional string vendor_name = 4;
+ optional int32 device_id = 5;
+ optional string device_name = 6;
+ repeated string driver = 7;
+}
+
+// Report device level status.
+message DeviceStatusReportRequest {
+ reserved 4, 7, 13, 20;
+
+ // The OS version reported by the device is a platform version
+ // e.g. 1435.0.2011_12_16_1635.
+ optional string os_version = 1;
+ optional string firmware_version = 2;
+
+ // "Verified", "Dev". Same as verified mode.
+ // If the mode is unknown, this field should not be set.
+ optional string boot_mode = 3;
+
+ // The browser version string as shown in the About dialog.
+ // e.g. 17.0.963.18.
+ optional string browser_version = 5;
+
+ // A list of periods when the device was active, aggregated by day by user.
+ repeated ActiveTimePeriod active_periods = 6;
+
+ // List of network interfaces.
+ repeated NetworkInterface network_interfaces = 8;
+
+ // List of recent device users, in descending order by last login time.
+ repeated DeviceUser users = 9;
+
+ // Disk space + other info about mounted/connected volumes.
+ repeated VolumeInfo volume_infos = 10;
+
+ // List of visible/configured networks
+ repeated NetworkState network_states = 11;
+
+ // Samples of CPU utilization (0-100), sampled once every 120 seconds.
+ // Deprecated: Use CpuUtilizationInfo instead.
+ repeated int32 cpu_utilization_pct_samples = 12 [deprecated = true];
+
+ // Total RAM on the device.
+ // To deprecate: Use SystemFreeRamInfo instead.
+ optional int64 system_ram_total = 14;
+
+ // Samples of free RAM [in bytes] (unreliable due to GC).
+ // Deprecated: Use SystemRamFreeInfo instead.
+ repeated int64 system_ram_free_samples = 15 [deprecated = true];
+
+ // Samples of CPU temperatures in Celsius, plus associated labels
+ // identifying which CPU produced the temperature measurement.
+ repeated CPUTempInfo cpu_temp_infos = 16;
+
+ // This field is set only when an OS update is needed because of the required
+ // platform version of an updated kiosk app is different from the current
+ // OS version.
+ optional OsUpdateStatus os_update_status = 17;
+
+ // Set only when there is an auto launched with zero delay Chrome or ARC kiosk
+ // app and it is currently running. Otherwise, this field is empty.
+ optional AppStatus running_kiosk_app = 18;
+
+ // Sound output volume level in range [0,100].
+ optional int32 sound_volume = 19;
+
+ // TPM version information.
+ optional TpmVersionInfo tpm_version_info = 21;
+
+ // Release channel (stable, beta, etc.).
+ optional Channel channel = 22;
+
+ // TPM status information.
+ optional TpmStatusInfo tpm_status_info = 23;
+
+ // Whether hardware write protect switch is on.
+ optional bool write_protect_switch = 24;
+
+ // Status of the power subsystem.
+ optional PowerStatus power_status = 25;
+
+ // Status of the storage subsystem.
+ optional StorageStatus storage_status = 26;
+
+ // Status of various main board components.
+ optional BoardStatus board_status = 27;
+
+ // Information about a system's various non-hardware elements. This includes
+ // information from cached VPD, CrosConfig, and DMI.
+ optional SystemStatus system_status = 28;
+
+ // Stateful Partition Information for user data.
+ optional StatefulPartitionInfo stateful_partition_info = 29;
+
+ // Samples of CPU utilization (0-100), sampled once every 120 seconds.
+ repeated CpuUtilizationInfo cpu_utilization_infos = 30;
+
+ // Samples of free RAM [in bytes] (unreliable due to GC).
+ repeated SystemFreeRamInfo system_ram_free_infos = 31;
+
+ // Information about a devices physical CPU(s).
+ repeated CpuInfo cpu_info = 32;
+
+ // Status of the graphics adapter(s) and display(s).
+ optional GraphicsStatus graphics_status = 33;
+
+ // Information about the crash report(s) generated from the local device.
+ repeated CrashReportInfo crash_report_infos = 34;
+
+ // Information of the device's current timezone.
+ optional TimezoneInfo timezone_info = 35;
+
+ // Information about the device's memory.
+ optional MemoryInfo memory_info = 36;
+
+ // Information about the device's backlights.
+ repeated BacklightInfo backlight_info = 37;
+
+ // Information about the device's fans.
+ repeated FanInfo fan_info = 38;
+
+ // Overall information about the device's CPUs.
+ optional GlobalCpuInfo global_cpu_info = 39;
+
+ // Information about the device's Bluetooth adapters.
+ repeated BluetoothAdapterInfo bluetooth_adapter_info = 40;
+
+ // Information from the device's SMBIOS.
+ optional SmbiosInfo smbios_info = 41;
+
+ // Information about the parameters passed to the kernel.
+ optional KernelParameters kernel_parameters = 42;
+
+ // Information about the device's EFI Variables from efivarfs.
+ optional EFIVars efi_vars = 43;
+
+ // KernelParameters(42) and EFIVars(43) are deprecated
+ // and rolled into BootInfo(44)
+
+ // Information about how the os was booted.
+ optional BootInfo boot_info = 44;
+
+ // Information about the device's network hardware.
+ repeated NetworkAdapterInfo network_adapter_info = 45;
+}
+
+message OsUpdateStatus {
+ enum UpdateStatus {
+ OS_UP_TO_DATE = 0;
+ OS_IMAGE_DOWNLOAD_NOT_STARTED = 1;
+ OS_IMAGE_DOWNLOAD_IN_PROGRESS = 2;
+ OS_UPDATE_NEED_REBOOT = 3;
+ }
+
+ optional UpdateStatus update_status = 1;
+
+ // New platform version of the os image being downloaded and applied. It
+ // is only set when update status is OS_IMAGE_DOWNLOAD_IN_PROGRESS or
+ // OS_UPDATE_NEED_REBOOT. Note this could be a dummy "0.0.0.0" for
+ // OS_UPDATE_NEED_REBOOT status for some edge cases, e.g. update engine is
+ // restarted without a reboot.
+ optional string new_platform_version = 2;
+
+ // New required platform version from the pending updated kiosk app.
+ optional string new_required_platform_version = 3;
+
+ // The timestamp of the last update check.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 last_checked_timestamp = 4;
+
+ // The timestamp of the last reboot.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 last_reboot_timestamp = 5;
+}
+
+// Provides status information for an installed app/extension.
+message AppStatus {
+ // ID of the installed app/extension for a Chrome app.
+ // Package name for ARC kiosk app.
+ optional string app_id = 1;
+
+ // Currently installed version of the app for a Chrome app.
+ // Empty for ARC kiosk app.
+ optional string extension_version = 2;
+
+ // Self-reported status summary (via chrome.reporting APIs)
+ optional string status = 3;
+
+ // If true, the application is currently in a self-reported error state.
+ optional bool error = 4;
+
+ // App required Chrome version, specified in app’s manifest file.
+ // Empty for ARC kiosk app.
+ optional string required_platform_version = 5;
+}
+
+// Provides all application types information.
+message AppInfo {
+ enum AppType {
+ TYPE_UNKNOWN = 0;
+ TYPE_ARC = 1; // Android app.
+ TYPE_BUILTIN = 2; // Built-in app.
+ TYPE_CROSTINI = 3; // Linux (via Crostini) app.
+ TYPE_EXTENSION = 4; // Extension-backed app.
+ TYPE_WEB = 5; // Web app.
+ TYPE_PLUGINVM = 6; // Plugin VM app.
+ TYPE_BOREALIS = 7; // Borealis app.
+ }
+
+ enum Status {
+ STATUS_UNKNOWN = 0;
+ STATUS_INSTALLED = 1; // Installed and launachable.
+ STATUS_DISABLED = 2; // Disabled or terminated.
+ STATUS_UNINSTALLED = 3; // Uninstalled by user.
+ }
+
+ // ID of the application as defined by the OS, except for web apps, where it
+ // is the start url.
+ optional string app_id = 1;
+
+ // Type of application (Chrome native, extension, Crostini, web app).
+ optional AppType app_type = 2;
+
+ // Name of the application as defined by the OS.
+ optional string app_name = 3;
+
+ // Identify if the app is installed, disabled, or uninstalled.
+ optional Status status = 4;
+
+ // The time the app was installed, if available.
+ optional int64 install_time = 5;
+
+ // Version of the application, if applicable.
+ optional string version = 7;
+
+ // A list of time periods when the app was active. These times are aggregated
+ // per day, are pruned on the device after reporting successfully, and are
+ // stored only for 30 days in the past.
+ repeated TimePeriod active_time_periods = 8;
+}
+
+// LINT.IfChange
+// Provides Android application permission.
+message AndroidAppPermission {
+ // Name of application permission.
+ optional string name = 1;
+
+ // Identify whether the application permission is granted.
+ optional bool granted = 2;
+
+ // Identify whether the application permission is managed.
+ optional bool managed = 3;
+}
+
+// Provides Android application information.
+message AndroidAppInfo {
+ enum AndroidAppStatus {
+ STATUS_UNKNOWN = 0;
+ STATUS_ENABLED = 1;
+ STATUS_SUSPENDED = 2;
+ STATUS_DISABLED = 3;
+ }
+
+ enum InstalledSource {
+ SOURCE_UNKNOWN = 0;
+ SOURCE_BY_ADMIN = 1;
+ SOURCE_BY_USER = 2;
+ SOURCE_NOT_INSTALLED = 3;
+ }
+
+ // ID of the Android application.
+ optional string app_id = 1;
+
+ // Name of the Android application.
+ optional string app_name = 2;
+
+ // Name of the Android application package.
+ optional string package_name = 3;
+
+ // Status of the Android application. It is set as STATUS_SUSPENDED if the
+ // application is suspended by specific policies.
+ optional AndroidAppStatus status = 4;
+
+ // Identify how the Android application is installed.
+ optional InstalledSource installed_source = 5;
+
+ // Package version of the Android application.
+ optional int32 version = 6;
+
+ // Permissions of the Android application.
+ repeated AndroidAppPermission permissions = 7;
+}
+// LINT.ThenChange(//depot/google3/java/com/google/chrome/cros/spanner/devicemanagement/schema/chrome_os.proto)
+
+// Chrome user profile level status.
+// Deprecated : Use ChromeUserProfileInfo instead.
+message ChromeUserProfileReport {
+ // A string to uniquely identify this profile within the browser.
+ optional string id = 1;
+ // A JSON encoded string containing both the “email†and “id†(obfuscated
+ // GaiaID) of the user signed in to the Chrome browser, if any.
+ optional string chrome_signed_in_user = 2;
+ // The list of extensions installed in the browser. This string contains
+ // the json encoded data as returned by the chrome.management.getAll() API.
+ optional string extension_data = 3;
+ // The list of plugins installed in the browser, one plugin name per repeated
+ // string. This string contains the JSON encoded data as returned by
+ // the navigator.plugins .
+ optional string plugins = 4;
+ // The list of browser policies set for this user profile and their sources.
+ // This string contains the json encoded data as generated by the
+ // chrome://policy page “Export to JSON†button.
+ optional string policy_data = 5;
+ // The last time the user level policies where fetched.
+ // [policy_fetched_timestamp] is milliseconds since Epoch in UTC timezone
+ // (Java time). For V1, we may need to rely on the DM server for this info.
+ optional int64 policy_fetched_timestamp = 6;
+ // The number of safe browsing warning pages the user has seen since the last
+ // report was successfully uploaded.
+ optional uint64 safe_browsing_warnings = 7;
+ // The number of safe browsing warning pages the user has clicked through
+ // since the last report was successfully uploaded.
+ optional uint64 safe_browsing_warnings_click_through = 8;
+ // The name of the loaded profile, which was entered by the user when creating
+ // the profile. Empty when in incognito mode.
+ optional string name = 9;
+ // A list of extensions requested for installation.
+ repeated ExtensionRequest extension_requests = 10;
+}
+
+// Sign in information of Profile.
+message ChromeSignedInUser {
+ // The email of the signed in user.
+ optional string email = 1;
+ // The obfuscated GaiaID of the signed in user.
+ optional string obfuscated_gaia_id = 2;
+}
+
+// Extension request information.
+message ExtensionRequest {
+ // ID of the installed app/extension for a Chrome app or extension.
+ optional string id = 1;
+
+ // When the user commits to requesting the extension.
+ // [request_timestamp] is milliseconds since Epoch in UTC timezone
+ // (Java time).
+ optional int64 request_timestamp = 2;
+
+ // User justification describing why the extension is being requested.
+ optional string justification = 3;
+}
+
+// Extension information.
+message Extension {
+ reserved 7, 12;
+
+ // ID of the installed app/extension for a Chrome app or extension.
+ optional string id = 1;
+ // Currently installed version of the extension.
+ optional string version = 2;
+ // The name of the extension.
+ optional string name = 3;
+ // The description of the extension that is provided by extension author.
+ optional string description = 4;
+
+ // The type of extension.
+ enum ExtensionType {
+ TYPE_UNKNOWN = 0;
+ TYPE_EXTENSION = 1;
+ TYPE_HOSTED_APP = 2;
+ TYPE_PACKAGED_APP = 3;
+ TYPE_LEGACY_PACKAGED_APP = 4;
+ TYPE_THEME = 5;
+ TYPE_USER_SCRIPT = 6;
+ TYPE_PLATFORM_APP = 7;
+ TYPE_LOGIN_SCREEN_EXTENSION = 8;
+ TYPE_CHROMEOS_SYSTEM_EXTENSION = 9;
+ }
+ optional ExtensionType app_type = 5;
+
+ // URL of the homepage.
+ optional string homepage_url = 6;
+
+ // The installation source of the extension.
+ enum InstallType {
+ // An extension that is installed by user or installed by default but not
+ // component extension.
+ TYPE_NORMAL = 0;
+ // An extension that is loaded as unpacked extension from chrome extension
+ // page or --load-extension command line switch.
+ TYPE_DEVELOPMENT = 1;
+ // An extension that is loaded from the settings in Window Registry or
+ // a preferences JSON file on Mac and Linux.
+ TYPE_SIDELOAD = 2;
+ // An extension that is loaded from policy settings.
+ TYPE_ADMIN = 3;
+ // Chrome component extension and unknown sources.
+ TYPE_OTHER = 4;
+ }
+ optional InstallType install_type = 8;
+
+ // True if the extension is currently enabled.
+ optional bool enabled = 9;
+
+ // The list of api based permissions the extension requires.
+ repeated string permissions = 10;
+
+ // The list of host based permissions the extension requires.
+ repeated string host_permissions = 11;
+
+ // True if the extension comes from web store.
+ optional bool from_webstore = 13;
+
+ // Manifest version of the extension.
+ optional int32 manifest_version = 14;
+}
+
+// Plugin information.
+message Plugin {
+ // The human friendly name of plugin.
+ optional string name = 1;
+
+ // Currently installed version of the plugin.
+ optional string version = 2;
+
+ // The file name from the path of the plugin.
+ optional string filename = 3;
+
+ // More details of the plugin.
+ optional string description = 4;
+}
+
+// Policy information.
+message Policy {
+ // The name of the policy.
+ optional string name = 1;
+
+ // The level of a policy determines its enforceability and whether users can
+ // override it or not.
+ enum PolicyLevel {
+ LEVEL_UNKNOWN = 0;
+ // Recommended policies are a default value configured by admins and users
+ // can choose to override it.
+ LEVEL_RECOMMENDED = 1;
+
+ // Mandatory policies must be enforced and users can't circumvent them.
+ LEVEL_MANDATORY = 2;
+ }
+ optional PolicyLevel level = 2;
+
+ // The scope of a policy flags whether it's applied to the current user or to
+ // the machine.
+ enum PolicyScope {
+ SCOPE_UNKNOWN = 0;
+ // User policies apply to current Session/Profile if it's cloud policy.
+ // Or apply to current OS user on Windows.
+ SCOPE_USER = 1;
+
+ // Machine policies apply to any users of the current machine.
+ SCOPE_MACHINE = 2;
+ }
+ optional PolicyScope scope = 3;
+
+ // The source of a policy indicates where its value is originating from.
+ enum PolicySource {
+ SOURCE_UNKNOWN = 0;
+ // A policy is set by Chrome when it's running in an
+ // enterprise environment.
+ SOURCE_ENTERPRISE_DEFAULT = 1;
+
+ // A policy is set by Google's cloud management tool.
+ SOURCE_CLOUD = 2;
+
+ // A policy is set by active directory on ChromeOS.
+ SOURCE_ACTIVE_DIRECTORY = 3;
+
+ // A policy is overridden by ChromeOS if it's running in a public session or
+ // kiosk mode.
+ SOURCE_DEVICE_LOCAL_ACCOUNT_OVERRIDE_DEPRECATED = 4;
+
+ // A policy is set by OS built-in tool on desktop.
+ SOURCE_PLATFORM = 5;
+
+ // A policy is set by Google's cloud management tool but has higher
+ // priority.
+ SOURCE_PRIORITY_CLOUD_DEPRECATED = 6;
+
+ // A policy is set by multiple sources and value has been merged.
+ SOURCE_MERGED = 7;
+
+ // A policy is set by command line switch for testing purpose.
+ SOURCE_COMMAND_LINE = 8;
+
+ // A policy is set by Google's cloud management tool in Ash and piped to
+ // Lacros.
+ SOURCE_CLOUD_FROM_ASH = 9;
+
+ // A policy that is set by the restricted managed guest session override.
+ SOURCE_RESTRICTED_MANAGED_GUEST_SESSION_OVERRIDE = 10;
+ }
+ optional PolicySource source = 4;
+
+ // The value of policy.
+ optional string value = 5;
+
+ // The error message of policy.
+ optional string error = 6;
+}
+
+// Extension policy information.
+message ExtensionPolicy {
+ // The id of extension that policies apply to.
+ optional string extension_id = 1;
+
+ // The list of policies that extension currently uses.
+ repeated Policy policies = 2;
+}
+
+// Cloud policy last fetch time.
+message PolicyFetchTimestamp {
+ // The type of cloud policy.
+ optional string type = 1;
+ // The last time the policies where fetched for the policy type.
+ // [timestamp] is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 timestamp = 2;
+}
+
+// Chrome user profile level status, used by activated Profiles. Profile name is
+// not listed here as they are in the ChromeUserProfileBasicInfo.
+message ChromeUserProfileInfo {
+ reserved 6;
+
+ // A string to uniquely identify this profile within the browser.
+ optional string id = 1;
+
+ // The name of the profile, which was entered by the user when creating
+ // the profile. Empty when in incognito mode
+ optional string name = 2;
+
+ // Indicates if the profile contains all details. Only active profiles can
+ // upload all needed details, idle profiles only upload |id| and |name|.
+ optional bool is_detail_available = 3;
+
+ // Gaia account information if the Profile is signed in.
+ optional ChromeSignedInUser chrome_signed_in_user = 4;
+
+ // A list of extensions installed in the browser.
+ repeated Extension extensions = 5;
+
+ // A list of extensions requested for installation.
+ repeated ExtensionRequest extension_requests = 10;
+
+ // A list of Chrome browser policies set for this user profile.
+ repeated Policy chrome_policies = 7;
+
+ // A list of extensions' policies set for this user profile. The policies is
+ // only added if the extension is installed.
+ repeated ExtensionPolicy extension_policies = 8;
+
+ // The last time the cloud policies where fetched for each policy type.
+ // Only one policy type which is google/chrome/machine-level-user uploads
+ // timestamp currently. More details in b/132973694
+ repeated PolicyFetchTimestamp policy_fetched_timestamps = 9;
+}
+
+// Report browser level status.
+message BrowserReport {
+ // The Chrome browser version, as seen from within Chrome code as opposed to
+ // user agent.
+ optional string browser_version = 1;
+
+ // Release channel (stable, beta, etc.).
+ optional Channel channel = 2;
+
+ // Required. The path to the browser executable so that we can uniquely
+ // identify it.
+ optional string executable_path = 3;
+
+ // Profile specific reports, one per profile.
+ // Deprecated by ChromeUserProfileInfo and only used by old Chrome browser.
+ repeated ChromeUserProfileReport chrome_user_profile_reports = 4;
+
+ // A list of all Profiles that are created in the current browser instance.
+ // Only activated Profiles are able to upload full details while the idle ones
+ // contain id and name only. Please note that some activated Profiles may not
+ // upload full details due to the limitation of the report size.
+ // These details will be uploaded in the following reports.
+ repeated ChromeUserProfileInfo chrome_user_profile_infos = 6;
+
+ // A list of plugins installed in the browser.
+ repeated Plugin plugins = 7;
+
+ // The installed version of the browser if it differs from |browser_version|,
+ // or absent otherwise. When present, it indicates that an update (of a higher
+ // or lower version) has been installed and will be the active version
+ // following a browser restart.
+ optional string installed_browser_version = 8;
+
+ // True for an extended stable channel installation.
+ optional bool is_extended_stable_channel = 9 [default = false];
+}
+
+// Report Operating system related information.
+message OSReport {
+ // A string contains OS name.
+ optional string name = 1;
+
+ // A string contains OS architecture.
+ optional string arch = 2;
+
+ // A string contains OS version.
+ optional string version = 3;
+}
+
+// An enum shows which information a partial CBCM report contains.
+enum PartialReportType {
+ UNSPECIFIED = 0;
+ EXTENSION_REQUEST = 1;
+}
+
+// Report the status of a Chrome installation on non-Chrome OS platform.
+message ChromeDesktopReportRequest {
+ // The name of the machine within its local network. The string is a JSON
+ // encoded structure with a single computername field.
+ // This field is replaced by computer_name and only used by old Chrome
+ // browser using the JSON legacy browser.
+ // TODO(b/189584065): Remove when usage goes to zero.
+ optional string machine_name = 1 [deprecated = true];
+ // OS info. The string is a an encoded JSON object as returned by
+ // chrome.runtime.getPlatformInfo.
+ // This field is replaced by OSReport and only used by old Chrome browser.
+ optional string os_info = 2;
+ // The user name from the OS point of view. The string is a JSON encoded
+ // structure with a single username field containing "DOMAIN\username".
+ // This field is replaced by os_user_name and only used by old Chrome browser.
+ optional string os_user = 3;
+ // Browser related info.
+ optional BrowserReport browser_report = 4;
+ // The device serial number (this might differ with the client ID, depending
+ // on the platform). Deprecated: Please use
+ // ChromeDesktopReportProtoProcessor.getBrowserDeviceIdentifier().getSerialNumber
+ // to extract the Serial Number from the Report Request.
+ optional string serial_number = 5 [deprecated = true];
+ // A string represents the name of computer. Deprecated: Please use
+ // ChromeDesktopReportProtoProcessor.getBrowserDeviceIdentifier().getSerialNumber
+ // to extract the Computer Name from the Report Request.
+ optional string computer_name = 6 [deprecated = true];
+ // Operating system related information.
+ optional OSReport os_report = 7;
+ // A string contains OS user name.
+ optional string os_user_name = 8;
+ // Device identifier for helping identify non-Chrome OS devices.
+ // TODO(crbug.com/1105938): This will also replace the computer_name and
+ // serial_number fields.
+ optional BrowserDeviceIdentifier browser_device_identifier = 9;
+ // A list of flags indicating that the report only contains particular
+ // information. When the list is empty or unset, the report should contain all
+ // information.
+ repeated PartialReportType partial_report_types = 10;
+ // Public key that can be used for attesting the machine.
+ optional string machine_attestation_key = 11;
+ // A string that represents the device model.
+ optional string device_model = 12;
+ // A string that represents the brand/manufacturer of a device.
+ optional string brand_name = 13;
+}
+
+// Report user level status on Chrome OS platform. Chrome OS equivalent of
+// ChromeDesktopReportRequest.
+message ChromeOsUserReportRequest {
+ // Browser related info.
+ optional BrowserReport browser_report = 1;
+ // Android applications installed in primary profile.
+ repeated AndroidAppInfo android_app_infos = 2;
+ // A list of flags indicates that the report only contains particular
+ // information. When list is empty or unset, the report should contains all
+ // information.
+ repeated PartialReportType partial_report_types = 3;
+}
+
+// Report user level status on all platforms. It includes the information of the
+// profile that user has signed in plus some basic browser and OS information.
+message ChromeProfileReportRequest {
+ // Browser related info
+ optional BrowserReport browser_report = 1;
+
+ // Basic OS information
+ optional OSReport os_report = 2;
+}
+
+// A validation issue from validating a policy value that was contained in
+// the payload of the policy fetch response.
+message PolicyValueValidationIssue {
+ // Policy name of the faulty value.
+ optional string policy_name = 1;
+
+ //# LINT.IfChange
+ enum ValueValidationIssueSeverity {
+ // Default value for when a severity is not specified.
+ VALUE_VALIDATION_ISSUE_SEVERITY_UNSPECIFIED = 0;
+
+ // This result is a warning. The policy blob has not been rejected.
+ VALUE_VALIDATION_ISSUE_SEVERITY_WARNING = 1;
+
+ // This result is an error. The policy blob was rejected completely and not
+ // updated on the device.
+ VALUE_VALIDATION_ISSUE_SEVERITY_ERROR = 2;
+ } // LINT.ThenChange(
+ // //depot/google3/chrome/cros/reporting/api/proto/policy_validation_report.proto)
+
+ // Severity of this policy value validation result.
+ optional ValueValidationIssueSeverity severity = 2;
+
+ // Message containing detailed information about the value validation warning
+ // or error (e.g. type and specific location). This message is intended as
+ // debug information for developers (not localized).
+ optional string debug_message = 3;
+}
+
+// This message is used to upload the result of cloud policy validation after a
+// PolicyFetchRequest.
+message PolicyValidationReportRequest {
+ // |policy_type| sent in PolicyFetchRequest on the request which
+ // returned policy with validation errors.
+ optional string policy_type = 1;
+
+ // |policy_token| from the PolicyFetchResponse. This is used to identify the
+ // specific policy fetch event that triggered this validation report.
+ optional string policy_token = 2;
+
+ // Specifies the result type of the validation.
+ // Each enum value can correspond to one of three client behaviors (noted as
+ // 'Client behavior' in the comment for each enum value):
+ // - Unknown:
+ // It is not known if the fetched policy blob was accepted or rejected.
+ // - Policy blob accepted:
+ // The client has accepted and applied the fetched policy blob.
+ // - Policy blob rejected:
+ // The client has completely rejected the fetched policy blob.
+ // LINT.IfChange
+ enum ValidationResultType {
+ // An enum value was received which is not known in this version of the
+ // proto.
+ // Client behavior: Unknown.
+ VALIDATION_RESULT_TYPE_ERROR_UNSPECIFIED = 0;
+ // Policy validated successfully.
+ // Client behavior: Policy blob accepted.
+ // Note: This result is here for completeness, the client will not send
+ // reports with this enum value.
+ VALIDATION_RESULT_TYPE_SUCCESS = 1;
+ // Bad signature on the initial key.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_INITIAL_SIGNATURE = 2;
+ // Bad signature.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_SIGNATURE = 3;
+ // Policy blob contains error code.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_ERROR_CODE_PRESENT = 4;
+ // Policy payload failed to decode.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_PAYLOAD_PARSE_ERROR = 5;
+ // Unexpected policy type.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_WRONG_POLICY_TYPE = 6;
+ // Unexpected settings entity id.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_WRONG_SETTINGS_ENTITY_ID = 7;
+ // Timestamp is missing or is older than the timestamp of the previous
+ // policy.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_TIMESTAMP = 8;
+ // DM token is empty or doesn't match.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_DM_TOKEN = 9;
+ // Device id is empty or doesn't match.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_DEVICE_ID = 10;
+ // Username doesn't match.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_USER = 11;
+ // Policy payload protobuf parse error.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_POLICY_PARSE_ERROR = 12;
+ // Policy key signature could not be verified using the hard-coded
+ // verification key.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_BAD_KEY_VERIFICATION_SIGNATURE = 13;
+ // There were validation warnings during validation of policy values in the
+ // payload. See |policy_value_validation_results|.
+ // Client behavior: Policy blob accepted.
+ VALIDATION_RESULT_TYPE_VALUE_WARNING = 14;
+ // There were validation errors during validation of policy values in the
+ // payload. There may also have been warnings. See
+ // |policy_value_validation_results| - that list will contain at least one
+ // payload validation errors, and zero or more payload validation warnings.
+ // Client behavior: Policy blob rejected.
+ VALIDATION_RESULT_TYPE_VALUE_ERROR = 15;
+ } // LINT.ThenChange(
+ // //depot/google3/chrome/cros/reporting/api/proto/policy_validation_report.proto)
+
+ // The validation result.
+ optional ValidationResultType validation_result_type = 3;
+
+ // Value validation issues in the policy payload. Will be filled if
+ // |validation_result_type| is VALIDATION_RESULT_TYPE_VALUE_WARNING
+ // or VALIDATION_RESULT_TYPE_VALUE_ERROR.
+ repeated PolicyValueValidationIssue policy_value_validation_issues = 4;
+}
+
+// Response from DMServer to a policy validation report.
+message PolicyValidationReportResponse {}
+
+message AndroidStatus {
+ // JSON string of ARC status report.
+ optional string status_payload = 1;
+ // DroidGuard response obtained from DroidGuard server.
+ optional string droid_guard_info = 2;
+}
+
+enum CrostiniAppType {
+ // The default terminal App.
+ CROSTINI_APP_TYPE_TERMINAL = 0;
+ // A registered interactive Crostini App which is
+ // not the default terminal app.
+ CROSTINI_APP_TYPE_INTERACTIVE = 1;
+ // Detected non-registered container applications.
+ CROSTINI_APP_TYPE_OTHER = 2;
+}
+
+message CrostiniApp {
+ // The default display name of the App.
+ optional string app_name = 1;
+ // The type of the App.
+ optional CrostiniAppType app_type = 2;
+
+ // Time stamp of last launch of the App with a three day granularity.
+ // The timestamp is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 last_launch_time_window_start_timestamp = 3;
+
+ // If available, the name of the Debian package belonging to this App.
+ optional string package_name = 4;
+ // If available, the version of the Debian package belonging to this App.
+ optional string package_version = 5;
+ // If available, a hash of the package belonging to this App.
+ optional string package_hash = 6;
+}
+
+message CrostiniStatus {
+ // Time stamp of last launch of a Crostini app with three day granularity,
+ // The timestamp is milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 last_launch_time_window_start_timestamp = 1;
+
+ // The VM image version at the time of the last launch.
+ optional string last_launch_vm_image_version = 2;
+ // The VM kernel version at the time of the last launch.
+ optional string last_launch_vm_kernel_version = 3;
+
+ // Contains information about each installed app at the time of the
+ // report.
+ repeated CrostiniApp installed_apps = 4;
+}
+
+// Report current active session (a user on one device) level status.
+message SessionStatusReportRequest {
+ reserved 1, 2, 3, 6;
+
+ // If this is a kiosk session, this is the device local account ID.
+ optional string device_local_account_id = 4;
+
+ // Information about kiosk app for kiosk session.
+ repeated AppStatus installed_apps = 5;
+
+ // Information about ARC status.
+ optional AndroidStatus android_status = 7;
+
+ // If this is a regular user session, this is the user's DMToken.
+ optional string user_dm_token = 8;
+
+ // Time zone id of the active user. Not set for enterprise users.
+ // Format of the id is as specified in tz database e.g. Pacific/Honolulu. For
+ // more details check third_party/icu/source/i18n/unicode/timezone.h.
+ optional string time_zone = 9;
+
+ // Information about Crostini status.
+ optional CrostiniStatus crostini_status = 10;
+
+ // Information about all applications for this user on this device, including
+ // uninstalled and disabled apps.
+ repeated AppInfo app_infos = 11;
+}
+
+// Response from DMServer to update devices' status.
+// It is possible that status report fails but policy request succeed. In such
+// case, the DeviceStatusReportResponse will contain an error code and the
+// device should re-send status report data in the next policy request. The
+// device should re-send report data if policy request fails, even if
+// DeviceStatusReportResponse contains no error code.
+message DeviceStatusReportResponse {
+ optional int32 error_code = 1;
+
+ // Human readable error message for customer support purpose.
+ optional string error_message = 2;
+}
+
+// Response from DMServer to a Chrome desktop report request. The report
+// upload errors will be set in the containing DeviceManagementResponse or
+// eventually at the HTTP level.
+message ChromeDesktopReportResponse {}
+
+// Response from DMServer to a ChromeOS user report request. The report
+// upload errors will be set in the containing DeviceManagementResponse or
+// eventually at the HTTP level.
+message ChromeOsUserReportResponse {}
+
+// Response from DMServer to a profile report request. The report
+// upload errors will be set in the containing DeviceManagementResponse or
+// eventually at the HTTP level.
+message ChromeProfileReportResponse {}
+
+// Response from DMServer to update user devices' status.
+// It is possible that status report fails but policy request succeed. In such
+// case, the SessionStatusReportResponse will contain an error code and the
+// device should re-send status report data in the next policy request. The
+// device should re-send report data if policy request fails, even if
+// SessionStatusReportResponse contains no error code.
+message SessionStatusReportResponse {
+ optional int32 error_code = 1;
+
+ // Human readable error message for customer support purpose.
+ optional string error_message = 2;
+}
+
+// Request from client to query device state using Private Set Membership (PSM).
+// Please see go/cros-enterprise-psm and go/cros-client-psm for more details.
+message PrivateSetMembershipRequest {
+ // A request proto from the RLWE PSM protocol.
+ optional PrivateSetMembershipRlweRequest rlwe_request = 1;
+}
+
+message PrivateSetMembershipResponse {
+ // A response proto from the RLWE PSM protocol.
+ optional PrivateSetMembershipRlweResponse rlwe_response = 1;
+}
+
+message PrivateSetMembershipRlweRequest {
+ // First request sent by the client for checking membership.
+ optional private_membership.rlwe.PrivateMembershipRlweOprfRequest
+ oprf_request = 1;
+
+ // Second request sent by the client for checking membership.
+ optional private_membership.rlwe.PrivateMembershipRlweQueryRequest
+ query_request = 2;
+}
+
+message PrivateSetMembershipRlweResponse {
+ // First response sent by the server for checking membership.
+ optional private_membership.rlwe.PrivateMembershipRlweOprfResponse
+ oprf_response = 1;
+
+ // Second response sent by the server for checking membership.
+ optional private_membership.rlwe.PrivateMembershipRlweQueryResponse
+ query_response = 2;
+}
+
+// Request from device to server to determine whether the device should
+// go through enterprise enrollment. Unlike the other requests, this request is
+// not authenticated.
+message DeviceAutoEnrollmentRequest {
+ // Device identifier hash, mod |modulus|.
+ // The type of the device identifier hash depends on |enrollment_check_type|.
+ // If |modulus| is 1, |remainder| should be 0.
+ // |remainder| should always be present.
+ optional int64 remainder = 1;
+
+ // Modulus of the hash used by the client. For now, it is a power of 2, but
+ // due to the strict constraint on how many serial numbers a bucket can
+ // contain, it may become non power of 2. If that happens, client-side needs
+ // to change its assumption.
+ // |modulus| should always be present, but setting |modulus| to 1 means that
+ // no bits of the client's hash are uploaded. |remainder| should be 0 in this
+ // case.
+ optional int64 modulus = 2;
+
+ enum EnrollmentCheckType {
+ // Unspecified.
+ ENROLLMENT_CHECK_TYPE_UNSPECIFIED = 0;
+ // Forced Re-Enrollment check with full SHA-256 hashes of the
+ // server-backed state key.
+ ENROLLMENT_CHECK_TYPE_FRE = 1;
+ // Forced Enrollment check with SHA-256 hashes of (brand code + “_†+ serial
+ // number), truncated to first 8 bytes each.
+ ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT = 2;
+ }
+
+ // Specifies the type of auto enrollment check that is being made.
+ // This also defines the format of the device identifier hash used in this
+ // exchange.
+ optional EnrollmentCheckType enrollment_check_type = 3
+ [default = ENROLLMENT_CHECK_TYPE_FRE];
+}
+
+// Response from server to auto-enrollment detection request.
+message DeviceAutoEnrollmentResponse {
+ // If this field is present, the other fields are ignored and the client
+ // should send a new DeviceAutoEnrollmentRequest with a |remainder|
+ // computed using this new |expected_modulus|. If this field is empty, the
+ // client's request was accepted.
+ // DMServer guarantees that if the modulus sent by client in
+ // DeviceAutoEnrollmentRequest matches server's expectation, this field
+ // is unset.
+ optional int64 expected_modulus = 1;
+
+ // List of hashes. If the client's hash matches any in this list, the
+ // client device should do enterprise enrollment. If it matches none,
+ // enrollment should be optional.
+ // The format of each entry depends on the |enrollment_check_type| that was
+ // set in the DeviceAutoEnrollmentRequest.
+ repeated bytes hashes = 2;
+}
+
+// Sent by the client to the server. The device management server keeps a
+// mapping of device identifiers to device state. Devices query this table after
+// hard reset in order recover state. This request is keyed just by the opaque
+// server-backed state key; there is no further authentication.
+message DeviceStateRetrievalRequest {
+ // Opaque, client-determined, unpredictable, stable and unique device
+ // identifier to retrieve state for. This field contains 32 bytes of data that
+ // looks essentially random to the server. It may be generated e.g. by running
+ // a concatenation of suitable device identifiers through a cryptographic hash
+ // algorithm such as SHA-256.
+ optional bytes server_backed_state_key = 1;
+}
+
+// Sent by the client to the server when in registered state to update the
+// device-determined device state keys.
+message DeviceStateKeyUpdateRequest {
+ // The client-determined state keys. To the server, these look like 32 bytes
+ // of random data. The client should generate these keys using a deterministic
+ // algorithm that takes stable device identifiers as an input and produces a
+ // key as the output, possibly by running the identifiers through a
+ // cryptographic hash function such as SHA-256.
+ repeated bytes server_backed_state_keys = 1;
+}
+
+// Server to client message carrying the device state response. Because the
+// request is not authenticated, the only protection against state extraction
+// from server is the unpredictability of the server-backed state ID. Thus, the
+// response should not contain any sensitive data. If the server doesn't know
+// the requested identifier, it just returns a message with restore_mode set to
+// RESTORE_MODE_NONE.
+message DeviceStateRetrievalResponse {
+ // Restorative action to take after device reset.
+ enum RestoreMode {
+ // No secondary state restoration.
+ RESTORE_MODE_NONE = 0;
+ // Enterprise enrollment requested, but user may skip.
+ RESTORE_MODE_REENROLLMENT_REQUESTED = 1;
+ // Enterprise enrollment is enforced and cannot be skipped.
+ RESTORE_MODE_REENROLLMENT_ENFORCED = 2;
+ // The device has been disabled by its owner. The device will show a warning
+ // screen and prevent the user from proceeding further.
+ RESTORE_MODE_DISABLED = 3;
+ // Enterprise enrollment is enforced using Zero-Touch and cannot be skipped.
+ RESTORE_MODE_REENROLLMENT_ZERO_TOUCH = 4;
+ }
+ // The server-indicated restore mode.
+ optional RestoreMode restore_mode = 1 [default = RESTORE_MODE_NONE];
+
+ // Primary domain the device is associated with.
+ optional string management_domain = 2;
+
+ // State that is relevant only when the |restore_mode| is
+ // |RESTORE_MODE_DISABLED|.
+ optional DisabledState disabled_state = 3;
+
+ // Initial device state if |restore_mode| is |RESTORE_MODE_NONE|.
+ optional DeviceInitialEnrollmentStateResponse initial_state_response = 4;
+}
+
+// Request from device to server to retrieve the enrollment mode and domain for
+// this device. The client will use this request when the
+// DeviceAutoEnrollmentRequest exchange with |enrollment_check_type| set to
+// |ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT| indicated that it should be
+// enrolled. This request is not authenticated.
+message DeviceInitialEnrollmentStateRequest {
+ // The serial number of the device.
+ optional string serial_number = 1;
+
+ // The 4-character brand code of the device.
+ optional string brand_code = 2;
+}
+
+// Response from server DeviceInitialEnrollmentStateRequest.
+message DeviceInitialEnrollmentStateResponse {
+ // Initial action to take after OOBE.
+ enum InitialEnrollmentMode {
+ // No initial enrollment.
+ INITIAL_ENROLLMENT_MODE_NONE = 0;
+ // Enterprise enrollment is enforced and cannot be skipped.
+ INITIAL_ENROLLMENT_MODE_ENROLLMENT_ENFORCED = 1;
+ // Zero-Touch (attestation-based) enrollment is enforced and cannot be
+ // skipped.
+ INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED = 2;
+ // The device has been disabled by its owner. The device will show a warning
+ // screen and prevent the user from proceeding further.
+ INITIAL_ENROLLMENT_MODE_DISABLED = 3;
+ }
+
+ // The server-indicated initial enrollment mode.
+ optional InitialEnrollmentMode initial_enrollment_mode = 1
+ [default = INITIAL_ENROLLMENT_MODE_NONE];
+
+ // The domain the device should be enrolled into.
+ optional string management_domain = 2;
+
+ // Whether the device comes packaged with a license or not.
+ optional bool is_license_packaged_with_device = 3;
+
+ // State that is relevant only when the |initial_enrollment_mode| is
+ // |INITIAL_ENROLLMENT_MODE_DISABLED|.
+ optional DisabledState disabled_state = 4;
+
+ // License Packaging SKU type.
+ // LINT.IfChange
+ enum LicensePackagingSKU {
+ // Not a License Packaged Device.
+ NOT_EXIST = 0;
+ // Enterprise SKU.
+ CHROME_ENTERPRISE = 1;
+ // Education SKU.
+ CHROME_EDUCATION = 2;
+ // Terminal SKU
+ CHROME_TERMINAL = 3;
+ }
+ // LINT.ThenChange(//depot/google3/google/chrome/licensepackaging/v1/service.proto)
+
+ // SKU Type for License Packaged Device.
+ optional LicensePackagingSKU license_packaging_sku = 5;
+}
+
+// Sent by the client to the server to pair the Host device with the Controller
+// device. The HTTP request contains an end-user OAuth token and only succeeds
+// if both Host and Controller devices belong to the end-user domain.
+message DevicePairingRequest {
+ // The device ID of the Host device.
+ optional string host_device_id = 1;
+
+ // The device ID of the Controller device.
+ optional string controller_device_id = 2;
+}
+
+// Response from the server to the device pairing request.
+message DevicePairingResponse {
+ // The client should check HTTP status code first. If HTTP status code is not
+ // 200 (e.g. 500 internal error), then it means the pairing fails. If HTTP
+ // status code is 200, then the client should check the status code within the
+ // response.
+ enum StatusCode {
+ SUCCESS = 0;
+
+ // A generic failure code for pairing.
+ FAILED = 1;
+
+ // The Host device cannot be found in the user's domain.
+ HOST_DEVICE_NOT_FOUND = 2;
+
+ // The Controller device cannot be found in the user's domain.
+ CONTROLLER_DEVICE_NOT_FOUND = 3;
+
+ // The Host device is deprovisioned.
+ HOST_DEVICE_DEPROVISIONED = 4;
+
+ // The Controller device is deprovisioned.
+ CONTROLLER_DEVICE_DEPROVISIONED = 5;
+ }
+
+ optional StatusCode status_code = 1 [default = FAILED];
+}
+
+// Sent by the client to the server to check if the devices are paired. The HTTP
+// request contains controller service account OAuth token as well as the
+// DMToken from the Host device.
+message CheckDevicePairingRequest {
+ // The device ID of the Host device.
+ optional string host_device_id = 1;
+
+ // The device ID of the Controller device.
+ optional string controller_device_id = 2;
+}
+
+// Response from the server to the check device pairing request.
+message CheckDevicePairingResponse {
+ // The client should check HTTP status code first. If HTTP status code is not
+ // 200 (e.g. 500 internal error), then it means the pairing status is unknown.
+ // If HTTP status code is 200, then the client should check the status code
+ // within the response.
+ enum StatusCode {
+ PAIRED = 0;
+
+ // The Host and Controller devices are not paired.
+ NOT_PAIRED = 1;
+
+ // The Host device cannot be found in the Host device domain.
+ HOST_DEVICE_NOT_FOUND = 2;
+
+ // The Controller device cannot be found in the Host device domain.
+ CONTROLLER_DEVICE_NOT_FOUND = 3;
+
+ // The Host device is deprovisioned.
+ HOST_DEVICE_DEPROVISIONED = 4;
+
+ // The Controller device is deprovisioned.
+ CONTROLLER_DEVICE_DEPROVISIONED = 5;
+
+ // Invalid controller identity.
+ INVALID_CONTROLLER_DEVICE_IDENTITY = 6;
+ }
+
+ optional StatusCode status_code = 1 [default = NOT_PAIRED];
+}
+
+// This protobuf defines a single remote command from server to client for
+// execution.
+message RemoteCommand {
+ // The names are used as part of metric names. If enumeration is updated
+ // the names should also be updated:
+ // - components/policy/core/common/cloud/enterprise_metrics.cc;
+ // - components/policy/core/common/remote_commands/remote_commands_service.cc;
+ // - Enterprise.RemoteCommandType in
+ // tools/metrics/histograms/metadata/enterprise/histograms.xml;
+ enum Type {
+ // Simple echo command for testing, will be ignored in production code.
+ COMMAND_ECHO_TEST = -1;
+
+ // Reboot the device.
+ DEVICE_REBOOT = 0;
+
+ // Take a screenshot.
+ DEVICE_SCREENSHOT = 1;
+
+ // Set device volume.
+ DEVICE_SET_VOLUME = 2;
+
+ // Force a refresh of device status (attributes and logs).
+ DEVICE_FETCH_STATUS = 3;
+
+ // Forwards a user command received from the management server to the ARC++
+ // side. The payload is opaque to Chrome OS.
+ USER_ARC_COMMAND = 4;
+
+ // Wipe all the users off of the device.
+ DEVICE_WIPE_USERS = 5;
+
+ // Start Chrome Remote Desktop session (limited to Kiosk sessions only).
+ DEVICE_START_CRD_SESSION = 6;
+
+ // Wipe the device (perform a powerwash).
+ DEVICE_REMOTE_POWERWASH = 7;
+
+ // Refresh the device machine certificate and re-upload it.
+ DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE = 8;
+
+ // Retrieve a list of available diagnostics routines.
+ DEVICE_GET_AVAILABLE_DIAGNOSTIC_ROUTINES = 9;
+
+ // Run a given diagnostics routine on the platform.
+ DEVICE_RUN_DIAGNOSTIC_ROUTINE = 10;
+
+ // Send a command or get an update from an existing diagnostics routine.
+ DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE = 11;
+
+ // Clear the cache and cookies associated with a given profile.
+ BROWSER_CLEAR_BROWSING_DATA = 12;
+
+ // Reset the cellular EUICC (Embedded Universal Integrated Circuit Card) on
+ // the device.
+ DEVICE_RESET_EUICC = 13;
+
+ // Rotates the attestation credentials associated with a browser device.
+ BROWSER_ROTATE_ATTESTATION_CREDENTIAL = 14;
+
+ // Please update metrics after adding a new item - see the comment above.
+ }
+
+ // The command type.
+ optional Type type = 1;
+
+ // An opaque unique identifier for the command. The client processes
+ // the commands in the order of the command list it receives.
+ optional int64 command_id = 2;
+
+ // The age of the command (in milliseconds) when it is sent from server to
+ // client, defined as current_server_time - command_generated_time.
+ optional int64 age_of_command = 3;
+
+ // Extra parameters for this command, expected to be a JSON string. The exact
+ // format of the JSON payload depends on the command type specified by the
+ // |type| field:
+ // |DEVICE_SCREENSHOT|: {"fileUploadUrl" : url_string}.
+ // |DEVICE_SET_VOLUME|: {"volume": volume_value}, where volume_value must be
+ // an integer between 0 and 100.
+ // |DEVICE_RUN_DIAGNOSTIC_ROUTINE|: {"routine" : routine_enum, "params" :
+ // params_dict}, where params_dict varies by routine.
+ // |DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE|: {"id" : id_integer, "command" :
+ // command, "includeOutput" : include_output_bool}, where command must be a
+ // valid chromeos::cros_healthd::mojom::DiagnosticRoutineCommandEnum.
+ optional string payload = 4;
+
+ // An identifier for the target this command is for. This is the same as
+ // the device_id in PolicyData. We rely on this identifier not being stable
+ // across powerwashes.
+ optional string target_device_id = 5;
+}
+
+// This protobuf defines the execution result of a single remote command
+// which will be sent back to the server.
+message RemoteCommandResult {
+ // If you change this, update policy.mojom/CommandResultType.
+ enum ResultType {
+ RESULT_IGNORED = 0; // The command was ignored as obsolete.
+ RESULT_FAILURE = 1; // The command could not be executed or parsed.
+ RESULT_SUCCESS = 2; // The command was successfully executed. Commands
+ // such as powerwash will return success before they
+ // are executed since state will be forgotten.
+ }
+
+ // The result of the command.
+ optional ResultType result = 1;
+
+ // The opaque unique identifier of the command. This value is copied from the
+ // RemoteCommand protobuf that contained the command.
+ optional int64 command_id = 2;
+
+ // The timestamp representing time at which the command was executed, if the
+ // result is RESULT_SUCCESS. The timestamp is milliseconds since Epoch in UTC
+ // timezone (Java time).
+ optional int64 timestamp = 3;
+
+ // Extra information sent to server as result of execution, expected to be a
+ // JSON string.
+ optional string payload = 4;
+}
+
+message DeviceRemoteCommandRequest {
+ // The command ID of the last command received from the server until
+ // now. Omitted if no commands have been received yet.
+ optional int64 last_command_unique_id = 1;
+
+ // The execution results of previously fetched commands.
+ // The client should send back a command result whenever possible.
+ repeated RemoteCommandResult command_results = 2;
+
+ // Whether the server should send secure commands or not.
+ optional bool send_secure_commands = 3;
+
+ // What type of signature to use. Only valid if send_secure_commmands is true.
+ // If NONE is passed, SHA1_RSA will be used instead for compatibility.
+ optional PolicyFetchRequest.SignatureType signature_type = 4;
+}
+
+message DeviceRemoteCommandResponse {
+ // The queue of pending, non secure commands. If this is present then there
+ // shall be no secure commands in this response (and vice versa).
+ repeated RemoteCommand commands = 1;
+
+ // The queue of pending, secure commands. If this is present then there shall
+ // be no non secure commands in this response (and vice versa).
+ //
+ // The secure_commands.data field contains a serialized PolicyData with a
+ // “google/chromeos/remotecommand†policy_type. The secure_commands.signature
+ // field is a signature of the data field with the policy key for the domain
+ // the device belongs to.
+ repeated SignedData secure_commands = 2;
+}
+
+// Sent by the client to the server to check if the current user is allowed
+// to update attributes (asset id and location). The HTTP request contains an
+// end-user OAuth token.
+message DeviceAttributeUpdatePermissionRequest {}
+
+// Response from the server specifying whether the current user is allowed to
+// update attributes (asset id and location).
+message DeviceAttributeUpdatePermissionResponse {
+ enum ResultType {
+ ATTRIBUTE_UPDATE_DISALLOWED = 0;
+ ATTRIBUTE_UPDATE_ALLOWED = 1;
+ }
+
+ optional ResultType result = 1;
+}
+
+// Sent by the client to the server to update device attributes (asset id and
+// location). The HTTP request contains an end-user OAuth token.
+message DeviceAttributeUpdateRequest {
+ // The user-generated asset identifier.
+ optional string asset_id = 1;
+
+ // The user input device location.
+ optional string location = 2;
+}
+
+// Response from the server to update device attributes (asset id and location).
+message DeviceAttributeUpdateResponse {
+ enum ResultType {
+ ATTRIBUTE_UPDATE_ERROR = 0;
+ ATTRIBUTE_UPDATE_SUCCESS = 1;
+ }
+
+ optional ResultType result = 1;
+}
+
+// Sent by the client to server to update the mapping from GCM id to device_id
+// on the server side.
+message GcmIdUpdateRequest {
+ optional string gcm_id = 1;
+}
+
+// Response for GcmIdUpdateRequest, an empty message for now.
+message GcmIdUpdateResponse {}
+
+// Request from device to server to check for Android-for-Work service with
+// DPC enforcement. Must be sent only for users who are not managed in Chrome
+// OS.
+// Provide user's OAuth token with your HTTP Request.
+message CheckAndroidManagementRequest {}
+
+// Response from server to device for check for Android-for-Work service with
+// DPC enforcement request.
+// SC_CONFLICT HTTP code is returned if DPC enforcement is required.
+message CheckAndroidManagementResponse {}
+
+// Request to register a new device (authenticated by enterprise enrollment
+// certificate). See http://go/zero-touch-chrome for details.
+// The response message will be the DeviceRegisterResponse.
+message CertificateBasedDeviceRegisterRequest {
+ // Signed request to register with a certificate. The signed_request.data
+ // field contains a CertificateBasedDeviceRegistrationData with a nonce
+ // (as added by the Chrome OS cryptohome client) appended. The
+ // signed_request.signature field is a signature of the data field signed
+ // with the enrollment certificate's private key.
+ optional SignedData signed_request = 1;
+}
+
+// Requested configuration to be passed along a registration request.
+message DeviceRegisterConfiguration {
+ // The device owner's email address.
+ optional string device_owner = 1;
+}
+
+message CertificateBasedDeviceRegistrationData {
+ enum CertificateType {
+ UNKNOWN = 0;
+ ENTERPRISE_ENROLLMENT_CERTIFICATE = 1;
+ }
+
+ optional CertificateType certificate_type = 1;
+ // Device certificate in X.509 format.
+ // We use CertificateFactory.generateCertificate() call and
+ // the certificate provided must be DER-encoded and may be supplied in binary
+ // or printable (Base64) encoding. If the certificate is provided in Base64
+ // encoding, it must be bounded at the beginning by
+ // -----BEGIN CERTIFICATE-----, and must be bounded at the end by
+ // -----END CERTIFICATE-----.
+ optional bytes device_certificate = 2;
+ // regular device registration request
+ optional DeviceRegisterRequest device_register_request = 3;
+ // Additional configuration to register the device.
+ optional DeviceRegisterConfiguration device_register_configuration = 4;
+}
+
+// Request to enroll a Chrome browser. Fields match identically named fields
+// in ChromeBrowserDeviceInfo.
+message RegisterBrowserRequest {
+ // The name of the machine within its local network.
+ optional string machine_name = 1;
+ // Platform, e.g., Windows or Mac.
+ optional string os_platform = 2;
+ // Platform specific version number, e.g., 6.1.7601.0 or 10.12.6
+ optional string os_version = 3;
+ // Device identifier for helping identify non-Chrome OS devices.
+ // TODO(crbug.com/1105938): This will also replace the machine_name field.
+ optional BrowserDeviceIdentifier browser_device_identifier = 4;
+ // The device model, e.g., iPad6,11
+ optional string device_model = 5;
+ // A string that represents the brand/manufacturer of a device.
+ optional string brand_name = 6;
+}
+
+// Gets an enrollment token to a managed Google Play account for using it with
+// Active Directory. Sent when a new user logs in with Active Directory and
+// opens Play Store for the first time.
+message ActiveDirectoryEnrollPlayUserRequest {
+ // A server-provider identifier for the previously established SAML session.
+ // If left empty and SAML authentication is required,
+ // ActiveDirectoryEnrollPlayUserResponse.saml_parameters.auth_redirect_url
+ // will contain initial Redirect required to start the SAML flow.
+ optional string auth_session_id = 1;
+}
+
+// The result when a new user logs in to Play Store with Active Directory.
+// 904 Arc Disabled HTTP error code is returned if the reason of the failure is
+// that ARC is not enabled for the domain.
+// 403 Forbidden HTTP error code is returned if the device can't get Managed
+// Google Play accounts.
+message ActiveDirectoryEnrollPlayUserResponse {
+ // The enrollment token which can be used to fetch a Managed Google Play
+ // account.
+ optional string enrollment_token = 1;
+ // The user id which identifies the user enrolled by this token. This user id
+ // is opaque to the client and is only used in the ActiveDirectoryPlayActivity
+ // requests.
+ optional string user_id = 2;
+ // If SAML authentication is required, SAML flow parameters are specified in
+ // this proto and both enrollment_token and user_id fields are left unset.
+ optional SamlParametersProto saml_parameters = 3;
+}
+
+message SamlParametersProto {
+ // Initial Redirect URL to start the SAML flow.
+ optional string auth_redirect_url = 1;
+ // Auth Session ID which the client is supposed to use in the subsequent
+ // DMServer request (to be sent after SAML flow completes).
+ optional string auth_session_id = 2;
+}
+
+// Gets a URL to the SAML IdP authentication flow for using it with public
+// SAML session. Sent when a user logs in to a SAML public session account.
+message PublicSamlUserRequest {
+ // Identifier for the public saml account. Same as
+ // DeviceLocalAccountInfoProto.account_id.
+ optional string account_id = 1;
+}
+
+// The result when a user logs in to a SAML public session account.
+message PublicSamlUserResponse {
+ // SAML flow parameters are specified in this proto.
+ optional SamlParametersProto saml_parameters = 1;
+}
+
+// Reports that a managed Google Play account is used. This makes the garbage
+// collection of accounts possible by reporting the ones which are still in use.
+message ActiveDirectoryPlayActivityRequest {
+ // The user id received in ActiveDirectoryEnrollPlayUserResponse which
+ // identifies the user.
+ optional string user_id = 1;
+}
+
+// Response to the Play account activity request.
+message ActiveDirectoryPlayActivityResponse {}
+
+// DEPRECATED: Request to retrieve available device licenses. User auth token
+// or auth cookie must be provided with DeviceManagementRequest when
+// CheckDeviceLicenseRequest is being sent.
+// See go/cdm-mixed-license-pool for more info
+message CheckDeviceLicenseRequest {}
+
+// Represents availability of a single license type.
+message LicenseAvailability {
+ // License type.
+ optional LicenseType license_type_deprecated = 1 [deprecated = true];
+
+ // Remaining available licenses (can be 0).
+ optional int32 available_licenses_deprecated = 2 [deprecated = true];
+}
+
+// DEPRECATED: Response to a check device license request.
+message CheckDeviceLicenseResponse {
+ enum LicenseSelectionMode {
+ // Should not happen, included for compatibility.
+ UNDEFINED = 0;
+ // User is allowed to choose license.
+ USER_SELECTION = 1;
+ // Admin controls license selection preferences through management UI.
+ ADMIN_SELECTION = 2;
+ }
+
+ // Policy setting value for license selection mode.
+ optional LicenseSelectionMode license_selection_mode_deprecated = 1
+ [deprecated = true];
+
+ // Provides available license counts for each purchased license type.
+ // This field would list each subscription for the domain even if all licenses
+ // have been used up (in which case available_licenses field is set to zero).
+ //
+ // If license_selection_mode == USER_SELECTION and license_availability
+ // contains more than one entry then device should display a screen asking
+ // user to choose license type and send selected license type value in the
+ // DeviceRegisterRequest.license_type field.
+ repeated LicenseAvailability license_availabilities_deprecated = 2
+ [deprecated = true];
+}
+
+// Sign in an Active Directory user using SAML SSO. The device management server
+// redirects the client to the Active Directory server in order to authenticate
+// and identify the Active Directory user. Active Directory redirects the client
+// back to the device management server with an assertion of the Active
+// Directory user's identity. The device management server then redirects the
+// client to Google's authentication service in order to provision the user on
+// the device.
+message ActiveDirectoryUserSigninRequest {}
+
+message ActiveDirectoryUserSigninResponse {
+ // Initial Redirect URL to start the SAML flow.
+ optional string auth_redirect_url = 1;
+}
+
+// Contains information about the TPM used on the device.
+message TpmVersionInfo {
+ enum GscVersion {
+ GSC_VERSION_UNSPECIFIED = 0;
+ GSC_VERSION_NOT_GSC = 1;
+ GSC_VERSION_CR50 = 2;
+ GSC_VERSION_TI50 = 3;
+ }
+ optional uint32 family = 1;
+ optional uint64 spec_level = 2;
+ optional uint32 manufacturer = 3;
+ optional uint32 tpm_model = 4;
+ optional uint64 firmware_version = 5;
+ optional string vendor_specific = 6;
+ optional GscVersion gsc_version = 7;
+ // This field represents the tpm device id and vendor id
+ // which is a combined value as declared in the TPM spec.
+ optional string did_vid = 8;
+}
+
+// Contains status of the TPM unit.
+message TpmStatusInfo {
+ optional bool enabled = 1;
+ optional bool owned = 2;
+ // This field was previously named "initialized", but that's not a valid name
+ // for a proto field since it generates isInitialized method for the Java
+ // binding which collides with the isInitialized method that exists for all
+ // Java protos.
+ optional bool tpm_initialized = 3;
+ optional bool attestation_prepared = 4;
+ optional bool attestation_enrolled = 5;
+ optional int32 dictionary_attack_counter = 6;
+ optional int32 dictionary_attack_threshold = 7;
+ optional bool dictionary_attack_lockout_in_effect = 8;
+ optional int32 dictionary_attack_lockout_seconds_remaining = 9;
+ // DEPRECATED: Not filled by the client anymore (since b/172748724).
+ optional bool boot_lockbox_finalized = 10 [deprecated = true];
+ optional bool owner_password_is_present = 11;
+ optional TpmSupportedFeatures tpm_supported_features = 12;
+}
+
+// Contains information about specific features of the TPM unit.
+message TpmSupportedFeatures {
+ optional bool is_allowed = 1;
+ optional bool support_pinweaver = 2;
+ optional bool support_runtime_selection = 3;
+ optional bool support_u2f = 4;
+}
+
+// System state included with some log events.
+message SystemState {
+ // VolumeInfo is reused from existing Chrome reporting.
+ repeated VolumeInfo volume_infos = 1;
+}
+
+// A single entry in the install log for an extension.
+message ExtensionInstallReportLogEvent {
+ // Enumerates the possible event types.
+ enum EventType {
+ // Not used.
+ LOG_EVENT_TYPE_UNKNOWN = 0;
+ // Requested by policy to install the extension.
+ POLICY_REQUEST = 1;
+ // Install success.
+ SUCCESS = 2;
+ // Request canceled.
+ CANCELED = 3;
+ // Connectivity state changed.
+ CONNECTIVITY_CHANGE = 4;
+ // Session state changed.
+ SESSION_STATE_CHANGE = 5;
+ // Extension installation failed.
+ INSTALLATION_FAILED = 6;
+ }
+
+ // Enumerates the possible changes in session state.
+ enum SessionStateChangeType {
+ // Not used.
+ SESSION_STATE_CHANGE_TYPE_UNKNOWN = 0;
+ // Session starting.
+ LOGIN = 1;
+ // Session ending.
+ LOGOUT = 2;
+ // Suspending.
+ SUSPEND = 3;
+ // Resuming.
+ RESUME = 4;
+ }
+
+ // Possible failure reasons. See InstallStageTracker::FailureReason for more
+ // details. InstallStageTracker::FailureReason is the main enum and this is
+ // a copy used for reporting purposes.
+ enum FailureReason {
+ FAILURE_REASON_UNKNOWN = 0;
+ INVALID_ID = 1;
+ MALFORMED_EXTENSION_SETTINGS = 2;
+ REPLACED_BY_ARC_APP = 3;
+ MALFORMED_EXTENSION_DICT = 4;
+ NOT_SUPPORTED_EXTENSION_DICT = 5;
+ MALFORMED_EXTENSION_DICT_FILE_PATH = 6;
+ MALFORMED_EXTENSION_DICT_VERSION = 7;
+ MALFORMED_EXTENSION_DICT_UPDATE_URL = 8;
+ LOCALE_NOT_SUPPORTED = 9;
+ NOT_PERFORMING_NEW_INSTALL = 10;
+ TOO_OLD_PROFILE = 11;
+ DO_NOT_INSTALL_FOR_ENTERPRISE = 12;
+ ALREADY_INSTALLED = 13;
+ CRX_FETCH_FAILED = 14;
+ MANIFEST_FETCH_FAILED = 15;
+ MANIFEST_INVALID = 16;
+ NO_UPDATE = 17;
+ CRX_INSTALL_ERROR_DECLINED = 18;
+ CRX_INSTALL_ERROR_SANDBOXED_UNPACKER_FAILURE = 19;
+ CRX_INSTALL_ERROR_OTHER = 20;
+ NO_UPDATE_URL = 21;
+ PENDING_ADD_FAILED = 22;
+ DOWNLOADER_ADD_FAILED = 23;
+ IN_PROGRESS = 24;
+ CRX_FETCH_URL_EMPTY = 25;
+ CRX_FETCH_URL_INVALID = 26;
+ OVERRIDDEN_BY_SETTINGS = 27;
+ REPLACED_BY_SYSTEM_APP = 28;
+ }
+
+ // Stage of extension installing process. See InstallStageTracker::Stage for
+ // more details. InstallStageTracker::Stage is the main enum and this is
+ // a copy used for reporting purposes. The entries are in the order they occur
+ // in the installation process.
+ enum InstallationStage {
+ INSTALLATION_STAGE_UNKNOWN = 0;
+ CREATED = 1;
+ PENDING = 2;
+ DOWNLOADING = 3;
+ INSTALLING = 4;
+ COMPLETE = 5;
+ }
+
+ // Type of current user. See user_manager::UserType for more details.
+ // user_manager::UserType is the main enum and this is a copy used for
+ // reporting purposes.
+ enum UserType {
+ USER_TYPE_UNKNOWN = 0;
+ USER_TYPE_REGULAR = 1;
+ USER_TYPE_GUEST = 2;
+ USER_TYPE_PUBLIC_ACCOUNT = 3;
+ USER_TYPE_SUPERVISED_DEPRECATED = 4 [deprecated = true];
+ USER_TYPE_KIOSK_APP = 5;
+ USER_TYPE_CHILD = 6;
+ USER_TYPE_ARC_KIOSK_APP = 7;
+ USER_TYPE_ACTIVE_DIRECTORY = 8;
+ USER_TYPE_WEB_KIOSK_APP = 9;
+ }
+
+ // Current stage of the extension downloading process. See
+ // ExtensionDownloaderDelegate::Stage for more details.
+ // ExtensionDownloaderDelegate::Stage is the main enum and this is a copy used
+ // for reporting purposes.
+ enum DownloadingStage {
+ DOWNLOADING_STAGE_UNKNOWN = 0;
+ DOWNLOAD_PENDING = 1;
+ QUEUED_FOR_MANIFEST = 2;
+ DOWNLOADING_MANIFEST = 3;
+ DOWNLOADING_MANIFEST_RETRY = 4;
+ PARSING_MANIFEST = 5;
+ MANIFEST_LOADED = 6;
+ QUEUED_FOR_CRX = 7;
+ DOWNLOADING_CRX = 8;
+ DOWNLOADING_CRX_RETRY = 9;
+ FINISHED = 10;
+ }
+
+ // Current stage of the extension creation process. See
+ // InstallStageTracker::InstallCreationStage for more details.
+ // InstallStageTracker::InstallCreationStage is the main enum and this is a
+ // copy used for reporting purposes.
+ enum InstallCreationStage {
+ INSTALL_CREATION_STAGE_UNKNOWN = 0;
+ CREATION_INITIATED = 1;
+ NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_FORCED = 2;
+ NOTIFIED_FROM_MANAGEMENT_INITIAL_CREATION_NOT_FORCED = 3;
+ NOTIFIED_FROM_MANAGEMENT = 4;
+ NOTIFIED_FROM_MANAGEMENT_NOT_FORCED = 5;
+ SEEN_BY_POLICY_LOADER = 6;
+ SEEN_BY_EXTERNAL_PROVIDER = 7;
+ }
+
+ // Status of cache when an attempt is made to fetch the extension from it
+ // during the downloading process. See
+ // ExtensionDownloaderDelegate::CacheStatus for more details.
+ // ExtensionDownloaderDelegate::CacheStatus is the main enum and this is a
+ // copy used for reporting purposes.
+ enum DownloadCacheStatus {
+ CACHE_UNKNOWN = 0;
+ CACHE_DISABLED = 1;
+ CACHE_MISS = 2;
+ CACHE_OUTDATED = 3;
+ CACHE_HIT = 4;
+ CACHE_HIT_ON_MANIFEST_FETCH_FAILURE = 5;
+ }
+
+ // All the ways SandboxedUnpacker can fail. See
+ // extensions::SandboxedUnpackerFailureReason for more details.
+ // extensions::SandboxedUnpackerFailureReason is the main enum and this is a
+ // copy used for reporting purposes.
+ enum SandboxedUnpackerFailureReason {
+ SANDBOXED_UNPACKER_FAILURE_REASON_UNKNOWN = 0;
+ COULD_NOT_GET_TEMP_DIRECTORY = 1;
+ COULD_NOT_CREATE_TEMP_DIRECTORY = 2;
+ FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY = 3;
+ COULD_NOT_GET_SANDBOX_FRIENDLY_PATH = 4;
+ COULD_NOT_LOCALIZE_EXTENSION = 5;
+ INVALID_MANIFEST = 6;
+ UNPACKER_CLIENT_FAILED = 7;
+ UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL = 8;
+ CRX_FILE_NOT_READABLE = 9;
+ CRX_HEADER_INVALID = 10;
+ CRX_MAGIC_NUMBER_INVALID = 11;
+ CRX_VERSION_NUMBER_INVALID = 12;
+ CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE = 13;
+ CRX_ZERO_KEY_LENGTH = 14;
+ CRX_ZERO_SIGNATURE_LENGTH = 15;
+ CRX_PUBLIC_KEY_INVALID = 16;
+ CRX_SIGNATURE_INVALID = 17;
+ CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED = 18;
+ CRX_SIGNATURE_VERIFICATION_FAILED = 19;
+ ERROR_SERIALIZING_MANIFEST_JSON = 20;
+ ERROR_SAVING_MANIFEST_JSON = 21;
+ COULD_NOT_READ_IMAGE_DATA_FROM_DISK_UNUSED = 22;
+ DECODED_IMAGES_DO_NOT_MATCH_THE_MANIFEST_UNUSED = 23;
+ INVALID_PATH_FOR_BROWSER_IMAGE = 24;
+ ERROR_REMOVING_OLD_IMAGE_FILE = 25;
+ INVALID_PATH_FOR_BITMAP_IMAGE = 26;
+ ERROR_RE_ENCODING_THEME_IMAGE = 27;
+ ERROR_SAVING_THEME_IMAGE = 28;
+ DEPRECATED_ABORTED_DUE_TO_SHUTDOWN = 29;
+ COULD_NOT_READ_CATALOG_DATA_FROM_DISK_UNUSED = 30;
+ INVALID_CATALOG_DATA = 31;
+ INVALID_PATH_FOR_CATALOG_UNUSED = 32;
+ ERROR_SERIALIZING_CATALOG = 33;
+ ERROR_SAVING_CATALOG = 34;
+ CRX_HASH_VERIFICATION_FAILED = 35;
+ UNZIP_FAILED = 36;
+ DIRECTORY_MOVE_FAILED = 37;
+ CRX_FILE_IS_DELTA_UPDATE = 38;
+ CRX_EXPECTED_HASH_INVALID = 39;
+ DEPRECATED_ERROR_PARSING_DNR_RULESET = 40;
+ ERROR_INDEXING_DNR_RULESET = 41;
+ CRX_REQUIRED_PROOF_MISSING = 42;
+ CRX_HEADER_VERIFIED_CONTENTS_UNCOMPRESSING_FAILURE = 43;
+ MALFORMED_VERIFIED_CONTENTS = 44;
+ COULD_NOT_CREATE_METADATA_DIRECTORY = 45;
+ COULD_NOT_WRITE_VERIFIED_CONTENTS_INTO_FILE = 46;
+ }
+
+ // Reason why extension failed due to failure reason MANIFEST_INVALID. See
+ // extensions::ManifestInvalidError for more details.
+ // extensions::ManifestInvalidError is the main enum and this is a
+ // copy used for reporting purposes.
+ enum ManifestInvalidError {
+ MANIFEST_INVALID_ERROR_UNKNOWN = 0;
+ XML_PARSING_FAILED = 1;
+ INVALID_XLMNS_ON_GUPDATE_TAG = 2;
+ MISSING_GUPDATE_TAG = 3;
+ INVALID_PROTOCOL_ON_GUPDATE_TAG = 4;
+ MISSING_APP_ID = 5;
+ MISSING_UPDATE_CHECK_TAGS = 6;
+ MULTIPLE_UPDATE_CHECK_TAGS = 7;
+ INVALID_PRODVERSION_MIN = 8;
+ EMPTY_CODEBASE_URL = 9;
+ INVALID_CODEBASE_URL = 10;
+ MISSING_VERSION_FOR_UPDATE_CHECK = 11;
+ INVALID_VERSION = 12;
+ BAD_UPDATE_SPECIFICATION = 13;
+ BAD_APP_STATUS = 14;
+ }
+
+ // Extended error code if the extension installation failed due to CRX install
+ // error. See extensions::CrxInstallErrorDetail for more details.
+ // extensions::CrxInstallErrorDetail is the main enum and this is a
+ // copy used for reporting purposes.
+ enum CrxInstallErrorDetail {
+ CRX_INSTALL_ERROR_DETAIL_UNKNOWN = 0;
+ CONVERT_USER_SCRIPT_TO_EXTENSION_FAILED = 1;
+ UNEXPECTED_ID = 2;
+ UNEXPECTED_VERSION = 3;
+ MISMATCHED_VERSION = 4;
+ CRX_ERROR_MANIFEST_INVALID = 5;
+ INSTALL_NOT_ENABLED = 6;
+ OFFSTORE_INSTALL_DISALLOWED = 7;
+ INCORRECT_APP_CONTENT_TYPE = 8;
+ NOT_INSTALLED_FROM_GALLERY = 9;
+ INCORRECT_INSTALL_HOST = 10;
+ DEPENDENCY_NOT_SHARED_MODULE = 11;
+ DEPENDENCY_OLD_VERSION = 12;
+ DEPENDENCY_NOT_ALLOWLISTED = 13;
+ UNSUPPORTED_REQUIREMENTS = 14;
+ EXTENSION_IS_BLOCKLISTED = 15;
+ DISALLOWED_BY_POLICY = 16;
+ KIOSK_MODE_ONLY = 17;
+ OVERLAPPING_WEB_EXTENT = 18;
+ CANT_DOWNGRADE_VERSION = 19;
+ MOVE_DIRECTORY_TO_PROFILE_FAILED = 20;
+ CANT_LOAD_EXTENSION = 21;
+ USER_CANCELED = 22;
+ USER_ABORTED = 23;
+ UPDATE_NON_EXISTING_EXTENSION = 24;
+ }
+
+ // Timestamp, in microseconds since epoch. Set for all log
+ // events.
+ optional int64 timestamp = 1;
+
+ // Event type. Set for all log events.
+ optional EventType event_type = 2;
+
+ // Total and available space on the stateful partition, in bytes. Set for
+ // event types INSTALLATION_FAILED and SUCCESS.
+ optional int64 stateful_total = 3;
+ optional int64 stateful_free = 4;
+
+ // Network state. Set for event type SESSION_STATE_CHANGE of type LOGIN and
+ // CONNECTIVITY_CHANGE.
+ optional bool online = 5;
+
+ // Type of session state change. Set for event type SESSION_STATE_CHANGE.
+ optional SessionStateChangeType session_state_change_type = 6;
+
+ // Type of failure reason. Set for event type INSTALLATION_FAILED.
+ optional FailureReason failure_reason = 7;
+
+ // Stage of installation process.
+ optional InstallationStage installation_stage = 8;
+
+ // Stage of downloading process.
+ optional DownloadingStage downloading_stage = 9;
+
+ // Type of the extension. Set for event type SUCCESS and sometimes (when
+ // possible) for INSTALLATION_FAILED.
+ optional Extension.ExtensionType extension_type = 10;
+
+ // Type of the current user.
+ optional UserType user_type = 11;
+
+ // Whether the current user is new.
+ optional bool is_new_user = 12;
+
+ // Whether the current failure is a admin side miconfiguration failure. Set
+ // for event type INSTALLATION_FAILED.
+ optional bool is_misconfiguration_failure = 13;
+
+ // Stage of install creation process.
+ optional InstallCreationStage install_creation_stage = 14;
+
+ // Status of cache during downloading process.
+ optional DownloadCacheStatus download_cache_status = 15;
+
+ // Detailed reason why unpacking of extension failed.
+ optional SandboxedUnpackerFailureReason unpacker_failure_reason = 16;
+
+ // Detailed reason why extension failed due to failure reason
+ // MANIFEST_INVALID.
+ optional ManifestInvalidError manifest_invalid_error = 17;
+
+ // Extended error code if the extension installation failed due to CRX install
+ // error.
+ optional CrxInstallErrorDetail crx_install_error_detail = 18;
+
+ // Fetch error code when failure_reason is CRX_FETCH_FAILED or
+ // MANIFEST_FETCH_FAILED.
+ optional int32 fetch_error_code = 19;
+
+ // Number of fetch tries made when failure reason is CRX_FETCH_FAILED or
+ // MANIFEST_FETCH_FAILED.
+ optional int32 fetch_tries = 20;
+}
+
+// A single entry in the push-install log for an app.
+message AppInstallReportLogEvent {
+ // Enumerates the possible event types.
+ enum EventType {
+ // Not used.
+ LOG_EVENT_TYPE_UNKNOWN = 0;
+ // Request received by device
+ SERVER_REQUEST = 1;
+ // Request forwarded to CloudDPC
+ CLOUDDPC_REQUEST = 2;
+ // Request forwarded to CloudDPS
+ CLOUDDPS_REQUEST = 3;
+ // Response received from CloudDPS
+ CLOUDDPS_RESPONSE = 4;
+ // Log line written by Phonesky
+ PHONESKY_LOG = 5;
+ // Install success
+ SUCCESS = 6;
+ // Request canceled
+ CANCELED = 7;
+ // Connectivity state changed
+ CONNECTIVITY_CHANGE = 8;
+ // Session state changed
+ SESSION_STATE_CHANGE = 9;
+ // Package installation started
+ INSTALLATION_STARTED = 10;
+ // Package installation finished
+ INSTALLATION_FINISHED = 11;
+ // Package installation failed
+ INSTALLATION_FAILED = 12;
+ // Direct install scheduled
+ DIRECT_INSTALL = 13;
+ // No more regular attempts to install
+ CLOUDDPC_MAIN_LOOP_FAILED = 14;
+ }
+
+ // Enumerates the possible changes in session state.
+ enum SessionStateChangeType {
+ // Not used.
+ SESSION_STATE_CHANGE_TYPE_UNKNOWN = 0;
+ // Session starting
+ LOGIN = 1;
+ // Session ending
+ LOGOUT = 2;
+ // Suspending
+ SUSPEND = 3;
+ // Resuming
+ RESUME = 4;
+ }
+
+ // Timestamp, in microseconds since epoch. Set for all log
+ // events.
+ optional int64 timestamp = 1;
+
+ // Event type. Set for all log events.
+ optional EventType event_type = 2;
+
+ // Total and available space on the stateful partition, in bytes. Set for
+ // event types SERVER_REQUEST, CLOUDDPS_RESPONSE, INSTALLATION_STARTED,
+ // INSTALLATION_FINISHED, INSTALLATION_FAILED and SUCCESS.
+ optional int64 stateful_total = 3;
+ optional int64 stateful_free = 4;
+
+ // CloudDPS response. Set for event type CLOUDDPS_RESPONSE.
+ optional int32 clouddps_response = 5;
+
+ // Log line written by Phonesky. Set for event type PHONESKY_LOG.
+ optional string phonesky_log = 6;
+
+ // Network state. Set for event type SESSION_STATE_CHANGE of type LOGIN and
+ // CONNECTIVITY_CHANGE.
+ optional bool online = 7;
+
+ // Type of session state change. Set for event type SESSION_STATE_CHANGE.
+ optional SessionStateChangeType session_state_change_type = 8;
+
+ // ARC++ Android id.
+ optional int64 android_id = 9;
+}
+
+// Log bucket for an extension.
+message ExtensionInstallReport {
+ // Extension id for the extension.
+ optional string extension_id = 1;
+
+ // Whether the log is incomplete, e.g. due to the log ring buffer overflowing
+ // or disk corruption.
+ optional bool incomplete = 2;
+
+ // Log events for the extension.
+ repeated ExtensionInstallReportLogEvent logs = 3;
+}
+
+// Log bucket for an ARC++ app.
+message AppInstallReport {
+ // Package name of the app.
+ optional string package = 1;
+
+ // Whether the log is incomplete, e.g. due to the log ring buffer overflowing
+ // or disk corruption.
+ optional bool incomplete = 2;
+
+ // Log events for the app.
+ repeated AppInstallReportLogEvent logs = 3;
+}
+
+// Push-install logs for all ARC++ apps.
+message AppInstallReportRequest {
+ // Log buckets for each app.
+ repeated AppInstallReport app_install_reports = 1;
+}
+
+// Installation logs for all extensions.
+message ExtensionInstallReportRequest {
+ // Log buckets for each extension.
+ repeated ExtensionInstallReport extension_install_reports = 1;
+}
+
+// Response from server after receiving a report on the status of app
+// push-installs.
+message AppInstallReportResponse {}
+
+// Request from device to stop using a previously issued service account.
+// The identity of a freshly-issued service account will be returned by a
+// subsequent device policy fetch (see the |service_account_identity| field in
+// |PolicyData| and auth codes tied to the new service account can be retrieved
+// by subsequent |DeviceServiceApiAccessRequest| requests.
+message RefreshAccountRequest {
+ enum AccountType {
+ ACCOUNT_TYPE_UNSPECIFIED = 0;
+
+ // Refresh demo mode user account.
+ // See go/cros-demo-mode and go/demo-mode-account-brainstorm.
+ CHROME_OS_DEMO_MODE = 1;
+ }
+
+ optional AccountType account_type = 1;
+}
+
+// Response from server after receiving a request to refresh the service
+// account.
+message RefreshAccountResponse {}
+
+// Request from device to upload RSU lookup key.
+message RsuLookupKeyUploadRequest {
+ // Google brand code for the given device SKU.
+ optional bytes board_id = 1;
+
+ // Hashed Cr50 device ID.
+ optional bytes cr50_hashed_device_id = 2;
+}
+
+// Response to {@code RsuLookupKeyUploadRequest}.
+message RsuLookupKeyUploadResponse {
+ // Whether RSU lookup key was received.
+ optional bool rsu_lookup_key_updated = 1;
+}
+
+// Information about an eSIM profile installed on an EUICC (Embedded Universal
+// Integrated Circuit Card).
+message ESimProfileInfo {
+ // ICCID of the eSIM profile. Upto 22 decimal digits encoded
+ // as ASCII string.
+ // Example: 3554020401587593554020.
+ optional string iccid = 1;
+
+ // SMDP (Subscription Management - Data Preparation) server address
+ // that was used to install this eSIM profile. Upto 255 characters
+ // encoded as UTF8 string.
+ // Example: LPA:1$esim.example.com$
+ optional string smdp_address = 2;
+}
+
+// Request from device to server to update information about EUICCs
+// on the device.
+message UploadEuiccInfoRequest {
+ // Number of EUICCs on the device.
+ optional uint32 euicc_count = 1;
+
+ // List of policy provisioned eSIM profiles on the device.
+ repeated ESimProfileInfo esim_profiles = 2;
+
+ // A boolean flag which indicates that the list of eSim profiles was cleared.
+ // We use this differentiate between intentionally empty profile list(after
+ // cleanup) or not yet provisioned one.
+ // On the server, we would ignore requests where |esim_profiles| is empty
+ // if |clear_profile_list| is not set to |true| to not accidentally clear the
+ // values previously stored for this device(which can happen in a case of
+ // device being in after-powerwash state, for example).
+ optional bool clear_profile_list = 3;
+}
+
+// Response from server to device for EUICC status update.
+message UploadEuiccInfoResponse {}
+
+// An event used for reporting when a new print job has completed.
+message PrintJobEvent {
+ message PrintJobConfiguration {
+ // The ID of the job. This and the device ID uniquely identify a print job
+ // in enterprise reporting.
+ optional string id = 1;
+
+ // The name of the document.
+ optional string title = 2;
+
+ // The final status of the job.
+ optional int32 status = 3;
+
+ // The time the print job was created in milliseconds since epoch.
+ optional int64 creation_timestamp_ms = 4;
+
+ // The time the print job was completed in milliseconds since epoch.
+ optional int64 completion_timestamp_ms = 5;
+
+ // The number of pages in the document.
+ optional int32 number_of_pages = 6;
+
+ // The settings of the print job.
+ optional PrintSettings settings = 7;
+ }
+
+ enum UserType {
+ UNKNOWN_USER_TYPE = 0;
+ REGULAR = 1;
+ GUEST = 2;
+ KIOSK = 3;
+ }
+
+ message Printer {
+ // Displayed name of the printer in the client.
+ optional string name = 1;
+
+ // URI of the printer, which serves as a unique identifier.
+ optional string uri = 2;
+
+ // Printer ID.
+ optional string id = 3;
+ }
+
+ // The finishing settings of the print job.
+ message PrintSettings {
+ enum ColorMode {
+ UNKNOWN_COLOR_MODE = 0;
+ BLACK_AND_WHITE = 1;
+ COLOR = 2;
+ }
+
+ enum DuplexMode {
+ UNKNOWN_DUPLEX_MODE = 0;
+ ONE_SIDED = 1;
+ TWO_SIDED_LONG_EDGE = 2;
+ TWO_SIDED_SHORT_EDGE = 3;
+ }
+
+ message MediaSize {
+ // Width (in micrometers)
+ optional int32 width = 1;
+
+ // Height (in micrometers)
+ optional int32 height = 2;
+
+ // Platform specific id to map it back to the particular media.
+ optional string vendor_id = 3;
+ }
+
+ optional ColorMode color = 1;
+
+ optional DuplexMode duplex = 2;
+
+ optional MediaSize media_size = 3;
+
+ optional int32 copies = 4;
+ }
+
+ optional PrintJobConfiguration job_configuration = 1;
+
+ optional UserType user_type = 2;
+
+ optional Printer printer = 3;
+}
+
+// Provides information about an installed app.
+message App {
+ // Enum listing the available types of the apps.
+ // Aligned with apps::mojom::AppType.
+ enum AppType {
+ // Unknown/undefined.
+ UNKNOWN = 0;
+ // ARC++/Android app.
+ ARC = 1;
+ // Built-in app.
+ BUILT_IN = 2;
+ // Linux/crostini app.
+ CROSTINI = 3;
+ // Chrome extension.
+ EXTENSION = 4;
+ // Progressive web app.
+ WEB = 5;
+ // Plugin VM app.
+ PLUGIN_VM = 6;
+ // Borealis VM app.
+ BOREALIS = 7;
+ }
+
+ // ID of the installed application. Package name for Android apps and 32
+ // character long app id for other applications (PWAs, Extensions, Built-in
+ // apps).
+ optional string app_id = 1;
+
+ // Type of the application.
+ optional AppType app_type = 2;
+
+ // Additional IDs of the installed application if exist.
+ // For example it will contain Chrome style 32 character long ids for Android
+ // apps, that use package name as their primary ID.
+ repeated string additional_app_id = 3;
+}
+
+// Information about app activity used for Per-App Time Limits feature.
+message AppActivity {
+ // Enumerates different states that the app can have.
+ enum AppState {
+ // State not known.
+ UNKNOWN = 0;
+ // Default state - no restrictions enforced.
+ DEFAULT = 1;
+ // Important app that cannot be blocked, because it is essential for the OS.
+ ALWAYS_AVAILABLE = 2;
+ // App blocked on the client.
+ BLOCKED = 3;
+ // App reached usage limit on the client.
+ LIMIT_REACHED = 4;
+ // App was uninstalled. It still might have some recent unreported activity.
+ UNINSTALLED = 5;
+ }
+
+ // App identifying information.
+ optional App app_info = 1;
+
+ // A list of time periods when the app was active.
+ repeated TimePeriod active_time_periods = 2;
+
+ // Timestamp when this activity data were populated.
+ // Specified in milliseconds since Epoch in UTC timezone (Java time).
+ optional int64 populated_at = 3;
+
+ // State of the app on client at the time of reporting. To maintain
+ // consistency and help debugging between client and Family Link.
+ optional AppState app_state = 4;
+}
+
+// Models a window for screen time.
+message ScreenTimeSpan {
+ optional TimePeriod time_period = 1;
+
+ // The actual activity duration during a particular time period window
+ // (in milliseconds).
+ optional int64 active_duration_ms = 2;
+}
+
+// Informs the server about the current state of a child user's session, to
+// allow parent supervision.
+message ChildStatusReportRequest {
+ // The user's DMToken.
+ optional string user_dm_token = 1;
+
+ // Timestamp of this status report in milliseconds since epoch.
+ optional int64 timestamp_ms = 2;
+
+ // Time zone id of the active user (e.g. America/Sao_Paulo).
+ // For more details check `third_party/icu/source/i18n/unicode/timezone.h`.
+ optional string time_zone = 3;
+
+ // A list of time spans when the screen was on during the user's session.
+ repeated ScreenTimeSpan screen_time_span = 4;
+
+ // Information about ARC status.
+ optional AndroidStatus android_status = 5;
+
+ // The OS version reported by the device is a platform version
+ // e.g. 1435.0.2011_12_16_1635.
+ optional string os_version = 6;
+
+ // "Verified", "Dev". Same as verified mode.
+ // If the mode is unknown, this field should not be set.
+ optional string boot_mode = 7;
+
+ // A list of per-app activity used for Per-App Time Limits feature.
+ // It might not be sent in every report.
+ repeated AppActivity app_activity = 8;
+
+ // A list of applications which are hidden from the user.
+ repeated App hidden_app = 9;
+
+ // Next id: 10.
+}
+
+// Response from DMServer to update user devices' status.
+// It is possible that status report fails but policy request succeed. In such
+// case, the ChildStatusReportResponse will contain an error code and the
+// device should re-send status report data in the next policy request. The
+// device should re-send report data if policy request fails, even if
+// ChildStatusReportResponse contains no error code.
+message ChildStatusReportResponse {
+ optional int32 error_code = 1;
+
+ // Human readable error message for customer support purpose.
+ optional string error_message = 2;
+}
+
+// Hashing Algorithm for Client Certificate Provisioning Flow.
+enum HashingAlgorithm {
+ // DO NOT USE
+ HASHING_ALGORITHM_UNSPECIFIED = 0;
+
+ SHA1 = 1;
+ SHA256 = 2;
+
+ // Do not hash the input data - assume it is input to the signature algorithm.
+ // The client supports this starting with M-89.
+ //
+ // For SigningAlgorithm RSA_PKCS1_V1_5, the client will perform PKCS#1 v1.5
+ // padding on |data_to_sign|. |data_to_sign| is expected to already contain a
+ // PKCS#1 DigestInfo prefix - the client will not attempt to add such a
+ // prefix. Also |data_to_sign| must be shorter than (key_size-11) bytes. If no
+ // other key size was specified in the
+ // RequiredClientCertificateFor{User,Device} policy, 2048 bytes is assumed.
+ NO_HASH = 3;
+}
+
+// Signing Algorithm for Client Certificate Provisioning Flow.
+enum SigningAlgorithm {
+ // DO NOT USE
+ SIGNING_ALGORITHM_UNSPECIFIED = 0;
+
+ RSA_PKCS1_V1_5 = 1;
+}
+
+// Client Certificate Provisioning Flow, Stage 1: Start a CSR request.
+// No additional fields because cert_profile_id and public_key are passed in the
+// outer message.
+message StartCsrRequest {}
+
+message StartCsrResponse {
+ // The client should register for FCM messages using this topic in order to
+ // receive notifications for the certificate provisioning process.
+ optional string invalidation_topic = 1;
+
+ // The verified access challenge.
+ optional bytes va_challenge = 2;
+
+ // Algorithm to hash data with before signing.
+ optional HashingAlgorithm hashing_algorithm = 5;
+
+ // Algorithm to sign data with for CSR creation.
+ optional SigningAlgorithm signing_algorithm = 3;
+
+ // Data to sign for CSR creation.
+ optional bytes data_to_sign = 4;
+}
+
+// Client Certificate Provisioning Flow, Stage 2: Finish the CSR request.
+message FinishCsrRequest {
+ // Verified access challenge response.
+ optional bytes va_challenge_response = 1;
+
+ // The signature generated using the private key.
+ optional bytes signature = 2;
+}
+
+message FinishCsrResponse {}
+
+// Client Certificate Provisioning Flow, Stage 3: Download the issued
+// certificate.
+message DownloadCertRequest {}
+
+message DownloadCertResponse {
+ // PEM-encoded issued certificate.
+ optional string pem_encoded_certificate = 1;
+}
+
+// Start / continue client certificate provisioning process for the profile
+// |cert_profile_id|.
+message ClientCertificateProvisioningRequest {
+ // The scope of the certificate. Similar to policy_type in PolicyFetchRequest.
+ // google/chromeos/device => a certificate for a device is being requested.
+ // google/chromeos/user => a certificate for a user is being requested.
+ optional string certificate_scope = 1;
+
+ // The id of the client certificate profile, specified in the policy.
+ optional string cert_profile_id = 2;
+
+ // The public key for which the certificate should be issued. It's a
+ // DER-serialized X.509 SubjectPublicKeyInfo.
+ optional bytes public_key = 3;
+
+ // Only filled if this is a request for a certificate for a user
+ optional string device_dm_token = 4;
+
+ oneof request {
+ StartCsrRequest start_csr_request = 5;
+ FinishCsrRequest finish_csr_request = 6;
+ DownloadCertRequest download_cert_request = 7;
+ }
+
+ // Received as part of policy for client certificate profiles. The client
+ // should not interpret this data and should forward it verbatim. DMServer
+ // uses |policy_version| as a hint to verify that the policy view of DMServer
+ // matches the view of Chrome OS device.
+ optional bytes policy_version = 8;
+}
+
+// Response for ClientCertificateProvisioningRequest.
+message ClientCertificateProvisioningResponse {
+ // Error conditions that the server side reports to the client that don't fit
+ // into the standard HTTP error schema.
+ // Note that HTTP errors can still be signaled for the client certificate
+ // provisioning requests, e.g. bad DMToken or internal errors will be
+ // propagated as HTTP errors.
+ enum Error {
+ UNDEFINED = 0;
+ // The backend has not received a certificate within the time limit.
+ TIMED_OUT = 1;
+ // The identity of the client could not be verified.
+ IDENTITY_VERIFICATION_ERROR = 2;
+ // The CA encountered an error when processing the certification request.
+ CA_ERROR = 3;
+ // The client has sent inconsistent data.
+ INCONSISTENT_DATA = 4;
+ // The backend does not accept the public key sent by the client.
+ BAD_PUBLIC_KEY = 5;
+ // The leaf CA certificate specified by the admin is invalid.
+ BAD_CA_CERTIFICATE_SPECIFIED = 6;
+ // The certificate received from the certificate enrollment agent cannot be
+ // parsed or is not issued by the leaf CA certificate specified by the
+ // admin.
+ BAD_CLIENT_CERTIFICATE_RECEIVED = 7;
+ // The CSR signature provided by the client is invalid.
+ INVALID_CSR_SIGNATURE = 8;
+ // A certificate signing request for the same public key has already been
+ // sent.
+ CSR_ALREADY_SENT = 9;
+ }
+
+ // If filled, the request can currently not be processed and the client
+ // is supposed to try again later using the same data.
+ // The value is the number of milliseconds when the client should
+ // automatically retry.
+ optional int64 try_again_later = 1;
+
+ oneof response {
+ Error error = 2;
+ StartCsrResponse start_csr_response = 3;
+ FinishCsrResponse finish_csr_response = 4;
+ DownloadCertResponse download_cert_response = 5;
+ }
+}
+
+// Request from browser to server to upload public key.
+// GoogleDMToken MUST be in HTTP Authorization header.
+message BrowserPublicKeyUploadRequest {
+ enum KeyTrustLevel {
+ // UNSPECIFIED.
+ KEY_TRUST_LEVEL_UNSPECIFIED = 0;
+ // Chrome Browser with the key stored in TPM.
+ CHROME_BROWSER_TPM_KEY = 1;
+ // Chrome Browser with the key stored at OS level.
+ CHROME_BROWSER_OS_KEY = 2;
+ }
+
+ enum KeyType {
+ KEY_TYPE_UNSPECIFIED = 0;
+ RSA_KEY = 1;
+ EC_KEY = 2;
+ }
+
+ // Public key in EC_NID_X9_62_PRIME256V1_PUBLIC_DER format.
+ optional bytes public_key = 1;
+ // Signature of the new public key signed using the existing key, empty for
+ // initial upload.
+ optional bytes signature = 2;
+ // Trust Level of the key.
+ optional KeyTrustLevel key_trust_level = 3;
+ // Type of the key.
+ optional KeyType key_type = 4;
+}
+
+// Response from server to device for cert upload request.
+message BrowserPublicKeyUploadResponse {
+ enum ResponseCode {
+ UNDEFINED = 0;
+ // The new key is uploaded successfully.
+ SUCCESS = 1;
+ // The signature could not be verified.
+ INVALID_SIGNATURE = 2;
+ }
+ optional ResponseCode response_code = 1;
+}
+
+// Request from the DMAgent on the device to the DMServer. This is
+// container for all requests from device to server. The overall HTTP
+// request MUST be in the following format:
+//
+// * HTTP method is POST
+// * Data mime type is application/x-protobuffer
+// * See GoogleContentTypeEnum.java
+// * HTTP parameters are (all required, all case sensitive):
+// * request: MUST BE one of
+// * api_authorization
+// * cert_upload
+// * check_device_pairing
+// * device_pairing
+// * device_state_retrieval
+// * enterprise_check
+// * enterprise_psm_check
+// * chrome_desktop_report
+// * chrome_os_user_report
+// * policy
+// * register
+// * status_upload
+// * unregister
+// * remote_commands
+// * attribute_update_permission
+// * attribute_update
+// * gcm_id_update
+// * check_android_management
+// * certificate_based_register
+// * active_directory_enroll_play_user
+// * active_directory_play_activity
+// * active_directory_user_signin
+// * register_browser
+// * policy_validation_report
+// * device_initial_enrollment_state
+// * refresh_account
+// * client_cert_provisioning
+// * browser_public_key_upload
+// * upload_euicc_info
+// * devicetype: MUST BE "1" for Android, "2" for Chrome OS or "3" for Chrome
+// browser.
+// * apptype: MUST BE Android or Chrome.
+// * deviceid: MUST BE no more than 64-char in [\x21-\x7E].
+// * agent: MUST BE no more than 64-char long.
+// * HTTP Authorization header MUST be in the following formats:
+// * For register for Chrome browsers
+// Authorization: GoogleEnrollmentToken token=<enrollment token>
+//
+// * For unregister, policy, status, cert_upload, remote_commands,
+// gcm_id_update, active_directory_enroll_play_user,
+// active_directory_play_activity, active_directory_user_signin,
+// policy_validation_report, chrome_desktop_report,
+// chrome_os_user_report, refresh_account, client_cert_provisioning requests
+// Authorization: GoogleDMToken token=<dm token from register>
+//
+// * The Authorization header isn't used for enterprise_check,
+// enterprise_psm_check, device_initial_enrollment_state or
+// certificate_based_register requests.
+// It is also not used for register requests and check_android_management
+// requests - for these requests, the OAuth token is passed in the "oauth"
+// HTTP query parameter.
+//
+// DeviceManagementRequest should only contain one request which matches the
+// HTTP query parameter - request, as mentioned in comments on the fields of the
+// message. Other requests within the container will be ignored.
+message DeviceManagementRequest {
+ reserved 24; // unused previous version of chrome_desktop_report_request.
+
+ // Register request.
+ // HTTP query parameter 'request': 'register'
+ optional DeviceRegisterRequest register_request = 1;
+
+ // Unregister request.
+ // HTTP query parameter 'request': 'unregister'
+ optional DeviceUnregisterRequest unregister_request = 2;
+
+ // Policy request.
+ // HTTP query parameter 'request': 'policy'
+ optional DevicePolicyRequest policy_request = 3;
+
+ // Update status.
+ // HTTP query parameter 'request': 'status'
+ // (this applies to all of the following three fields, and they can all be
+ // present in the same request).
+ optional DeviceStatusReportRequest device_status_report_request = 4;
+ optional SessionStatusReportRequest session_status_report_request = 5;
+ optional ChildStatusReportRequest child_status_report_request = 30;
+
+ // Auto-enrollment detection.
+ // HTTP query parameter 'request': 'enterprise_check'
+ optional DeviceAutoEnrollmentRequest auto_enrollment_request = 6;
+
+ // EMCert upload (for remote attestation)
+ // HTTP query parameter 'request': 'cert_upload'
+ optional DeviceCertUploadRequest cert_upload_request = 7;
+
+ // Request for OAuth2 authorization codes to access Google services.
+ // HTTP query parameter 'request': 'api_authorization'
+ optional DeviceServiceApiAccessRequest service_api_access_request = 8;
+
+ // Device-state retrieval.
+ // HTTP query parameter 'request': 'device_state_retrieval'
+ optional DeviceStateRetrievalRequest device_state_retrieval_request = 9;
+
+ // Device state key update.
+ // HTTP query parameter 'request': 'policy'
+ optional DeviceStateKeyUpdateRequest device_state_key_update_request = 10;
+
+ // Pair two devices.
+ // HTTP query parameter 'request': 'device_pairing'
+ optional DevicePairingRequest device_pairing_request = 11;
+
+ // Check if two devices are paired.
+ // HTTP query parameter 'request': 'check_device_pairing'
+ optional CheckDevicePairingRequest check_device_pairing_request = 12;
+
+ // Remote command fetching.
+ // HTTP query parameter 'request': 'remote_commands'
+ optional DeviceRemoteCommandRequest remote_command_request = 13;
+
+ // Check permission for updating device attribute.
+ // HTTP query parameter 'request': 'attribute_update_permission'
+ optional DeviceAttributeUpdatePermissionRequest
+ device_attribute_update_permission_request = 14;
+
+ // Update device attribute.
+ // HTTP query parameter 'request': 'attribute_update'
+ optional DeviceAttributeUpdateRequest device_attribute_update_request = 15;
+
+ // Update the GCM id to device_id mapping.
+ // HTTP query parameter 'request': 'gcm_id_update'
+ optional GcmIdUpdateRequest gcm_id_update_request = 16;
+
+ // Check if user is a managed Android-for-Work user with DPC enforcement.
+ // HTTP query parameter 'request': 'check_android_management'
+ optional CheckAndroidManagementRequest check_android_management_request = 17;
+
+ // Request to register with a registration certificate.
+ // HTTP query parameter 'request': 'certificate_based_register'
+ optional CertificateBasedDeviceRegisterRequest
+ certificate_based_register_request = 18;
+
+ // Gets an enrollment token to a Managed Google Play Account for using it with
+ // Active Directory.
+ // HTTP query parameter 'request': 'active_directory_enroll_play_user'
+ optional ActiveDirectoryEnrollPlayUserRequest
+ active_directory_enroll_play_user_request = 19;
+
+ // Reports that a Play account is used.
+ // HTTP query parameter 'request': 'active_directory_play_activity'
+ optional ActiveDirectoryPlayActivityRequest
+ active_directory_play_activity_request = 20;
+
+ // Request device license information.
+ optional CheckDeviceLicenseRequest check_device_license_request_deprecated =
+ 21 [deprecated = true];
+
+ // Initiate an Active Directory user signin.
+ // HTTP query parameter 'request': 'active_directory_user_signin'
+ optional ActiveDirectoryUserSigninRequest
+ active_directory_user_signin_request = 22;
+
+ // Request to register a browser independently of its users.
+ // HTTP query parameter 'request': 'register_browser'
+ optional RegisterBrowserRequest register_browser_request = 23;
+
+ // A report on the status of app push-installs.
+ // HTTP query parameter 'request': 'app_install_report'
+ optional AppInstallReportRequest app_install_report_request = 25;
+
+ // A Chrome desktop report request.
+ // HTTP query parameter 'request': 'chrome_desktop_report'
+ optional ChromeDesktopReportRequest chrome_desktop_report_request = 26;
+
+ // Result of validating fetched policy on the client.
+ // HTTP query parameter 'request': 'policy_validation_report'
+ optional PolicyValidationReportRequest policy_validation_report_request = 27;
+
+ // Query for initial enrollment details.
+ // HTTP query parameter 'request': 'device_initial_enrollment_state'
+ optional DeviceInitialEnrollmentStateRequest
+ device_initial_enrollment_state_request = 28;
+
+ // Request from device to wipe an old account and get a new account.
+ // HTTP query parameter 'request': 'refresh_account'
+ optional RefreshAccountRequest refresh_account_request = 29;
+
+ // Request from device to upload RSU lookup key.
+ // TODO(crbug/1284656): seems unused
+ optional RsuLookupKeyUploadRequest rsu_lookup_key_upload_request = 31;
+
+ // Request from device for SAML IdP URL address.
+ // HTTP query parameter 'request': 'public_saml_user_request'
+ optional PublicSamlUserRequest public_saml_user_request = 32;
+
+ // A ChromeOS user report request.
+ // HTTP query parameter 'request': 'chrome_os_user_report'
+ optional ChromeOsUserReportRequest chrome_os_user_report_request = 33;
+
+ // Request to start / continue client certificate provisioning process.
+ // HTTP query parameter 'request': 'client_cert_provisioning'
+ optional ClientCertificateProvisioningRequest
+ client_certificate_provisioning_request = 34;
+
+ // A report on the status of extension install process.
+ // TODO(crbug/1284656): seems unused.
+ optional ExtensionInstallReportRequest extension_install_report_request = 35;
+
+ // Request to check user account for smart enrollment.
+ // HTTP query parameter 'request': 'check_user_account'
+ optional CheckUserAccountRequest check_user_account_request = 36;
+
+ // Request from device to check the state stored in PSM. Currently, it is used
+ // for ZTE/LP device initial enrollment state check.
+ // HTTP query parameter 'request': 'enterprise_psm_check'
+ optional PrivateSetMembershipRequest private_set_membership_request = 37;
+
+ // Request from browser to upload public key.
+ // HTTP query parameter 'request': 'browser_public_key_upload'
+ optional BrowserPublicKeyUploadRequest browser_public_key_upload_request = 38;
+
+ // Request to upload info about eSIM provisioning.
+ // HTTP query parameter 'request': 'upload_euicc_info'
+ optional UploadEuiccInfoRequest upload_euicc_info_request = 39;
+
+ // Request to upload profile usage information.
+ // HTTP query parameter 'request': 'chrome_profile_report'
+ optional ChromeProfileReportRequest chrome_profile_report_request = 40;
+
+ // Next id: 41.
+}
+
+// Response from server to device.
+//
+// For release clients, DMServer returns errors using HTTP Status Code, so that
+// clients only need to check one place for all error codes. It is also easier
+// to perform log analysis and customer support since HTTP Status Code is easily
+// visible in the logs.
+//
+// Possible HTTP status codes:
+// 200 OK: valid response is returned to client.
+// 400 Bad Request: invalid argument or payload.
+// 401 Unauthorized: invalid auth cookie/OAuth token or DM token.
+// 402 Missing license.
+// 403 Forbidden: device management is not allowed (e.g. user may not enroll).
+// 404 Not Found: the request URL is invalid.
+// 405 Invalid serial number.
+// 406 Domain mismatch (e.g. device is enrolling into the wrong domain, should
+// force-re-enroll into another domain).
+// 409 Device id conflict.
+// 410 Device Not Found: the device id is not found.
+// 412 Pending approval. Used with ClientCertificateProvisioningRequest to
+// indicate that a certificate has not been issued yet.
+// 413 Request payload too large.
+// 417 Consumer (non-dasher) account can not enroll with packaged license.
+// 429 Too many requests.
+// 500 Internal Server Error: most likely a bug in DM server.
+// 503 Service Unavailable: most likely a backend error.
+// 902 Policy Not Found: the policy is not found.
+// 903 Deprovisioned: the device has been deprovisioned.
+// 904 Arc Disabled: ARC is not enabled on the domain.
+// 905 Can not enroll packaged license device to domainless customer.
+// 906 TOS has not been accepted by customer.
+// 907 Illegal account for packaged EDU license.
+message DeviceManagementResponse {
+ reserved 1, 24;
+
+ // Error message.
+ optional string error_message = 2;
+
+ // Register response
+ optional DeviceRegisterResponse register_response = 3;
+
+ // Unregister response
+ optional DeviceUnregisterResponse unregister_response = 4;
+
+ // Policy response.
+ optional DevicePolicyResponse policy_response = 5;
+
+ // Update status report response.
+ optional DeviceStatusReportResponse device_status_report_response = 6;
+ optional SessionStatusReportResponse session_status_report_response = 7;
+ optional ChildStatusReportResponse child_status_report_response = 29;
+
+ // Auto-enrollment detection response.
+ optional DeviceAutoEnrollmentResponse auto_enrollment_response = 8;
+
+ // EMCert upload response.
+ optional DeviceCertUploadResponse cert_upload_response = 9;
+
+ // Response to OAuth2 authorization code request.
+ optional DeviceServiceApiAccessResponse service_api_access_response = 10;
+
+ // Device-state retrieval.
+ optional DeviceStateRetrievalResponse device_state_retrieval_response = 11;
+
+ // Response to device pairing request.
+ optional DevicePairingResponse device_pairing_response = 12;
+
+ // Response to check device pairing request.
+ optional CheckDevicePairingResponse check_device_pairing_response = 13;
+
+ // Response to remote command request.
+ optional DeviceRemoteCommandResponse remote_command_response = 14;
+
+ // Response to check device attribute update permission.
+ optional DeviceAttributeUpdatePermissionResponse
+ device_attribute_update_permission_response = 15;
+
+ // Response to update device attribute.
+ optional DeviceAttributeUpdateResponse device_attribute_update_response = 16;
+
+ // Response to GCM id update request.
+ optional GcmIdUpdateResponse gcm_id_update_response = 17;
+
+ // Response to check Android management request.
+ optional CheckAndroidManagementResponse check_android_management_response =
+ 18;
+
+ // Response to an Active Directory Play user enrollment request.
+ optional ActiveDirectoryEnrollPlayUserResponse
+ active_directory_enroll_play_user_response = 19;
+
+ // Response to a Play activity request.
+ optional ActiveDirectoryPlayActivityResponse
+ active_directory_play_activity_response = 20;
+
+ // Response to a check device license request.
+ optional CheckDeviceLicenseResponse check_device_license_response_deprecated =
+ 21 [deprecated = true];
+
+ // Response to a request initiating an Active Directory user signin.
+ optional ActiveDirectoryUserSigninResponse
+ active_directory_user_signin_response = 22;
+
+ // Response to a Chrome desktop report request.
+ optional ChromeDesktopReportResponse chrome_desktop_report_response = 23;
+
+ // Response a report on the status of app push-installs
+ optional AppInstallReportResponse app_install_report_response = 25;
+
+ // Response to a policy validation report.
+ optional PolicyValidationReportResponse policy_validation_report_response =
+ 26;
+
+ // Response to initial enrollment details query.
+ optional DeviceInitialEnrollmentStateResponse
+ device_initial_enrollment_state_response = 27;
+
+ // Response to refresh account request.
+ optional RefreshAccountResponse refresh_account_response = 28;
+
+ // Response to RSU lookup key upload request.
+ optional RsuLookupKeyUploadResponse rsu_lookup_key_upload_response = 30;
+
+ // Response to public SAML session user request.
+ optional PublicSamlUserResponse public_saml_user_response = 31;
+
+ // Response to a ChromeOS user report request.
+ optional ChromeOsUserReportResponse chrome_os_user_report_response = 32;
+
+ // Response to a client certificate provisioning request.
+ optional ClientCertificateProvisioningResponse
+ client_certificate_provisioning_response = 33;
+
+ // Response to a checking user account type for smart enrollment.
+ optional CheckUserAccountResponse check_user_account_response = 34;
+
+ // Response to a client private set membership request.
+ optional PrivateSetMembershipResponse private_set_membership_response = 35;
+
+ // Response to browser public key upload request.
+ optional BrowserPublicKeyUploadResponse browser_public_key_upload_response =
+ 36;
+
+ // Response to EUICC info upload request.
+ optional UploadEuiccInfoResponse upload_euicc_info_response = 37;
+
+ // Response to a Chrome Profile report request.
+ optional ChromeProfileReportResponse chrome_profile_report_response = 38;
+
+ // Next id: 39.
+}
+
+// Device State Information stored in the server is retrieval at
+// enrollment process. Learn more at go/cros-enterprise-psm
+message DeviceStateRetrievalInfo {
+ // Whether the device should retrieve initial state or not.
+ optional bool has_initial_state = 1;
+}
diff --git a/chromium/components/policy/proto/install_attributes.proto b/chromium/components/policy/proto/install_attributes.proto
new file mode 100644
index 00000000000..a1f0368f8e2
--- /dev/null
+++ b/chromium/components/policy/proto/install_attributes.proto
@@ -0,0 +1,20 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package cryptohome;
+
+message SerializedInstallAttributes {
+ message Attribute {
+ required string name = 1;
+ required bytes value = 2;
+ }
+ // Specifies the version of the install attributes used to write the file.
+ // Should be incremented when fallback behavior is needed.
+ optional uint32 version = 1 [default = 1];
+ repeated Attribute attributes = 2;
+}
diff --git a/chromium/components/policy/proto/policy_common_definitions.proto b/chromium/components/policy/proto/policy_common_definitions.proto
new file mode 100644
index 00000000000..d2fdadc99ff
--- /dev/null
+++ b/chromium/components/policy/proto/policy_common_definitions.proto
@@ -0,0 +1,50 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+// Everything below this comment will be synchronized between client and server
+// repos ( go/cros-proto-sync ).
+
+message StringList {
+ repeated string entries = 1;
+}
+
+message PolicyOptions {
+ enum PolicyMode {
+ // The given settings are applied regardless of user choice.
+ MANDATORY = 0;
+ // The user may choose to override the given settings.
+ RECOMMENDED = 1;
+ // No policy value is present and the policy should be ignored.
+ UNSET = 2;
+ }
+ optional PolicyMode mode = 1 [default = MANDATORY];
+}
+
+message BooleanPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool value = 2;
+}
+
+message IntegerPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional int64 value = 2;
+}
+
+message StringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string value = 2;
+}
+
+message StringListPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional StringList value = 2;
+}
diff --git a/chromium/components/policy/proto/policy_proto_export.h b/chromium/components/policy/proto/policy_proto_export.h
new file mode 100644
index 00000000000..c8240ce7c0d
--- /dev/null
+++ b/chromium/components/policy/proto/policy_proto_export.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_PROTO_POLICY_PROTO_EXPORT_H_
+#define COMPONENTS_POLICY_PROTO_POLICY_PROTO_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(POLICY_PROTO_COMPILATION)
+#define POLICY_PROTO_EXPORT __declspec(dllexport)
+#else
+#define POLICY_PROTO_EXPORT __declspec(dllimport)
+#endif // defined(POLICY_PROTO_COMPILATION)
+
+#if defined(POLICY_CHROME_SETTINGS_PROTO_COMPILATION)
+#define POLICY_CHROME_SETTINGS_PROTO_EXPORT __declspec(dllexport)
+#else
+#define POLICY_CHROME_SETTINGS_PROTO_EXPORT __declspec(dllimport)
+#endif // defined(POLICY_PROTO_COMPILATION)
+
+#else // defined(WIN32)
+
+#if defined(POLICY_PROTO_COMPILATION)
+#define POLICY_PROTO_EXPORT __attribute__((visibility("default")))
+#else
+#define POLICY_PROTO_EXPORT
+#endif // defined(POLICY_PROTO_COMPILATION)
+
+#if defined(POLICY_CHROME_SETTINGS_PROTO_COMPILATION)
+#define POLICY_CHROME_SETTINGS_PROTO_EXPORT \
+ __attribute__((visibility("default")))
+#else
+#define POLICY_CHROME_SETTINGS_PROTO_EXPORT
+#endif // defined(POLICY_PROTO_COMPILATION)
+
+#endif // defined(WIN32)
+
+#else // defined(COMPONENT_BUILD)
+
+#define POLICY_PROTO_EXPORT
+#define POLICY_CHROME_SETTINGS_PROTO_EXPORT
+
+#endif // defined(COMPONENT_BUILD)
+
+#endif // COMPONENTS_POLICY_PROTO_POLICY_PROTO_EXPORT_H_
diff --git a/chromium/components/policy/proto/policy_signing_key.proto b/chromium/components/policy/proto/policy_signing_key.proto
new file mode 100644
index 00000000000..a5886e15b31
--- /dev/null
+++ b/chromium/components/policy/proto/policy_signing_key.proto
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+// Contains a signing key and its signature.
+message PolicySigningKey {
+ // The key used to verify policy blobs sent down from the server.
+ optional bytes signing_key = 1;
+
+ // The signature for this signing key (verified using a hard-coded key
+ // stored in the Chrome binary). This is essentially a certificate (key
+ // signed with another well-known key that establishes a trust root).
+ optional bytes signing_key_signature = 2;
+
+ // This is the hard-coded verification key used to generate/verify the
+ // signing_key_signature. We track this in the cache data so we know which
+ // verification key to use when validating the cached policy (important when
+ // doing key rotation).
+ optional bytes verification_key = 3;
+}
diff --git a/chromium/components/policy/proto/secure_connect.proto b/chromium/components/policy/proto/secure_connect.proto
new file mode 100644
index 00000000000..71519b0cf88
--- /dev/null
+++ b/chromium/components/policy/proto/secure_connect.proto
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+import "policy_common_definitions.proto";
+
+package enterprise_management;
+
+// Response message for GetManagedAccountsSigninRestriction.
+message GetManagedAccountsSigninRestrictionResponse {
+ optional string policy_value = 1;
+ optional PolicyOptions policy_options = 2;
+ optional bool has_error = 3;
+} \ No newline at end of file
diff --git a/chromium/components/policy/test_support/.style.yapf b/chromium/components/policy/test_support/.style.yapf
new file mode 100644
index 00000000000..c8016337fc2
--- /dev/null
+++ b/chromium/components/policy/test_support/.style.yapf
@@ -0,0 +1,4 @@
+[style]
+# Old python files should follow the old style, as mentioned in
+# https://chromium.googlesource.com/chromium/src/+/main/styleguide/python/python.md#our-previous-python-style.
+indent_width = 2
diff --git a/chromium/components/policy/test_support/DEPS b/chromium/components/policy/test_support/DEPS
new file mode 100644
index 00000000000..882cd295534
--- /dev/null
+++ b/chromium/components/policy/test_support/DEPS
@@ -0,0 +1,16 @@
+include_rules = [
+ # Run
+ #
+ # buildtools/checkdeps/checkdeps.py components/policy
+ #
+ # to test.
+
+ "+crypto",
+ '+google_apis/gaia/gaia_auth_util.h',
+ "+net",
+ "+services/network/public/cpp",
+ "+services/network/public/mojom",
+ "+services/network/test",
+ "+third_party/re2",
+]
+
diff --git a/chromium/components/policy/test_support/bootstrap_deps b/chromium/components/policy/test_support/bootstrap_deps
new file mode 100644
index 00000000000..09d92456c36
--- /dev/null
+++ b/chromium/components/policy/test_support/bootstrap_deps
@@ -0,0 +1,16 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+deps = {
+ "src/components/policy/test_support":
+ "https://src.chromium.org/chrome/trunk/src/components/policy/test_support",
+ "src/net/tools/testserver":
+ "https://src.chromium.org/chrome/trunk/src/net/tools/testserver",
+ "src/third_party/protobuf/python/google":
+ "https://src.chromium.org/chrome/trunk/src/third_party/protobuf/python/google",
+ "src/tools/telemetry":
+ "https://src.chromium.org/chrome/trunk/src/tools/telemetry",
+ "src/third_party/catapult":
+ "https://src.chromium.org/chrome/trunk/src/third_party/catapult",
+}
diff --git a/chromium/components/policy/test_support/client_storage.cc b/chromium/components/policy/test_support/client_storage.cc
new file mode 100644
index 00000000000..bd6480e3745
--- /dev/null
+++ b/chromium/components/policy/test_support/client_storage.cc
@@ -0,0 +1,112 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/client_storage.h"
+
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "crypto/sha2.h"
+
+namespace policy {
+
+ClientStorage::ClientInfo::ClientInfo() = default;
+
+ClientStorage::ClientInfo::ClientInfo(
+ const ClientStorage::ClientInfo& client_info) = default;
+
+ClientStorage::ClientInfo& ClientStorage::ClientInfo::operator=(
+ const ClientStorage::ClientInfo& client_info) = default;
+
+ClientStorage::ClientInfo::ClientInfo(ClientStorage::ClientInfo&& client_info) =
+ default;
+
+ClientStorage::ClientInfo& ClientStorage::ClientInfo::operator=(
+ ClientStorage::ClientInfo&& client_info) = default;
+
+ClientStorage::ClientInfo::~ClientInfo() = default;
+
+ClientStorage::ClientStorage() = default;
+
+ClientStorage::ClientStorage(ClientStorage&& client_storage) = default;
+
+ClientStorage& ClientStorage::operator=(ClientStorage&& client_storage) =
+ default;
+
+ClientStorage::~ClientStorage() = default;
+
+void ClientStorage::RegisterClient(const ClientInfo& client_info) {
+ CHECK(!client_info.device_id.empty());
+
+ clients_[client_info.device_id] = client_info;
+ registered_tokens_[client_info.device_token] = client_info.device_id;
+}
+
+bool ClientStorage::HasClient(const std::string& device_id) const {
+ return clients_.find(device_id) != clients_.end();
+}
+
+const ClientStorage::ClientInfo& ClientStorage::GetClient(
+ const std::string& device_id) const {
+ const ClientInfo* const client_info = GetClientOrNull(device_id);
+ CHECK(client_info);
+
+ return *client_info;
+}
+
+const ClientStorage::ClientInfo* ClientStorage::GetClientOrNull(
+ const std::string& device_id) const {
+ auto it = clients_.find(device_id);
+ return it == clients_.end() ? nullptr : &it->second;
+}
+
+const ClientStorage::ClientInfo* ClientStorage::LookupByStateKey(
+ const std::string& state_key) const {
+ for (auto const& [device_id, client_info] : clients_) {
+ if (base::Contains(client_info.state_keys, state_key))
+ return &client_info;
+ }
+ return nullptr;
+}
+
+bool ClientStorage::DeleteClient(const std::string& device_token) {
+ auto it = registered_tokens_.find(device_token);
+ if (it == registered_tokens_.end())
+ return false;
+
+ const std::string& device_id = it->second;
+ DCHECK(!device_id.empty());
+ auto it_clients = clients_.find(device_id);
+ DCHECK(it_clients != clients_.end());
+
+ clients_.erase(it_clients, clients_.end());
+ registered_tokens_.erase(it, registered_tokens_.end());
+ return true;
+}
+
+size_t ClientStorage::GetNumberOfRegisteredClients() const {
+ return clients_.size();
+}
+
+std::vector<std::string> ClientStorage::GetMatchingStateKeyHashes(
+ uint64_t modulus,
+ uint64_t remainder) const {
+ std::vector<std::string> hashes;
+ for (const auto& [device_id, client_info] : clients_) {
+ for (const std::string& key : client_info.state_keys) {
+ std::string hash = crypto::SHA256HashString(key);
+ uint64_t hash_remainder = 0;
+ // Simulate long division in base 256, which allows us to interpret
+ // individual chars in our hash as digits. We only care about the
+ // remainder and hence do not compute the quotient in each iteration. This
+ // assumes big-endian byte order.
+ for (uint64_t digit : hash)
+ hash_remainder = (hash_remainder * 256 + digit) % modulus;
+ if (hash_remainder == remainder)
+ hashes.push_back(hash);
+ }
+ }
+ return hashes;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/client_storage.h b/chromium/components/policy/test_support/client_storage.h
new file mode 100644
index 00000000000..999909eeb9e
--- /dev/null
+++ b/chromium/components/policy/test_support/client_storage.h
@@ -0,0 +1,81 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_CLIENT_STORAGE_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_CLIENT_STORAGE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace policy {
+
+// Stores information about registered clients.
+class ClientStorage {
+ public:
+ struct ClientInfo {
+ ClientInfo();
+ ClientInfo(const ClientInfo& client_info);
+ ClientInfo& operator=(const ClientInfo& client_info);
+ ClientInfo(ClientInfo&& client_info);
+ ClientInfo& operator=(ClientInfo&& client_info);
+ ~ClientInfo();
+
+ std::string device_id;
+ std::string device_token;
+ std::string machine_name;
+ absl::optional<std::string> username;
+ std::vector<std::string> state_keys;
+ std::set<std::string> allowed_policy_types;
+ };
+
+ ClientStorage();
+ ClientStorage(ClientStorage&& client_storage);
+ ClientStorage& operator=(ClientStorage&& client_storage);
+ ~ClientStorage();
+
+ // Register client so the server returns policy without the client having to
+ // make a registration call. This could be called at any time (before or after
+ // starting the server).
+ void RegisterClient(const ClientInfo& client_info);
+
+ // Returns true if there is a client associated with |device_id|.
+ bool HasClient(const std::string& device_id) const;
+
+ // Returns the client info associated with |device_id|. Fails if there is no
+ // such a client.
+ const ClientInfo& GetClient(const std::string& device_id) const;
+
+ // Returns the client info associated with |device_id| or nullptr if there is
+ // no such a client.
+ const ClientInfo* GetClientOrNull(const std::string& device_id) const;
+
+ // Returns the client info associated with |state_key| or nullptr if there is
+ // no such a client.
+ const ClientInfo* LookupByStateKey(const std::string& state_key) const;
+
+ // Returns true if deletion of client with token |device_token| succeeded.
+ bool DeleteClient(const std::string& device_token);
+
+ // Returns the number of clients registered.
+ size_t GetNumberOfRegisteredClients() const;
+
+ // Returns hashes for all state keys registered with the server, which, when
+ // divied by |modulus|, result in the specified |remainder|.
+ std::vector<std::string> GetMatchingStateKeyHashes(uint64_t modulus,
+ uint64_t remainder) const;
+
+ private:
+ // Key: device ids.
+ std::map<std::string, ClientInfo> clients_;
+ // Maps device tokens to device IDs.
+ std::map<std::string, std::string> registered_tokens_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_CLIENT_STORAGE_H_
diff --git a/chromium/components/policy/test_support/client_storage_unittest.cc b/chromium/components/policy/test_support/client_storage_unittest.cc
new file mode 100644
index 00000000000..5b2a2760120
--- /dev/null
+++ b/chromium/components/policy/test_support/client_storage_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/client_storage.h"
+
+#include "base/strings/string_piece.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr const char kDeviceId1[] = "1";
+constexpr const char kDeviceId2[] = "2";
+constexpr const char kStateKey1[] = "bbb";
+constexpr const char kStateKey2[] = "ggg";
+constexpr const char kStateKey3[] = "fff";
+constexpr const char kStateKey4[] = "ccc";
+constexpr const char kDeviceToken[] = "device-token";
+constexpr const char kNonExistingDeviceToken[] = "non-existing-device-token";
+constexpr const uint64_t kModulus = 3;
+constexpr const uint64_t kRemainder = 2;
+// Following SHA256 hashes produce |kRemainder| when divided by |kModulus|.
+constexpr base::StringPiece kSHA256HashForStateKey1(
+ "\x3e\x74\x4b\x9d\xc3\x93\x89\xba\xf0\xc5\xa0\x66\x05\x89\xb8\x40\x2f\x3d"
+ "\xbb\x49\xb8\x9b\x3e\x75\xf2\xc9\x35\x58\x52\xa3\xc6\x77",
+ 32);
+constexpr base::StringPiece kSHA256HashForStateKey4(
+ "\x64\xda\xa4\x4a\xd4\x93\xff\x28\xa9\x6e\xff\xab\x6e\x77\xf1\x73\x2a\x3d"
+ "\x97\xd8\x32\x41\x58\x1b\x37\xdb\xd7\x0a\x7a\x49\x00\xfe",
+ 32);
+
+void RegisterClient(const std::string& device_token,
+ ClientStorage* client_storage) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id = kDeviceId1;
+ client_info.device_token = device_token;
+
+ client_storage->RegisterClient(client_info);
+ ASSERT_EQ(client_storage->GetNumberOfRegisteredClients(), 1u);
+ ASSERT_EQ(client_storage->GetClient(kDeviceId1).device_token, device_token);
+}
+
+} // namespace
+
+TEST(ClientStorageTest, Unregister_Success) {
+ ClientStorage client_storage;
+ RegisterClient(kDeviceToken, &client_storage);
+
+ ASSERT_TRUE(client_storage.DeleteClient(kDeviceToken));
+ EXPECT_EQ(client_storage.GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST(ClientStorageTest, Unregister_NonExistingClient) {
+ ClientStorage client_storage;
+ RegisterClient(kDeviceToken, &client_storage);
+
+ ASSERT_FALSE(client_storage.DeleteClient(kNonExistingDeviceToken));
+ ASSERT_EQ(client_storage.GetNumberOfRegisteredClients(), 1u);
+ EXPECT_EQ(client_storage.GetClient(kDeviceId1).device_token, kDeviceToken);
+}
+
+TEST(ClientStorageTest, GetMatchingStateKeyHashes) {
+ ClientStorage client_storage;
+ ClientStorage::ClientInfo client_info1;
+ client_info1.device_id = kDeviceId1;
+ client_info1.state_keys = {kStateKey1, kStateKey2};
+ client_storage.RegisterClient(client_info1);
+ ClientStorage::ClientInfo client_info2;
+ client_info2.device_id = kDeviceId2;
+ client_info2.state_keys = {kStateKey3, kStateKey4};
+ client_storage.RegisterClient(client_info2);
+
+ std::vector<std::string> matching_hashes =
+ client_storage.GetMatchingStateKeyHashes(kModulus, kRemainder);
+
+ EXPECT_THAT(matching_hashes,
+ testing::UnorderedElementsAreArray(
+ {kSHA256HashForStateKey1, kSHA256HashForStateKey4}));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/embedded_policy_test_server.cc b/chromium/components/policy/test_support/embedded_policy_test_server.cc
new file mode 100644
index 00000000000..106a1d6cfc9
--- /dev/null
+++ b/chromium/components/policy/test_support/embedded_policy_test_server.cc
@@ -0,0 +1,215 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#if !BUILDFLAG(IS_ANDROID)
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#endif // !BUILDFLAG(IS_ANDROID)
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/failing_request_handler.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_api_authorization.h"
+#include "components/policy/test_support/request_handler_for_auto_enrollment.h"
+#include "components/policy/test_support/request_handler_for_check_android_management.h"
+#include "components/policy/test_support/request_handler_for_chrome_desktop_report.h"
+#include "components/policy/test_support/request_handler_for_device_attribute_update.h"
+#include "components/policy/test_support/request_handler_for_device_attribute_update_permission.h"
+#include "components/policy/test_support/request_handler_for_device_initial_enrollment_state.h"
+#include "components/policy/test_support/request_handler_for_device_state_retrieval.h"
+#include "components/policy/test_support/request_handler_for_policy.h"
+#include "components/policy/test_support/request_handler_for_psm_auto_enrollment.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "components/policy/test_support/request_handler_for_register_cert_based.h"
+#include "components/policy/test_support/request_handler_for_register_device_and_user.h"
+#include "components/policy/test_support/request_handler_for_remote_commands.h"
+#include "components/policy/test_support/request_handler_for_status_upload.h"
+#include "components/policy/test_support/request_handler_for_unregister.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "crypto/sha2.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using ::net::test_server::EmbeddedTestServer;
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
+
+namespace policy {
+
+namespace {
+
+const char kExternalPolicyDataPath[] = "/externalpolicydata";
+const char kExternalPolicyTypeParam[] = "policy_type";
+const char kExternalEntityIdParam[] = "entity_id";
+
+std::unique_ptr<HttpResponse> LogStatusAndReturn(
+ GURL url,
+ std::unique_ptr<HttpResponse> response) {
+ if (!response)
+ return nullptr;
+
+ CustomHttpResponse* basic_response =
+ static_cast<CustomHttpResponse*>(response.get());
+ if (basic_response->code() == net::HTTP_OK) {
+ DLOG(INFO) << "Request succeeded: " << url;
+ } else {
+ DLOG(INFO) << "Request failed with error code " << basic_response->code()
+ << " (" << basic_response->content() << "): " << url;
+ }
+ return response;
+}
+
+} // namespace
+
+const char kFakeDeviceToken[] = "fake_device_management_token";
+const char kInvalidEnrollmentToken[] = "invalid_enrollment_token";
+
+EmbeddedPolicyTestServer::RequestHandler::RequestHandler(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : client_storage_(client_storage), policy_storage_(policy_storage) {}
+
+EmbeddedPolicyTestServer::RequestHandler::~RequestHandler() = default;
+
+EmbeddedPolicyTestServer::EmbeddedPolicyTestServer()
+ : http_server_(EmbeddedTestServer::TYPE_HTTP),
+ client_storage_(std::make_unique<ClientStorage>()),
+ policy_storage_(std::make_unique<PolicyStorage>()) {
+ RegisterHandler(std::make_unique<RequestHandlerForApiAuthorization>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForAutoEnrollment>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForCheckAndroidManagement>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForChromeDesktopReport>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForDeviceAttributeUpdate>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(
+ std::make_unique<RequestHandlerForDeviceAttributeUpdatePermission>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(
+ std::make_unique<RequestHandlerForDeviceInitialEnrollmentState>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForDeviceStateRetrieval>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForPolicy>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForPsmAutoEnrollment>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForRegisterBrowser>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForRegisterCertBased>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForRegisterDeviceAndUser>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForRemoteCommands>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForStatusUpload>(
+ client_storage_.get(), policy_storage_.get()));
+ RegisterHandler(std::make_unique<RequestHandlerForUnregister>(
+ client_storage_.get(), policy_storage_.get()));
+
+ http_server_.RegisterDefaultHandler(base::BindRepeating(
+ &EmbeddedPolicyTestServer::HandleRequest, base::Unretained(this)));
+}
+
+EmbeddedPolicyTestServer::~EmbeddedPolicyTestServer() = default;
+
+bool EmbeddedPolicyTestServer::Start() {
+ return http_server_.Start();
+}
+
+GURL EmbeddedPolicyTestServer::GetServiceURL() const {
+ return http_server_.GetURL("/device_management");
+}
+
+void EmbeddedPolicyTestServer::RegisterHandler(
+ std::unique_ptr<EmbeddedPolicyTestServer::RequestHandler> request_handler) {
+ request_handlers_[request_handler->RequestType()] =
+ std::move(request_handler);
+}
+
+void EmbeddedPolicyTestServer::ConfigureRequestError(
+ const std::string& request_type,
+ net::HttpStatusCode error_code) {
+ RegisterHandler(std::make_unique<FailingRequestHandler>(
+ client_storage_.get(), policy_storage_.get(), request_type, error_code));
+}
+
+#if !BUILDFLAG(IS_ANDROID)
+void EmbeddedPolicyTestServer::UpdateExternalPolicy(
+ const std::string& type,
+ const std::string& entity_id,
+ const std::string& raw_policy) {
+ // Register raw policy to be served by external endpoint.
+ policy_storage()->SetExternalPolicyPayload(type, entity_id, raw_policy);
+
+ // Register proto policy with details on how to fetch the raw policy.
+ GURL external_policy_url = http_server_.GetURL(kExternalPolicyDataPath);
+ external_policy_url = net::AppendOrReplaceQueryParameter(
+ external_policy_url, kExternalPolicyTypeParam, type);
+ external_policy_url = net::AppendOrReplaceQueryParameter(
+ external_policy_url, kExternalEntityIdParam, entity_id);
+
+ enterprise_management::ExternalPolicyData external_policy_data;
+ external_policy_data.set_download_url(external_policy_url.spec());
+ external_policy_data.set_secure_hash(crypto::SHA256HashString(raw_policy));
+ policy_storage()->SetPolicyPayload(type, entity_id,
+ external_policy_data.SerializeAsString());
+}
+#endif // !BUILDFLAG(IS_ANDROID)
+
+std::unique_ptr<HttpResponse> EmbeddedPolicyTestServer::HandleRequest(
+ const HttpRequest& request) {
+ GURL url = request.GetURL();
+ DLOG(INFO) << "Request URL: " << url;
+
+ if (url.path() == kExternalPolicyDataPath)
+ return HandleExternalPolicyDataRequest(url);
+
+ std::string request_type = KeyValueFromUrl(url, dm_protocol::kParamRequest);
+ auto it = request_handlers_.find(request_type);
+ if (it == request_handlers_.end()) {
+ LOG(ERROR) << "No request handler for: " << url;
+ return nullptr;
+ }
+
+ if (!MeetsServerSideRequirements(url)) {
+ return LogStatusAndReturn(
+ url, CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ "URL must define device type, app type, and device id."));
+ }
+
+ return LogStatusAndReturn(url, it->second->HandleRequest(request));
+}
+
+std::unique_ptr<HttpResponse>
+EmbeddedPolicyTestServer::HandleExternalPolicyDataRequest(const GURL& url) {
+ DCHECK_EQ(url.path(), kExternalPolicyDataPath);
+ std::string policy_type = KeyValueFromUrl(url, kExternalPolicyTypeParam);
+ std::string entity_id = KeyValueFromUrl(url, kExternalEntityIdParam);
+ std::string policy_payload =
+ policy_storage_->GetExternalPolicyPayload(policy_type, entity_id);
+ std::unique_ptr<HttpResponse> response;
+ if (policy_payload.empty()) {
+ response = CreateHttpResponse(
+ net::HTTP_NOT_FOUND,
+ "No external policy payload for specified policy type and entity ID");
+ } else {
+ response = CreateHttpResponse(net::HTTP_OK, policy_payload);
+ }
+ return LogStatusAndReturn(url, std::move(response));
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/embedded_policy_test_server.h b/chromium/components/policy/test_support/embedded_policy_test_server.h
new file mode 100644
index 00000000000..736b7cb7e3a
--- /dev/null
+++ b/chromium/components/policy/test_support/embedded_policy_test_server.h
@@ -0,0 +1,114 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace test_server {
+class HttpResponse;
+struct HttpRequest;
+} // namespace test_server
+} // namespace net
+
+namespace policy {
+
+class ClientStorage;
+class PolicyStorage;
+
+extern const char kFakeDeviceToken[];
+extern const char kInvalidEnrollmentToken[];
+
+// Runs a fake implementation of the cloud policy server on the local machine.
+class EmbeddedPolicyTestServer {
+ public:
+ class RequestHandler {
+ public:
+ RequestHandler(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ virtual ~RequestHandler();
+
+ // Returns the value associated with the "request_type" query param handled
+ // by this request handler.
+ virtual std::string RequestType() = 0;
+
+ // Returns a response if this request can be handled by this handler, or
+ // nullptr otherwise.
+ virtual std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) = 0;
+
+ const ClientStorage* client_storage() const { return client_storage_; }
+ ClientStorage* client_storage() { return client_storage_; }
+
+ const PolicyStorage* policy_storage() const { return policy_storage_; }
+ PolicyStorage* policy_storage() { return policy_storage_; }
+
+ private:
+ raw_ptr<ClientStorage> client_storage_;
+ raw_ptr<PolicyStorage> policy_storage_;
+ };
+
+ EmbeddedPolicyTestServer();
+ EmbeddedPolicyTestServer(const EmbeddedPolicyTestServer&) = delete;
+ EmbeddedPolicyTestServer& operator=(const EmbeddedPolicyTestServer&) = delete;
+ virtual ~EmbeddedPolicyTestServer();
+
+ // Initializes and waits until the server is ready to accept requests.
+ bool Start();
+
+ ClientStorage* client_storage() const { return client_storage_.get(); }
+
+ PolicyStorage* policy_storage() const { return policy_storage_.get(); }
+
+ // Returns the service URL.
+ GURL GetServiceURL() const;
+
+ // Public so it can be used by tests.
+ void RegisterHandler(std::unique_ptr<EmbeddedPolicyTestServer::RequestHandler>
+ request_handler);
+
+ // Configures requests of a given |request_type| to always fail with
+ // |error_code|.
+ void ConfigureRequestError(const std::string& request_type,
+ net::HttpStatusCode error_code);
+
+#if !BUILDFLAG(IS_ANDROID)
+ // Updates policy selected by |type| and optional |entity_id|. The
+ // |raw_policy| is served via an external endpoint. This does not trigger
+ // policy invalidation, hence test authors must manually trigger a policy
+ // fetch.
+ void UpdateExternalPolicy(const std::string& type,
+ const std::string& entity_id,
+ const std::string& raw_policy);
+#endif // !BUILDFLAG(IS_ANDROID)
+
+ private:
+ // Default request handler.
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request);
+
+ // Request handler for external policy data.
+ std::unique_ptr<net::test_server::HttpResponse>
+ HandleExternalPolicyDataRequest(const GURL& request);
+
+ net::test_server::EmbeddedTestServer http_server_;
+ std::map<std::string, std::unique_ptr<RequestHandler>> request_handlers_;
+ std::unique_ptr<ClientStorage> client_storage_;
+ std::unique_ptr<PolicyStorage> policy_storage_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_H_
diff --git a/chromium/components/policy/test_support/embedded_policy_test_server_test_base.cc b/chromium/components/policy/test_support/embedded_policy_test_server_test_base.cc
new file mode 100644
index 00000000000..6d1582f8d31
--- /dev/null
+++ b/chromium/components/policy/test_support/embedded_policy_test_server_test_base.cc
@@ -0,0 +1,181 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/run_loop.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "net/base/url_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_shared_url_loader_factory.h"
+
+using enterprise_management::DeviceManagementResponse;
+
+namespace policy {
+
+EmbeddedPolicyTestServerTestBase::EmbeddedPolicyTestServerTestBase()
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
+ url_loader_factory_(
+ base::MakeRefCounted<network::TestSharedURLLoaderFactory>()) {}
+
+EmbeddedPolicyTestServerTestBase::~EmbeddedPolicyTestServerTestBase() = default;
+
+void EmbeddedPolicyTestServerTestBase::SetUp() {
+ test_server_.Start();
+
+ resource_request_ = std::make_unique<network::ResourceRequest>();
+ resource_request_->method = net::HttpRequestHeaders::kPostMethod;
+ resource_request_->url = test_server_.GetServiceURL();
+}
+
+void EmbeddedPolicyTestServerTestBase::AddQueryParam(const std::string& key,
+ const std::string& value) {
+ CHECK(resource_request_);
+ CHECK(!key.empty());
+ CHECK(!value.empty());
+
+ resource_request_->url =
+ net::AppendQueryParameter(resource_request_->url, key, value);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetURL(const GURL& url) {
+ resource_request_->url = url;
+}
+
+void EmbeddedPolicyTestServerTestBase::SetMethod(const std::string& method) {
+ resource_request_->method = method;
+}
+
+void EmbeddedPolicyTestServerTestBase::SetAppType(const std::string& app_type) {
+ AddQueryParam(dm_protocol::kParamAppType, app_type);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetDeviceIdParam(
+ const std::string& device_id) {
+ AddQueryParam(dm_protocol::kParamDeviceID, device_id);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetDeviceType(
+ const std::string& device_type) {
+ AddQueryParam(dm_protocol::kParamDeviceType, device_type);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetOAuthToken(
+ const std::string& oauth_token) {
+ AddQueryParam(dm_protocol::kParamOAuthToken, oauth_token);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetRequestTypeParam(
+ const std::string& request_type) {
+ AddQueryParam(dm_protocol::kParamRequest, request_type);
+}
+
+void EmbeddedPolicyTestServerTestBase::SetEnrollmentTokenHeader(
+ const std::string& enrollment_token) {
+ CHECK(resource_request_);
+ CHECK(!enrollment_token.empty());
+
+ resource_request_->headers.SetHeader(
+ dm_protocol::kAuthHeader,
+ std::string(dm_protocol::kEnrollmentTokenAuthHeaderPrefix)
+ .append(enrollment_token));
+}
+
+void EmbeddedPolicyTestServerTestBase::SetDeviceTokenHeader(
+ const std::string& device_token) {
+ CHECK(resource_request_);
+ CHECK(!device_token.empty());
+
+ resource_request_->headers.SetHeader(
+ dm_protocol::kAuthHeader,
+ std::string(dm_protocol::kDMTokenAuthHeaderPrefix).append(device_token));
+}
+
+void EmbeddedPolicyTestServerTestBase::SetGoogleLoginTokenHeader(
+ const std::string& user_email) {
+ CHECK(resource_request_);
+ CHECK(!user_email.empty());
+
+ resource_request_->headers.SetHeader(
+ dm_protocol::kAuthHeader,
+ std::string(dm_protocol::kServiceTokenAuthHeaderPrefix)
+ .append(user_email));
+}
+
+void EmbeddedPolicyTestServerTestBase::SetPayload(
+ const enterprise_management::DeviceManagementRequest&
+ device_management_request) {
+ CHECK(payload_.empty());
+
+ payload_ = device_management_request.SerializeAsString();
+}
+
+void EmbeddedPolicyTestServerTestBase::StartRequestAndWait() {
+ CHECK(resource_request_);
+ CHECK(!url_loader_);
+
+ url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request_),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ if (!payload_.empty())
+ url_loader_->AttachStringForUpload(payload_, "application/protobuf");
+
+ base::RunLoop run_loop;
+ url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ url_loader_factory_.get(),
+ base::BindOnce(&EmbeddedPolicyTestServerTestBase::DownloadedToString,
+ base::Unretained(this), run_loop.QuitClosure()));
+ run_loop.Run();
+}
+
+void EmbeddedPolicyTestServerTestBase::DownloadedToString(
+ base::OnceClosure callback,
+ std::unique_ptr<std::string> response_body) {
+ CHECK(!done_);
+ CHECK(!response_body_);
+ CHECK(callback);
+
+ response_body_ = std::move(response_body);
+ done_ = true;
+ std::move(callback).Run();
+}
+
+int EmbeddedPolicyTestServerTestBase::GetResponseCode() const {
+ CHECK(done_);
+ CHECK(url_loader_->ResponseInfo());
+ CHECK(url_loader_->ResponseInfo()->headers);
+
+ return url_loader_->ResponseInfo()->headers->response_code();
+}
+
+bool EmbeddedPolicyTestServerTestBase::HasResponseBody() const {
+ return response_body_ != nullptr;
+}
+
+std::string EmbeddedPolicyTestServerTestBase::GetResponseBody() const {
+ CHECK(response_body_);
+
+ return *response_body_;
+}
+
+DeviceManagementResponse
+EmbeddedPolicyTestServerTestBase::GetDeviceManagementResponse() const {
+ CHECK(response_body_);
+
+ DeviceManagementResponse device_management_response;
+ device_management_response.ParseFromString(*response_body_);
+ return device_management_response;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/embedded_policy_test_server_test_base.h b/chromium/components/policy/test_support/embedded_policy_test_server_test_base.h
new file mode 100644
index 00000000000..6e90eaca0d6
--- /dev/null
+++ b/chromium/components/policy/test_support/embedded_policy_test_server_test_base.h
@@ -0,0 +1,91 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_TEST_BASE_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_TEST_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+struct ResourceRequest;
+} // namespace network
+
+namespace policy {
+
+class ClientStorage;
+class PolicyStorage;
+
+class EmbeddedPolicyTestServerTestBase : public testing::Test {
+ public:
+ EmbeddedPolicyTestServerTestBase();
+ EmbeddedPolicyTestServerTestBase(const EmbeddedPolicyTestServerTestBase&) =
+ delete;
+ EmbeddedPolicyTestServerTestBase& operator=(
+ const EmbeddedPolicyTestServerTestBase&) = delete;
+ ~EmbeddedPolicyTestServerTestBase() override;
+
+ void SetUp() override;
+
+ // Helper functions to set request components.
+ void SetURL(const GURL& url);
+ void SetMethod(const std::string& method);
+ void SetAppType(const std::string& app_type);
+ void SetDeviceIdParam(const std::string& device_id);
+ void SetDeviceType(const std::string& device_type);
+ void SetOAuthToken(const std::string& oauth_token);
+ void SetRequestTypeParam(const std::string& request_type);
+ void SetEnrollmentTokenHeader(const std::string& enrollment_token);
+ void SetDeviceTokenHeader(const std::string& device_token);
+ void SetGoogleLoginTokenHeader(const std::string& user_email);
+ void SetPayload(const enterprise_management::DeviceManagementRequest&
+ device_management_request);
+
+ // Makes a request to the test server and waits until a response is ready.
+ void StartRequestAndWait();
+
+ // Helper functions for accessing response data.
+ int GetResponseCode() const;
+ bool HasResponseBody() const;
+ std::string GetResponseBody() const;
+ enterprise_management::DeviceManagementResponse GetDeviceManagementResponse()
+ const;
+
+ EmbeddedPolicyTestServer* test_server() { return &test_server_; }
+
+ ClientStorage* client_storage() { return test_server_.client_storage(); }
+
+ PolicyStorage* policy_storage() { return test_server_.policy_storage(); }
+
+ private:
+ // Adds a query param to the |resource_request_|.
+ void AddQueryParam(const std::string& key, const std::string& value);
+
+ // Callback to be provided for the network request. Invokes |callback| when
+ // done.
+ void DownloadedToString(base::OnceClosure callback,
+ std::unique_ptr<std::string> response_body);
+
+ base::test::TaskEnvironment task_environment_;
+ EmbeddedPolicyTestServer test_server_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+ std::unique_ptr<network::ResourceRequest> resource_request_;
+ std::string payload_;
+ std::unique_ptr<std::string> response_body_;
+ bool done_ = false;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_EMBEDDED_POLICY_TEST_SERVER_TEST_BASE_H_
diff --git a/chromium/components/policy/test_support/embedded_policy_test_server_unittest.cc b/chromium/components/policy/test_support/embedded_policy_test_server_unittest.cc
new file mode 100644
index 00000000000..63da4dad304
--- /dev/null
+++ b/chromium/components/policy/test_support/embedded_policy_test_server_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#if !BUILDFLAG(IS_ANDROID)
+#include "components/policy/proto/chrome_extension_policy.pb.h"
+#endif // !BUILDFLAG(IS_ANDROID)
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kFakeDeviceId[] = "fake_device_id";
+constexpr char kFakeRequestType[] = "fake_request_type";
+constexpr char kInvalidRequestType[] = "invalid_request_type";
+constexpr char kResponseBodyYay[] = "Yay!!!";
+#if !BUILDFLAG(IS_ANDROID)
+constexpr char kFakeExtensionId[] = "fake_extension_id";
+constexpr char kRawPolicyPayload[] = R"({"foo": "bar"})";
+constexpr base::StringPiece kSHA256HashForRawPolicyPayload(
+ "\x42\x6f\xc0\x4f\x04\xbf\x8f\xdb\x58\x31\xdc\x37\xbb\xb6\xdc\xf7\x0f\x63"
+ "\xa3\x7e\x05\xa6\x8c\x6e\xa5\xf6\x3e\x85\xae\x57\x93\x76",
+ 32);
+#endif // !BUILDFLAG(IS_ANDROID)
+
+class FakeRequestHandler : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ FakeRequestHandler() : RequestHandler(nullptr, nullptr) {}
+ ~FakeRequestHandler() override = default;
+
+ std::string RequestType() override { return kFakeRequestType; }
+
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override {
+ return CreateHttpResponse(net::HTTP_OK, kResponseBodyYay);
+ }
+};
+
+} // namespace
+
+class EmbeddedPolicyTestServerTest : public EmbeddedPolicyTestServerTestBase {
+ public:
+ EmbeddedPolicyTestServerTest() = default;
+ ~EmbeddedPolicyTestServerTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ test_server()->RegisterHandler(std::make_unique<FakeRequestHandler>());
+ }
+};
+
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_InvalidRequestType) {
+ SetRequestTypeParam(kInvalidRequestType);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_NOT_FOUND);
+}
+
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_Success) {
+ SetRequestTypeParam(kFakeRequestType);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kFakeDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ EXPECT_EQ(kResponseBodyYay, GetResponseBody());
+}
+
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_MissingAppType) {
+ SetRequestTypeParam(kFakeRequestType);
+ SetDeviceIdParam(kFakeDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_MissingDeviceId) {
+ SetRequestTypeParam(kFakeRequestType);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_MissingDeviceType) {
+ SetRequestTypeParam(kFakeRequestType);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kFakeDeviceId);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+#if !BUILDFLAG(IS_ANDROID)
+TEST_F(EmbeddedPolicyTestServerTest, HandleRequest_PolicyViaExternalEndpoint) {
+ test_server()->UpdateExternalPolicy(dm_protocol::kChromeExtensionPolicyType,
+ kFakeExtensionId, kRawPolicyPayload);
+
+ std::string policy_data = test_server()->policy_storage()->GetPolicyPayload(
+ dm_protocol::kChromeExtensionPolicyType, kFakeExtensionId);
+ ASSERT_FALSE(policy_data.empty());
+ enterprise_management::ExternalPolicyData data;
+ ASSERT_TRUE(data.ParseFromString(policy_data));
+ EXPECT_EQ(data.secure_hash(), kSHA256HashForRawPolicyPayload);
+ ASSERT_TRUE(data.has_download_url());
+
+ SetMethod(net::HttpRequestHeaders::kGetMethod);
+ SetURL(GURL(data.download_url()));
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ EXPECT_EQ(GetResponseBody(), kRawPolicyPayload);
+}
+#endif // !BUILDFLAG(IS_ANDROID)
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/failing_request_handler.cc b/chromium/components/policy/test_support/failing_request_handler.cc
new file mode 100644
index 00000000000..0c39f976922
--- /dev/null
+++ b/chromium/components/policy/test_support/failing_request_handler.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/failing_request_handler.h"
+
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace policy {
+
+FailingRequestHandler::FailingRequestHandler(ClientStorage* client_storage,
+ PolicyStorage* policy_storage,
+ const std::string& request_type,
+ net::HttpStatusCode error_code)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage),
+ request_type_(request_type),
+ error_code_(error_code) {}
+
+FailingRequestHandler::~FailingRequestHandler() = default;
+
+std::string FailingRequestHandler::RequestType() {
+ return request_type_;
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+FailingRequestHandler::HandleRequest(
+ const net::test_server::HttpRequest& request) {
+ return CreateHttpResponse(error_code_, "Preconfigured error");
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/failing_request_handler.h b/chromium/components/policy/test_support/failing_request_handler.h
new file mode 100644
index 00000000000..7b5f2842f2d
--- /dev/null
+++ b/chromium/components/policy/test_support/failing_request_handler.h
@@ -0,0 +1,36 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_FAILING_REQUEST_HANDLER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_FAILING_REQUEST_HANDLER_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "net/http/http_status_code.h"
+
+namespace policy {
+
+// Handler that always returns specified error code for a given request type.
+class FailingRequestHandler : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ FailingRequestHandler(ClientStorage* client_storage,
+ PolicyStorage* policy_storage,
+ const std::string& request_type,
+ net::HttpStatusCode error_code);
+ FailingRequestHandler(FailingRequestHandler&& handler) = delete;
+ FailingRequestHandler& operator=(FailingRequestHandler&& handler) = delete;
+ ~FailingRequestHandler() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+
+ private:
+ std::string request_type_;
+ net::HttpStatusCode error_code_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_FAILING_REQUEST_HANDLER_H_
diff --git a/chromium/components/policy/test_support/failing_request_handler_unittest.cc b/chromium/components/policy/test_support/failing_request_handler_unittest.cc
new file mode 100644
index 00000000000..373352bfc7a
--- /dev/null
+++ b/chromium/components/policy/test_support/failing_request_handler_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/failing_request_handler.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class FailingRequestHandlerTest : public EmbeddedPolicyTestServerTestBase {
+ public:
+ FailingRequestHandlerTest() = default;
+ ~FailingRequestHandlerTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestRegister);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(FailingRequestHandlerTest, HandleRequest) {
+ test_server()->ConfigureRequestError(dm_protocol::kValueRequestRegister,
+ net::HTTP_METHOD_NOT_ALLOWED);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_METHOD_NOT_ALLOWED);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/policy_storage.cc b/chromium/components/policy/test_support/policy_storage.cc
new file mode 100644
index 00000000000..bb6643c18e9
--- /dev/null
+++ b/chromium/components/policy/test_support/policy_storage.cc
@@ -0,0 +1,128 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/policy_storage.h"
+#include "base/big_endian.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "crypto/sha2.h"
+
+namespace policy {
+
+namespace {
+
+const char kPolicyKeySeparator[] = "/";
+
+std::string GetPolicyKey(const std::string& policy_type,
+ const std::string& entity_id) {
+ if (entity_id.empty())
+ return policy_type;
+ return base::StrCat({policy_type, kPolicyKeySeparator, entity_id});
+}
+
+} // namespace
+
+PolicyStorage::PolicyStorage()
+ : signature_provider_(std::make_unique<SignatureProvider>()) {}
+
+PolicyStorage::PolicyStorage(PolicyStorage&& policy_storage) = default;
+
+PolicyStorage& PolicyStorage::operator=(PolicyStorage&& policy_storage) =
+ default;
+
+PolicyStorage::~PolicyStorage() = default;
+
+std::string PolicyStorage::GetPolicyPayload(
+ const std::string& policy_type,
+ const std::string& entity_id) const {
+ auto it = policy_payloads_.find(GetPolicyKey(policy_type, entity_id));
+ return it == policy_payloads_.end() ? std::string() : it->second;
+}
+
+std::vector<std::string> PolicyStorage::GetEntityIdsForType(
+ const std::string& policy_type) {
+ std::string prefix = base::StrCat({policy_type, kPolicyKeySeparator});
+ std::vector<std::string> ids;
+ const size_t prefix_length = prefix.length();
+ for (const auto& [policy_key, payload] : policy_payloads_) {
+ if (base::StartsWith(policy_key, prefix))
+ ids.push_back(policy_key.substr(prefix_length));
+ }
+ return ids;
+}
+
+void PolicyStorage::SetPolicyPayload(const std::string& policy_type,
+ const std::string& policy_payload) {
+ SetPolicyPayload(policy_type, std::string(), policy_payload);
+}
+
+void PolicyStorage::SetPolicyPayload(const std::string& policy_type,
+ const std::string& entity_id,
+ const std::string& policy_payload) {
+ policy_payloads_[GetPolicyKey(policy_type, entity_id)] = policy_payload;
+}
+
+std::string PolicyStorage::GetExternalPolicyPayload(
+ const std::string& policy_type,
+ const std::string& entity_id) {
+ std::string policy_key = GetPolicyKey(policy_type, entity_id);
+ return external_policy_payloads_.contains(policy_key)
+ ? external_policy_payloads_.at(policy_key)
+ : std::string();
+}
+
+void PolicyStorage::SetExternalPolicyPayload(
+ const std::string& policy_type,
+ const std::string& entity_id,
+ const std::string& policy_payload) {
+ external_policy_payloads_[GetPolicyKey(policy_type, entity_id)] =
+ policy_payload;
+}
+
+void PolicyStorage::SetPsmEntry(const std::string& brand_serial_id,
+ const PolicyStorage::PsmEntry& psm_entry) {
+ psm_entries_[brand_serial_id] = psm_entry;
+}
+
+const PolicyStorage::PsmEntry* PolicyStorage::GetPsmEntry(
+ const std::string& brand_serial_id) const {
+ auto it = psm_entries_.find(brand_serial_id);
+ if (it == psm_entries_.end())
+ return nullptr;
+ return &it->second;
+}
+
+void PolicyStorage::SetInitialEnrollmentState(
+ const std::string& brand_serial_id,
+ const PolicyStorage::InitialEnrollmentState& initial_enrollment_state) {
+ initial_enrollment_states_[brand_serial_id] = initial_enrollment_state;
+}
+
+const PolicyStorage::InitialEnrollmentState*
+PolicyStorage::GetInitialEnrollmentState(
+ const std::string& brand_serial_id) const {
+ auto it = initial_enrollment_states_.find(brand_serial_id);
+ if (it == initial_enrollment_states_.end())
+ return nullptr;
+ return &it->second;
+}
+
+std::vector<std::string> PolicyStorage::GetMatchingSerialHashes(
+ uint64_t modulus,
+ uint64_t remainder) const {
+ std::vector<std::string> hashes;
+ for (const auto& [serial, enrollment_state] : initial_enrollment_states_) {
+ uint64_t hash = 0UL;
+ uint8_t hash_bytes[sizeof(hash)];
+ crypto::SHA256HashString(serial, hash_bytes, sizeof(hash));
+ base::ReadBigEndian(hash_bytes, &hash);
+ if (hash % modulus == remainder) {
+ hashes.emplace_back(reinterpret_cast<const char*>(hash_bytes),
+ sizeof(hash));
+ }
+ }
+ return hashes;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/policy_storage.h b/chromium/components/policy/test_support/policy_storage.h
new file mode 100644
index 00000000000..bbb831862da
--- /dev/null
+++ b/chromium/components/policy/test_support/policy_storage.h
@@ -0,0 +1,200 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_POLICY_STORAGE_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_POLICY_STORAGE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/time/time.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/signature_provider.h"
+
+namespace policy {
+
+class SignatureProvider;
+
+// Stores preferences about policies to be applied to registered browsers.
+class PolicyStorage {
+ public:
+ PolicyStorage();
+ PolicyStorage(PolicyStorage&& policy_storage);
+ PolicyStorage& operator=(PolicyStorage&& policy_storage);
+ virtual ~PolicyStorage();
+
+ // Returns the serialized proto associated with |policy_type| and optional
+ // |entity_id|. Returns empty string if there is no such association.
+ std::string GetPolicyPayload(
+ const std::string& policy_type,
+ const std::string& entity_id = std::string()) const;
+ std::vector<std::string> GetEntityIdsForType(const std::string& policy_type);
+
+ // Associates the serialized proto stored in |policy_payload| with
+ // |policy_type| and optional |entity_id|.
+ void SetPolicyPayload(const std::string& policy_type,
+ const std::string& policy_payload);
+ void SetPolicyPayload(const std::string& policy_type,
+ const std::string& entity_id,
+ const std::string& policy_payload);
+
+ // Returns the raw payload to be served by an external endpoint and associated
+ // with |policy_type| and optional |entity_id|. Returns empty string if there
+ // is no such association.
+ std::string GetExternalPolicyPayload(const std::string& policy_type,
+ const std::string& entity_id);
+
+ // Associates the |raw_payload| to be served via an external endpoint with
+ // |policy_type| and optional |entity_id|.
+ void SetExternalPolicyPayload(const std::string& policy_type,
+ const std::string& entity_id,
+ const std::string& raw_payload);
+
+ SignatureProvider* signature_provider() const {
+ return signature_provider_.get();
+ }
+ void set_signature_provider(
+ std::unique_ptr<SignatureProvider> signature_provider) {
+ signature_provider_ = std::move(signature_provider);
+ }
+
+ const std::string& robot_api_auth_code() const {
+ return robot_api_auth_code_;
+ }
+ void set_robot_api_auth_code(const std::string& robot_api_auth_code) {
+ robot_api_auth_code_ = robot_api_auth_code;
+ }
+
+ bool has_kiosk_license() const { return has_kiosk_license_; }
+ void set_has_kiosk_license(bool has_kiosk_license) {
+ has_kiosk_license_ = has_kiosk_license;
+ }
+
+ bool has_enterprise_license() const { return has_enterprise_license_; }
+ void set_has_enterprise_license(bool has_enterprise_license) {
+ has_enterprise_license_ = has_enterprise_license;
+ }
+
+ const std::string& service_account_identity() const {
+ return service_account_identity_;
+ }
+ void set_service_account_identity(
+ const std::string& service_account_identity) {
+ service_account_identity_ = service_account_identity;
+ }
+
+ const base::flat_set<std::string>& managed_users() const {
+ return managed_users_;
+ }
+ void add_managed_user(const std::string& managed_user) {
+ managed_users_.insert(managed_user);
+ }
+
+ std::string policy_user() const { return policy_user_; }
+ void set_policy_user(const std::string& policy_user) {
+ policy_user_ = policy_user;
+ }
+
+ const std::string& policy_invalidation_topic() const {
+ return policy_invalidation_topic_;
+ }
+ void set_policy_invalidation_topic(
+ const std::string& policy_invalidation_topic) {
+ policy_invalidation_topic_ = policy_invalidation_topic;
+ }
+
+ base::Time timestamp() const { return timestamp_; }
+ void set_timestamp(const base::Time& timestamp) { timestamp_ = timestamp; }
+
+ bool allow_set_device_attributes() { return allow_set_device_attributes_; }
+ void set_allow_set_device_attributes(bool allow_set_device_attributes) {
+ allow_set_device_attributes_ = allow_set_device_attributes;
+ }
+
+ struct DeviceState {
+ std::string management_domain;
+ enterprise_management::DeviceStateRetrievalResponse::RestoreMode
+ restore_mode;
+ };
+
+ const DeviceState& device_state() { return device_state_; }
+ void set_device_state(const DeviceState& device_state) {
+ device_state_ = device_state;
+ }
+
+ struct PsmEntry {
+ int psm_execution_result;
+ int64_t psm_determination_timestamp;
+ };
+
+ void SetPsmEntry(const std::string& brand_serial_id,
+ const PsmEntry& psm_entry);
+
+ const PsmEntry* GetPsmEntry(const std::string& brand_serial_id) const;
+
+ struct InitialEnrollmentState {
+ enterprise_management::DeviceInitialEnrollmentStateResponse::
+ InitialEnrollmentMode initial_enrollment_mode;
+ std::string management_domain;
+ };
+
+ void SetInitialEnrollmentState(
+ const std::string& brand_serial_id,
+ const InitialEnrollmentState& initial_enrollment_state);
+
+ const InitialEnrollmentState* GetInitialEnrollmentState(
+ const std::string& brand_serial_id) const;
+
+ // Returns hashes for brand serial IDs whose initial enrollment state is
+ // registered on the server. Only hashes, which, when divied by |modulus|,
+ // result in the specified |remainder|, are returned.
+ std::vector<std::string> GetMatchingSerialHashes(uint64_t modulus,
+ uint64_t remainder) const;
+
+ private:
+ // Maps policy keys to a serialized proto representing the policies to be
+ // applied for the type (e.g. CloudPolicySettings, ChromeDeviceSettingsProto).
+ base::flat_map<std::string, std::string> policy_payloads_;
+
+ // Maps policy keys to a raw policy data served via an external endpoint.
+ base::flat_map<std::string, std::string> external_policy_payloads_;
+
+ std::unique_ptr<SignatureProvider> signature_provider_;
+
+ std::string robot_api_auth_code_;
+
+ std::string service_account_identity_;
+
+ base::flat_set<std::string> managed_users_;
+
+ std::string policy_user_;
+
+ std::string policy_invalidation_topic_;
+
+ base::Time timestamp_;
+
+ bool allow_set_device_attributes_ = true;
+
+ DeviceState device_state_;
+
+ bool has_kiosk_license_ = true;
+
+ bool has_enterprise_license_ = true;
+
+ // Maps brand serial ID to PsmEntry.
+ base::flat_map<std::string, PsmEntry> psm_entries_;
+
+ // Maps brand serial ID to InitialEnrollmentState.
+ base::flat_map<std::string, InitialEnrollmentState>
+ initial_enrollment_states_;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_POLICY_STORAGE_H_
diff --git a/chromium/components/policy/test_support/policy_storage_unittest.cc b/chromium/components/policy/test_support/policy_storage_unittest.cc
new file mode 100644
index 00000000000..d735a4b158d
--- /dev/null
+++ b/chromium/components/policy/test_support/policy_storage_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/policy_storage.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr const char kFakePolicyType[] = "fake_policy_type";
+constexpr const char kFakeEntityId[] = "fake_entity_id";
+constexpr const char kRawPolicyPayload[] = R"({"foo": "bar"})";
+
+} // namespace
+
+TEST(PolicyStorageTest, StoresExternalPolicyData) {
+ PolicyStorage policy_storage;
+ policy_storage.SetExternalPolicyPayload(kFakePolicyType, kFakeEntityId,
+ kRawPolicyPayload);
+
+ EXPECT_EQ(
+ policy_storage.GetExternalPolicyPayload(kFakePolicyType, kFakeEntityId),
+ kRawPolicyPayload);
+ // Check that external policy payloads are stored separately from regular
+ // policy payloads.
+ EXPECT_THAT(policy_storage.GetPolicyPayload(kFakePolicyType, kFakeEntityId),
+ testing::IsEmpty());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_api_authorization.cc b/chromium/components/policy/test_support/request_handler_for_api_authorization.cc
new file mode 100644
index 00000000000..e9e37987b87
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_api_authorization.cc
@@ -0,0 +1,44 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_api_authorization.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForApiAuthorization::RequestHandlerForApiAuthorization(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForApiAuthorization::~RequestHandlerForApiAuthorization() =
+ default;
+
+std::string RequestHandlerForApiAuthorization::RequestType() {
+ return dm_protocol::kValueRequestApiAuthorization;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForApiAuthorization::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementResponse device_management_response;
+ device_management_response.mutable_service_api_access_response()
+ ->set_auth_code(policy_storage()->robot_api_auth_code());
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_api_authorization.h b/chromium/components/policy/test_support/request_handler_for_api_authorization.h
new file mode 100644
index 00000000000..cc230b21c9a
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_api_authorization.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_API_AUTHORIZATION_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_API_AUTHORIZATION_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `api_authorization`.
+class RequestHandlerForApiAuthorization
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForApiAuthorization(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForApiAuthorization(
+ RequestHandlerForApiAuthorization&& handler) = delete;
+ RequestHandlerForApiAuthorization& operator=(
+ RequestHandlerForApiAuthorization&& handler) = delete;
+ ~RequestHandlerForApiAuthorization() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_API_AUTHORIZATION_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_api_authorization_unittest.cc b/chromium/components/policy/test_support/request_handler_for_api_authorization_unittest.cc
new file mode 100644
index 00000000000..99d68184c05
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_api_authorization_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_api_authorization.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kRobotApiAuthCode[] = "fake_auth_code";
+
+} // namespace
+
+class RequestHandlerForApiAuthorizationTest
+ : public EmbeddedPolicyTestServerTestBase {
+ public:
+ RequestHandlerForApiAuthorizationTest() = default;
+ ~RequestHandlerForApiAuthorizationTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestApiAuthorization);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForApiAuthorizationTest, HandleRequest) {
+ policy_storage()->set_robot_api_auth_code(kRobotApiAuthCode);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(response.service_api_access_response().auth_code(),
+ kRobotApiAuthCode);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_auto_enrollment.cc b/chromium/components/policy/test_support/request_handler_for_auto_enrollment.cc
new file mode 100644
index 00000000000..6c4a5055f60
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_auto_enrollment.cc
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_auto_enrollment.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+void AddHashes(const std::vector<std::string>& hashes,
+ em::DeviceAutoEnrollmentResponse* response) {
+ for (const std::string& hash : hashes)
+ *response->add_hashes() = hash;
+}
+
+} // namespace
+
+RequestHandlerForAutoEnrollment::RequestHandlerForAutoEnrollment(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForAutoEnrollment::~RequestHandlerForAutoEnrollment() = default;
+
+std::string RequestHandlerForAutoEnrollment::RequestType() {
+ return dm_protocol::kValueRequestAutoEnrollment;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForAutoEnrollment::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::DeviceAutoEnrollmentRequest& enrollment_request =
+ device_management_request.auto_enrollment_request();
+
+ em::DeviceManagementResponse device_management_response;
+ em::DeviceAutoEnrollmentResponse* enrollment_response =
+ device_management_response.mutable_auto_enrollment_response();
+ switch (enrollment_request.modulus()) {
+ case 1:
+ if (enrollment_request.enrollment_check_type() ==
+ enterprise_management::DeviceAutoEnrollmentRequest::
+ ENROLLMENT_CHECK_TYPE_FRE) {
+ AddHashes(
+ client_storage()->GetMatchingStateKeyHashes(
+ enrollment_request.modulus(), enrollment_request.remainder()),
+ enrollment_response);
+ } else if (enrollment_request.enrollment_check_type() ==
+ enterprise_management::DeviceAutoEnrollmentRequest::
+ ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT) {
+ AddHashes(
+ policy_storage()->GetMatchingSerialHashes(
+ enrollment_request.modulus(), enrollment_request.remainder()),
+ enrollment_response);
+ }
+ break;
+ case 32:
+ enrollment_response->set_expected_modulus(1);
+ break;
+ }
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_auto_enrollment.h b/chromium/components/policy/test_support/request_handler_for_auto_enrollment.h
new file mode 100644
index 00000000000..9e13d65b304
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_auto_enrollment.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_AUTO_ENROLLMENT_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_AUTO_ENROLLMENT_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `enterprise_check`.
+class RequestHandlerForAutoEnrollment
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForAutoEnrollment(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForAutoEnrollment(RequestHandlerForAutoEnrollment&& handler) =
+ delete;
+ RequestHandlerForAutoEnrollment& operator=(
+ RequestHandlerForAutoEnrollment&& handler) = delete;
+ ~RequestHandlerForAutoEnrollment() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_AUTO_ENROLLMENT_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_auto_enrollment_unittest.cc b/chromium/components/policy/test_support/request_handler_for_auto_enrollment_unittest.cc
new file mode 100644
index 00000000000..80ceb08b5cc
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_auto_enrollment_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+using testing::IsEmpty;
+using testing::UnorderedElementsAreArray;
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kBrandSerial[] = "ABC21345748";
+constexpr char kTrimmedSHA256HashForBrandSerial[] =
+ "\x8C\xB0\x9C\xC2\x11\x70\x9B\xB1";
+constexpr char kStateKey1[] = "fake_state_key_1";
+constexpr char kStateKey2[] = "fake_state_key_2";
+constexpr char kSHA256HashForStateKey1[] =
+ "\xB0\x58\x21\x15\x1E\xF5\xEE\x95\x50\xE7\x7D\xB5\x62\x8F\x44\x5A\xE2\x83"
+ "\xB1\xD1\x2C\x87\x22\x85\x50\xF9\x9C\x33\x34\x0F\x42\x13";
+constexpr char kSHA256HashForStateKey2[] =
+ "\xBC\x11\x2A\x4D\x1A\x7F\xA8\x66\xCA\x4F\xF4\xD8\xC3\x0B\xC3\x5B\x83\x0A"
+ "\x82\xF1\x2C\x0C\x1A\xBE\x34\xA7\xAD\xF6\x29\x88\x18\x6D";
+
+} // namespace
+
+class RequestHandlerForAutoEnrollmentTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForAutoEnrollmentTest() = default;
+ ~RequestHandlerForAutoEnrollmentTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestAutoEnrollment);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForAutoEnrollmentTest, HandleRequest_ForcedEnrollment) {
+ policy_storage()->SetInitialEnrollmentState(
+ kBrandSerial, PolicyStorage::InitialEnrollmentState{});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceAutoEnrollmentRequest* enrollment_request =
+ device_management_request.mutable_auto_enrollment_request();
+ enrollment_request->set_enrollment_check_type(
+ em::DeviceAutoEnrollmentRequest::ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT);
+ // This matches any serial hash since dividing by 1 always gives remainder 0.
+ enrollment_request->set_modulus(1);
+ enrollment_request->set_remainder(0);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_THAT(response.auto_enrollment_response().hashes(),
+ UnorderedElementsAreArray({kTrimmedSHA256HashForBrandSerial}));
+}
+
+TEST_F(RequestHandlerForAutoEnrollmentTest,
+ HandleRequest_ForcedEnrollmentMismatchingRemainder) {
+ policy_storage()->SetInitialEnrollmentState(
+ kBrandSerial, PolicyStorage::InitialEnrollmentState{});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceAutoEnrollmentRequest* enrollment_request =
+ device_management_request.mutable_auto_enrollment_request();
+ enrollment_request->set_enrollment_check_type(
+ em::DeviceAutoEnrollmentRequest::ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT);
+ // Set impossible remainder to ensure than no serial hash matches it.
+ enrollment_request->set_modulus(1);
+ enrollment_request->set_remainder(1);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_THAT(response.auto_enrollment_response().hashes(), IsEmpty());
+}
+
+TEST_F(RequestHandlerForAutoEnrollmentTest, HandleRequest_ForcedReEnrollment) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id = kDeviceId;
+ client_info.state_keys.push_back(kStateKey1);
+ client_info.state_keys.push_back(kStateKey2);
+ client_storage()->RegisterClient(client_info);
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceAutoEnrollmentRequest* enrollment_request =
+ device_management_request.mutable_auto_enrollment_request();
+ enrollment_request->set_enrollment_check_type(
+ em::DeviceAutoEnrollmentRequest::ENROLLMENT_CHECK_TYPE_FRE);
+ // This matches any serial hash since dividing by 1 always gives remainder 0.
+ enrollment_request->set_modulus(1);
+ enrollment_request->set_remainder(0);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_THAT(response.auto_enrollment_response().hashes(),
+ UnorderedElementsAreArray(
+ {kSHA256HashForStateKey1, kSHA256HashForStateKey2}));
+}
+
+TEST_F(RequestHandlerForAutoEnrollmentTest, HandleRequest_Modulus32) {
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceAutoEnrollmentRequest* enrollment_request =
+ device_management_request.mutable_auto_enrollment_request();
+ enrollment_request->set_modulus(32);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(response.auto_enrollment_response().expected_modulus(), 1);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_check_android_management.cc b/chromium/components/policy/test_support/request_handler_for_check_android_management.cc
new file mode 100644
index 00000000000..45eb5a13396
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_check_android_management.cc
@@ -0,0 +1,56 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_check_android_management.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+const char kManagedAuthToken[] = "managed-auth-token";
+const char kUnmanagedAuthToken[] = "unmanaged-auth-token";
+
+RequestHandlerForCheckAndroidManagement::
+ RequestHandlerForCheckAndroidManagement(ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForCheckAndroidManagement::
+ ~RequestHandlerForCheckAndroidManagement() = default;
+
+std::string RequestHandlerForCheckAndroidManagement::RequestType() {
+ return dm_protocol::kValueRequestCheckAndroidManagement;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForCheckAndroidManagement::HandleRequest(
+ const HttpRequest& request) {
+ std::string oauth_token;
+ net::GetValueForKeyInQuery(request.GetURL(), dm_protocol::kParamOAuthToken,
+ &oauth_token);
+
+ em::DeviceManagementResponse response;
+ response.mutable_check_android_management_response();
+ if (oauth_token == kManagedAuthToken)
+ return CreateHttpResponse(net::HTTP_CONFLICT, response.SerializeAsString());
+ if (oauth_token == kUnmanagedAuthToken)
+ return CreateHttpResponse(net::HTTP_OK, response.SerializeAsString());
+ return CreateHttpResponse(net::HTTP_FORBIDDEN, "Invalid OAuth token");
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_check_android_management.h b/chromium/components/policy/test_support/request_handler_for_check_android_management.h
new file mode 100644
index 00000000000..c7343d620d3
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_check_android_management.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHECK_ANDROID_MANAGEMENT_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHECK_ANDROID_MANAGEMENT_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Auth token for Android managed accounts.
+extern const char kManagedAuthToken[];
+// Auth token for other Android unmanaged accounts.
+extern const char kUnmanagedAuthToken[];
+
+// Handler for request type `check_android_management`.
+class RequestHandlerForCheckAndroidManagement
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForCheckAndroidManagement(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForCheckAndroidManagement(
+ RequestHandlerForCheckAndroidManagement&& handler) = delete;
+ RequestHandlerForCheckAndroidManagement& operator=(
+ RequestHandlerForCheckAndroidManagement&& handler) = delete;
+ ~RequestHandlerForCheckAndroidManagement() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHECK_ANDROID_MANAGEMENT_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_check_android_management_unittest.cc b/chromium/components/policy/test_support/request_handler_for_check_android_management_unittest.cc
new file mode 100644
index 00000000000..185867f83d8
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_check_android_management_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_check_android_management.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForCheckAndroidManagementTest
+ : public EmbeddedPolicyTestServerTestBase {
+ public:
+ RequestHandlerForCheckAndroidManagementTest() = default;
+ ~RequestHandlerForCheckAndroidManagementTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestCheckAndroidManagement);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForCheckAndroidManagementTest, HandleRequest_Success) {
+ SetOAuthToken(kUnmanagedAuthToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_TRUE(response.has_check_android_management_response());
+}
+
+TEST_F(RequestHandlerForCheckAndroidManagementTest, HandleRequest_Conflict) {
+ SetOAuthToken(kManagedAuthToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_CONFLICT);
+}
+
+TEST_F(RequestHandlerForCheckAndroidManagementTest, HandleRequest_Forbidden) {
+ SetOAuthToken("invalid-auth-token");
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_FORBIDDEN);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.cc b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.cc
new file mode 100644
index 00000000000..b8102ad4663
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.cc
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_chrome_desktop_report.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForChromeDesktopReport::RequestHandlerForChromeDesktopReport(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForChromeDesktopReport::~RequestHandlerForChromeDesktopReport() =
+ default;
+
+std::string RequestHandlerForChromeDesktopReport::RequestType() {
+ return dm_protocol::kValueRequestChromeDesktopReport;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForChromeDesktopReport::HandleRequest(
+ const HttpRequest& request) {
+ // Empty responses indicate a successful upload.
+ em::DeviceManagementResponse device_management_response;
+ device_management_response.mutable_chrome_desktop_report_response();
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.h b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.h
new file mode 100644
index 00000000000..15d572cafdc
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHROME_DESKTOP_REPORT_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHROME_DESKTOP_REPORT_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `chrome_desktop_report`.
+class RequestHandlerForChromeDesktopReport
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForChromeDesktopReport(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForChromeDesktopReport(
+ RequestHandlerForChromeDesktopReport&& handler) = delete;
+ RequestHandlerForChromeDesktopReport& operator=(
+ RequestHandlerForChromeDesktopReport&& handler) = delete;
+ ~RequestHandlerForChromeDesktopReport() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_CHROME_DESKTOP_REPORT_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report_unittest.cc b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report_unittest.cc
new file mode 100644
index 00000000000..119e8249cfa
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_chrome_desktop_report_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_chrome_desktop_report.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForChromeDesktopReportTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForChromeDesktopReportTest() = default;
+ ~RequestHandlerForChromeDesktopReportTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestChromeDesktopReport);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForChromeDesktopReportTest, HandleRequest_Success) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_TRUE(response.has_chrome_desktop_report_response());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update.cc b/chromium/components/policy/test_support/request_handler_for_device_attribute_update.cc
new file mode 100644
index 00000000000..1d98d40884e
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_device_attribute_update.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForDeviceAttributeUpdate::RequestHandlerForDeviceAttributeUpdate(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForDeviceAttributeUpdate::
+ ~RequestHandlerForDeviceAttributeUpdate() = default;
+
+std::string RequestHandlerForDeviceAttributeUpdate::RequestType() {
+ return dm_protocol::kValueRequestDeviceAttributeUpdate;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForDeviceAttributeUpdate::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementResponse response;
+ response.mutable_device_attribute_update_response()->set_result(
+ em::DeviceAttributeUpdateResponse::ATTRIBUTE_UPDATE_SUCCESS);
+ return CreateHttpResponse(net::HTTP_OK, response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update.h b/chromium/components/policy/test_support/request_handler_for_device_attribute_update.h
new file mode 100644
index 00000000000..36783bf8fba
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `device_attribute_update`.
+class RequestHandlerForDeviceAttributeUpdate
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForDeviceAttributeUpdate(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForDeviceAttributeUpdate(
+ RequestHandlerForDeviceAttributeUpdate&& handler) = delete;
+ RequestHandlerForDeviceAttributeUpdate& operator=(
+ RequestHandlerForDeviceAttributeUpdate&& handler) = delete;
+ ~RequestHandlerForDeviceAttributeUpdate() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.cc b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.cc
new file mode 100644
index 00000000000..4d846b30d03
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.cc
@@ -0,0 +1,55 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_device_attribute_update_permission.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForDeviceAttributeUpdatePermission::
+ RequestHandlerForDeviceAttributeUpdatePermission(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForDeviceAttributeUpdatePermission::
+ ~RequestHandlerForDeviceAttributeUpdatePermission() = default;
+
+std::string RequestHandlerForDeviceAttributeUpdatePermission::RequestType() {
+ return dm_protocol::kValueRequestDeviceAttributeUpdatePermission;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForDeviceAttributeUpdatePermission::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementResponse device_management_response;
+ em::DeviceAttributeUpdatePermissionResponse::ResultType result =
+ policy_storage()->allow_set_device_attributes()
+ ? em::DeviceAttributeUpdatePermissionResponse::
+ ATTRIBUTE_UPDATE_ALLOWED
+ : em::DeviceAttributeUpdatePermissionResponse::
+ ATTRIBUTE_UPDATE_DISALLOWED;
+ device_management_response
+ .mutable_device_attribute_update_permission_response()
+ ->set_result(result);
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.h b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.h
new file mode 100644
index 00000000000..4ee54f5bf0b
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission.h
@@ -0,0 +1,33 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_PERMISSION_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_PERMISSION_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `device_attribute_update_permission`.
+class RequestHandlerForDeviceAttributeUpdatePermission
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForDeviceAttributeUpdatePermission(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForDeviceAttributeUpdatePermission(
+ RequestHandlerForDeviceAttributeUpdatePermission&& handler) = delete;
+ RequestHandlerForDeviceAttributeUpdatePermission& operator=(
+ RequestHandlerForDeviceAttributeUpdatePermission&& handler) = delete;
+ ~RequestHandlerForDeviceAttributeUpdatePermission() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_ATTRIBUTE_UPDATE_PERMISSION_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission_unittest.cc b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission_unittest.cc
new file mode 100644
index 00000000000..64444c2f6f9
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_permission_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForDeviceAttributeUpdatePermissionTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForDeviceAttributeUpdatePermissionTest() = default;
+ ~RequestHandlerForDeviceAttributeUpdatePermissionTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(
+ dm_protocol::kValueRequestDeviceAttributeUpdatePermission);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForDeviceAttributeUpdatePermissionTest,
+ HandleRequest_Allowed) {
+ policy_storage()->set_allow_set_device_attributes(true);
+
+ em::DeviceManagementRequest device_management_request;
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(
+ response.device_attribute_update_permission_response().result(),
+ em::DeviceAttributeUpdatePermissionResponse::ATTRIBUTE_UPDATE_ALLOWED);
+}
+
+TEST_F(RequestHandlerForDeviceAttributeUpdatePermissionTest,
+ HandleRequest_Disallowed) {
+ policy_storage()->set_allow_set_device_attributes(false);
+
+ em::DeviceManagementRequest device_management_request;
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(
+ response.device_attribute_update_permission_response().result(),
+ em::DeviceAttributeUpdatePermissionResponse::ATTRIBUTE_UPDATE_DISALLOWED);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_attribute_update_unittest.cc b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_unittest.cc
new file mode 100644
index 00000000000..325715ba4c5
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_attribute_update_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForDeviceAttributeUpdateTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForDeviceAttributeUpdateTest() = default;
+ ~RequestHandlerForDeviceAttributeUpdateTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestDeviceAttributeUpdate);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForDeviceAttributeUpdateTest, HandleRequest) {
+ em::DeviceManagementRequest device_management_request;
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(response.device_attribute_update_response().result(),
+ em::DeviceAttributeUpdateResponse::ATTRIBUTE_UPDATE_SUCCESS);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.cc b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.cc
new file mode 100644
index 00000000000..3e47e31d016
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.cc
@@ -0,0 +1,58 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_device_initial_enrollment_state.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForDeviceInitialEnrollmentState::
+ RequestHandlerForDeviceInitialEnrollmentState(ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForDeviceInitialEnrollmentState::
+ ~RequestHandlerForDeviceInitialEnrollmentState() = default;
+
+std::string RequestHandlerForDeviceInitialEnrollmentState::RequestType() {
+ return dm_protocol::kValueRequestInitialEnrollmentStateRetrieval;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForDeviceInitialEnrollmentState::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::DeviceInitialEnrollmentStateRequest& state_request =
+ device_management_request.device_initial_enrollment_state_request();
+
+ const PolicyStorage::InitialEnrollmentState* state =
+ policy_storage()->GetInitialEnrollmentState(
+ state_request.brand_code() + "_" + state_request.serial_number());
+ em::DeviceManagementResponse device_management_response;
+ em::DeviceInitialEnrollmentStateResponse* state_response =
+ device_management_response
+ .mutable_device_initial_enrollment_state_response();
+ state_response->set_initial_enrollment_mode(state->initial_enrollment_mode);
+ state_response->set_management_domain(state->management_domain);
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.h b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.h
new file mode 100644
index 00000000000..9205a90c77a
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_INITIAL_ENROLLMENT_STATE_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_INITIAL_ENROLLMENT_STATE_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `device_state_retrieval`.
+class RequestHandlerForDeviceInitialEnrollmentState
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForDeviceInitialEnrollmentState(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForDeviceInitialEnrollmentState(
+ RequestHandlerForDeviceInitialEnrollmentState&& handler) = delete;
+ RequestHandlerForDeviceInitialEnrollmentState& operator=(
+ RequestHandlerForDeviceInitialEnrollmentState&& handler) = delete;
+ ~RequestHandlerForDeviceInitialEnrollmentState() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_INITIAL_ENROLLMENT_STATE_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state_unittest.cc b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state_unittest.cc
new file mode 100644
index 00000000000..cb047863760
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_initial_enrollment_state_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kBrandCode[] = "Google Pixel";
+constexpr char kSerialNumber[] = "AXD123145";
+constexpr char kManagementDomain[] = "example.com";
+
+} // namespace
+
+class RequestHandlerForDeviceInitialEnrollmentStateTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForDeviceInitialEnrollmentStateTest() = default;
+ ~RequestHandlerForDeviceInitialEnrollmentStateTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(
+ dm_protocol::kValueRequestInitialEnrollmentStateRetrieval);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForDeviceInitialEnrollmentStateTest, HandleRequest) {
+ policy_storage()->SetInitialEnrollmentState(
+ base::StrCat({kBrandCode, "_", kSerialNumber}),
+ PolicyStorage::InitialEnrollmentState{
+ .initial_enrollment_mode = em::DeviceInitialEnrollmentStateResponse::
+ INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED,
+ .management_domain = kManagementDomain});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceInitialEnrollmentStateRequest* enrollment_request =
+ device_management_request
+ .mutable_device_initial_enrollment_state_request();
+ enrollment_request->set_brand_code(kBrandCode);
+ enrollment_request->set_serial_number(kSerialNumber);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(response.device_initial_enrollment_state_response()
+ .initial_enrollment_mode(),
+ em::DeviceInitialEnrollmentStateResponse::
+ INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED);
+ EXPECT_EQ(
+ response.device_initial_enrollment_state_response().management_domain(),
+ kManagementDomain);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.cc b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.cc
new file mode 100644
index 00000000000..fb5a929cac1
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_device_state_retrieval.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForDeviceStateRetrieval::RequestHandlerForDeviceStateRetrieval(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForDeviceStateRetrieval::
+ ~RequestHandlerForDeviceStateRetrieval() = default;
+
+std::string RequestHandlerForDeviceStateRetrieval::RequestType() {
+ return dm_protocol::kValueRequestDeviceStateRetrieval;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForDeviceStateRetrieval::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const std::string& server_backed_state_key =
+ device_management_request.device_state_retrieval_request()
+ .server_backed_state_key();
+
+ em::DeviceManagementResponse device_management_response;
+ if (client_storage()->LookupByStateKey(server_backed_state_key)) {
+ em::DeviceStateRetrievalResponse* device_state_retrieval_response =
+ device_management_response.mutable_device_state_retrieval_response();
+ PolicyStorage::DeviceState device_state = policy_storage()->device_state();
+ if (!device_state.management_domain.empty()) {
+ device_state_retrieval_response->set_management_domain(
+ device_state.management_domain);
+ }
+ device_state_retrieval_response->set_restore_mode(
+ device_state.restore_mode);
+ }
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.h b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.h
new file mode 100644
index 00000000000..c5765a38ba8
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_STATE_RETRIEVAL_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_STATE_RETRIEVAL_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `device_state_retrieval`.
+class RequestHandlerForDeviceStateRetrieval
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForDeviceStateRetrieval(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForDeviceStateRetrieval(
+ RequestHandlerForDeviceStateRetrieval&& handler) = delete;
+ RequestHandlerForDeviceStateRetrieval& operator=(
+ RequestHandlerForDeviceStateRetrieval&& handler) = delete;
+ ~RequestHandlerForDeviceStateRetrieval() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_DEVICE_STATE_RETRIEVAL_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_device_state_retrieval_unittest.cc b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval_unittest.cc
new file mode 100644
index 00000000000..edc202ad5f4
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_device_state_retrieval_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kStateKey1[] = "fake_state_key_1";
+constexpr char kStateKey2[] = "fake_state_key_2";
+constexpr char kManagementDomain[] = "example.com";
+
+} // namespace
+
+class RequestHandlerForDeviceStateRetrievalTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForDeviceStateRetrievalTest() = default;
+ ~RequestHandlerForDeviceStateRetrievalTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestDeviceStateRetrieval);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForDeviceStateRetrievalTest, HandleRequest) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id = kDeviceId;
+ client_info.state_keys.push_back(kStateKey1);
+ client_info.state_keys.push_back(kStateKey2);
+ client_storage()->RegisterClient(client_info);
+ policy_storage()->set_device_state(PolicyStorage::DeviceState{
+ .management_domain = kManagementDomain,
+ .restore_mode = enterprise_management::DeviceStateRetrievalResponse::
+ RESTORE_MODE_REENROLLMENT_ZERO_TOUCH});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceStateRetrievalRequest* request =
+ device_management_request.mutable_device_state_retrieval_request();
+ request->set_server_backed_state_key(kStateKey2);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_EQ(
+ response.device_state_retrieval_response().restore_mode(),
+ em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH);
+ EXPECT_EQ(response.device_state_retrieval_response().management_domain(),
+ kManagementDomain);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_policy.cc b/chromium/components/policy/test_support/request_handler_for_policy.cc
new file mode 100644
index 00000000000..569d98b67d1
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_policy.cc
@@ -0,0 +1,246 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_policy.h"
+
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/signature_provider.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForPolicy::RequestHandlerForPolicy(ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForPolicy::~RequestHandlerForPolicy() = default;
+
+std::string RequestHandlerForPolicy::RequestType() {
+ return dm_protocol::kValueRequestPolicy;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForPolicy::HandleRequest(
+ const HttpRequest& request) {
+ const base::flat_set<std::string> kCloudPolicyTypes{
+ dm_protocol::kChromeDevicePolicyType,
+ dm_protocol::kChromeExtensionPolicyType,
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ dm_protocol::kChromeMachineLevelUserCloudPolicyAndroidType,
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+ dm_protocol::kChromePublicAccountPolicyType,
+ dm_protocol::kChromeSigninExtensionPolicyType,
+ dm_protocol::kChromeUserPolicyType,
+ };
+ const base::flat_set<std::string> kExtensionPolicyTypes{
+ dm_protocol::kChromeExtensionPolicyType,
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+ dm_protocol::kChromeSigninExtensionPolicyType,
+ };
+
+ std::string request_device_token;
+ if (!GetDeviceTokenFromRequest(request, &request_device_token))
+ return CreateHttpResponse(net::HTTP_UNAUTHORIZED, "Invalid device token.");
+
+ const ClientStorage::ClientInfo* client_info =
+ client_storage()->GetClientOrNull(
+ KeyValueFromUrl(request.GetURL(), dm_protocol::kParamDeviceID));
+ if (!client_info || client_info->device_token != request_device_token)
+ return CreateHttpResponse(net::HTTP_GONE, "Invalid device token.");
+
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+
+ // If this is a public account request, use the |settings_entity_id| from the
+ // request as the |username|. This is required to validate policy for
+ // extensions in device-local accounts.
+ ClientStorage::ClientInfo modified_client_info(*client_info);
+ for (const auto& fetch_request :
+ device_management_request.policy_request().requests()) {
+ if (fetch_request.policy_type() ==
+ dm_protocol::kChromePublicAccountPolicyType) {
+ modified_client_info.username = fetch_request.settings_entity_id();
+ client_info = &modified_client_info;
+ break;
+ }
+ }
+
+ em::DeviceManagementResponse device_management_response;
+ for (const auto& fetch_request :
+ device_management_request.policy_request().requests()) {
+ const std::string& policy_type = fetch_request.policy_type();
+ // TODO(crbug.com/1221328): Add other policy types as needed.
+ if (!base::Contains(kCloudPolicyTypes, policy_type)) {
+ return CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ base::StringPrintf("Invalid policy_type: %s", policy_type.c_str()));
+ }
+
+ std::string error_msg;
+ if (base::Contains(kExtensionPolicyTypes, policy_type)) {
+ if (!ProcessCloudPolicyForExtensions(
+ fetch_request, *client_info,
+ device_management_response.mutable_policy_response(),
+ &error_msg)) {
+ return CreateHttpResponse(net::HTTP_BAD_REQUEST, error_msg);
+ }
+ } else if (!ProcessCloudPolicy(
+ fetch_request, *client_info,
+ device_management_response.mutable_policy_response()
+ ->add_responses(),
+ &error_msg)) {
+ return CreateHttpResponse(net::HTTP_BAD_REQUEST, error_msg);
+ }
+ }
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+bool RequestHandlerForPolicy::ProcessCloudPolicy(
+ const em::PolicyFetchRequest& fetch_request,
+ const ClientStorage::ClientInfo& client_info,
+ em::PolicyFetchResponse* fetch_response,
+ std::string* error_msg) {
+ const std::string& policy_type = fetch_request.policy_type();
+ if (client_info.allowed_policy_types.find(policy_type) ==
+ client_info.allowed_policy_types.end()) {
+ error_msg->assign("Policy type not allowed for token: ")
+ .append(policy_type);
+ return false;
+ }
+
+ // Determine the current key on the client.
+ const SignatureProvider::SigningKey* client_key = nullptr;
+ const SignatureProvider* signature_provider =
+ policy_storage()->signature_provider();
+ int public_key_version = fetch_request.public_key_version();
+ if (fetch_request.has_public_key_version()) {
+ client_key = signature_provider->GetKeyByVersion(public_key_version);
+ if (!client_key) {
+ error_msg->assign(base::StringPrintf("Invalid public key version: %d",
+ public_key_version));
+ return false;
+ }
+ }
+
+ // Choose the key for signing the policy.
+ int signing_key_version = signature_provider->current_key_version();
+ if (fetch_request.has_public_key_version() &&
+ signature_provider->rotate_keys()) {
+ signing_key_version = public_key_version + 1;
+ }
+ const SignatureProvider::SigningKey* signing_key =
+ signature_provider->GetKeyByVersion(signing_key_version);
+ if (!signing_key) {
+ error_msg->assign(base::StringPrintf(
+ "Can't find signin key for version: %d", signing_key_version));
+ return false;
+ }
+
+ em::PolicyData policy_data;
+ policy_data.set_policy_type(policy_type);
+ policy_data.set_timestamp(policy_storage()->timestamp().is_null()
+ ? base::Time::Now().ToJavaTime()
+ : policy_storage()->timestamp().ToJavaTime());
+ policy_data.set_request_token(client_info.device_token);
+ policy_data.set_policy_value(policy_storage()->GetPolicyPayload(
+ policy_type, fetch_request.settings_entity_id()));
+ policy_data.set_settings_entity_id(fetch_request.settings_entity_id());
+ policy_data.set_machine_name(client_info.machine_name);
+ policy_data.set_service_account_identity(
+ policy_storage()->service_account_identity().empty()
+ ? "policy-testserver-service-account-identity@gmail.com"
+ : policy_storage()->service_account_identity());
+ policy_data.set_device_id(client_info.device_id);
+ policy_data.set_username(
+ client_info.username.value_or(policy_storage()->policy_user().empty()
+ ? "username@example.com"
+ : policy_storage()->policy_user()));
+ policy_data.set_policy_invalidation_topic(
+ policy_storage()->policy_invalidation_topic());
+
+ if (fetch_request.signature_type() != em::PolicyFetchRequest::NONE)
+ policy_data.set_public_key_version(signing_key_version);
+
+ policy_data.SerializeToString(fetch_response->mutable_policy_data());
+
+ if (fetch_request.signature_type() == em::PolicyFetchRequest::SHA1_RSA) {
+ // Sign the serialized policy data.
+ if (!signing_key->Sign(fetch_response->policy_data(),
+ fetch_response->mutable_policy_data_signature())) {
+ error_msg->assign("Error signing policy_data");
+ return false;
+ }
+
+ if (!fetch_request.has_public_key_version() ||
+ public_key_version != signing_key_version) {
+ fetch_response->set_new_public_key(signing_key->public_key());
+ }
+
+ // Set the verification signature appropriate for the policy domain.
+ // TODO(http://crbug.com/328038): Use the enrollment domain for public
+ // accounts when we add key validation for ChromeOS.
+ std::string domain =
+ gaia::ExtractDomainName(gaia::SanitizeEmail(policy_data.username()));
+ if (!signing_key->GetSignatureForDomain(
+ domain,
+ fetch_response
+ ->mutable_new_public_key_verification_signature_deprecated())) {
+ error_msg->assign(
+ base::StringPrintf("No signature for domain: %s", domain.c_str()));
+ return false;
+ }
+
+ if (client_key &&
+ !client_key->Sign(fetch_response->new_public_key(),
+ fetch_response->mutable_new_public_key_signature())) {
+ error_msg->assign("Error signing new_public_key");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool RequestHandlerForPolicy::ProcessCloudPolicyForExtensions(
+ const em::PolicyFetchRequest& fetch_request,
+ const ClientStorage::ClientInfo& client_info,
+ em::DevicePolicyResponse* response,
+ std::string* error_msg) {
+ // Send one PolicyFetchResponse for each extension configured on the server as
+ // the client does not actually tell us which extensions it has installed to
+ // protect user privacy.
+ std::vector<std::string> ids =
+ policy_storage()->GetEntityIdsForType(fetch_request.policy_type());
+ for (const std::string& id : ids) {
+ em::PolicyFetchRequest fetch_request_with_id;
+ fetch_request_with_id.CopyFrom(fetch_request);
+ fetch_request_with_id.set_settings_entity_id(id);
+ if (!ProcessCloudPolicy(fetch_request_with_id, client_info,
+ response->add_responses(), error_msg)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_policy.h b/chromium/components/policy/test_support/request_handler_for_policy.h
new file mode 100644
index 00000000000..e8b2594cd81
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_policy.h
@@ -0,0 +1,60 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_POLICY_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_POLICY_H_
+
+#include <string>
+
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace enterprise_management {
+class DevicePolicyResponse;
+class PolicyFetchRequest;
+class PolicyFetchResponse;
+} // namespace enterprise_management
+
+namespace policy {
+
+// Handler for request type `policy`.
+class RequestHandlerForPolicy
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForPolicy(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForPolicy(RequestHandlerForPolicy&& handler) = delete;
+ RequestHandlerForPolicy& operator=(RequestHandlerForPolicy&& handler) =
+ delete;
+ ~RequestHandlerForPolicy() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+
+ private:
+ // Add to |fetch_response| the policies associated with |client| according to
+ // |policy_type|. Returns true is request is well-formed, or false otherwise
+ // (in which case, |error_msg| is set with the corresponding error message).
+ bool ProcessCloudPolicy(
+ const enterprise_management::PolicyFetchRequest& fetch_request,
+ const ClientStorage::ClientInfo& client,
+ enterprise_management::PolicyFetchResponse* fetch_response,
+ std::string* error_msg);
+
+ // Add to |response| the policies associated with |client_info| for extension
+ // policy type in |fetch_request|. Returns true is request is well-formed, or
+ // false otherwise (in which case, |error_msg| is set with the corresponding
+ // error message).
+ bool ProcessCloudPolicyForExtensions(
+ const enterprise_management::PolicyFetchRequest& fetch_request,
+ const ClientStorage::ClientInfo& client_info,
+ enterprise_management::DevicePolicyResponse* response,
+ std::string* error_msg);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_POLICY_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_policy_unittest.cc b/chromium/components/policy/test_support/request_handler_for_policy_unittest.cc
new file mode 100644
index 00000000000..e6d58ec2de3
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_policy_unittest.cc
@@ -0,0 +1,435 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_policy.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kDeviceToken[] = "fake_device_token";
+constexpr char kMachineName[] = "machine_name";
+constexpr char kPolicyInvalidationTopic[] = "policy_invalidation_topic";
+constexpr char kUsername[] = "user-for-policy@example.com";
+#if !BUILDFLAG(IS_ANDROID)
+constexpr char kPublicAccountEntityId[] = "test_user";
+constexpr char kExtensionId[] = "extension_id";
+#endif // !BUILDFLAG(IS_ANDROID)
+
+} // namespace
+
+class RequestHandlerForPolicyTest : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForPolicyTest() = default;
+ ~RequestHandlerForPolicyTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestPolicy);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_NoDeviceToken) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+}
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_NoRegisteredClient) {
+ SetDeviceTokenHeader(kDeviceToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_GONE);
+}
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_UnmatchedDeviceId) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = "registered-device-token";
+ client_info.device_id = kDeviceId;
+ client_info.machine_name = kMachineName;
+ client_storage()->RegisterClient(std::move(client_info));
+
+ SetDeviceTokenHeader(kDeviceToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_GONE);
+}
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_InvalidPolicyType) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.machine_name = kMachineName;
+ client_storage()->RegisterClient(std::move(client_info));
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* fetch_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ fetch_request->set_policy_type("invalid-policy-type");
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_UnauthorizedPolicyType) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.machine_name = kMachineName;
+ client_info.allowed_policy_types.insert(dm_protocol::kChromeDevicePolicyType);
+ client_storage()->RegisterClient(std::move(client_info));
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* fetch_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ fetch_request->set_policy_type(dm_protocol::kChromeRemoteCommandPolicyType);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_Success_NoSignedPolicies) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.machine_name = kMachineName;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ client_storage()->RegisterClient(client_info);
+
+ policy_storage()->set_policy_invalidation_topic(kPolicyInvalidationTopic);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_savingbrowserhistorydisabled()
+ ->mutable_policy_options()
+ ->set_mode(em::PolicyOptions::MANDATORY);
+ settings.mutable_savingbrowserhistorydisabled()->set_value(true);
+ policy_storage()->SetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ settings.SerializeAsString());
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* fetch_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ fetch_request->set_policy_type(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+
+ ASSERT_EQ(device_management_response.policy_response().responses_size(), 1);
+ em::PolicyData policy_data;
+ policy_data.ParseFromString(
+ device_management_response.policy_response().responses(0).policy_data());
+ EXPECT_EQ(policy_data.policy_type(),
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ EXPECT_EQ(policy_data.request_token(), client_info.device_token);
+ EXPECT_EQ(policy_data.policy_value(),
+ policy_storage()->GetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType));
+ EXPECT_EQ(policy_data.machine_name(), client_info.machine_name);
+ EXPECT_FALSE(policy_data.service_account_identity().empty());
+ EXPECT_EQ(policy_data.device_id(), client_info.device_id);
+ EXPECT_EQ(policy_data.username(), kUsername);
+ EXPECT_EQ(policy_data.policy_invalidation_topic(), kPolicyInvalidationTopic);
+ EXPECT_FALSE(policy_data.has_public_key_version());
+}
+
+TEST_F(RequestHandlerForPolicyTest,
+ HandleRequest_Success_SignedPoliciesWithoutClientKey) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ client_storage()->RegisterClient(client_info);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_savingbrowserhistorydisabled()
+ ->mutable_policy_options()
+ ->set_mode(em::PolicyOptions::MANDATORY);
+ settings.mutable_savingbrowserhistorydisabled()->set_value(true);
+ policy_storage()->SetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ settings.SerializeAsString());
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* fetch_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ fetch_request->set_policy_type(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+
+ ASSERT_EQ(device_management_response.policy_response().responses_size(), 1);
+ const em::PolicyFetchResponse& fetch_response =
+ device_management_response.policy_response().responses(0);
+ em::PolicyData policy_data;
+ policy_data.ParseFromString(fetch_response.policy_data());
+ EXPECT_EQ(policy_data.policy_type(),
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ EXPECT_EQ(policy_data.policy_value(),
+ policy_storage()->GetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType));
+ EXPECT_EQ(policy_data.public_key_version(),
+ policy_storage()->signature_provider()->current_key_version());
+
+ EXPECT_FALSE(fetch_response.policy_data_signature().empty());
+ EXPECT_FALSE(fetch_response.new_public_key_verification_signature_deprecated()
+ .empty());
+ EXPECT_TRUE(fetch_response.new_public_key_signature().empty());
+}
+
+TEST_F(RequestHandlerForPolicyTest,
+ HandleRequest_Success_SignedPoliciesWithClientKey) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ client_storage()->RegisterClient(client_info);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_savingbrowserhistorydisabled()
+ ->mutable_policy_options()
+ ->set_mode(em::PolicyOptions::MANDATORY);
+ settings.mutable_savingbrowserhistorydisabled()->set_value(true);
+ policy_storage()->SetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ settings.SerializeAsString());
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* fetch_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ fetch_request->set_policy_type(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA);
+ // Sets client key to a key different than the current key in signature
+ // provider (1), to force setting |new_public_key_signature| in the fetch
+ // response.
+ fetch_request->set_public_key_version(2);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+
+ ASSERT_EQ(device_management_response.policy_response().responses_size(), 1);
+ const em::PolicyFetchResponse& fetch_response =
+ device_management_response.policy_response().responses(0);
+ em::PolicyData policy_data;
+ policy_data.ParseFromString(fetch_response.policy_data());
+ EXPECT_EQ(policy_data.policy_type(),
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType);
+ EXPECT_EQ(policy_data.policy_value(),
+ policy_storage()->GetPolicyPayload(
+ dm_protocol::kChromeMachineLevelUserCloudPolicyType));
+ EXPECT_EQ(policy_data.public_key_version(),
+ policy_storage()->signature_provider()->current_key_version());
+
+ EXPECT_FALSE(fetch_response.policy_data_signature().empty());
+ EXPECT_FALSE(fetch_response.new_public_key_verification_signature_deprecated()
+ .empty());
+ EXPECT_FALSE(fetch_response.new_public_key_signature().empty());
+}
+
+#if !BUILDFLAG(IS_ANDROID)
+TEST_F(RequestHandlerForPolicyTest, HandleRequest_Success_ExtensionPolicies) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ dm_protocol::kChromeExtensionPolicyType);
+ client_storage()->RegisterClient(client_info);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_extensionsettings()->mutable_value()->assign(
+ "extension-policy");
+ policy_storage()->SetPolicyPayload(dm_protocol::kChromeExtensionPolicyType,
+ kExtensionId,
+ settings.SerializeAsString());
+
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.mutable_policy_request()
+ ->add_requests()
+ ->set_policy_type(dm_protocol::kChromeExtensionPolicyType);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+
+ ASSERT_EQ(device_management_response.policy_response().responses_size(), 1);
+ const em::PolicyFetchResponse& fetch_response =
+ device_management_response.policy_response().responses(0);
+ em::PolicyData policy_data;
+ policy_data.ParseFromString(fetch_response.policy_data());
+ EXPECT_EQ(policy_data.policy_type(), dm_protocol::kChromeExtensionPolicyType);
+ EXPECT_EQ(policy_data.policy_value(),
+ policy_storage()->GetPolicyPayload(
+ dm_protocol::kChromeExtensionPolicyType, kExtensionId));
+}
+
+TEST_F(RequestHandlerForPolicyTest,
+ HandleRequest_ExtensionPoliciesWithMissingSigningKey) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+ client_storage()->RegisterClient(client_info);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_extensionsettings()->mutable_value()->assign(
+ "extension-policy");
+ policy_storage()->SetPolicyPayload(
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType, kExtensionId,
+ settings.SerializeAsString());
+ policy_storage()->signature_provider()->set_current_key_version(-1);
+
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.mutable_policy_request()
+ ->add_requests()
+ ->set_policy_type(
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForPolicyTest,
+ HandleRequest_ExtensionsInPublicAccounts_SetCorrectPolicyDataUsername) {
+ ClientStorage::ClientInfo client_info;
+ client_info.device_token = kDeviceToken;
+ client_info.device_id = kDeviceId;
+ client_info.username = kUsername;
+ client_info.allowed_policy_types.insert(
+ {dm_protocol::kChromeExtensionPolicyType,
+ dm_protocol::kChromePublicAccountPolicyType});
+ client_storage()->RegisterClient(client_info);
+
+ em::CloudPolicySettings settings;
+ settings.mutable_extensionsettings()->mutable_value()->assign(
+ "extension-policy");
+ policy_storage()->SetPolicyPayload(dm_protocol::kChromeExtensionPolicyType,
+ kExtensionId,
+ settings.SerializeAsString());
+
+ std::vector<policy::SignatureProvider::SigningKey> universal_signing_keys;
+ universal_signing_keys.push_back(policy::SignatureProvider::SigningKey(
+ policy::PolicyBuilder::CreateTestSigningKey(),
+ {{"*", policy::PolicyBuilder::GetTestSigningKeySignature()}}));
+ policy_storage()->signature_provider()->set_signing_keys(
+ std::move(universal_signing_keys));
+
+ em::DeviceManagementRequest device_management_request;
+ em::PolicyFetchRequest* extension_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ extension_request->set_policy_type(dm_protocol::kChromeExtensionPolicyType);
+ extension_request->set_signature_type(
+ enterprise_management::PolicyFetchRequest::SHA1_RSA);
+ em::PolicyFetchRequest* public_account_request =
+ device_management_request.mutable_policy_request()->add_requests();
+ public_account_request->set_policy_type(
+ dm_protocol::kChromePublicAccountPolicyType);
+ public_account_request->set_settings_entity_id(kPublicAccountEntityId);
+ public_account_request->set_signature_type(
+ enterprise_management::PolicyFetchRequest::SHA1_RSA);
+
+ SetDeviceTokenHeader(kDeviceToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+
+ ASSERT_EQ(device_management_response.policy_response().responses_size(), 2);
+ const em::PolicyFetchResponse& extension_fetch_response =
+ device_management_response.policy_response().responses(0);
+ em::PolicyData extension_policy_data;
+ extension_policy_data.ParseFromString(extension_fetch_response.policy_data());
+ EXPECT_EQ(extension_policy_data.username(), kPublicAccountEntityId);
+
+ const em::PolicyFetchResponse& public_account_fetch_response =
+ device_management_response.policy_response().responses(0);
+ em::PolicyData public_account_policy_data;
+ public_account_policy_data.ParseFromString(
+ public_account_fetch_response.policy_data());
+ EXPECT_EQ(public_account_policy_data.username(), kPublicAccountEntityId);
+}
+#endif // !BUILDFLAG(IS_ANDROID)
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.cc b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.cc
new file mode 100644
index 00000000000..df820f83375
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.cc
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_psm_auto_enrollment.h"
+
+#include "base/containers/contains.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr const char* kPsmMembershipEncryptedTestIds[] = {
+ "54455354/111111", // Brand code "TEST" (as hex), serial number "111111".
+};
+
+} // namespace
+
+RequestHandlerForPsmAutoEnrollment::RequestHandlerForPsmAutoEnrollment(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForPsmAutoEnrollment::~RequestHandlerForPsmAutoEnrollment() =
+ default;
+
+std::string RequestHandlerForPsmAutoEnrollment::RequestType() {
+ return dm_protocol::kValueRequestPsmHasDeviceState;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForPsmAutoEnrollment::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::PrivateSetMembershipRequest& psm_request =
+ device_management_request.private_set_membership_request();
+
+ em::DeviceManagementResponse device_management_response;
+ em::PrivateSetMembershipResponse* psm_response =
+ device_management_response.mutable_private_set_membership_response();
+ const auto& rlwe_request = psm_request.rlwe_request();
+ if (rlwe_request.has_oprf_request()) {
+ if (rlwe_request.oprf_request().encrypted_ids_size() == 0) {
+ return CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ "PSM RLWE OPRF request must contain encrypted_ids field");
+ }
+ psm_response->mutable_rlwe_response()
+ ->mutable_oprf_response()
+ ->add_doubly_encrypted_ids()
+ ->set_queried_encrypted_id(
+ rlwe_request.oprf_request().encrypted_ids(0));
+ } else if (rlwe_request.has_query_request()) {
+ if (rlwe_request.query_request().queries_size() == 0) {
+ return CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ "PSM RLWE query request must contain queries field");
+ }
+ auto* pir_response = psm_response->mutable_rlwe_response()
+ ->mutable_query_response()
+ ->add_pir_responses();
+ const auto& encrypted_id =
+ rlwe_request.query_request().queries(0).queried_encrypted_id();
+ pir_response->set_queried_encrypted_id(encrypted_id);
+ pir_response->mutable_pir_response()->set_plaintext_entry_size(
+ base::Contains(kPsmMembershipEncryptedTestIds, encrypted_id)
+ ? kPirResponseHasMembership
+ : kPirResponseHasNoMembership);
+ } else {
+ return CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ "PSM RLWE oprf_request, or query_request fields must be filled");
+ }
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.h b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.h
new file mode 100644
index 00000000000..23b732af0b6
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment.h
@@ -0,0 +1,37 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_PSM_AUTO_ENROLLMENT_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_PSM_AUTO_ENROLLMENT_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `enterprise_psm_check`.
+class RequestHandlerForPsmAutoEnrollment
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ enum PirResponse {
+ kPirResponseHasMembership = 1,
+ kPirResponseHasNoMembership = 2,
+ };
+
+ RequestHandlerForPsmAutoEnrollment(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForPsmAutoEnrollment(
+ RequestHandlerForPsmAutoEnrollment&& handler) = delete;
+ RequestHandlerForPsmAutoEnrollment& operator=(
+ RequestHandlerForPsmAutoEnrollment&& handler) = delete;
+ ~RequestHandlerForPsmAutoEnrollment() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_PSM_AUTO_ENROLLMENT_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment_unittest.cc b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment_unittest.cc
new file mode 100644
index 00000000000..6c7fab55d0e
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_psm_auto_enrollment_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_psm_auto_enrollment.h"
+#include "base/strings/strcat.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kEncryptedId1[] = "fake/ecrypted-id";
+constexpr char kEncryptedId2[] = "54455354/111111";
+
+} // namespace
+
+class RequestHandlerForPsmAutoEnrollmentTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForPsmAutoEnrollmentTest() = default;
+ ~RequestHandlerForPsmAutoEnrollmentTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestPsmHasDeviceState);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForPsmAutoEnrollmentTest, HandleRequest_OprfRequest) {
+ em::DeviceManagementRequest device_management_request;
+ em::PrivateSetMembershipRequest* request =
+ device_management_request.mutable_private_set_membership_request();
+ request->mutable_rlwe_request()->mutable_oprf_request()->add_encrypted_ids(
+ kEncryptedId1);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ ASSERT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .oprf_response()
+ .doubly_encrypted_ids_size(),
+ 1);
+ EXPECT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .oprf_response()
+ .doubly_encrypted_ids(0)
+ .queried_encrypted_id(),
+ kEncryptedId1);
+}
+
+TEST_F(RequestHandlerForPsmAutoEnrollmentTest,
+ HandleRequest_QueryRequestNoMembership) {
+ em::DeviceManagementRequest device_management_request;
+ em::PrivateSetMembershipRequest* request =
+ device_management_request.mutable_private_set_membership_request();
+ request->mutable_rlwe_request()
+ ->mutable_query_request()
+ ->add_queries()
+ ->set_queried_encrypted_id(kEncryptedId1);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ ASSERT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses_size(),
+ 1);
+ EXPECT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses(0)
+ .queried_encrypted_id(),
+ kEncryptedId1);
+ EXPECT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses(0)
+ .pir_response()
+ .plaintext_entry_size(),
+ RequestHandlerForPsmAutoEnrollment::kPirResponseHasNoMembership);
+}
+
+TEST_F(RequestHandlerForPsmAutoEnrollmentTest,
+ HandleRequest_QueryRequestHasMembership) {
+ em::DeviceManagementRequest device_management_request;
+ em::PrivateSetMembershipRequest* request =
+ device_management_request.mutable_private_set_membership_request();
+ request->mutable_rlwe_request()
+ ->mutable_query_request()
+ ->add_queries()
+ ->set_queried_encrypted_id(kEncryptedId2);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ ASSERT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses_size(),
+ 1);
+ EXPECT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses(0)
+ .queried_encrypted_id(),
+ kEncryptedId2);
+ EXPECT_EQ(response.private_set_membership_response()
+ .rlwe_response()
+ .query_response()
+ .pir_responses(0)
+ .pir_response()
+ .plaintext_entry_size(),
+ RequestHandlerForPsmAutoEnrollment::kPirResponseHasMembership);
+}
+
+TEST_F(RequestHandlerForPsmAutoEnrollmentTest,
+ HandleRequest_MissingRequestFields) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.mutable_private_set_membership_request();
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_browser.cc b/chromium/components/policy/test_support/request_handler_for_register_browser.cc
new file mode 100644
index 00000000000..04426d7a3cb
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_browser.cc
@@ -0,0 +1,83 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForRegisterBrowser::RequestHandlerForRegisterBrowser(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForRegisterBrowser::~RequestHandlerForRegisterBrowser() = default;
+
+std::string RequestHandlerForRegisterBrowser::RequestType() {
+ return dm_protocol::kValueRequestTokenEnrollment;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForRegisterBrowser::HandleRequest(
+ const HttpRequest& request) {
+ std::string enrollment_token;
+ if (!GetEnrollmentTokenFromRequest(request, &enrollment_token)) {
+ return CreateHttpResponse(net::HTTP_UNAUTHORIZED,
+ "Missing enrollment token.");
+ }
+
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::RegisterBrowserRequest& register_browser_request =
+ device_management_request.register_browser_request();
+
+ // Machine name is empty on mobile.
+ if (register_browser_request.os_platform() != "Android" &&
+ register_browser_request.os_platform() != "iOS" &&
+ register_browser_request.machine_name().empty()) {
+ LOG(ERROR) << "OS platform: " << register_browser_request.os_platform();
+ return CreateHttpResponse(net::HTTP_BAD_REQUEST,
+ "Machine name must be non-empty on Desktop.");
+ }
+
+ if (enrollment_token == kInvalidEnrollmentToken) {
+ return CreateHttpResponse(net::HTTP_UNAUTHORIZED,
+ "Invalid enrollment token.");
+ }
+
+ std::string device_token = kFakeDeviceToken;
+ em::DeviceManagementResponse device_management_response;
+ device_management_response.mutable_register_response()
+ ->set_device_management_token(device_token);
+
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id =
+ KeyValueFromUrl(request.GetURL(), dm_protocol::kParamDeviceID);
+ client_info.device_token = device_token;
+ client_info.machine_name = register_browser_request.machine_name();
+ client_info.allowed_policy_types.insert(
+ {dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+ dm_protocol::kChromeMachineLevelUserCloudPolicyAndroidType,
+ dm_protocol::kChromeMachineLevelExtensionCloudPolicyType});
+ client_storage()->RegisterClient(std::move(client_info));
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_browser.h b/chromium/components/policy/test_support/request_handler_for_register_browser.h
new file mode 100644
index 00000000000..741761b7483
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_browser.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_BROWSER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_BROWSER_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `register_browser`.
+class RequestHandlerForRegisterBrowser
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForRegisterBrowser(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForRegisterBrowser(RequestHandlerForRegisterBrowser&& handler) =
+ delete;
+ RequestHandlerForRegisterBrowser& operator=(
+ RequestHandlerForRegisterBrowser&& handler) = delete;
+ ~RequestHandlerForRegisterBrowser() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_BROWSER_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_register_browser_unittest.cc b/chromium/components/policy/test_support/request_handler_for_register_browser_unittest.cc
new file mode 100644
index 00000000000..c5404dd57c1
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_browser_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kEnrollmentToken[] = "fake_enrollment_token";
+constexpr char kMachineName[] = "fake_machine_name";
+
+} // namespace
+
+class RequestHandlerForRegisterBrowserTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForRegisterBrowserTest() = default;
+ ~RequestHandlerForRegisterBrowserTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestTokenEnrollment);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForRegisterBrowserTest, HandleRequest_NoEnrollmentToken) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterBrowserTest,
+ HandleRequest_NoDeviceInformation_Desktop) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.mutable_register_browser_request()->set_os_platform(
+ "Windows");
+
+ SetEnrollmentTokenHeader(kEnrollmentToken);
+ SetPayload(em::DeviceManagementRequest());
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterBrowserTest,
+ HandleRequest_InvalidEnrollmentToken) {
+ em::DeviceManagementRequest device_management_request;
+ em::RegisterBrowserRequest* register_browser_request =
+ device_management_request.mutable_register_browser_request();
+ register_browser_request->set_os_platform("Windows");
+ register_browser_request->set_machine_name(kMachineName);
+
+ SetEnrollmentTokenHeader(kInvalidEnrollmentToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterBrowserTest, HandleRequest_Success) {
+ em::DeviceManagementRequest device_management_request;
+ em::RegisterBrowserRequest* register_browser_request =
+ device_management_request.mutable_register_browser_request();
+ register_browser_request->set_os_platform("Windows");
+ register_browser_request->set_machine_name(kMachineName);
+
+ SetEnrollmentTokenHeader(kEnrollmentToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+ EXPECT_EQ(
+ device_management_response.register_response().device_management_token(),
+ kFakeDeviceToken);
+
+ ASSERT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+ const ClientStorage::ClientInfo* client_info =
+ client_storage()->GetClientOrNull(kDeviceId);
+ ASSERT_NE(client_info, nullptr);
+ EXPECT_EQ(client_info->device_id, kDeviceId);
+ EXPECT_EQ(client_info->device_token, kFakeDeviceToken);
+ EXPECT_EQ(client_info->machine_name, kMachineName);
+}
+
+TEST_F(RequestHandlerForRegisterBrowserTest,
+ HandleRequest_Success_NoDeviceInformation_Mobile) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.mutable_register_browser_request()->set_os_platform(
+ "Android");
+
+ SetEnrollmentTokenHeader(kEnrollmentToken);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+ EXPECT_EQ(
+ device_management_response.register_response().device_management_token(),
+ kFakeDeviceToken);
+
+ ASSERT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+ const ClientStorage::ClientInfo* client_info =
+ client_storage()->GetClientOrNull(kDeviceId);
+ ASSERT_NE(client_info, nullptr);
+ EXPECT_EQ(client_info->device_id, kDeviceId);
+ EXPECT_EQ(client_info->device_token, kFakeDeviceToken);
+ EXPECT_TRUE(client_info->machine_name.empty());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_cert_based.cc b/chromium/components/policy/test_support/request_handler_for_register_cert_based.cc
new file mode 100644
index 00000000000..eded2038f25
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_cert_based.cc
@@ -0,0 +1,66 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_register_cert_based.h"
+
+#include <set>
+#include <string>
+
+#include "base/guid.h"
+#include "base/notreached.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/request_handler_for_register_device_and_user.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForRegisterCertBased::RequestHandlerForRegisterCertBased(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : RequestHandlerForRegisterDeviceAndUser(client_storage, policy_storage) {}
+
+RequestHandlerForRegisterCertBased::~RequestHandlerForRegisterCertBased() =
+ default;
+
+std::string RequestHandlerForRegisterCertBased::RequestType() {
+ return dm_protocol::kValueRequestCertBasedRegister;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForRegisterCertBased::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::SignedData& signed_req =
+ device_management_request.certificate_based_register_request()
+ .signed_request();
+ em::CertificateBasedDeviceRegistrationData parsed_req;
+ std::string data = signed_req.data().substr(
+ 0, signed_req.data().size() - signed_req.extra_data_bytes());
+ if (!parsed_req.ParseFromString(data))
+ return CreateHttpResponse(net::HTTP_BAD_REQUEST, "Invalid request");
+ if (parsed_req.certificate_type() !=
+ em::CertificateBasedDeviceRegistrationData::
+ ENTERPRISE_ENROLLMENT_CERTIFICATE) {
+ return CreateHttpResponse(net::HTTP_FORBIDDEN,
+ "Invalid certificate type for registration");
+ }
+ const em::DeviceRegisterRequest& register_request =
+ parsed_req.device_register_request();
+
+ return RegisterDeviceAndSendResponse(request, register_request, "");
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_cert_based.h b/chromium/components/policy/test_support/request_handler_for_register_cert_based.h
new file mode 100644
index 00000000000..28108425bd5
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_cert_based.h
@@ -0,0 +1,33 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_CERT_BASED_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_CERT_BASED_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "components/policy/test_support/request_handler_for_register_device_and_user.h"
+
+namespace policy {
+
+// Handler for request type `certificate_based_register`.
+class RequestHandlerForRegisterCertBased
+ : public RequestHandlerForRegisterDeviceAndUser {
+ public:
+ RequestHandlerForRegisterCertBased(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForRegisterCertBased(
+ RequestHandlerForRegisterCertBased&& handler) = delete;
+ RequestHandlerForRegisterCertBased& operator=(
+ RequestHandlerForRegisterCertBased&& handler) = delete;
+ ~RequestHandlerForRegisterCertBased() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_CERT_BASED_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_register_cert_based_unittest.cc b/chromium/components/policy/test_support/request_handler_for_register_cert_based_unittest.cc
new file mode 100644
index 00000000000..a3fb930fd23
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_cert_based_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+using testing::IsEmpty;
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kMachineModel[] = "iPhone 10";
+constexpr char kBrandCode[] = "iPhone";
+constexpr char kMachineId[] = "11123";
+constexpr char kExtraData[] = "fake_extra_data";
+
+} // namespace
+
+class RequestHandlerForRegisterCertBasedTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForRegisterCertBasedTest() = default;
+ ~RequestHandlerForRegisterCertBasedTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestCertBasedRegister);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForRegisterCertBasedTest, HandleRequest_Success) {
+ em::CertificateBasedDeviceRegistrationData register_data;
+ register_data.set_certificate_type(
+ em::CertificateBasedDeviceRegistrationData::
+ ENTERPRISE_ENROLLMENT_CERTIFICATE);
+ em::DeviceRegisterRequest* register_request =
+ register_data.mutable_device_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+
+ em::DeviceManagementRequest device_management_request;
+ em::CertificateBasedDeviceRegisterRequest* cert_request =
+ device_management_request.mutable_certificate_based_register_request();
+ cert_request->mutable_signed_request()->set_data(
+ register_data.SerializeAsString() + kExtraData);
+ cert_request->mutable_signed_request()->set_extra_data_bytes(
+ std::string(kExtraData).length());
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_FALSE(response.register_response().device_management_token().empty());
+ EXPECT_FALSE(response.register_response().machine_name().empty());
+ EXPECT_EQ(response.register_response().enrollment_type(),
+ em::DeviceRegisterResponse::ENTERPRISE);
+
+ ASSERT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+ const ClientStorage::ClientInfo* client_info =
+ client_storage()->GetClientOrNull(kDeviceId);
+ ASSERT_NE(client_info, nullptr);
+ EXPECT_EQ(client_info->device_id, kDeviceId);
+ EXPECT_EQ(client_info->device_token,
+ response.register_response().device_management_token());
+ EXPECT_EQ(client_info->machine_name,
+ response.register_response().machine_name());
+ EXPECT_FALSE(client_info->username.has_value());
+ EXPECT_FALSE(client_info->allowed_policy_types.empty());
+}
+
+TEST_F(RequestHandlerForRegisterCertBasedTest, HandleRequest_InvalidData) {
+ em::DeviceManagementRequest device_management_request;
+ em::CertificateBasedDeviceRegisterRequest* cert_request =
+ device_management_request.mutable_certificate_based_register_request();
+ cert_request->mutable_signed_request()->set_data(kExtraData);
+ cert_request->mutable_signed_request()->set_extra_data_bytes(0);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForRegisterCertBasedTest,
+ HandleRequest_MissingCertificateType) {
+ em::CertificateBasedDeviceRegistrationData register_data;
+ em::DeviceManagementRequest device_management_request;
+ em::CertificateBasedDeviceRegisterRequest* cert_request =
+ device_management_request.mutable_certificate_based_register_request();
+ cert_request->mutable_signed_request()->set_data(
+ register_data.SerializeAsString());
+ cert_request->mutable_signed_request()->set_extra_data_bytes(0);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_FORBIDDEN);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_device_and_user.cc b/chromium/components/policy/test_support/request_handler_for_register_device_and_user.cc
new file mode 100644
index 00000000000..85118dd5688
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_device_and_user.cc
@@ -0,0 +1,195 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_register_device_and_user.h"
+
+#include <set>
+#include <string>
+
+#include "base/guid.h"
+#include "base/notreached.h"
+#include "base/strings/stringprintf.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+void AddAllowedPolicyTypes(em::DeviceRegisterRequest::Type type,
+ std::set<std::string>* allowed_policy_types) {
+ switch (type) {
+ // TODO(crbug.com/1289442): Remove this case once the type is correctly set
+ // for request type `register`.
+ case em::DeviceRegisterRequest::TT:
+ allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType});
+ break;
+ case em::DeviceRegisterRequest::USER:
+ allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType,
+ dm_protocol::kChromeExtensionPolicyType});
+ break;
+ case em::DeviceRegisterRequest::DEVICE:
+ allowed_policy_types->insert(
+ {dm_protocol::kChromeDevicePolicyType,
+ dm_protocol::kChromePublicAccountPolicyType,
+ dm_protocol::kChromeExtensionPolicyType,
+ dm_protocol::kChromeSigninExtensionPolicyType});
+ break;
+ case em::DeviceRegisterRequest::BROWSER:
+ allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType,
+ dm_protocol::kChromeExtensionPolicyType});
+ break;
+ case em::DeviceRegisterRequest::ANDROID_BROWSER:
+ allowed_policy_types->insert({dm_protocol::kChromeUserPolicyType});
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+std::unique_ptr<HttpResponse> ValidatePsmFields(
+ const em::DeviceRegisterRequest& register_request,
+ const PolicyStorage* policy_storage) {
+ const PolicyStorage::PsmEntry* psm_entry = policy_storage->GetPsmEntry(
+ register_request.brand_code() + "_" + register_request.machine_id());
+ if (!psm_entry)
+ return nullptr;
+
+ if (!register_request.has_psm_execution_result() ||
+ !register_request.has_psm_determination_timestamp_ms()) {
+ return CreateHttpResponse(net::HTTP_BAD_REQUEST,
+ "DeviceRegisterRequest must have all required "
+ "PSM execution fields.");
+ }
+
+ if (register_request.psm_execution_result() !=
+ psm_entry->psm_execution_result ||
+ psm_entry->psm_determination_timestamp !=
+ register_request.psm_determination_timestamp_ms()) {
+ return CreateHttpResponse(
+ net::HTTP_BAD_REQUEST,
+ "DeviceRegisterRequest must have all correct PSM execution values");
+ }
+
+ return nullptr;
+}
+
+std::unique_ptr<HttpResponse> ValidateLicenses(
+ const em::DeviceRegisterRequest& register_request,
+ const PolicyStorage* policy_storage) {
+ bool is_enterprise_license = true;
+ if (register_request.has_license_type() &&
+ register_request.license_type().license_type() ==
+ em::LicenseType_LicenseTypeEnum::LicenseType_LicenseTypeEnum_KIOSK) {
+ is_enterprise_license = false;
+ }
+
+ if ((is_enterprise_license && policy_storage->has_enterprise_license()) ||
+ (!is_enterprise_license && policy_storage->has_kiosk_license())) {
+ return nullptr;
+ }
+
+ return CreateHttpResponse(net::HTTP_PAYMENT_REQUIRED, "No license.");
+}
+
+} // namespace
+
+RequestHandlerForRegisterDeviceAndUser::RequestHandlerForRegisterDeviceAndUser(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForRegisterDeviceAndUser::
+ ~RequestHandlerForRegisterDeviceAndUser() = default;
+
+std::string RequestHandlerForRegisterDeviceAndUser::RequestType() {
+ return dm_protocol::kValueRequestRegister;
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForRegisterDeviceAndUser::HandleRequest(
+ const HttpRequest& request) {
+ // Only checks the the oauth token is set, but doesn't use it yet. User will
+ // be obtained from the policy storage.
+ // TODO(http://crbug.com/1227123): Add support for authentication.
+ std::string google_login;
+ if (!GetGoogleLoginFromRequest(request, &google_login))
+ return CreateHttpResponse(net::HTTP_UNAUTHORIZED, "User not authorized.");
+
+ const base::flat_set<std::string>& managed_users =
+ policy_storage()->managed_users();
+ if (managed_users.empty()) {
+ return CreateHttpResponse(net::HTTP_INTERNAL_SERVER_ERROR,
+ "No managed users.");
+ }
+
+ const std::string& policy_user = policy_storage()->policy_user();
+ if (managed_users.find("*") == managed_users.end() &&
+ managed_users.find(policy_user) == managed_users.end()) {
+ return CreateHttpResponse(net::HTTP_FORBIDDEN, "Unmanaged.");
+ }
+
+ em::DeviceManagementRequest device_management_request;
+ device_management_request.ParseFromString(request.content);
+ const em::DeviceRegisterRequest& register_request =
+ device_management_request.register_request();
+
+ std::unique_ptr<HttpResponse> error_response =
+ ValidatePsmFields(register_request, policy_storage());
+ if (error_response)
+ return error_response;
+
+ error_response = ValidateLicenses(register_request, policy_storage());
+ if (error_response)
+ return error_response;
+
+ return RegisterDeviceAndSendResponse(request, register_request, policy_user);
+}
+
+std::unique_ptr<HttpResponse>
+RequestHandlerForRegisterDeviceAndUser::RegisterDeviceAndSendResponse(
+ const HttpRequest& request,
+ const em::DeviceRegisterRequest& register_request,
+ const std::string& policy_user) {
+ std::string device_id =
+ KeyValueFromUrl(request.GetURL(), dm_protocol::kParamDeviceID);
+ std::string device_token = base::GUID::GenerateRandomV4().AsLowercaseString();
+ std::string machine_name = base::StringPrintf(
+ "%s - %s", register_request.machine_model().c_str(), device_id.c_str());
+
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id = device_id;
+ client_info.device_token = device_token;
+ client_info.machine_name = machine_name;
+ if (!policy_user.empty())
+ client_info.username = policy_user;
+ AddAllowedPolicyTypes(register_request.type(),
+ &client_info.allowed_policy_types);
+ client_storage()->RegisterClient(std::move(client_info));
+
+ em::DeviceManagementResponse device_management_response;
+ em::DeviceRegisterResponse* register_response =
+ device_management_response.mutable_register_response();
+ register_response->set_device_management_token(device_token);
+ register_response->set_machine_name(machine_name);
+ register_response->set_enrollment_type(
+ em::DeviceRegisterResponse::ENTERPRISE);
+
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_register_device_and_user.h b/chromium/components/policy/test_support/request_handler_for_register_device_and_user.h
new file mode 100644
index 00000000000..1aba73ae96a
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_device_and_user.h
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_DEVICE_AND_USER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_DEVICE_AND_USER_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+#include "device_management_backend.pb.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace policy {
+
+// Handler for request type `register` (registration of devices and managed
+// users).
+class RequestHandlerForRegisterDeviceAndUser
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForRegisterDeviceAndUser(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForRegisterDeviceAndUser(
+ RequestHandlerForRegisterDeviceAndUser&& handler) = delete;
+ RequestHandlerForRegisterDeviceAndUser& operator=(
+ RequestHandlerForRegisterDeviceAndUser&& handler) = delete;
+ ~RequestHandlerForRegisterDeviceAndUser() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+
+ protected:
+ std::unique_ptr<net::test_server::HttpResponse> RegisterDeviceAndSendResponse(
+ const net::test_server::HttpRequest& request,
+ const enterprise_management::DeviceRegisterRequest& register_request,
+ const std::string& policy_user);
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REGISTER_DEVICE_AND_USER_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_register_device_and_user_unittest.cc b/chromium/components/policy/test_support/request_handler_for_register_device_and_user_unittest.cc
new file mode 100644
index 00000000000..51cb7828d1b
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_register_device_and_user_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/strcat.h"
+#include "components/policy/test_support/request_handler_for_register_browser.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "device_management_backend.pb.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kAllowedUserEmail[] = "user@example.com";
+constexpr char kDisallowedUserEmail[] = "invalid-user@example.com";
+constexpr char kAllowedUserOAuthToken[] = "oauth-token-for-user";
+constexpr char kDisallowedUserOAuthToken[] = "oauth-token-for-invalid-user";
+constexpr char kMachineModel[] = "iPhone 10";
+constexpr char kBrandCode[] = "iPhone";
+constexpr char kMachineId[] = "11123";
+
+} // namespace
+
+class RequestHandlerForRegisterDeviceAndUserTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForRegisterDeviceAndUserTest() = default;
+ ~RequestHandlerForRegisterDeviceAndUserTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestRegister);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_NoEnrollmentToken) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_NoManagedUsers) {
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_INTERNAL_SERVER_ERROR);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_UserNotAllowed) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kDisallowedUserOAuthToken);
+ policy_storage()->set_policy_user(kDisallowedUserEmail);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_FORBIDDEN);
+
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest, HandleRequest_Success) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+ policy_storage()->set_policy_user(kAllowedUserEmail);
+ policy_storage()->SetPsmEntry(
+ base::StrCat({kBrandCode, "_", kMachineId}),
+ PolicyStorage::PsmEntry{
+ .psm_execution_result =
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ .psm_determination_timestamp = 42});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceRegisterRequest* register_request =
+ device_management_request.mutable_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+ register_request->set_psm_execution_result(
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE);
+ register_request->set_psm_determination_timestamp_ms(42);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+
+ ASSERT_TRUE(HasResponseBody());
+ em::DeviceManagementResponse device_management_response =
+ GetDeviceManagementResponse();
+ const em::DeviceRegisterResponse& register_response =
+ device_management_response.register_response();
+ EXPECT_FALSE(register_response.device_management_token().empty());
+ EXPECT_FALSE(register_response.machine_name().empty());
+ EXPECT_EQ(register_response.enrollment_type(),
+ em::DeviceRegisterResponse::ENTERPRISE);
+
+ ASSERT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+ const ClientStorage::ClientInfo* client_info =
+ client_storage()->GetClientOrNull(kDeviceId);
+ ASSERT_NE(client_info, nullptr);
+ EXPECT_EQ(client_info->device_id, kDeviceId);
+ EXPECT_EQ(client_info->device_token,
+ register_response.device_management_token());
+ EXPECT_EQ(client_info->machine_name, register_response.machine_name());
+ EXPECT_EQ(client_info->username, kAllowedUserEmail);
+ EXPECT_FALSE(client_info->allowed_policy_types.empty());
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_NoPsmExecutionResult) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+ policy_storage()->set_policy_user(kAllowedUserEmail);
+ policy_storage()->SetPsmEntry(
+ base::StrCat({kBrandCode, "_", kMachineId}),
+ PolicyStorage::PsmEntry{
+ .psm_execution_result =
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ .psm_determination_timestamp = 42});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceRegisterRequest* register_request =
+ device_management_request.mutable_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+ register_request->set_psm_determination_timestamp_ms(42);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_NoPsmDeterminationTimestamp) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+ policy_storage()->set_policy_user(kAllowedUserEmail);
+ policy_storage()->SetPsmEntry(
+ base::StrCat({kBrandCode, "_", kMachineId}),
+ PolicyStorage::PsmEntry{
+ .psm_execution_result =
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ .psm_determination_timestamp = 42});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceRegisterRequest* register_request =
+ device_management_request.mutable_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+ register_request->set_psm_execution_result(
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_MismatchingPsmExecutionResult) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+ policy_storage()->set_policy_user(kAllowedUserEmail);
+ policy_storage()->SetPsmEntry(
+ base::StrCat({kBrandCode, "_", kMachineId}),
+ PolicyStorage::PsmEntry{
+ .psm_execution_result =
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ .psm_determination_timestamp = 42});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceRegisterRequest* register_request =
+ device_management_request.mutable_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+ register_request->set_psm_execution_result(
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
+ register_request->set_psm_determination_timestamp_ms(42);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+TEST_F(RequestHandlerForRegisterDeviceAndUserTest,
+ HandleRequest_MismatchingPsmDeterminationTimestamp) {
+ policy_storage()->add_managed_user(kAllowedUserEmail);
+ SetGoogleLoginTokenHeader(kAllowedUserOAuthToken);
+ policy_storage()->set_policy_user(kAllowedUserEmail);
+ policy_storage()->SetPsmEntry(
+ base::StrCat({kBrandCode, "_", kMachineId}),
+ PolicyStorage::PsmEntry{
+ .psm_execution_result =
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE,
+ .psm_determination_timestamp = 42});
+
+ em::DeviceManagementRequest device_management_request;
+ em::DeviceRegisterRequest* register_request =
+ device_management_request.mutable_register_request();
+ register_request->set_machine_model(kMachineModel);
+ register_request->set_type(em::DeviceRegisterRequest::USER);
+ register_request->set_brand_code(kBrandCode);
+ register_request->set_machine_id(kMachineId);
+ register_request->set_psm_execution_result(
+ em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
+ register_request->set_psm_determination_timestamp_ms(24);
+ SetPayload(device_management_request);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_BAD_REQUEST);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_remote_commands.cc b/chromium/components/policy/test_support/request_handler_for_remote_commands.cc
new file mode 100644
index 00000000000..e719c1ac0d2
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_remote_commands.cc
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_remote_commands.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForRemoteCommands::RequestHandlerForRemoteCommands(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForRemoteCommands::~RequestHandlerForRemoteCommands() = default;
+
+std::string RequestHandlerForRemoteCommands::RequestType() {
+ return dm_protocol::kValueRequestRemoteCommands;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForRemoteCommands::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementResponse response;
+ response.mutable_remote_command_response();
+ return CreateHttpResponse(net::HTTP_OK, response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_remote_commands.h b/chromium/components/policy/test_support/request_handler_for_remote_commands.h
new file mode 100644
index 00000000000..ceca73195a4
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_remote_commands.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REMOTE_COMMANDS_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REMOTE_COMMANDS_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `remote_commands`.
+class RequestHandlerForRemoteCommands
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForRemoteCommands(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForRemoteCommands(RequestHandlerForRemoteCommands&& handler) =
+ delete;
+ RequestHandlerForRemoteCommands& operator=(
+ RequestHandlerForRemoteCommands&& handler) = delete;
+ ~RequestHandlerForRemoteCommands() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_REMOTE_COMMANDS_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_remote_commands_unittest.cc b/chromium/components/policy/test_support/request_handler_for_remote_commands_unittest.cc
new file mode 100644
index 00000000000..a789fdc4c53
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_remote_commands_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_remote_commands.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForRemoteCommandsTest
+ : public EmbeddedPolicyTestServerTestBase {
+ public:
+ RequestHandlerForRemoteCommandsTest() = default;
+ ~RequestHandlerForRemoteCommandsTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestRemoteCommands);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForRemoteCommandsTest, HandleRequest) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_TRUE(response.has_remote_command_response());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_status_upload.cc b/chromium/components/policy/test_support/request_handler_for_status_upload.cc
new file mode 100644
index 00000000000..32679ac55a2
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_status_upload.cc
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_status_upload.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForStatusUpload::RequestHandlerForStatusUpload(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForStatusUpload::~RequestHandlerForStatusUpload() = default;
+
+std::string RequestHandlerForStatusUpload::RequestType() {
+ return dm_protocol::kValueRequestUploadStatus;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForStatusUpload::HandleRequest(
+ const HttpRequest& request) {
+ em::DeviceManagementResponse response;
+ response.mutable_device_status_report_response();
+ response.mutable_session_status_report_response();
+ return CreateHttpResponse(net::HTTP_OK, response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_status_upload.h b/chromium/components/policy/test_support/request_handler_for_status_upload.h
new file mode 100644
index 00000000000..f48f539c676
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_status_upload.h
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_STATUS_UPLOAD_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_STATUS_UPLOAD_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `status_upload`.
+class RequestHandlerForStatusUpload
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForStatusUpload(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForStatusUpload(RequestHandlerForStatusUpload&& handler) =
+ delete;
+ RequestHandlerForStatusUpload& operator=(
+ RequestHandlerForStatusUpload&& handler) = delete;
+ ~RequestHandlerForStatusUpload() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_STATUS_UPLOAD_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_status_upload_unittest.cc b/chromium/components/policy/test_support/request_handler_for_status_upload_unittest.cc
new file mode 100644
index 00000000000..f2a16a58418
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_status_upload_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_status_upload.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "components/policy/test_support/policy_storage.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+
+} // namespace
+
+class RequestHandlerForStatusUploadTest
+ : public EmbeddedPolicyTestServerTestBase {
+ public:
+ RequestHandlerForStatusUploadTest() = default;
+ ~RequestHandlerForStatusUploadTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestUploadStatus);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+ }
+};
+
+TEST_F(RequestHandlerForStatusUploadTest, HandleRequest) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ ASSERT_TRUE(HasResponseBody());
+ auto response = GetDeviceManagementResponse();
+ EXPECT_TRUE(response.has_device_status_report_response());
+ EXPECT_TRUE(response.has_session_status_report_response());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_unregister.cc b/chromium/components/policy/test_support/request_handler_for_unregister.cc
new file mode 100644
index 00000000000..ff8360d97dc
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_unregister.cc
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_unregister.h"
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/test_server_helpers.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+RequestHandlerForUnregister::RequestHandlerForUnregister(
+ ClientStorage* client_storage,
+ PolicyStorage* policy_storage)
+ : EmbeddedPolicyTestServer::RequestHandler(client_storage, policy_storage) {
+}
+
+RequestHandlerForUnregister::~RequestHandlerForUnregister() = default;
+
+std::string RequestHandlerForUnregister::RequestType() {
+ return dm_protocol::kValueRequestUnregister;
+}
+
+std::unique_ptr<HttpResponse> RequestHandlerForUnregister::HandleRequest(
+ const HttpRequest& request) {
+ std::string request_device_token;
+ if (!GetDeviceTokenFromRequest(request, &request_device_token) ||
+ !client_storage()->DeleteClient(request_device_token)) {
+ return CreateHttpResponse(net::HTTP_UNAUTHORIZED, "Invalid device token.");
+ }
+
+ em::DeviceManagementResponse device_management_response;
+ device_management_response.mutable_unregister_response();
+ return CreateHttpResponse(net::HTTP_OK,
+ device_management_response.SerializeAsString());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/request_handler_for_unregister.h b/chromium/components/policy/test_support/request_handler_for_unregister.h
new file mode 100644
index 00000000000..19aedfb2c85
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_unregister.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
+
+#include "components/policy/test_support/embedded_policy_test_server.h"
+
+namespace policy {
+
+// Handler for request type `unregister`.
+class RequestHandlerForUnregister
+ : public EmbeddedPolicyTestServer::RequestHandler {
+ public:
+ RequestHandlerForUnregister(ClientStorage* client_storage,
+ PolicyStorage* policy_storage);
+ RequestHandlerForUnregister(RequestHandlerForUnregister&& handler) = delete;
+ RequestHandlerForUnregister& operator=(
+ RequestHandlerForUnregister&& handler) = delete;
+ ~RequestHandlerForUnregister() override;
+
+ // EmbeddedPolicyTestServer::RequestHandler:
+ std::string RequestType() override;
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) override;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_REQUEST_HANDLER_FOR_UNREGISTER_H_
diff --git a/chromium/components/policy/test_support/request_handler_for_unregister_unittest.cc b/chromium/components/policy/test_support/request_handler_for_unregister_unittest.cc
new file mode 100644
index 00000000000..a8d6b7e07a8
--- /dev/null
+++ b/chromium/components/policy/test_support/request_handler_for_unregister_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/request_handler_for_unregister.h"
+
+#include <utility>
+
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/test_support/client_storage.h"
+#include "components/policy/test_support/embedded_policy_test_server_test_base.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kDeviceId[] = "fake_device_id";
+constexpr char kDeviceToken[] = "fake_device_token";
+constexpr char kNonExistingDeviceToken[] = "non_existing_device_token";
+
+} // namespace
+
+class RequestHandlerForUnregisterTest
+ : public EmbeddedPolicyTestServerTestBase {
+ protected:
+ RequestHandlerForUnregisterTest() = default;
+ ~RequestHandlerForUnregisterTest() override = default;
+
+ void SetUp() override {
+ EmbeddedPolicyTestServerTestBase::SetUp();
+
+ SetRequestTypeParam(dm_protocol::kValueRequestUnregister);
+ SetAppType(dm_protocol::kValueAppType);
+ SetDeviceIdParam(kDeviceId);
+ SetDeviceType(dm_protocol::kValueDeviceType);
+
+ ClientStorage::ClientInfo client_info;
+ client_info.device_id = kDeviceId;
+ client_info.device_token = kDeviceToken;
+ client_storage()->RegisterClient(client_info);
+ }
+};
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_NoDeviceToken) {
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+}
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_ClientNotFound) {
+ SetDeviceTokenHeader(kNonExistingDeviceToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_UNAUTHORIZED);
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 1u);
+}
+
+TEST_F(RequestHandlerForUnregisterTest, HandleRequest_DeleteClient) {
+ SetDeviceTokenHeader(kDeviceToken);
+
+ StartRequestAndWait();
+
+ EXPECT_EQ(GetResponseCode(), net::HTTP_OK);
+ EXPECT_EQ(client_storage()->GetNumberOfRegisteredClients(), 0u);
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/signature_provider.cc b/chromium/components/policy/test_support/signature_provider.cc
new file mode 100644
index 00000000000..7cd67871b04
--- /dev/null
+++ b/chromium/components/policy/test_support/signature_provider.cc
@@ -0,0 +1,217 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/signature_provider.h"
+
+#include <stdint.h>
+
+#include "base/base64.h"
+#include "base/check.h"
+#include "base/containers/span.h"
+#include "base/hash/sha1.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/signature_creator.h"
+
+namespace policy {
+
+namespace {
+
+constexpr char kSigningKey1[] =
+ "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2c3KzcPqvnJ5HCk3OZkf1"
+ "LMO8Ht4dw4FO2U0EmKvpo0zznj4RwUdmKobH1AFWzwZP4CDY2M67MsukE/1Jnbx1QIDAQ"
+ "ABAkBkKcLZa/75hHVz4PR3tZaw34PATlfxEG6RiRIwXlf/FFlfGIZOSxdW/I1A3XRl0/9"
+ "nZMuctBSKBrcTRZQWfT/hAiEA9g8xbQbMO6BEH/XCRSsQbPlvj4c9wDtVEzeAzZ/ht9kC"
+ "IQDiml+/lXS1emqml711jJcYJNYJzdy1lL/ieKogR59oXQIhAK+Pl4xa1U2VxAWpq7r+R"
+ "vH55wdZT03hB4p2h4gvEzXBAiAkw9kvE0eZPiBZoRrrHIFTOH7FnnHlwBmV2+/2RsiVPQ"
+ "IhAKqx/4qisivvmoM/xbzUagfoxwsu1A/4mGjhBKiS0BCq";
+
+constexpr char kSigningKey2[] =
+ "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmZhreV04M3knCi6wibr49"
+ "oDesHny1G33PKOX9ko8pcxAiu9ZqsKCj7wNW2PGqnLi81fddACwQtYn5xdhCtzB9wIDAQ"
+ "ABAkA0z8m0cy8N08xundspoFZWO71WJLgv/peSDBYGI0RzJR1l9Np355EukQUQwRs5XrL"
+ "3vRQZy2vDqeiR96epkAhRAiEAzJ4DVI8k3pAl7CGv5icqFkJ02viExIwehhIEXBcB6p0C"
+ "IQDAKmzpoRpBEZRQ9xrTvPOi+Ea8Jnd478BU7CI/LFfgowIgMfLIoVWoDGRnvXKju60Hy"
+ "xNB70oHLut9cADp64j6QMkCIDrgxN4QbmrhaAAmtiGKE1wrlgCwCIsVamiasSOKAqLhAi"
+ "EAo/ItVcFtQPod97qG71CY/O4JzOciuU6AMhprs181vfM=";
+
+constexpr char kTestDomain1Signature1[] =
+ "l+sT5mziei/GbmiP7VtRCCfwpZcg7uKbW2OlnK5B/TTELutjEIAMdHduNBwbO44qOn"
+ "/5c7YrtkXbBehaaDYFPGI6bGTbDmG9KRxhS+DaB7opgfCQWLi79Gn/jytKLZhRN/VS"
+ "y+PEbezqMi3d1/xDxlThwWZDNwnhv9ER/Nu/32ZTjzgtqonSn2CQtwXCIILm4FdV/1"
+ "/BdmZG+Ge4i4FTqYtInir5YFe611KXU/AveGhQGBIAXo4qYg1IqbVrvKBSU9dlI6Sl"
+ "9TJJLbJ3LGaXuljgFhyMAl3gcy7ftC9MohEmwa+sc7y2mOAgYQ5SSmyAtQwQgAkX9J"
+ "3+tfxjmoA/dg==";
+
+constexpr char kTestDomain1Signature2[] =
+ "cO0nQjRptkeefKDw5QpJSQDavHABxUvbR9Wvoa235OG9Whw1RFqq2ye6pKnI3ezW6/"
+ "7b4ANcpi5a7HV5uF8K7gWyYdxY8NHLeyrbwXxg5j6HAmHmkP1UZcf/dAnWqo7cW8g4"
+ "DIQOhC43KkveMYJ2HnelwdXt/7zqkbe8/3Yj4nhjAUeARx86Sb8Nzydwkrvqs5Jw/x"
+ "5LG+BODExrXXcGu/ubDlW4ivJFqfNUPQysqBXSMY2XCHPJDx3eECLGVVN/fFAWWgjM"
+ "HFObAriAt0b18cc9Nr0mAt4Qq1oDzWcAHCPHE+5dr8Uf46BUrMLJRNRKCY7rrsoIin"
+ "9Be9gs3W+Aww==";
+
+constexpr char kTestDomain2Signature1[] =
+ "TzBiigZKwBdr6lyP6tUDsw+Q9wYO1Yepyxm0O4JZ4RID32L27sWzC1/hwC51fRcCvP"
+ "luEVIW6mH+BFODXMrteUFWfbbG7jgV+Wg+QdzMqgJjxhNKFXPTsZ7/286LAd1vBY/A"
+ "nGd8Wog6AhzfrgMbLNsH794GD0xIUwRvXUWFNP8pClj5VPgQnJrIA9aZwW8FNGbteA"
+ "HacFB0T/oqP5s7XT4Qvkj14RLmCgTwEM8Vcpqy5teJaF8yN17wniveddoOQGH6s0HC"
+ "ocprEccrH5fP/WVAPxCfx4vVYQY5q4CZ4K3f6dTC2FV4IDelM6dugEkvSS02YCzDaO"
+ "N+Z7IwElzTKg==";
+
+constexpr char kTestDomain2Signature2[] =
+ "mr+9CCYvR0cTvPwlzkxqlpGYy55gY7cPiIkPAPoql51yHK1tkMTOSFru8Dy/nMt+0o"
+ "4z7WO60F1wnIBGkQxnTj/DsO6QpCYi7oHqtLmZ2jsLQFlMyvPGUtpJEFvRwjr/TNbh"
+ "6RqUtz1LQFuJQ848kBrx7nkte1L8SuPDExgx+Q3LtbNj4SuTdvMUBMvEERXiLuwfFL"
+ "BefGjtsqfWETQVlJTCW7xcqOLedIX8UYgEDBpDOZ23A3GzCShuBsIut5m87R5mODht"
+ "EUmKNDK1+OMc6SyDpf+r48Wph4Db1bVaKy8fcpSNJOwEgsrmH7/+owKPGcN7I5jYAF"
+ "Z2PGxHTQ9JNA==";
+
+constexpr char kTestDomain3Signature1[] =
+ "T0wXC5w3GXyovA09pyOLX7ui/NI603UfbZXYyTbHI7xtzCIaHVPH35Nx4zdqVrdsej"
+ "ErQ12yVLDDIJokY4Yl+/fj/zrkAPxThI+TNQ+jo0i+al05PuopfpzvCzIXiZBbkbyW"
+ "3XfedxXP3IPN2XU2/3vX+ZXUNG6pxeETem64kGezkjkUraqnHw3JVzwJYHhpMcwdLP"
+ "PYK6V23BbEHEVBtQZd/ledXacz7gOzm1zGni4e+vxA2roAdJWyhbjU0dTKNNUsZmMv"
+ "ryQH9Af1Jw+dqs0RAbhcJXm2i8EUWIgNv6aMn1Z2DzZwKKjXsKgcYSRo8pdYa8RZAo"
+ "UExd9roA9a5w==";
+
+constexpr char kTestDomain3Signature2[] =
+ "o5MVSo4bRwIJ/aooGyXpRXsEsWPG8fNA2UTG8hgwnLYhNeJCCnLs/vW2vdp0URE8jn"
+ "qiG4N8KjbuiGw0rJtO1EygdLfpnMEtqYlFjrOie38sy92l/AwohXj6luYzMWL+FqDu"
+ "WQeXasjgyY4s9BOLQVDEnEj3pvqhrk/mXvMwUeXGpbxTNbWAd0C8BTZrGOwU/kIXxo"
+ "vAMGg8L+rQaDwBTEnMsMZcvlrIyqSg5v4BxCWuL3Yd2xvUqZEUWRp1aKetsHRnz5hw"
+ "H7WK7DzvKepDn06XjPG9lchi448U3HB3PRKtCzfO3nD9YXMKTuqRpKPF8PeK11CWh1"
+ "DBvBYwi20vbQ==";
+
+std::unique_ptr<crypto::RSAPrivateKey> DecodePrivateKey(
+ const char* const encoded) {
+ std::string to_decrypt;
+ if (!base::Base64Decode(encoded, &to_decrypt))
+ return nullptr;
+
+ return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(
+ base::as_bytes(base::make_span(to_decrypt)));
+}
+
+bool ExportPublicKeyAsString(const crypto::RSAPrivateKey& private_key,
+ std::string* public_key) {
+ std::vector<uint8_t> public_key_vec;
+ if (!private_key.ExportPublicKey(&public_key_vec))
+ return false;
+
+ public_key->assign(reinterpret_cast<const char*>(public_key_vec.data()),
+ public_key_vec.size());
+ return true;
+}
+
+std::string Decode64(const std::string& str64) {
+ std::string result;
+ CHECK(base::Base64Decode(str64, &result));
+ return result;
+}
+
+void InitSigningKeys(std::vector<SignatureProvider::SigningKey>* signing_keys) {
+ signing_keys->push_back(SignatureProvider::SigningKey(
+ DecodePrivateKey(kSigningKey1),
+ {
+ {SignatureProvider::kTestDomain1, Decode64(kTestDomain1Signature1)},
+ {SignatureProvider::kTestDomain2, Decode64(kTestDomain2Signature1)},
+ {SignatureProvider::kTestDomain3, Decode64(kTestDomain3Signature1)},
+ }));
+
+ signing_keys->push_back(SignatureProvider::SigningKey(
+ DecodePrivateKey(kSigningKey2),
+ {
+ {SignatureProvider::kTestDomain1, Decode64(kTestDomain1Signature2)},
+ {SignatureProvider::kTestDomain2, Decode64(kTestDomain2Signature2)},
+ {SignatureProvider::kTestDomain3, Decode64(kTestDomain3Signature2)},
+ }));
+}
+
+} // namespace
+
+constexpr char SignatureProvider::kTestDomain1[];
+constexpr char SignatureProvider::kTestDomain2[];
+constexpr char SignatureProvider::kTestDomain3[];
+
+SignatureProvider::SigningKey::SigningKey(
+ std::unique_ptr<crypto::RSAPrivateKey> private_key,
+ const std::map<std::string, std::string>& signatures)
+ : private_key_(std::move(private_key)), signatures_(signatures) {
+ CHECK(private_key_);
+ CHECK(ExportPublicKeyAsString(*private_key_, &public_key_));
+}
+
+SignatureProvider::SigningKey::SigningKey(
+ SignatureProvider::SigningKey&& signing_key) = default;
+
+SignatureProvider::SigningKey& SignatureProvider::SigningKey::operator=(
+ SignatureProvider::SigningKey&& signing_key) = default;
+
+SignatureProvider::SigningKey::~SigningKey() = default;
+
+bool SignatureProvider::SigningKey::GetSignatureForDomain(
+ const std::string& domain,
+ std::string* signature) const {
+ auto domain_signature = signatures_.find(domain);
+ if (domain_signature != signatures_.end()) {
+ signature->assign(domain_signature->second);
+ return true;
+ }
+
+ auto wildcard_signature = signatures_.find("*");
+ if (wildcard_signature != signatures_.end()) {
+ signature->assign(wildcard_signature->second);
+ return true;
+ }
+
+ return false;
+}
+
+bool SignatureProvider::SigningKey::Sign(const std::string& str,
+ std::string* signature) const {
+ std::vector<uint8_t> signature_vec;
+ std::string sha1 = base::SHA1HashString(str);
+ if (!crypto::SignatureCreator::Sign(
+ private_key_.get(), crypto::SignatureCreator::SHA1,
+ base::as_bytes(base::make_span(sha1)).data(), sha1.size(),
+ &signature_vec)) {
+ return false;
+ }
+
+ signature->assign(reinterpret_cast<const char*>(signature_vec.data()),
+ signature_vec.size());
+ return true;
+}
+
+SignatureProvider::SignatureProvider() {
+ InitSigningKeys(&signing_keys_);
+}
+
+SignatureProvider::SignatureProvider(SignatureProvider&& signature_provider) =
+ default;
+
+SignatureProvider& SignatureProvider::operator=(
+ SignatureProvider&& signature_provider) = default;
+
+SignatureProvider::~SignatureProvider() = default;
+
+const SignatureProvider::SigningKey* SignatureProvider::GetKeyByVersion(
+ int key_version) const {
+ // |key_version| is 1-based.
+ if (key_version < 1)
+ return nullptr;
+ size_t key_index = static_cast<size_t>(key_version) - 1;
+ if (key_index >= signing_keys_.size()) {
+ if (!rotate_keys())
+ return nullptr;
+ key_index %= signing_keys_.size();
+ }
+ return &signing_keys_[key_index];
+}
+
+const SignatureProvider::SigningKey* SignatureProvider::GetCurrentKey() const {
+ return GetKeyByVersion(current_key_version());
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/signature_provider.h b/chromium/components/policy/test_support/signature_provider.h
new file mode 100644
index 00000000000..327cb705fe2
--- /dev/null
+++ b/chromium/components/policy/test_support/signature_provider.h
@@ -0,0 +1,100 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_SIGNATURE_PROVIDER_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_SIGNATURE_PROVIDER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace crypto {
+class RSAPrivateKey;
+} // namespace crypto
+
+namespace policy {
+
+// Provides access to predefined test signing keys and allows for data signing
+// using those keys. Keys are indexed and retrieved by 1-based key versions.
+class SignatureProvider {
+ public:
+ // Provides access to a predefined test signing key.
+ class SigningKey {
+ public:
+ SigningKey(std::unique_ptr<crypto::RSAPrivateKey> private_key,
+ const std::map<std::string, std::string>& signatures);
+ SigningKey(SigningKey&& signing_key);
+ SigningKey& operator=(SigningKey&& signing_key);
+ ~SigningKey();
+
+ // Looks up the domain's signature in the passed dictionary. Returns true if
+ // domain is in |signatures_| or false otherwise.
+ bool GetSignatureForDomain(const std::string& domain,
+ std::string* signature) const;
+
+ // Signs |str| using the private key.
+ bool Sign(const std::string& str, std::string* signature) const;
+
+ const std::string& public_key() const { return public_key_; }
+
+ private:
+ // The key used for signing.
+ std::unique_ptr<crypto::RSAPrivateKey> private_key_;
+
+ // The public key corresponding to |private_key_|.
+ std::string public_key_;
+
+ // Maps domains to the corresponding signatures.
+ std::map<std::string, std::string> signatures_;
+ };
+
+ // Domains with pre-computed signatures.
+ static constexpr char kTestDomain1[] = "example.com";
+ static constexpr char kTestDomain2[] = "chromepolicytest.com";
+ static constexpr char kTestDomain3[] = "managedchrome.com";
+
+ SignatureProvider();
+ SignatureProvider(SignatureProvider&& signature_provider);
+ SignatureProvider& operator=(SignatureProvider&& signature_provider);
+ virtual ~SignatureProvider();
+
+ // Returns the key corresponding to |key_version| (1-based) or nullptr if
+ // |key_version| is out-of-bounds. Used when a key version is specified by the
+ // client.
+ const SigningKey* GetKeyByVersion(int key_version) const;
+
+ // Shortcut for |GetKeyByVersion(current_key_version_)|, used when the client
+ // doesn't specify the key version to be used.
+ const SigningKey* GetCurrentKey() const;
+
+ const std::vector<SigningKey>& signing_keys() const { return signing_keys_; }
+ void set_signing_keys(std::vector<SigningKey> signing_keys) {
+ signing_keys_ = std::move(signing_keys);
+ }
+
+ int current_key_version() const { return current_key_version_; }
+ void set_current_key_version(int current_key_version) {
+ current_key_version_ = current_key_version;
+ }
+
+ bool rotate_keys() const { return rotate_keys_; }
+ void set_rotate_keys(bool rotate_keys) { rotate_keys_ = rotate_keys; }
+
+ private:
+ std::vector<SigningKey> signing_keys_;
+
+ // The key version to be used if no key version is defined by the client.
+ int current_key_version_ = 1;
+
+ // Whether to rotate signing keys or to fail when last key is reached. The
+ // policy keys will be rotated in a round-robin fashion for each policy
+ // request (by default, the |current_key_version_| will be used for all
+ // requests).
+ bool rotate_keys_ = false;
+};
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_SIGNATURE_PROVIDER_H_
diff --git a/chromium/components/policy/test_support/signature_provider_unittest.cc b/chromium/components/policy/test_support/signature_provider_unittest.cc
new file mode 100644
index 00000000000..d6b4316dba6
--- /dev/null
+++ b/chromium/components/policy/test_support/signature_provider_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/signature_provider.h"
+
+#include <utility>
+
+#include "crypto/rsa_private_key.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+// Param: 1-based key version to be tested.
+typedef ::testing::TestWithParam<int> SignatureProviderWithValidKeyIndexTest;
+
+void CheckSignatureForDomain(const SignatureProvider::SigningKey* signing_key,
+ const std::string& domain,
+ bool expected_success) {
+ std::string signature;
+ bool success = signing_key->GetSignatureForDomain(domain, &signature);
+ ASSERT_EQ(expected_success, success);
+ EXPECT_NE(expected_success, signature.empty());
+}
+
+TEST_P(SignatureProviderWithValidKeyIndexTest, Test) {
+ SignatureProvider provider;
+
+ provider.set_current_key_version(GetParam());
+ const SignatureProvider::SigningKey* signing_key = provider.GetCurrentKey();
+ ASSERT_EQ(provider.GetKeyByVersion(GetParam()), signing_key);
+ ASSERT_TRUE(signing_key);
+
+ EXPECT_FALSE(signing_key->public_key().empty());
+
+ CheckSignatureForDomain(signing_key, SignatureProvider::kTestDomain1, true);
+ CheckSignatureForDomain(signing_key, SignatureProvider::kTestDomain2, true);
+ CheckSignatureForDomain(signing_key, SignatureProvider::kTestDomain3, true);
+ CheckSignatureForDomain(signing_key, "some-random-domain.com", false);
+
+ std::string signature;
+ EXPECT_TRUE(signing_key->Sign("some-string", &signature));
+ EXPECT_FALSE(signature.empty());
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ SignatureProviderWithValidKeyIndexTest,
+ testing::ValuesIn({1, 2}));
+
+// Param: 1-based key version to be tested.
+typedef ::testing::TestWithParam<int> SignatureProviderWithInvalidKeyIndexTest;
+
+TEST_P(SignatureProviderWithInvalidKeyIndexTest, DomainSignatures) {
+ SignatureProvider provider;
+
+ provider.set_current_key_version(GetParam());
+ EXPECT_FALSE(provider.GetCurrentKey());
+ EXPECT_FALSE(provider.GetKeyByVersion(GetParam()));
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ SignatureProviderWithInvalidKeyIndexTest,
+ testing::ValuesIn({-1, 0, 3}));
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/test_server_helpers.cc b/chromium/components/policy/test_support/test_server_helpers.cc
new file mode 100644
index 00000000000..faf92512117
--- /dev/null
+++ b/chromium/components/policy/test_support/test_server_helpers.cc
@@ -0,0 +1,101 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/test_support/test_server_helpers.h"
+
+#include <utility>
+#include "base/ranges/algorithm.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace policy {
+
+using ::net::test_server::BasicHttpResponse;
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
+
+namespace {
+
+// C++ does not offer a mechanism to check if a given status code is present in
+// net::HttpStatusCode enum. To allow distinguishing standard HTTP status code
+// from custom ones, we define this array that will contain all standard codes.
+constexpr net::HttpStatusCode kStandardHttpStatusCodes[] = {
+#define HTTP_STATUS(label, code, reason) net::HttpStatusCode(code),
+#include "net/http/http_status_code_list.h"
+#undef HTTP_STATUS
+};
+
+} // namespace
+
+void CustomHttpResponse::SendResponse(
+ base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) {
+ std::string reason = "Custom";
+ // The implementation of the BasicHttpResponse::reason() calls
+ // net::GetHttpReasonPhrase, which requires status code to be a standard HTTP
+ // status code and crashes otherwise. Hence we avoid calling it if a custom
+ // HTTP code is used.
+ // TODO(crbug/1280752): Make GetHttpReasonPhrase support custom codes instead.
+ if (base::ranges::lower_bound(kStandardHttpStatusCodes, code()) !=
+ base::ranges::end(kStandardHttpStatusCodes)) {
+ reason = BasicHttpResponse::reason();
+ }
+ delegate->SendHeadersContentAndFinish(code(), reason, BuildHeaders(),
+ content());
+}
+
+std::string KeyValueFromUrl(GURL url, const std::string& key) {
+ std::string value;
+ return net::GetValueForKeyInQuery(url, key, &value) ? value : std::string();
+}
+
+bool MeetsServerSideRequirements(GURL url) {
+ std::string device_id = KeyValueFromUrl(url, dm_protocol::kParamDeviceID);
+ return KeyValueFromUrl(url, dm_protocol::kParamDeviceType) ==
+ dm_protocol::kValueDeviceType &&
+ KeyValueFromUrl(url, dm_protocol::kParamAppType) ==
+ dm_protocol::kValueAppType &&
+ !device_id.empty() && device_id.size() <= 64;
+}
+
+bool GetTokenFromAuthorization(const HttpRequest& request,
+ const std::string& token_header_prefix,
+ std::string* out) {
+ auto authorization = request.headers.find(dm_protocol::kAuthHeader);
+ return authorization != request.headers.end() &&
+ re2::RE2::FullMatch(authorization->second,
+ token_header_prefix + "(.+)", out);
+}
+
+bool GetEnrollmentTokenFromRequest(const HttpRequest& request,
+ std::string* out) {
+ return GetTokenFromAuthorization(
+ request, dm_protocol::kEnrollmentTokenAuthHeaderPrefix, out);
+}
+
+bool GetDeviceTokenFromRequest(const HttpRequest& request, std::string* out) {
+ return GetTokenFromAuthorization(request,
+ dm_protocol::kDMTokenAuthHeaderPrefix, out);
+}
+
+bool GetGoogleLoginFromRequest(const net::test_server::HttpRequest& request,
+ std::string* out) {
+ return net::GetValueForKeyInQuery(request.GetURL(), "oauth_token", out) ||
+ GetTokenFromAuthorization(
+ request, dm_protocol::kServiceTokenAuthHeaderPrefix, out);
+}
+
+std::unique_ptr<HttpResponse> CreateHttpResponse(net::HttpStatusCode code,
+ const std::string& content) {
+ auto response = std::make_unique<CustomHttpResponse>();
+ response->set_content_type("text/plain");
+ response->set_code(code);
+ response->set_content(content);
+ return response;
+}
+
+} // namespace policy
diff --git a/chromium/components/policy/test_support/test_server_helpers.h b/chromium/components/policy/test_support/test_server_helpers.h
new file mode 100644
index 00000000000..4804a0309c7
--- /dev/null
+++ b/chromium/components/policy/test_support/test_server_helpers.h
@@ -0,0 +1,69 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_TEST_SUPPORT_TEST_SERVER_HELPERS_H_
+#define COMPONENTS_POLICY_TEST_SUPPORT_TEST_SERVER_HELPERS_H_
+
+#include <memory>
+#include <string>
+
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace test_server {
+class HttpResponse;
+struct HttpRequest;
+} // namespace test_server
+} // namespace net
+
+namespace policy {
+
+// HTTP Response that supports custom HTTP status codes.
+class CustomHttpResponse : public net::test_server::BasicHttpResponse {
+ public:
+ void SendResponse(
+ base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override;
+};
+
+// Returns the value associated with `key` in `url`'s query or empty string if
+// `key` is not present.
+std::string KeyValueFromUrl(GURL url, const std::string& key);
+
+// Check server-side requirements, as defined in
+// device_management_backend.proto.
+bool MeetsServerSideRequirements(GURL url);
+
+// Returns true if a token is specified in the request URL with prefix
+// `token_header_prefix`, in which case the token is copied to `out`.
+
+bool GetTokenFromAuthorization(const net::test_server::HttpRequest& request,
+ const std::string& token_header_prefix,
+ std::string* out);
+
+// Returns true if an enrollment token is specified in the request URL, in which
+// case the enrollment token is copied to `out`.
+bool GetEnrollmentTokenFromRequest(const net::test_server::HttpRequest& request,
+ std::string* out);
+
+// Returns true if a device token is specified in the request URL, in which case
+// the device token is copied to `out`.
+bool GetDeviceTokenFromRequest(const net::test_server::HttpRequest& request,
+ std::string* out);
+
+// Returns true if an auth toke is specified in the request URL with the
+// oauth_token parameter or if it is set as GoogleLogin token from the
+// Authorization header. The token is copied to `out` if available.
+bool GetGoogleLoginFromRequest(const net::test_server::HttpRequest& request,
+ std::string* out);
+
+// Returns a text/plain HttpResponse with a given `code` and `content`.
+std::unique_ptr<net::test_server::HttpResponse> CreateHttpResponse(
+ net::HttpStatusCode code,
+ const std::string& content);
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_TEST_SUPPORT_TEST_SERVER_HELPERS_H_
diff --git a/chromium/components/policy/tools/.style.yapf b/chromium/components/policy/tools/.style.yapf
new file mode 100644
index 00000000000..b4ebbe24670
--- /dev/null
+++ b/chromium/components/policy/tools/.style.yapf
@@ -0,0 +1,6 @@
+[style]
+based_on_style = pep8
+
+# New directories should use a .style.yapf that does not include the following:
+column_limit = 80
+indent_width = 2
diff --git a/chromium/components/policy/tools/OWNERS b/chromium/components/policy/tools/OWNERS
new file mode 100644
index 00000000000..e01016b928c
--- /dev/null
+++ b/chromium/components/policy/tools/OWNERS
@@ -0,0 +1,9 @@
+# Prefer enterprise-policy-review@ to individual owners. Sending code
+# reviews to the alias allows the owners to distribute code review loads.
+enterprise-policy-review@google.com
+
+# When making changes, also update ToolOwners in the GwsQ config:
+# http://google3/chrome/enterprise/gwsq/enterprise-policy-review.gwsq
+hendrich@chromium.org
+janagrill@google.com
+ydago@chromium.org
diff --git a/chromium/components/policy/tools/PRESUBMIT.py b/chromium/components/policy/tools/PRESUBMIT.py
new file mode 100644
index 00000000000..131e19742c9
--- /dev/null
+++ b/chromium/components/policy/tools/PRESUBMIT.py
@@ -0,0 +1,47 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+
+
+USE_PYTHON3 = True
+
+
+def _RunOtherPresubmit(function_name, input_api, output_api):
+ # Apply the PRESUBMIT for components/policy/resources to run the syntax check
+ component_resources_path = os.path.join('components', 'policy', 'resources')
+
+ # Skip the presubmit if //components/policy/resources is changed as well.
+ if any(component_resources_path in os.path.dirname(changed_file.LocalPath())
+ for changed_file in input_api.change.AffectedFiles()):
+ return []
+
+ presubmit_path = os.path.join(input_api.change.RepositoryRoot(),
+ component_resources_path, 'PRESUBMIT.py')
+ presubmit_content = input_api.ReadFile(presubmit_path)
+ global_vars = {}
+ exec(presubmit_content, global_vars)
+ return global_vars[function_name](input_api, output_api)
+
+
+def _RunPythonUnitTests(input_api, output_api):
+ tests = input_api.canned_checks.GetUnitTestsInDirectory(
+ input_api,
+ output_api,
+ directory='.',
+ files_to_check=[r'^.+_test\.py$'],
+ run_on_python2=False)
+ return input_api.RunTests(tests)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ output = _RunOtherPresubmit("CheckChangeOnUpload", input_api, output_api)
+ output.extend(_RunPythonUnitTests(input_api, output_api))
+ return output
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ output = _RunOtherPresubmit("CheckChangeOnCommit", input_api, output_api)
+ output.extend(_RunPythonUnitTests(input_api, output_api))
+ return output
diff --git a/chromium/components/policy/tools/generate_extension_admx.py b/chromium/components/policy/tools/generate_extension_admx.py
new file mode 100755
index 00000000000..b6eb5e7d5d4
--- /dev/null
+++ b/chromium/components/policy/tools/generate_extension_admx.py
@@ -0,0 +1,404 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Creates a ADMX group policy template file from an extension schema.json file.
+
+generate_extension_admx.py --name <name> --id <id> --schema <schema_file>
+ --admx <admx_file> --adml <adml_file>
+
+<name> is the human-readable name of the extension.
+<id> is the 32-character extension ID.
+<schema_file> is the file path of the input schema.json file.
+<admx_file> is the file path of the output ADMX file.
+<adml_file> is the file path of the output ADML language file (e.g. in en-US).
+
+Example:
+ Download the managed bookmarks extension from
+ https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/mv2-archive/extensions/managed_bookmarks
+ to obtain the schema.json file.
+
+ generate_extension_admx.py --name 'Managed Bookmarks'
+ --id 'gihmafigllmhbppdfjnfecimiohcljba'
+ --schema '/path/to/schema.json'
+ --admx '/path/to/managed_bookmarks.admx'
+ --adml '/path/to/en-US/managed_bookmarks.adml'
+
+'''
+
+import re
+import sys
+
+from argparse import ArgumentParser
+from xml.dom import minidom
+
+class AdmxGenerator(object):
+ '''Generates ADMX and ADML templates'''
+
+ def __init__(self, extension_name, extension_id, schema):
+ self._extension_name = extension_name
+ self._extension_id = extension_id
+ self._schema = schema
+ self._schema_map = {}
+ self._strings_seen = {}
+ self._admx_doc = None
+ self._adml_doc = None
+ self._policies_elem = None
+ self._string_table_elem = None
+ self._presentation_table_elem = None
+
+ # Registry key for policies. Treat all policies as mandatory. Recommended
+ # policies would use 'Recommended' instead of 'Policy'.
+ self._REGISTRY_KEY = \
+ 'Software\\Policies\\Google\\Chrome\\3rdparty\\extensions\\' + \
+ extension_id + '\\Policy'
+
+ def CreateTemplateXml(self):
+ '''
+ Creates ADMX and ADML templates.
+
+ @return (ADMX xml, ADML xml) tuple.
+
+ '''
+
+ # ADML must be first as ADMX uses the ADML doc to write strings.
+ self._BeginAdmlTemplate()
+ self._BeginAdmxTemplate()
+
+ root_category_name = 'extension'
+
+ # Add a category element for the root
+ self._AddCategory(self._extension_name, root_category_name,
+ 'Google:Cat_Google')
+
+ properties = self._schema['properties']
+ for policy_name, policy_schema in properties.items():
+ self._AddPolicy(policy_name, policy_schema, root_category_name,
+ self._REGISTRY_KEY)
+
+ return self._ToPrettyXml(self._admx_doc.toxml()), self._ToPrettyXml(
+ self._adml_doc.toxml())
+
+ def _AddElement(self, parent, name):
+ '''
+ Adds an element named |name| as child of |parent|.
+
+ @return The new XML element.
+
+ '''
+ doc = parent.ownerDocument
+ element = doc.createElement(name)
+ parent.appendChild(element)
+ return element
+
+ def _SetAttribute(self, elem, name, value, string_id=None):
+ '''
+ Sets the attribute |name| = |value| on the element |elem|. If |string_id|
+ is given, a new string with that ID is added to the strings table in the
+ ADML file.
+
+ '''
+ string_id = self._ToId(string_id)
+ if (string_id):
+ elem.setAttribute(name, '$(string.%s)' % string_id)
+ self._AddString(string_id, value)
+ else:
+ elem.setAttribute(name, value)
+
+ def _ToId(self, id_str):
+ '''
+ Replaces all non-alphanumeric characters by underscores.
+
+ '''
+ return re.sub('[^0-9a-zA-Z]+', '_', id_str) if id_str else None
+
+ def _AddString(self, string_id, text):
+ '''
+ Adds a string with ID |string_id| to the strings table in the ADML doc or
+ reuses an existing string.
+
+ '''
+ string_id = self._ToId(string_id)
+ if string_id in self._strings_seen:
+ assert text == self._strings_seen[string_id]
+ else:
+ self._strings_seen[string_id] = text
+ string_elem = self._AddElement(self._string_table_elem, 'string')
+ self._SetAttribute(string_elem, 'id', string_id)
+ string_elem.appendChild(self._adml_doc.createTextNode(text))
+
+ def _AddNamespace(self, namespaces_elem, elem_name, namespace, prefix):
+ '''
+ Adds an ADMX namespace node.
+
+ '''
+ namespace_elem = self._AddElement(namespaces_elem, elem_name)
+ self._SetAttribute(namespace_elem, 'namespace', namespace)
+ self._SetAttribute(namespace_elem, 'prefix', prefix)
+
+ def _AddCategory(self, display_name, category_full_name,
+ parent_category_full_name):
+ '''
+ Adds an ADMX category.
+
+ Args:
+ display_name: The human-readable name of the category.
+ category_full_name:
+ A unique name for the category.
+ This is used in 'name' attributes, which may only contain lowercase
+ letters, uppercase letters, digits and the underscore character.
+ parent_category_full_name: The unique 'full name' of the parent category.
+ '''
+ category_elem = self._AddElement(self.categories_elem_, 'category')
+ self._SetAttribute(category_elem, 'displayName', display_name,
+ category_full_name)
+ self._SetAttribute(category_elem, 'name', category_full_name)
+ parent_category_elem = self._AddElement(category_elem, 'parentCategory')
+ self._SetAttribute(parent_category_elem, 'ref', parent_category_full_name)
+
+ def _BeginAdmlTemplate(self):
+ '''
+ Writes the header of the ADML doc.
+
+ '''
+ dom_impl = minidom.getDOMImplementation('')
+ self._adml_doc = dom_impl.createDocument(None, 'policyDefinitionResources',
+ None)
+ root_elem = self._adml_doc.documentElement
+ self._SetAttribute(root_elem, 'revision', '1.0')
+ self._SetAttribute(root_elem, 'schemaVersion', '1.0')
+
+ self._AddElement(root_elem, 'displayName')
+ self._AddElement(root_elem, 'description')
+ resources_elem = self._AddElement(root_elem, 'resources')
+ self._string_table_elem = self._AddElement(resources_elem, 'stringTable')
+ self._presentation_table_elem = self._AddElement(resources_elem,
+ 'presentationTable')
+
+ def _BeginAdmxTemplate(self):
+ '''
+ Writes the header of the ADMX doc.
+
+ '''
+ dom_impl = minidom.getDOMImplementation('')
+ self._admx_doc = dom_impl.createDocument(None, 'policyDefinitions', None)
+ root_elem = self._admx_doc.documentElement
+ self._SetAttribute(root_elem, 'revision', '1.0')
+ self._SetAttribute(root_elem, 'schemaVersion', '1.0')
+
+ namespaces_elem = self._AddElement(root_elem, 'policyNamespaces')
+ self._AddNamespace(namespaces_elem, 'target',
+ 'Google.Policies.ThirdParty.' + self._extension_id,
+ 'extension')
+ self._AddNamespace(namespaces_elem, 'using', 'Google.Policies', 'Google')
+ self._AddNamespace(namespaces_elem, 'using', 'Microsoft.Policies.Windows',
+ 'windows')
+
+ resources_elem = self._AddElement(root_elem, 'resources')
+ self._SetAttribute(resources_elem, 'minRequiredRevision', '1.0')
+
+ supported_on_elem = self._AddElement(root_elem, 'supportedOn')
+ definitions_elem = self._AddElement(supported_on_elem, 'definitions')
+ definition_elem = self._AddElement(definitions_elem, 'definition')
+ self._SetAttribute(definition_elem, 'displayName',
+ 'Microsoft Windows 7 or later', 'SUPPORTED_WIN7')
+ self._SetAttribute(definition_elem, 'name', 'SUPPORTED_WIN7')
+
+ self.categories_elem_ = self._AddElement(root_elem, 'categories')
+ self._policies_elem = self._AddElement(root_elem, 'policies')
+
+ def _AddPolicy(self, policy_name, policy_schema, parent_category_full_name,
+ parent_key):
+ '''
+ Adds a policy with name |policy_name| and schema data |policy_schema| to
+ the ADMX/ADML docs.
+ Args:
+ policy_name: The name of the policy.
+ policy_schema: Schema data of the policy.
+ parent_category_full_name:
+ The unique 'full name' of the category this policy should be placed
+ under.
+ This is used in 'name' attributes, which may only contain lowercase
+ letters, uppercase letters, digits and the underscore character.
+ parenty_key: The registry key of the parent.
+ '''
+ policy_id = self._ToId(policy_name)
+ full_name = parent_category_full_name + '_' + policy_id
+ policy_title = policy_schema.get('title', policy_name)
+
+ if 'id' in policy_schema:
+ # Keep id map for referenced schema.
+ self._schema_map[policy_schema['id']] = policy_schema
+ elif ('$ref' in policy_schema):
+ # Instantiate referenced schema.
+ referenced_schema = self._schema_map[policy_schema['$ref']]
+ for key, value in referenced_schema.items():
+ if not key in policy_schema:
+ policy_schema[key] = value
+
+ # For 'object' type items create a new category (folder) and add children.
+ if (policy_schema['type'] == 'object'):
+ self._AddCategory(policy_title, full_name, parent_category_full_name)
+ properties = policy_schema['properties']
+ for child_policy_name, child_policy_schema in properties.items():
+ self._AddPolicy(child_policy_name, child_policy_schema, full_name,
+ parent_key + '\\' + policy_name)
+ else:
+ policy_elem = self._AddElement(self._policies_elem, 'policy')
+ policy_desc = policy_schema.get('description', None)
+ self._SetAttribute(policy_elem, 'name', full_name)
+ self._SetAttribute(policy_elem, 'class', 'Both')
+ self._SetAttribute(policy_elem, 'displayName', policy_title, full_name)
+ if policy_desc:
+ self._SetAttribute(policy_elem, 'explainText', policy_desc,
+ full_name + '_Explain')
+ self._SetAttribute(policy_elem, 'presentation',
+ '$(presentation.%s)' % full_name)
+ self._SetAttribute(policy_elem, 'key', parent_key)
+
+ parent_category_elem = self._AddElement(policy_elem, 'parentCategory')
+ self._SetAttribute(parent_category_elem, 'ref', parent_category_full_name)
+
+ supported_on_elem = self._AddElement(policy_elem, 'supportedOn')
+ self._SetAttribute(supported_on_elem, 'ref', 'SUPPORTED_WIN7')
+
+ desc_id = full_name + '_Part'
+ presentation_elem = self._AddElement(self._presentation_table_elem,
+ 'presentation')
+ self._SetAttribute(presentation_elem, 'id', full_name)
+ if policy_schema['type'] == 'boolean':
+ self._SetAttribute(policy_elem, 'valueName', policy_id)
+
+ enabled_value_elem = self._AddElement(policy_elem, 'enabledValue')
+ decimal_elem = self._AddElement(enabled_value_elem, 'decimal')
+ self._SetAttribute(decimal_elem, 'value', '1')
+
+ disabled_value_elem = self._AddElement(policy_elem, 'disabledValue')
+ decimal_elem = self._AddElement(disabled_value_elem, 'decimal')
+ self._SetAttribute(decimal_elem, 'value', '0')
+ elif policy_schema['type'] == 'integer':
+ elements_elem = self._AddElement(policy_elem, 'elements')
+ decimal_elem = self._AddElement(elements_elem, 'decimal')
+ self._SetAttribute(decimal_elem, 'id', desc_id)
+ self._SetAttribute(decimal_elem, 'valueName', policy_id)
+
+ textbox_elem = self._AddElement(presentation_elem, 'decimalTextBox')
+ self._SetAttribute(textbox_elem, 'refId', desc_id)
+ textbox_elem.appendChild(self._adml_doc.createTextNode(policy_title))
+ elif (policy_schema['type'] == 'string' or
+ policy_schema['type'] == 'number'):
+ # Note: 'number' are doubles, but ADMX only supports integers
+ # (decimal), thus use 'string' and rely on string-to-double
+ # conversion in RegistryDict.
+ elements_elem = self._AddElement(policy_elem, 'elements')
+ text_elem = self._AddElement(elements_elem, 'text')
+ self._SetAttribute(text_elem, 'id', desc_id)
+ self._SetAttribute(text_elem, 'valueName', policy_id)
+
+ textbox_elem = self._AddElement(presentation_elem, 'textBox')
+ self._SetAttribute(textbox_elem, 'refId', desc_id)
+ label_elem = self._AddElement(textbox_elem, 'label')
+ label_elem.appendChild(self._adml_doc.createTextNode(policy_title))
+ elif policy_schema['type'] == 'array':
+ elements_elem = self._AddElement(policy_elem, 'elements')
+ list_elem = self._AddElement(elements_elem, 'list')
+ self._SetAttribute(list_elem, 'id', desc_id)
+ self._SetAttribute(list_elem, 'key', parent_key + '\\' + policy_name)
+ self._SetAttribute(list_elem, 'valuePrefix', None)
+
+ listbox_elem = self._AddElement(presentation_elem, 'listBox')
+ self._SetAttribute(listbox_elem, 'refId', desc_id)
+ listbox_elem.appendChild(self._adml_doc.createTextNode(policy_title))
+ else:
+ raise Exception('Unhandled schema type "%s"' % policy_schema['type'])
+
+ def _ToPrettyXml(self, xml):
+ # return doc.toprettyxml(indent=' ')
+ # The above pretty-printer does not print the doctype and adds spaces
+ # around texts, e.g.:
+ # <string>
+ # value of the string
+ # </string>
+ # This is problematic both for the OSX Workgroup Manager (plist files) and
+ # the Windows Group Policy Editor (admx files). What they need instead:
+ # <string>value of string</string>
+ # So we use a hacky pretty printer here. It assumes that there are no
+ # mixed-content nodes.
+ # Get all the XML content in a one-line string.
+
+ # Determine where the line breaks will be. (They will only be between tags.)
+ lines = xml[1:len(xml) - 1].split('><')
+ indent = ''
+ # Determine indent for each line.
+ for i, line in enumerate(lines):
+ if line[0] == '/':
+ # If the current line starts with a closing tag, decrease indent before
+ # printing.
+ indent = indent[2:]
+ lines[i] = indent + '<' + line + '>'
+ if (line[0] not in ['/', '?', '!'] and '</' not in line and
+ line[len(line) - 1] != '/'):
+ # If the current line starts with an opening tag and does not conatin a
+ # closing tag, increase indent after the line is printed.
+ indent += ' '
+ # Reconstruct XML text from the lines.
+ return '\n'.join(lines)
+
+
+def ConvertJsonToAdmx(extension_id, extension_name, schema_file, admx_file,
+ adml_file):
+ '''
+ Loads the schema.json file |schema_file|, generates the ADMX and ADML docs and
+ saves them to |admx_file| and |adml_file|, respectively. The name of the
+ template and the registry keys are determined from the |extension_name| and
+ the 32-byte |extension_id|, respectively.
+
+ '''
+
+ # Load that schema.
+ with open(schema_file, 'r') as f:
+ schema = eval(f.read())
+
+ admx_generator = AdmxGenerator(extension_id, extension_name, schema)
+ admx, adml = admx_generator.CreateTemplateXml()
+
+ with open(admx_file, 'w') as f:
+ f.write(admx)
+ with open(adml_file, 'w') as f:
+ f.write(adml)
+
+
+def main():
+ '''Main function, usage see top of file.'''
+ parser = ArgumentParser(usage=__doc__)
+ parser.add_argument(
+ '--name',
+ dest='extension_name',
+ help='extension name (e.g. Managed Bookmarks)')
+ parser.add_argument(
+ '--id',
+ dest='extension_id',
+ help='extension id (e.g. gihmafigllmhbppdfjnfecimiohcljba)')
+ parser.add_argument(
+ '--schema',
+ dest='schema_file',
+ help='Input schema.json file for the extension',
+ metavar='FILE')
+ parser.add_argument(
+ '--admx', dest='admx_file', help='Output ADMX file', metavar='FILE')
+ parser.add_argument(
+ '--adml', dest='adml_file', help='Output ADML file', metavar='FILE')
+ args = parser.parse_args()
+
+ if (not args.extension_name or not args.extension_id or
+ not args.schema_file or not args.admx_file or not args.adml_file):
+ parser.print_help()
+ return 1
+
+ ConvertJsonToAdmx(args.extension_name, args.extension_id, args.schema_file,
+ args.admx_file, args.adml_file)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/components/policy/tools/generate_policy_source.py b/chromium/components/policy/tools/generate_policy_source.py
new file mode 100755
index 00000000000..10863698283
--- /dev/null
+++ b/chromium/components/policy/tools/generate_policy_source.py
@@ -0,0 +1,1897 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''python3 %(prog)s [options]
+
+Pass at least:
+--chrome-version-file <path to src/chrome/VERSION> or --all-chrome-versions
+--target-platform <which platform the target code will be generated for and can
+ be one of (win, mac, linux, chromeos, ios)>
+--policy-templates-file <path to the policy_templates.json input file>.'''
+
+
+from argparse import ArgumentParser
+from collections import defaultdict
+from collections import namedtuple
+from collections import OrderedDict
+from functools import cmp_to_key
+from functools import partial
+import ast
+import codecs
+import json
+import os
+import re
+import sys
+import textwrap
+
+sys.path.insert(
+ 0,
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ 'third_party', 'six', 'src'))
+
+import six
+
+from xml.sax.saxutils import escape as xml_escape
+
+if sys.version_info.major == 2:
+ string_type = basestring
+else:
+ string_type = str
+
+CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
+CHROMIUM_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Chromium'
+PLATFORM_STRINGS = {
+ 'chrome_frame': ['win'],
+ 'chrome_os': ['chrome_os'],
+ 'android': ['android'],
+ 'webview_android': ['android'],
+ 'ios': ['ios'],
+ 'chrome.win': ['win'],
+ 'chrome.linux': ['linux'],
+ 'chrome.mac': ['mac'],
+ 'chrome.*': ['win', 'mac', 'linux', 'fuchsia'],
+ 'chrome.win7': ['win'],
+}
+
+
+class PolicyDetails:
+ """Parses a policy template and caches all its details."""
+
+ # Maps policy types to a tuple with 4 other types:
+ # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
+ # references external data
+ # - the equivalent Protobuf field type
+ # - the name of one of the protobufs for shared policy types
+ # - the equivalent type in Android's App Restriction Schema
+ TYPE_MAP = {
+ 'dict': ('Type::DICTIONARY', 'string', 'String', 'string'),
+ 'external': ('TYPE_EXTERNAL', 'string', 'String', 'invalid'),
+ 'int': ('Type::INTEGER', 'int64', 'Integer', 'integer'),
+ 'int-enum': ('Type::INTEGER', 'int64', 'Integer', 'choice'),
+ 'list': ('Type::LIST', 'StringList', 'StringList', 'string'),
+ 'main': ('Type::BOOLEAN', 'bool', 'Boolean', 'bool'),
+ 'string': ('Type::STRING', 'string', 'String', 'string'),
+ 'string-enum': ('Type::STRING', 'string', 'String', 'choice'),
+ 'string-enum-list': ('Type::LIST', 'StringList', 'StringList',
+ 'multi-select'),
+ }
+
+ class EnumItem:
+
+ def __init__(self, item):
+ self.caption = PolicyDetails._RemovePlaceholders(item['caption'])
+ self.value = item['value']
+
+ def _ConvertPlatform(self, platform):
+ '''Converts product platform string in policy_templates.json to platform
+ string that is defined in build config.'''
+ if platform not in PLATFORM_STRINGS:
+ raise RuntimeError('Platform "%s" is not supported' % platform)
+ return PLATFORM_STRINGS[platform]
+
+ def __init__(self, policy, chrome_major_version, target_platform, valid_tags):
+ self.id = policy['id']
+ self.name = policy['name']
+ self.tags = policy.get('tags', None)
+ self._CheckTagsValidity(valid_tags)
+ features = policy.get('features', {})
+ self.can_be_recommended = features.get('can_be_recommended', False)
+ self.can_be_mandatory = features.get('can_be_mandatory', True)
+ self.internal_only = features.get('internal_only', False)
+ self.metapolicy_type = features.get('metapolicy_type', '')
+ self.is_deprecated = policy.get('deprecated', False)
+ self.is_device_only = policy.get('device_only', False)
+ self.per_profile = features.get('per_profile', False)
+ self.supported_chrome_os_management = policy.get(
+ 'supported_chrome_os_management', ['active_directory', 'google_cloud'])
+ self.schema = policy['schema']
+ self.validation_schema = policy.get('validation_schema')
+ self.has_enterprise_default = 'default_for_enterprise_users' in policy
+ if self.has_enterprise_default:
+ self.enterprise_default = policy['default_for_enterprise_users']
+ self.cloud_only = features.get('cloud_only', False)
+
+ self.platforms = set()
+ self.future_on = set()
+ for platform, version_range in map(lambda s: s.split(':'),
+ policy.get('supported_on', [])):
+ split_result = version_range.split('-')
+ if len(split_result) != 2:
+ raise RuntimeError('supported_on must have exactly one dash: "%s"' % p)
+ (version_min, version_max) = split_result
+ if version_min == '':
+ raise RuntimeError('supported_on must define a start version: "%s"' % p)
+
+ # Skip if filtering by Chromium version and the current Chromium version
+ # does not support the policy.
+ if chrome_major_version:
+ if (int(version_min) > chrome_major_version or
+ version_max != '' and int(version_max) < chrome_major_version):
+ continue
+ self.platforms.update(self._ConvertPlatform(platform))
+
+ for platform in policy.get('future_on', []):
+ self.future_on.update(self._ConvertPlatform(platform))
+
+ if self.is_device_only and self.platforms.union(self.future_on) > set(
+ ['chrome_os']):
+ raise RuntimeError('device_only is only allowed for Chrome OS: "%s"' %
+ self.name)
+
+ self.is_supported = (target_platform in self.platforms
+ or target_platform in self.future_on)
+ self.is_future = target_platform in self.future_on
+
+ if policy['type'] not in PolicyDetails.TYPE_MAP:
+ raise NotImplementedError(
+ 'Unknown policy type for %s: %s' % (policy['name'], policy['type']))
+ self.policy_type, self.protobuf_type, self.policy_protobuf_type, \
+ self.restriction_type = PolicyDetails.TYPE_MAP[policy['type']]
+
+ self.desc = '\n'.join(
+ map(str.strip,
+ PolicyDetails._RemovePlaceholders(policy['desc']).splitlines()))
+ self.caption = PolicyDetails._RemovePlaceholders(policy['caption'])
+ self.max_size = policy.get('max_size', 0)
+
+ items = policy.get('items')
+ if items is None:
+ self.items = None
+ else:
+ self.items = [PolicyDetails.EnumItem(entry) for entry in items]
+
+ PH_PATTERN = re.compile('<ph[^>]*>([^<]*|[^<]*<ex>([^<]*)</ex>[^<]*)</ph>')
+
+ def _CheckTagsValidity(self, valid_tags):
+ if self.tags == None:
+ raise RuntimeError('Policy ' + self.name + ' has to contain a list of '
+ 'tags!\n An empty list is also valid but means '
+ 'setting this policy can never harm the user\'s '
+ 'privacy or security.\n')
+ for tag in self.tags:
+ if not tag in valid_tags:
+ raise RuntimeError('Invalid Tag:' + tag + '!\n'
+ 'Chose a valid tag from \'risk_tag_definitions\' (a '
+ 'subproperty of root in policy_templates.json)!')
+
+ # Simplistic grit placeholder stripper.
+ @staticmethod
+ def _RemovePlaceholders(text):
+ result = ''
+ pos = 0
+ for m in PolicyDetails.PH_PATTERN.finditer(text):
+ result += text[pos:m.start(0)]
+ result += m.group(2) or m.group(1)
+ pos = m.end(0)
+ result += text[pos:]
+ return result
+
+
+class PolicyAtomicGroup:
+ """Parses a policy atomic group and caches its name and policy names"""
+
+ def __init__(self, policy_group, available_policies,
+ policies_already_in_group):
+ self.id = policy_group['id']
+ self.name = policy_group['name']
+ self.policies = policy_group.get('policies', None)
+ self._CheckPoliciesValidity(available_policies, policies_already_in_group)
+
+ def _CheckPoliciesValidity(self, available_policies,
+ policies_already_in_group):
+ if self.policies == None or len(self.policies) <= 0:
+ raise RuntimeError('Atomic policy group ' + self.name +
+ ' has to contain a list of '
+ 'policies!\n')
+ for policy in self.policies:
+ if policy in policies_already_in_group:
+ raise RuntimeError('Policy: ' + policy +
+ ' cannot be in more than one atomic group '
+ 'in policy_templates.json)!')
+ policies_already_in_group.add(policy)
+ if not policy in available_policies:
+ raise RuntimeError('Invalid policy: ' + policy + ' in atomic group ' +
+ self.name + '.\n')
+
+
+def ParseVersionFile(version_path):
+ chrome_major_version = None
+ for line in open(version_path, 'r').readlines():
+ key, val = line.rstrip('\r\n').split('=', 1)
+ if key == 'MAJOR':
+ chrome_major_version = val
+ break
+ if chrome_major_version is None:
+ raise RuntimeError('VERSION file does not contain major version.')
+ return int(chrome_major_version)
+
+
+def main():
+ parser = ArgumentParser(usage=__doc__)
+ parser.add_argument(
+ '--pch',
+ '--policy-constants-header',
+ dest='header_path',
+ help='generate header file of policy constants',
+ metavar='FILE')
+ parser.add_argument(
+ '--pcc',
+ '--policy-constants-source',
+ dest='source_path',
+ help='generate source file of policy constants',
+ metavar='FILE')
+ parser.add_argument(
+ '--cpp',
+ '--cloud-policy-protobuf',
+ dest='cloud_policy_proto_path',
+ help='generate cloud policy protobuf file',
+ metavar='FILE')
+ parser.add_argument(
+ '--cpfrp',
+ '--cloud-policy-full-runtime-protobuf',
+ dest='cloud_policy_full_runtime_proto_path',
+ help='generate cloud policy full runtime protobuf',
+ metavar='FILE')
+ parser.add_argument(
+ '--csp',
+ '--chrome-settings-protobuf',
+ dest='chrome_settings_proto_path',
+ help='generate chrome settings protobuf file',
+ metavar='FILE')
+ parser.add_argument(
+ '--policy-common-definitions-protobuf',
+ dest='policy_common_definitions_proto_path',
+ help='policy common definitions protobuf file path',
+ metavar='FILE')
+ parser.add_argument(
+ '--policy-common-definitions-full-runtime-protobuf',
+ dest='policy_common_definitions_full_runtime_proto_path',
+ help='generate policy common definitions full runtime protobuf file',
+ metavar='FILE')
+ parser.add_argument(
+ '--csfrp',
+ '--chrome-settings-full-runtime-protobuf',
+ dest='chrome_settings_full_runtime_proto_path',
+ help='generate chrome settings full runtime protobuf',
+ metavar='FILE')
+ parser.add_argument(
+ '--ard',
+ '--app-restrictions-definition',
+ dest='app_restrictions_path',
+ help='generate an XML file as specified by '
+ 'Android\'s App Restriction Schema',
+ metavar='FILE')
+ parser.add_argument(
+ '--rth',
+ '--risk-tag-header',
+ dest='risk_header_path',
+ help='generate header file for policy risk tags',
+ metavar='FILE')
+ parser.add_argument(
+ '--crospch',
+ '--cros-policy-constants-header',
+ dest='cros_constants_header_path',
+ help='generate header file of policy constants for use in '
+ 'Chrome OS',
+ metavar='FILE')
+ parser.add_argument(
+ '--crospcc',
+ '--cros-policy-constants-source',
+ dest='cros_constants_source_path',
+ help='generate source file of policy constants for use in '
+ 'Chrome OS',
+ metavar='FILE')
+ parser.add_argument(
+ '--chrome-version-file',
+ dest='chrome_version_file',
+ help='path to src/chrome/VERSION',
+ metavar='FILE')
+ parser.add_argument(
+ '--all-chrome-versions',
+ action='store_true',
+ dest='all_chrome_versions',
+ default=False,
+ help='do not restrict generated policies by chrome version')
+ parser.add_argument(
+ '--target-platform',
+ dest='target_platform',
+ help='the platform the generated code should run on - can be one of'
+ '(win, mac, linux, chromeos, fuchsia)',
+ metavar='PLATFORM')
+ parser.add_argument(
+ '--policy-templates-file',
+ dest='policy_templates_file',
+ help='path to the policy_templates.json input file',
+ metavar='FILE')
+ args = parser.parse_args()
+
+ has_arg_error = False
+
+ if not args.target_platform:
+ print('Error: Missing --target-platform=<platform>')
+ has_arg_error = True
+
+ if not args.policy_templates_file:
+ print('Error: Missing'
+ ' --policy-templates-file=<path to policy_templates.json>')
+ has_arg_error = True
+
+ if not args.chrome_version_file and not args.all_chrome_versions:
+ print('Error: Missing'
+ ' --chrome-version-file=<path to src/chrome/VERSION>\n'
+ ' or --all-chrome-versions')
+ has_arg_error = True
+
+ if has_arg_error:
+ print('')
+ parser.print_help()
+ return 2
+
+ version_path = args.chrome_version_file
+ target_platform = args.target_platform
+ template_file_name = args.policy_templates_file
+
+ # --target-platform accepts "chromeos" as its input because that's what is
+ # used within GN. Within policy templates, "chrome_os" is used instead.
+ if target_platform == 'chromeos':
+ target_platform = 'chrome_os'
+
+ if args.all_chrome_versions:
+ chrome_major_version = None
+ else:
+ chrome_major_version = ParseVersionFile(version_path)
+
+ template_file_contents = _LoadJSONFile(template_file_name)
+ risk_tags = RiskTags(template_file_contents)
+ policy_details = [
+ PolicyDetails(policy, chrome_major_version, target_platform,
+ risk_tags.GetValidTags())
+ for policy in template_file_contents['policy_definitions']
+ if policy['type'] != 'group'
+ ]
+ risk_tags.ComputeMaxTags(policy_details)
+ sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
+
+ policy_details_set = list(map((lambda x: x.name), policy_details))
+ policies_already_in_group = set()
+ policy_atomic_groups = [
+ PolicyAtomicGroup(group, policy_details_set, policies_already_in_group)
+ for group in template_file_contents['policy_atomic_group_definitions']
+ ]
+ sorted_policy_atomic_groups = sorted(
+ policy_atomic_groups, key=lambda group: group.name)
+
+
+ def GenerateFile(path, writer, sorted=False, xml=False):
+ if path:
+ with codecs.open(path, 'w', encoding='utf-8') as f:
+ _OutputGeneratedWarningHeader(f, template_file_name, xml)
+ writer(sorted and sorted_policy_details or policy_details,
+ sorted and sorted_policy_atomic_groups or policy_atomic_groups,
+ target_platform, f, risk_tags)
+
+ if args.header_path:
+ GenerateFile(args.header_path, _WritePolicyConstantHeader, sorted=True)
+ if args.source_path:
+ GenerateFile(args.source_path, _WritePolicyConstantSource, sorted=True)
+ if args.risk_header_path:
+ GenerateFile(args.risk_header_path, _WritePolicyRiskTagHeader)
+ if args.cloud_policy_proto_path:
+ GenerateFile(args.cloud_policy_proto_path, _WriteCloudPolicyProtobuf)
+ if (args.policy_common_definitions_full_runtime_proto_path and
+ args.policy_common_definitions_proto_path):
+ GenerateFile(
+ args.policy_common_definitions_full_runtime_proto_path,
+ partial(_WritePolicyCommonDefinitionsFullRuntimeProtobuf,
+ args.policy_common_definitions_proto_path))
+ if args.cloud_policy_full_runtime_proto_path:
+ GenerateFile(args.cloud_policy_full_runtime_proto_path,
+ partial(_WriteCloudPolicyProtobuf, is_full_runtime=True))
+ if args.chrome_settings_proto_path:
+ GenerateFile(args.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
+ if args.chrome_settings_full_runtime_proto_path:
+ GenerateFile(args.chrome_settings_full_runtime_proto_path,
+ partial(_WriteChromeSettingsProtobuf, is_full_runtime=True))
+
+ if target_platform == 'android' and args.app_restrictions_path:
+ GenerateFile(args.app_restrictions_path, _WriteAppRestrictions, xml=True)
+
+ # Generated code for Chrome OS (unused in Chromium).
+ if args.cros_constants_header_path:
+ GenerateFile(
+ args.cros_constants_header_path,
+ _WriteChromeOSPolicyConstantsHeader,
+ sorted=True)
+ if args.cros_constants_source_path:
+ GenerateFile(
+ args.cros_constants_source_path,
+ _WriteChromeOSPolicyConstantsSource,
+ sorted=True)
+
+ return 0
+
+
+#------------------ shared helpers ---------------------------------#
+
+
+def _OutputGeneratedWarningHeader(f, template_file_path, xml_style):
+ left_margin = '//'
+ if xml_style:
+ left_margin = ' '
+ f.write('<?xml version="1.0" encoding="utf-8"?>\n' '<!--\n')
+ else:
+ f.write('//\n')
+
+ f.write(left_margin + ' DO NOT MODIFY THIS FILE DIRECTLY!\n')
+ f.write(left_margin + ' IT IS GENERATED BY generate_policy_source.py\n')
+ f.write(left_margin + ' FROM ' + template_file_path + '\n')
+
+ if xml_style:
+ f.write('-->\n\n')
+ else:
+ f.write(left_margin + '\n\n')
+
+
+COMMENT_WRAPPER = textwrap.TextWrapper()
+COMMENT_WRAPPER.width = 80
+COMMENT_WRAPPER.initial_indent = '// '
+COMMENT_WRAPPER.subsequent_indent = '// '
+COMMENT_WRAPPER.replace_whitespace = False
+
+
+# Writes a comment, each line prefixed by // and wrapped to 80 spaces.
+def _OutputComment(f, comment):
+ for line in six.ensure_text(comment).splitlines():
+ if len(line) == 0:
+ f.write('//')
+ else:
+ f.write(COMMENT_WRAPPER.fill(line))
+ f.write('\n')
+
+
+def _LoadJSONFile(json_file):
+ with codecs.open(json_file, 'r', encoding='utf-8') as f:
+ text = f.read()
+ return ast.literal_eval(text)
+
+
+def _GetSupportedChromeUserPolicies(policies, protobuf_type):
+ return [
+ p for p in policies if p.is_supported and not p.is_device_only
+ and p.policy_protobuf_type == protobuf_type
+ ]
+
+
+#------------------ policy constants header ------------------------#
+
+
+# Return a list of all policies of type |metapolicy_type|.
+def _GetMetapoliciesOfType(policies, metapolicy_type):
+ return [
+ policy.name for policy in policies
+ if policy.metapolicy_type == metapolicy_type
+ ]
+
+
+def _WritePolicyConstantHeader(policies, policy_atomic_groups, target_platform,
+ f, risk_tags):
+ f.write('''#ifndef COMPONENTS_POLICY_POLICY_CONSTANTS_H_
+#define COMPONENTS_POLICY_POLICY_CONSTANTS_H_
+
+#include <cstdint>
+#include <string>
+
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace enterprise_management {
+class BooleanPolicyProto;
+class CloudPolicySettings;
+class IntegerPolicyProto;
+class StringListPolicyProto;
+class StringPolicyProto;
+}
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace internal {
+struct SchemaData;
+}
+
+''')
+
+ if target_platform == 'win':
+ f.write('// The windows registry path where Chrome policy '
+ 'configuration resides.\n'
+ 'extern const wchar_t kRegistryChromePolicyKey[];\n')
+
+ f.write('''#if BUILDFLAG(IS_CHROMEOS)
+// Sets default profile policies values for enterprise users.
+void SetEnterpriseUsersProfileDefaults(PolicyMap* policy_map);
+// Sets default system-wide policies values for enterprise users.
+void SetEnterpriseUsersSystemWideDefaults(PolicyMap* policy_map);
+// Sets all default values for enterprise users.
+void SetEnterpriseUsersDefaults(PolicyMap* policy_map);
+#endif
+
+// Returns the PolicyDetails for |policy| if |policy| is a known
+// Chrome policy, otherwise returns nullptr.
+const PolicyDetails* GetChromePolicyDetails(
+const std::string& policy);
+
+// Returns the schema data of the Chrome policy schema.
+const internal::SchemaData* GetChromeSchemaData();
+
+''')
+ f.write('// Key names for the policy settings.\n' 'namespace key {\n\n')
+ for policy in policies:
+ # TODO(joaodasilva): Include only supported policies in
+ # configuration_policy_handler.cc and configuration_policy_handler_list.cc
+ # so that these names can be conditional on 'policy.is_supported'.
+ # http://crbug.com/223616
+ f.write('extern const char k' + policy.name + '[];\n')
+ f.write('\n} // namespace key\n\n')
+
+ f.write('// Group names for the policy settings.\n' 'namespace group {\n\n')
+ for group in policy_atomic_groups:
+ f.write('extern const char k' + group.name + '[];\n')
+ f.write('\n} // namespace group\n\n')
+
+ f.write('struct AtomicGroup {\n'
+ ' const short id;\n'
+ ' const char* policy_group;\n'
+ ' const char* const* policies;\n'
+ '};\n\n')
+
+ f.write('extern const AtomicGroup kPolicyAtomicGroupMappings[];\n\n')
+ f.write('extern const size_t kPolicyAtomicGroupMappingsLength;\n\n')
+
+ # Declare arrays of metapolicies.
+ f.write('// Arrays of metapolicies.\n' 'namespace metapolicy {\n\n')
+ f.write('extern const char* const kMerge[%s];\n' %
+ len(_GetMetapoliciesOfType(policies, METAPOLICY_TYPE['merge'])))
+ f.write('extern const char* const kPrecedence[%s];\n\n' %
+ len(_GetMetapoliciesOfType(policies, METAPOLICY_TYPE['precedence'])))
+ f.write('} // namespace metapolicy\n\n')
+
+ f.write('enum class StringPolicyType {\n'
+ ' STRING,\n'
+ ' JSON,\n'
+ ' EXTERNAL,\n'
+ '};\n\n')
+
+ # User policy proto pointers, one struct for each protobuf type.
+ protobuf_types = _GetProtobufTypes()
+ for protobuf_type in protobuf_types:
+ _WriteChromePolicyAccessHeader(policies, f, protobuf_type)
+
+ f.write('constexpr int64_t kDevicePolicyExternalDataResourceCacheSize = %d;\n'
+ % _ComputeTotalDevicePolicyExternalDataMaxSize(policies))
+
+ f.write('\n} // namespace policy\n\n'
+ '#endif // COMPONENTS_POLICY_POLICY_CONSTANTS_H_\n')
+
+
+def _WriteChromePolicyAccessHeader(policies, f, protobuf_type):
+ supported_user_policies = _GetSupportedChromeUserPolicies(
+ policies, protobuf_type)
+ f.write('// Read access to the protobufs of all supported %s user policies.\n'
+ % protobuf_type.lower())
+ f.write('struct %sPolicyAccess {\n' % protobuf_type)
+ f.write(' const char* policy_key;\n'
+ ' bool per_profile;\n'
+ ' bool (*has_proto)(const em::CloudPolicySettings& policy);\n'
+ ' const em::%sPolicyProto& (*get_proto)(\n'
+ ' const em::CloudPolicySettings& policy);\n' % protobuf_type)
+ if protobuf_type == 'String':
+ f.write(' const StringPolicyType type;\n')
+ f.write('};\n')
+ f.write('extern const std::array<%sPolicyAccess, %d> k%sPolicyAccess;\n\n' %
+ (protobuf_type, len(supported_user_policies), protobuf_type))
+
+
+def _ComputeTotalDevicePolicyExternalDataMaxSize(policies):
+ total_device_policy_external_data_max_size = 0
+ for policy in policies:
+ if policy.is_device_only and policy.policy_type == 'TYPE_EXTERNAL':
+ total_device_policy_external_data_max_size += policy.max_size
+ return total_device_policy_external_data_max_size
+
+
+#------------------ policy constants source ------------------------#
+
+SchemaNodeKey = namedtuple('SchemaNodeKey',
+ 'schema_type extra is_sensitive_value')
+SchemaNode = namedtuple(
+ 'SchemaNode',
+ 'schema_type extra is_sensitive_value has_sensitive_children comments')
+PropertyNode = namedtuple('PropertyNode', 'key schema')
+PropertiesNode = namedtuple(
+ 'PropertiesNode',
+ 'begin end pattern_end required_begin required_end additional name')
+RestrictionNode = namedtuple('RestrictionNode', 'first second')
+
+# A mapping of the simple schema types to base::Value::Types.
+SIMPLE_SCHEMA_NAME_MAP = {
+ 'boolean': 'Type::BOOLEAN',
+ 'integer': 'Type::INTEGER',
+ 'null': 'Type::NONE',
+ 'number': 'Type::DOUBLE',
+ 'string': 'Type::STRING',
+}
+
+METAPOLICY_TYPE = {
+ 'merge': 'merge',
+ 'precedence': 'precedence',
+}
+
+INVALID_INDEX = -1
+MIN_INDEX = -1
+MAX_INDEX = (1 << 15) - 1 # signed short in c++
+MIN_POLICY_ID = 0
+MAX_POLICY_ID = (1 << 16) - 1 # unsigned short
+MIN_EXTERNAL_DATA_SIZE = 0
+MAX_EXTERNAL_DATA_SIZE = (1 << 32) - 1 # unsigned int32
+
+
+class SchemaNodesGenerator:
+ """Builds the internal structs to represent a JSON schema."""
+
+ def __init__(self, shared_strings):
+ """Creates a new generator.
+
+ |shared_strings| is a map of strings to a C expression that evaluates to
+ that string at runtime. This mapping can be used to reuse existing string
+ constants."""
+ self.shared_strings = shared_strings
+ self.key_index_map = {} # |SchemaNodeKey| -> index in |schema_nodes|
+ self.schema_nodes = [] # List of |SchemaNode|s
+ self.property_nodes = [] # List of |PropertyNode|s
+ self.properties_nodes = [] # List of |PropertiesNode|s
+ self.restriction_nodes = [] # List of |RestrictionNode|s
+ self.required_properties = []
+ self.int_enums = []
+ self.string_enums = []
+ self.ranges = {}
+ self.id_map = {}
+
+ def GetString(self, s):
+ if s in self.shared_strings:
+ return self.shared_strings[s]
+ # Generate JSON escaped string, which is slightly different from desired
+ # C/C++ escaped string. Known differences includes unicode escaping format.
+ return json.dumps(s)
+
+ def AppendSchema(self, schema_type, extra, is_sensitive_value, comment=''):
+ # Find existing schema node with same structure.
+ key_node = SchemaNodeKey(schema_type, extra, is_sensitive_value)
+ if key_node in self.key_index_map:
+ index = self.key_index_map[key_node]
+ if comment:
+ self.schema_nodes[index].comments.add(comment)
+ return index
+
+ # Create new schema node.
+ index = len(self.schema_nodes)
+ comments = {comment} if comment else set()
+ schema_node = SchemaNode(schema_type, extra, is_sensitive_value, False,
+ comments)
+ self.schema_nodes.append(schema_node)
+ self.key_index_map[key_node] = index
+ return index
+
+ def AppendRestriction(self, first, second):
+ r = RestrictionNode(str(first), str(second))
+ if not r in self.ranges:
+ self.ranges[r] = len(self.restriction_nodes)
+ self.restriction_nodes.append(r)
+ return self.ranges[r]
+
+ def GetSimpleType(self, name, is_sensitive_value):
+ return self.AppendSchema(SIMPLE_SCHEMA_NAME_MAP[name], INVALID_INDEX,
+ is_sensitive_value, 'simple type: ' + name)
+
+ def SchemaHaveRestriction(self, schema):
+ return any(keyword in schema
+ for keyword in ['minimum', 'maximum', 'enum', 'pattern'])
+
+ def IsConsecutiveInterval(self, seq):
+ sortedSeq = sorted(seq)
+ return all(
+ sortedSeq[i] + 1 == sortedSeq[i + 1] for i in range(len(sortedSeq) - 1))
+
+ def GetEnumIntegerType(self, schema, is_sensitive_value, name):
+ assert all(type(x) == int for x in schema['enum'])
+ possible_values = schema['enum']
+ if self.IsConsecutiveInterval(possible_values):
+ index = self.AppendRestriction(max(possible_values), min(possible_values))
+ return self.AppendSchema(
+ 'Type::INTEGER', index, is_sensitive_value,
+ 'integer with enumeration restriction (use range instead): %s' % name)
+ offset_begin = len(self.int_enums)
+ self.int_enums += possible_values
+ offset_end = len(self.int_enums)
+ return self.AppendSchema('Type::INTEGER',
+ self.AppendRestriction(offset_begin, offset_end),
+ is_sensitive_value,
+ 'integer with enumeration restriction: %s' % name)
+
+ def GetEnumStringType(self, schema, is_sensitive_value, name):
+ assert all(type(x) == str for x in schema['enum'])
+ offset_begin = len(self.string_enums)
+ self.string_enums += schema['enum']
+ offset_end = len(self.string_enums)
+ return self.AppendSchema('Type::STRING',
+ self.AppendRestriction(offset_begin, offset_end),
+ is_sensitive_value,
+ 'string with enumeration restriction: %s' % name)
+
+ def GetEnumType(self, schema, is_sensitive_value, name):
+ if len(schema['enum']) == 0:
+ raise RuntimeError('Empty enumeration in %s' % name)
+ elif schema['type'] == 'integer':
+ return self.GetEnumIntegerType(schema, is_sensitive_value, name)
+ elif schema['type'] == 'string':
+ return self.GetEnumStringType(schema, is_sensitive_value, name)
+ else:
+ raise RuntimeError('Unknown enumeration type in %s' % name)
+
+ def GetPatternType(self, schema, is_sensitive_value, name):
+ if schema['type'] != 'string':
+ raise RuntimeError('Unknown pattern type in %s' % name)
+ pattern = schema['pattern']
+ # Try to compile the pattern to validate it, note that the syntax used
+ # here might be slightly different from re2.
+ # TODO(binjin): Add a python wrapper of re2 and use it here.
+ re.compile(pattern)
+ index = len(self.string_enums)
+ self.string_enums.append(pattern)
+ return self.AppendSchema('Type::STRING', self.AppendRestriction(
+ index, index), is_sensitive_value,
+ 'string with pattern restriction: %s' % name)
+
+ def GetRangedType(self, schema, is_sensitive_value, name):
+ if schema['type'] != 'integer':
+ raise RuntimeError('Unknown ranged type in %s' % name)
+ min_value_set, max_value_set = False, False
+ if 'minimum' in schema:
+ min_value = int(schema['minimum'])
+ min_value_set = True
+ if 'maximum' in schema:
+ max_value = int(schema['maximum'])
+ max_value_set = True
+ if min_value_set and max_value_set and min_value > max_value:
+ raise RuntimeError('Invalid ranged type in %s' % name)
+ index = self.AppendRestriction(
+ str(max_value) if max_value_set else 'INT_MAX',
+ str(min_value) if min_value_set else 'INT_MIN')
+ return self.AppendSchema('Type::INTEGER', index, is_sensitive_value,
+ 'integer with ranged restriction: %s' % name)
+
+ def Generate(self, schema, name):
+ """Generates the structs for the given schema.
+
+ |schema|: a valid JSON schema in a dictionary.
+ |name|: the name of the current node, for the generated comments."""
+ if '$ref' in schema:
+ if 'id' in schema:
+ raise RuntimeError("Schemas with a $ref can't have an id")
+ if not isinstance(schema['$ref'], string_type):
+ raise RuntimeError("$ref attribute must be a string")
+ return schema['$ref']
+
+ is_sensitive_value = schema.get('sensitiveValue', False)
+ assert type(is_sensitive_value) is bool
+
+ if schema['type'] in SIMPLE_SCHEMA_NAME_MAP:
+ if not self.SchemaHaveRestriction(schema):
+ # Simple types use shared nodes.
+ return self.GetSimpleType(schema['type'], is_sensitive_value)
+ elif 'enum' in schema:
+ return self.GetEnumType(schema, is_sensitive_value, name)
+ elif 'pattern' in schema:
+ return self.GetPatternType(schema, is_sensitive_value, name)
+ else:
+ return self.GetRangedType(schema, is_sensitive_value, name)
+
+ if schema['type'] == 'array':
+ return self.AppendSchema(
+ 'Type::LIST',
+ self.GenerateAndCollectID(schema['items'], 'items of ' + name),
+ is_sensitive_value)
+ elif schema['type'] == 'object':
+ # Reserve an index first, so that dictionaries come before their
+ # properties. This makes sure that the root node is the first in the
+ # SchemaNodes array.
+ # This however, prevents de-duplication for object schemas since we could
+ # only determine duplicates after all child schema nodes are generated as
+ # well and then we couldn't remove the newly created schema node without
+ # invalidating all child schema indices.
+ index = len(self.schema_nodes)
+ self.schema_nodes.append(
+ SchemaNode('Type::DICTIONARY', INVALID_INDEX, is_sensitive_value,
+ False, {name}))
+
+ if 'additionalProperties' in schema:
+ additionalProperties = self.GenerateAndCollectID(
+ schema['additionalProperties'], 'additionalProperties of ' + name)
+ else:
+ additionalProperties = INVALID_INDEX
+
+ # Properties must be sorted by name, for the binary search lookup.
+ # Note that |properties| must be evaluated immediately, so that all the
+ # recursive calls to Generate() append the necessary child nodes; if
+ # |properties| were a generator then this wouldn't work.
+ sorted_properties = sorted(schema.get('properties', {}).items())
+ properties = [
+ PropertyNode(
+ self.GetString(key), self.GenerateAndCollectID(subschema, key))
+ for key, subschema in sorted_properties
+ ]
+
+ pattern_properties = []
+ for pattern, subschema in schema.get('patternProperties', {}).items():
+ pattern_properties.append(
+ PropertyNode(
+ self.GetString(pattern),
+ self.GenerateAndCollectID(subschema, pattern)))
+
+ begin = len(self.property_nodes)
+ self.property_nodes += properties
+ end = len(self.property_nodes)
+ self.property_nodes += pattern_properties
+ pattern_end = len(self.property_nodes)
+
+ if index == 0:
+ self.root_properties_begin = begin
+ self.root_properties_end = end
+
+ required_begin = len(self.required_properties)
+ required_properties = schema.get('required', [])
+ assert type(required_properties) is list
+ assert all(type(x) == str for x in required_properties)
+ self.required_properties += required_properties
+ required_end = len(self.required_properties)
+
+ # Check that each string in |required_properties| is in |properties|.
+ properties = schema.get('properties', {})
+ for name in required_properties:
+ assert name in properties
+
+ extra = len(self.properties_nodes)
+ self.properties_nodes.append(
+ PropertiesNode(begin, end, pattern_end, required_begin, required_end,
+ additionalProperties, name))
+
+ # Update index at |extra| now, since that was filled with a dummy value
+ # when the schema node was created.
+ self.schema_nodes[index] = self.schema_nodes[index]._replace(extra=extra)
+ return index
+ else:
+ assert False
+
+ def GenerateAndCollectID(self, schema, name):
+ """A wrapper of Generate(), will take the return value, check and add 'id'
+ attribute to self.id_map. The wrapper needs to be used for every call to
+ Generate().
+ """
+ index = self.Generate(schema, name)
+ if 'id' not in schema:
+ return index
+ id_str = schema['id']
+ if id_str in self.id_map:
+ raise RuntimeError('Duplicated id: ' + id_str)
+ self.id_map[id_str] = index
+ return index
+
+ def Write(self, f):
+ """Writes the generated structs to the given file.
+
+ |f| an open file to write to."""
+ f.write('const internal::SchemaNode kSchemas[] = {\n'
+ '// Type' + ' ' * 27 +
+ 'Extra IsSensitiveValue HasSensitiveChildren\n')
+ for schema_node in self.schema_nodes:
+ assert schema_node.extra >= MIN_INDEX and schema_node.extra <= MAX_INDEX
+ comment = ('\n' + ' ' * 69 + '// ').join(sorted(schema_node.comments))
+ f.write(' { base::Value::%-19s %4s %-16s %-5s }, // %s\n' %
+ (schema_node.schema_type + ',', str(schema_node.extra) + ',',
+ str(schema_node.is_sensitive_value).lower() + ',',
+ str(schema_node.has_sensitive_children).lower(), comment))
+ f.write('};\n\n')
+
+ if self.property_nodes:
+ f.write('const internal::PropertyNode kPropertyNodes[] = {\n'
+ '// Property' + ' ' * 61 + 'Schema\n')
+ for property_node in self.property_nodes:
+ f.write(' { %-64s %6d },\n' % (property_node.key + ',',
+ property_node.schema))
+ f.write('};\n\n')
+
+ if self.properties_nodes:
+ f.write('const internal::PropertiesNode kProperties[] = {\n'
+ '// Begin End PatternEnd RequiredBegin RequiredEnd'
+ ' Additional Properties\n')
+ for properties_node in self.properties_nodes:
+ for i in range(0, len(properties_node) - 1):
+ assert (properties_node[i] >= MIN_INDEX and
+ properties_node[i] <= MAX_INDEX)
+ f.write(
+ ' { %5d, %5d, %5d, %5d, %10d, %5d }, // %s\n' % properties_node)
+ f.write('};\n\n')
+
+ if self.restriction_nodes:
+ f.write('const internal::RestrictionNode kRestrictionNodes[] = {\n')
+ f.write('// FIRST, SECOND\n')
+ for restriction_node in self.restriction_nodes:
+ f.write(' {{ %-8s %4s}},\n' % (restriction_node.first + ',',
+ restriction_node.second))
+ f.write('};\n\n')
+
+ if self.required_properties:
+ f.write('const char* const kRequiredProperties[] = {\n')
+ for required_property in self.required_properties:
+ f.write(' %s,\n' % self.GetString(required_property))
+ f.write('};\n\n')
+
+ if self.int_enums:
+ f.write('const int kIntegerEnumerations[] = {\n')
+ for possible_values in self.int_enums:
+ f.write(' %d,\n' % possible_values)
+ f.write('};\n\n')
+
+ if self.string_enums:
+ f.write('const char* const kStringEnumerations[] = {\n')
+ for possible_values in self.string_enums:
+ f.write(' %s,\n' % self.GetString(possible_values))
+ f.write('};\n\n')
+
+ f.write('const internal::SchemaData* GetChromeSchemaData() {\n')
+ f.write(' static const internal::SchemaData kChromeSchemaData = {\n'
+ ' kSchemas,\n')
+ f.write(' kPropertyNodes,\n' if self.property_nodes else ' nullptr,\n')
+ f.write(' kProperties,\n' if self.properties_nodes else ' nullptr,\n')
+ f.write(' kRestrictionNodes,\n' if self.
+ restriction_nodes else ' nullptr,\n')
+ f.write(' kRequiredProperties,\n' if self.
+ required_properties else ' nullptr,\n')
+ f.write(' kIntegerEnumerations,\n' if self.int_enums else ' nullptr,\n')
+ f.write(
+ ' kStringEnumerations,\n' if self.string_enums else ' nullptr,\n')
+ f.write(' %d, // validation_schema root index\n' %
+ self.validation_schema_root_index)
+ f.write(' };\n\n')
+ f.write(' return &kChromeSchemaData;\n' '}\n\n')
+
+ def GetByID(self, id_str):
+ if not isinstance(id_str, string_type):
+ return id_str
+ if id_str not in self.id_map:
+ raise RuntimeError('Invalid $ref: ' + id_str)
+ return self.id_map[id_str]
+
+ def ResolveID(self, index, tuple_type, params):
+ simple_tuple = params[:index] + (self.GetByID(
+ params[index]),) + params[index + 1:]
+ return tuple_type(*simple_tuple)
+
+ def ResolveReferences(self):
+ """Resolve reference mapping, required to be called after Generate()
+
+ After calling Generate(), the type of indices used in schema structures
+ might be either int or string. An int type suggests that it's a resolved
+ index, but for string type it's unresolved. Resolving a reference is as
+ simple as looking up for corresponding ID in self.id_map, and replace the
+ old index with the mapped index.
+ """
+ self.schema_nodes = list(
+ map(partial(self.ResolveID, 1, SchemaNode), self.schema_nodes))
+ self.property_nodes = list(
+ map(partial(self.ResolveID, 1, PropertyNode), self.property_nodes))
+ self.properties_nodes = list(
+ map(partial(self.ResolveID, 3, PropertiesNode), self.properties_nodes))
+
+ def FindSensitiveChildren(self):
+ """Wrapper function, which calls FindSensitiveChildrenRecursive().
+ """
+ if self.schema_nodes:
+ self.FindSensitiveChildrenRecursive(0, set())
+
+ def FindSensitiveChildrenRecursive(self, index, handled_schema_nodes):
+ """Recursively compute |has_sensitive_children| for the schema node at
+ |index| and all its child elements. A schema has sensitive children if any
+ of its children has |is_sensitive_value|==True or has sensitive children
+ itself.
+ """
+ node = self.schema_nodes[index]
+ if index in handled_schema_nodes:
+ return node.has_sensitive_children or node.is_sensitive_value
+
+ handled_schema_nodes.add(index)
+ has_sensitive_children = False
+ if node.schema_type == 'Type::DICTIONARY':
+ properties_node = self.properties_nodes[node.extra]
+ # Iterate through properties and patternProperties.
+ for property_index in range(properties_node.begin,
+ properties_node.pattern_end - 1):
+ sub_index = self.property_nodes[property_index].schema
+ has_sensitive_children |= self.FindSensitiveChildrenRecursive(
+ sub_index, handled_schema_nodes)
+ # AdditionalProperties
+ if properties_node.additional != INVALID_INDEX:
+ sub_index = properties_node.additional
+ has_sensitive_children |= self.FindSensitiveChildrenRecursive(
+ sub_index, handled_schema_nodes)
+ elif node.schema_type == 'Type::LIST':
+ sub_index = node.extra
+ has_sensitive_children |= self.FindSensitiveChildrenRecursive(
+ sub_index, handled_schema_nodes)
+
+ if has_sensitive_children:
+ self.schema_nodes[index] = self.schema_nodes[index]._replace(
+ has_sensitive_children=True)
+
+ return has_sensitive_children or node.is_sensitive_value
+
+
+def _GenerateDefaultValue(value):
+ """Converts a JSON object into a base::Value entry. Returns a tuple, the first
+ entry being a list of declaration statements to define the variable, the
+ second entry being a way to access the variable.
+
+ If no definition is needed, the first return value will be an empty list. If
+ any error occurs, the second return value will be None (ie, no way to fetch
+ the value).
+
+ |value|: The deserialized value to convert to base::Value."""
+ if type(value) == bool or type(value) == int:
+ return [], 'base::Value(%s)' % json.dumps(value)
+ elif type(value) == str:
+ return [], 'base::Value("%s")' % value
+ elif type(value) == list:
+ setup = ['base::Value default_value(base::Value::Type::LIST);']
+ for entry in value:
+ decl, fetch = _GenerateDefaultValue(entry)
+ # Nested lists are not supported.
+ if decl:
+ return [], None
+ setup.append('default_value.Append(%s);' % fetch)
+ return setup, 'std::move(default_value)'
+ return [], None
+
+
+def _WritePolicyConstantSource(policies, policy_atomic_groups, target_platform,
+ f, risk_tags):
+ f.write('''#include "components/policy/policy_constants.h"
+
+#include <algorithm>
+#include <climits>
+#include <iterator>
+#include <memory>
+
+#include "base/check_op.h"
+#include "base/values.h"
+#include "build/branding_buildflags.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_internal.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/risk_tag.h"
+
+namespace policy {
+
+''')
+
+ # Generate the Chrome schema.
+ chrome_schema = {
+ 'type': 'object',
+ 'properties': {},
+ }
+ chrome_validation_schema = {
+ 'type': 'object',
+ 'properties': {},
+ }
+ shared_strings = {}
+ for policy in policies:
+ shared_strings[policy.name] = "key::k%s" % policy.name
+ if policy.is_supported:
+ chrome_schema['properties'][policy.name] = policy.schema
+ if policy.validation_schema is not None:
+ (chrome_validation_schema['properties'][policy.name]
+ ) = policy.validation_schema
+
+ # Note: this list must be kept in sync with the known property list of the
+ # Chrome schema, so that binary searching in the PropertyNode array gets the
+ # right index on this array as well. See the implementation of
+ # GetChromePolicyDetails() below.
+ # TODO(crbug.com/1074336): kChromePolicyDetails shouldn't be declare if there
+ # is no policy.
+ f.write('''[[maybe_unused]] const PolicyDetails kChromePolicyDetails[] = {
+// is_deprecated is_future is_device_policy id max_external_data_size, risk tags
+''')
+ for policy in policies:
+ if policy.is_supported:
+ assert policy.id >= MIN_POLICY_ID and policy.id <= MAX_POLICY_ID
+ assert (policy.max_size >= MIN_EXTERNAL_DATA_SIZE and
+ policy.max_size <= MAX_EXTERNAL_DATA_SIZE)
+ f.write(' // %s\n' % policy.name)
+ f.write(' { %-14s%-10s%-17s%4s,%22s, %s },\n' %
+ ('true,' if policy.is_deprecated else 'false,',
+ 'true,' if policy.is_future else 'false, ',
+ 'true,' if policy.is_device_only else 'false,', policy.id,
+ policy.max_size, risk_tags.ToInitString(policy.tags)))
+ f.write('};\n\n')
+
+ schema_generator = SchemaNodesGenerator(shared_strings)
+ schema_generator.GenerateAndCollectID(chrome_schema, 'root node')
+
+ if chrome_validation_schema['properties']:
+ schema_generator.validation_schema_root_index = \
+ schema_generator.GenerateAndCollectID(chrome_validation_schema,
+ 'validation_schema root node')
+ else:
+ schema_generator.validation_schema_root_index = INVALID_INDEX
+
+ schema_generator.ResolveReferences()
+ schema_generator.FindSensitiveChildren()
+ schema_generator.Write(f)
+
+ f.write('\n')
+
+ if schema_generator.property_nodes:
+ f.write('namespace {\n')
+
+ f.write('bool CompareKeys(const internal::PropertyNode& node,\n'
+ ' const std::string& key) {\n'
+ ' return node.key < key;\n'
+ '}\n\n')
+
+ f.write('} // namespace\n\n')
+
+ if target_platform == 'win':
+ f.write('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)\n'
+ 'const wchar_t kRegistryChromePolicyKey[] = '
+ 'L"' + CHROME_POLICY_KEY + '";\n'
+ '#else\n'
+ 'const wchar_t kRegistryChromePolicyKey[] = '
+ 'L"' + CHROMIUM_POLICY_KEY + '";\n'
+ '#endif\n\n')
+
+ # Setting enterprise defaults code generation.
+ profile_policy_enterprise_defaults = ""
+ system_wide_policy_enterprise_defaults = ""
+
+ for policy in policies:
+ if policy.has_enterprise_default and policy.is_supported:
+ declare_default_stmts, fetch_default = _GenerateDefaultValue(
+ policy.enterprise_default)
+ if not fetch_default:
+ raise RuntimeError('Type %s of policy %s is not supported at '
+ 'enterprise defaults' %
+ (policy.policy_type, policy.name))
+
+ # Convert declare_default_stmts to a string with the correct indentation.
+ if declare_default_stmts:
+ declare_default = ' %s\n' % '\n '.join(declare_default_stmts)
+ else:
+ declare_default = ''
+
+ setting_enterprise_default = ''' if (!policy_map->Get(key::k%s)) {
+ %s
+ policy_map->Set(key::k%s,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_USER,
+ POLICY_SOURCE_ENTERPRISE_DEFAULT,
+ %s,
+ nullptr);
+ }
+''' % (policy.name, declare_default, policy.name, fetch_default)
+
+ if policy.per_profile:
+ profile_policy_enterprise_defaults += setting_enterprise_default
+ else:
+ system_wide_policy_enterprise_defaults += setting_enterprise_default
+
+ f.write('#if BUILDFLAG(IS_CHROMEOS)')
+ f.write('''
+void SetEnterpriseUsersProfileDefaults(PolicyMap* policy_map) {
+%s
+}
+''' % profile_policy_enterprise_defaults)
+ f.write('''
+void SetEnterpriseUsersSystemWideDefaults(PolicyMap* policy_map) {
+%s
+}
+''' % system_wide_policy_enterprise_defaults)
+
+ f.write('''
+void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {
+ SetEnterpriseUsersProfileDefaults(policy_map);
+ SetEnterpriseUsersSystemWideDefaults(policy_map);
+}
+''')
+ f.write('#endif\n\n')
+
+ f.write('const PolicyDetails* GetChromePolicyDetails('
+ 'const std::string& policy) {\n')
+ if schema_generator.property_nodes:
+ f.write(' // First index in kPropertyNodes of the Chrome policies.\n'
+ ' static const int begin_index = %s;\n'
+ ' // One-past-the-end of the Chrome policies in kPropertyNodes.\n'
+ ' static const int end_index = %s;\n' %
+ (schema_generator.root_properties_begin,
+ schema_generator.root_properties_end))
+ f.write(''' const internal::PropertyNode* begin =
+ kPropertyNodes + begin_index;
+ const internal::PropertyNode* end = kPropertyNodes + end_index;
+ const internal::PropertyNode* it =
+ std::lower_bound(begin, end, policy, CompareKeys);
+ if (it == end || it->key != policy)
+ return nullptr;
+ // This relies on kPropertyNodes from begin_index to end_index
+ // having exactly the same policies (and in the same order) as
+ // kChromePolicyDetails, so that binary searching on the first
+ // gets the same results as a binary search on the second would.
+ // However, kPropertyNodes has the policy names and
+ // kChromePolicyDetails doesn't, so we obtain the index into
+ // the second array by searching the first to avoid duplicating
+ // the policy name pointers.
+ // Offsetting |it| from |begin| here obtains the index we're
+ // looking for.
+ size_t index = it - begin;
+ CHECK_LT(index, std::size(kChromePolicyDetails));
+ return kChromePolicyDetails + index;
+''')
+ else:
+ f.write('return nullptr;')
+ f.write('}\n\n')
+
+ f.write('namespace key {\n\n')
+ for policy in policies:
+ # TODO(joaodasilva): Include only supported policies in
+ # configuration_policy_handler.cc and configuration_policy_handler_list.cc
+ # so that these names can be conditional on 'policy.is_supported'.
+ # http://crbug.com/223616
+ f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
+ f.write('\n} // namespace key\n\n')
+
+ f.write('namespace group {\n\n')
+ for group in policy_atomic_groups:
+ f.write('const char k{name}[] = "{name}";\n'.format(name=group.name))
+ f.write('\n')
+ f.write('namespace {\n\n')
+ for group in policy_atomic_groups:
+ f.write('const char* const %s[] = {' % (group.name))
+ for policy in group.policies:
+ f.write('key::k%s, ' % (policy))
+ f.write('nullptr};\n')
+ f.write('\n} // namespace\n')
+ f.write('\n} // namespace group\n\n')
+
+ atomic_groups_length = 0
+ f.write('const AtomicGroup kPolicyAtomicGroupMappings[] = {\n')
+ for group in policy_atomic_groups:
+ atomic_groups_length += 1
+ f.write(' {')
+ f.write(' {id}, group::k{name}, group::{name}'.format(
+ id=group.id, name=group.name))
+ f.write(' },\n')
+ f.write('};\n\n')
+ f.write('const size_t kPolicyAtomicGroupMappingsLength = %s;\n\n' %
+ (atomic_groups_length))
+
+ f.write('namespace metapolicy {\n\n')
+ # Populate merge metapolicy array.
+ merge_metapolicies = _GetMetapoliciesOfType(policies,
+ METAPOLICY_TYPE['merge'])
+ f.write('const char* const kMerge[%s] = {\n' % len(merge_metapolicies))
+ for metapolicy in merge_metapolicies:
+ f.write(' key::k%s,\n' % metapolicy)
+ f.write('};\n\n')
+
+ # Populate precedence metapolicy array.
+ precedence_metapolicies = _GetMetapoliciesOfType(
+ policies, METAPOLICY_TYPE['precedence'])
+ f.write('const char* const kPrecedence[%s] = {\n' %
+ len(precedence_metapolicies))
+ for metapolicy in precedence_metapolicies:
+ f.write(' key::k%s,\n' % metapolicy)
+ f.write('};\n\n')
+ f.write('} // namespace metapolicy\n\n')
+
+ protobuf_types = _GetProtobufTypes()
+ for protobuf_type in protobuf_types:
+ _WriteChromePolicyAccessSource(policies, f, protobuf_type)
+
+ f.write('\n} // namespace policy\n')
+
+
+# Return the StringPolicyType enum value for a particular policy type.
+def _GetStringPolicyType(policy_type):
+ if policy_type == 'Type::STRING':
+ return 'StringPolicyType::STRING'
+ elif policy_type == 'Type::DICTIONARY':
+ return 'StringPolicyType::JSON'
+ elif policy_type == 'TYPE_EXTERNAL':
+ return 'StringPolicyType::EXTERNAL'
+ raise RuntimeError('Invalid string type: ' + policy_type + '!\n')
+
+
+# Writes an array that contains the pointers to the proto field for each policy
+# in |policies| of the given |protobuf_type|.
+def _WriteChromePolicyAccessSource(policies, f, protobuf_type):
+ supported_user_policies = _GetSupportedChromeUserPolicies(
+ policies, protobuf_type)
+ f.write('const std::array<%sPolicyAccess, %d> k%sPolicyAccess {{\n' %
+ (protobuf_type, len(supported_user_policies), protobuf_type))
+ for policy in supported_user_policies:
+ name = policy.name
+ lowercase_name = name.lower()
+
+ if protobuf_type == 'String':
+ extra_args = ',\n ' + _GetStringPolicyType(policy.policy_type)
+ else:
+ extra_args = ''
+
+ chunk_number = _ChunkNumber(policy.id)
+ if chunk_number == 0:
+ has_proto = 'policy.has_%s()' % lowercase_name
+ get_proto = 'policy.%s()' % lowercase_name
+ else:
+ has_subproto = 'policy.has_subproto%d() &&\n' % chunk_number
+ has_policy = ' policy.subproto%d().has_%s()' % (
+ chunk_number, lowercase_name)
+ has_proto = has_subproto + has_policy
+ get_proto = 'policy.subproto%d().%s()' % (chunk_number, lowercase_name)
+
+ f.write(' {key::k%s,\n'
+ ' %s,\n'
+ ' [](const em::CloudPolicySettings& policy) {\n'
+ ' return %s;\n'
+ ' },\n'
+ ' [](const em::CloudPolicySettings& policy)\n'
+ ' -> const em::%sPolicyProto& {\n'
+ ' return %s;\n'
+ ' }%s\n'
+ ' },\n' % (name, str(policy.per_profile).lower(), has_proto,
+ protobuf_type, get_proto, extra_args))
+ f.write('}};\n\n')
+
+
+#------------------ policy risk tag header -------------------------#
+
+
+class RiskTags(object):
+ '''Generates files and strings to translate the parsed risk tags.'''
+
+ # TODO(fhorschig|tnagel): Add, Check & Generate translation descriptions.
+
+ def __init__(self, template_file_contents):
+ self.max_tags = None
+ self.enum_for_tag = OrderedDict() # Ordered by severity as stated in JSON.
+ self._ReadRiskTagMetaData(template_file_contents)
+
+ def GenerateEnum(self):
+ values = [' ' + self.enum_for_tag[tag] for tag in self.enum_for_tag]
+ values.append(' RISK_TAG_COUNT')
+ values.append(' RISK_TAG_NONE')
+ enum_text = 'enum RiskTag : uint8_t {\n'
+ enum_text += ',\n'.join(values) + '\n};\n'
+ return enum_text
+
+ def GetMaxTags(self):
+ return str(self.max_tags)
+
+ def GetValidTags(self):
+ return [tag for tag in self.enum_for_tag]
+
+ def ToInitString(self, tags):
+ all_tags = [self._ToEnum(tag) for tag in tags]
+ all_tags += ["RISK_TAG_NONE" for missing in range(len(tags), self.max_tags)]
+ str_tags = "{ " + ", ".join(all_tags) + " }"
+ return "\n ".join(textwrap.wrap(str_tags, 69))
+
+ def ComputeMaxTags(self, policies):
+ self.max_tags = 0
+ for policy in policies:
+ if not policy.is_supported or policy.tags == None:
+ continue
+ self.max_tags = max(len(policy.tags), self.max_tags)
+
+ def _ToEnum(self, tag):
+ if tag in self.enum_for_tag:
+ return self.enum_for_tag[tag]
+ raise RuntimeError('Invalid Tag:' + tag + '!\n'
+ 'Chose a valid tag from \'risk_tag_definitions\' (a '
+ 'subproperty of root in policy_templates.json)!')
+
+ def _ReadRiskTagMetaData(self, template_file_contents):
+ for tag in template_file_contents['risk_tag_definitions']:
+ if tag.get('name', None) == None:
+ raise RuntimeError('Tag in \'risk_tag_definitions\' without '
+ 'description found!')
+ if tag.get('description', None) == None:
+ raise RuntimeError('Tag ' + tag['name'] + ' has no description!')
+ if tag.get('user-description', None) == None:
+ raise RuntimeError('Tag ' + tag['name'] + ' has no user-description!')
+ self.enum_for_tag[tag['name']] = "RISK_TAG_" + tag['name'].replace(
+ "-", "_").upper()
+
+
+def _WritePolicyRiskTagHeader(policies, policy_atomic_groups, target_platform,
+ f, risk_tags):
+ f.write('''#ifndef CHROME_COMMON_POLICY_RISK_TAG_H_
+#define CHROME_COMMON_POLICY_RISK_TAG_H_
+
+#include <stddef.h>
+
+namespace policy {
+
+// The tag of a policy indicates which impact a policy can have on
+// a user's privacy and/or security. Ordered descending by
+// impact.
+// The explanation of the single tags is stated in
+// policy_templates.json within the 'risk_tag_definitions' tag.
+''')
+ f.write(risk_tags.GenerateEnum() + '\n')
+ f.write('// This constant describes how many risk tags were used by the\n'
+ '// policy which uses the most risk tags.\n'
+ 'const size_t kMaxRiskTagCount = ' + risk_tags.GetMaxTags() + ';\n'
+ '\n'
+ '} // namespace policy\n\n'
+ '\n'
+ '#endif // CHROME_COMMON_POLICY_RISK_TAG_H_')
+
+
+#------------------ policy protobufs -------------------------------#
+
+# This code applies to both Active Directory and Google cloud management.
+
+CHROME_SETTINGS_PROTO_HEAD = '''
+syntax = "proto2";
+
+{full_runtime_comment}option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+// For StringList and PolicyOptions.
+import "policy_common_definitions{full_runtime_suffix}.proto";
+
+'''
+
+CLOUD_POLICY_PROTO_HEAD = '''
+syntax = "proto2";
+
+{full_runtime_comment}option optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+import "policy_common_definitions{full_runtime_suffix}.proto";
+
+'''
+
+# Field IDs [1..RESERVED_IDS] will not be used in the wrapping protobuf.
+RESERVED_IDS = 2
+
+# All user policies with ID <= |_LAST_TOP_LEVEL_POLICY_ID| are added to the top
+# level of ChromeSettingsProto and CloudPolicySettings, whereas all policies
+# with ID > |_LAST_TOP_LEVEL_POLICY_ID| are nested into sub-protos. See
+# https://crbug.com/1237044 for more details.
+_LAST_TOP_LEVEL_POLICY_ID = 1015
+
+# The approximate number of policies in one nested chunk for user policies with
+# ID > |_LAST_TOP_LEVEL_POLICY_ID|. See https://crbug.com/1237044 for more
+# details.
+_CHUNK_SIZE = 800
+
+
+def _WritePolicyProto(f, policy):
+ _OutputComment(f, policy.caption + '\n\n' + policy.desc)
+ if policy.items is not None:
+ _OutputComment(f, '\nValid values:')
+ for item in policy.items:
+ _OutputComment(f, ' %s: %s' % (str(item.value), item.caption))
+ if policy.policy_type == 'Type::DICTIONARY':
+ _OutputComment(
+ f, '\nValue schema:\n%s' % json.dumps(
+ policy.schema, sort_keys=True, indent=4, separators=(',', ': ')))
+ _OutputComment(
+ f, '\nSupported on: %s' %
+ ', '.join(sorted(list(policy.platforms.union(policy.future_on)))))
+ if policy.can_be_recommended and not policy.can_be_mandatory:
+ _OutputComment(
+ f, '\nNote: this policy must have a RECOMMENDED ' +
+ 'PolicyMode set in PolicyOptions.')
+ f.write('message %sProto {\n' % policy.name)
+ f.write(' optional PolicyOptions policy_options = 1;\n')
+ f.write(' optional %s %s = 2;\n' % (policy.protobuf_type, policy.name))
+ f.write('}\n\n')
+
+
+def _ChunkNumber(policy_id):
+ # Compute which chunk the policy should go to. Chunk 0 contains the legacy
+ # policies, whereas subsequent chunks contain nested policies.
+ if policy_id <= _LAST_TOP_LEVEL_POLICY_ID:
+ return 0
+ else:
+ return (policy_id - _LAST_TOP_LEVEL_POLICY_ID - 1) // _CHUNK_SIZE + 1
+
+
+def _FieldNumber(policy_id, chunk_number):
+ if chunk_number == 0:
+ # For the top-level policies the field number in the proto file is the
+ # same as the id assigned to the policy in policy_templates.json,
+ # skipping the RESERVED_IDS.
+ return policy_id + RESERVED_IDS
+ else:
+ # For the nested policies, the field numbers should always be in the
+ # range [1, _CHUNK_SIZE], therefore we calculate them using this formula.
+ return (policy_id - _LAST_TOP_LEVEL_POLICY_ID - 1) % _CHUNK_SIZE + 1
+
+
+def _WriteChromeSettingsProtobuf(policies,
+ policy_atomic_groups,
+ target_platform,
+ f,
+ risk_tags,
+ is_full_runtime=False):
+ # For full runtime, disable LITE_RUNTIME switch and import full runtime
+ # version of policy_common_definitions.proto.
+ full_runtime_comment = '//' if is_full_runtime else ''
+ full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
+ f.write(
+ CHROME_SETTINGS_PROTO_HEAD.format(
+ full_runtime_comment=full_runtime_comment,
+ full_runtime_suffix=full_runtime_suffix))
+ fields = defaultdict(list)
+ f.write('// PBs for individual settings.\n\n')
+ for policy in policies:
+ # Note: This protobuf also gets the unsupported policies, since it's an
+ # exhaustive list of all the supported user policies on any platform.
+ if not policy.is_device_only:
+ # Write the individual policy proto into the file
+ _WritePolicyProto(f, policy)
+
+ chunk_number = _ChunkNumber(policy.id)
+ field_number = _FieldNumber(policy.id, chunk_number)
+
+ # Add to |fields| in order to eventually add to ChromeSettingsProto.
+ fields[chunk_number].append(' optional %sProto %s = %s;\n' %
+ (policy.name, policy.name, field_number))
+
+ sorted_chunk_numbers = sorted(fields.keys())
+
+ if len(sorted_chunk_numbers) > 1:
+ f.write('// --------------------------------------------------\n'
+ '// PBs for policies with ID > %d.\n\n' % _LAST_TOP_LEVEL_POLICY_ID)
+
+ for sorted_chunk_number in sorted_chunk_numbers:
+ if sorted_chunk_number == 0:
+ continue
+ f.write('message ChromeSettingsSubProto%d {\n' % sorted_chunk_number)
+ f.write(''.join(fields[sorted_chunk_number]))
+ f.write('}\n\n')
+
+ f.write('// --------------------------------------------------\n'
+ '// Big wrapper PB containing the above groups.\n\n'
+ 'message ChromeSettingsProto {\n')
+
+ for sorted_chunk_number in sorted_chunk_numbers:
+ if sorted_chunk_number == 0:
+ f.write(''.join(fields[sorted_chunk_number]))
+ else:
+ f.write(' optional ChromeSettingsSubProto%d subProto%d = %s;\n' %
+ (sorted_chunk_number, sorted_chunk_number,
+ _LAST_TOP_LEVEL_POLICY_ID + RESERVED_IDS + sorted_chunk_number))
+
+ f.write('}\n')
+
+
+def _WriteCloudPolicyProtobuf(policies,
+ policy_atomic_groups,
+ target_platform,
+ f,
+ risk_tags,
+ is_full_runtime=False):
+ # For full runtime, disable LITE_RUNTIME switch and import full runtime
+ # version of policy_common_definitions.proto.
+ full_runtime_comment = '//' if is_full_runtime else ''
+ full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
+ f.write(
+ CLOUD_POLICY_PROTO_HEAD.format(full_runtime_comment=full_runtime_comment,
+ full_runtime_suffix=full_runtime_suffix))
+
+ fields = defaultdict(list)
+
+ for policy in policies:
+ if not policy.is_supported or policy.is_device_only:
+ continue
+
+ chunk_number = _ChunkNumber(policy.id)
+ field_number = _FieldNumber(policy.id, chunk_number)
+
+ # Add to |fields| in order to eventually add to CloudPolicyProto.
+ fields[chunk_number].append(
+ ' optional %sPolicyProto %s = %s;\n' %
+ (policy.policy_protobuf_type, policy.name, field_number))
+
+ sorted_chunk_numbers = sorted(fields.keys())
+
+ if len(sorted_chunk_numbers) > 1:
+ for sorted_chunk_number in sorted_chunk_numbers:
+ if sorted_chunk_number == 0:
+ continue
+ f.write('message CloudPolicySubProto%d {\n' % sorted_chunk_number)
+ f.write(''.join(fields[sorted_chunk_number]))
+ f.write('}\n\n')
+
+ f.write('message CloudPolicySettings {\n')
+
+ for sorted_chunk_number in sorted_chunk_numbers:
+ if sorted_chunk_number == 0:
+ f.write(''.join(fields[sorted_chunk_number]))
+ else:
+ f.write(' optional CloudPolicySubProto%d subProto%d = %s;\n' %
+ (sorted_chunk_number, sorted_chunk_number,
+ _LAST_TOP_LEVEL_POLICY_ID + RESERVED_IDS + sorted_chunk_number))
+
+ f.write('}\n')
+
+
+def _WritePolicyCommonDefinitionsFullRuntimeProtobuf(
+ policy_common_definitions_proto_path, policies, policy_atomic_groups,
+ target_platform, f, risk_tags):
+ # For full runtime, disable LITE_RUNTIME switch
+ with open(policy_common_definitions_proto_path, 'r') as proto_file:
+ policy_common_definitions_proto_code = proto_file.read()
+ f.write(
+ policy_common_definitions_proto_code.replace(
+ "option optimize_for = LITE_RUNTIME;",
+ "//option optimize_for = LITE_RUNTIME;"))
+
+
+#------------------ Chrome OS policy constants header --------------#
+
+# This code applies to Active Directory management only.
+
+
+# Filter for _GetSupportedChromeOSPolicies().
+def _IsSupportedChromeOSPolicy(type, policy):
+ # Filter out unsupported policies.
+ if not policy.is_supported:
+ return False
+ # Filter out device policies if user policies are requested.
+ if type == 'user' and policy.is_device_only:
+ return False
+ # Filter out user policies if device policies are requested.
+ if type == 'device' and not policy.is_device_only:
+ return False
+ # Filter out non-Active-Directory policies.
+ if 'active_directory' not in policy.supported_chrome_os_management:
+ return False
+ return True
+
+
+# Returns a list of supported user and/or device policies by filtering
+# |policies|. |type| may be 'user', 'device' or 'both'.
+def _GetSupportedChromeOSPolicies(policies, type):
+ if (type not in ['user', 'device', 'both']):
+ raise RuntimeError('Unsupported type "%s"' % type)
+
+ return list(filter(partial(_IsSupportedChromeOSPolicy, type), policies))
+
+
+# Returns a list of supported user and/or device |policies| additionally
+# filtered by |protobuf_type|, which may be any of |_GetProtobufTypes|.
+def _GetSupportedChromeOSPoliciesForProtobufType(policies, type, protobuf_type):
+ supported_policies = _GetSupportedChromeOSPolicies(policies, type)
+
+ return [
+ p for p in supported_policies if p.policy_protobuf_type == protobuf_type
+ ]
+
+
+# Returns the list of all policy.policy_protobuf_type strings from |policies|.
+def _GetProtobufTypes():
+ return sorted(['Integer', 'Boolean', 'String', 'StringList'])
+
+
+# Writes the definition of an array that contains the pointers to the mutable
+# proto field for each policy in |policies| of the given |protobuf_type|.
+def _WriteChromeOSPolicyAccessHeader(supported_policies, f, protobuf_type):
+ f.write('// Access to the mutable protobuf function of all supported '
+ '%s user\n// policies.\n' % protobuf_type.lower())
+ f.write('struct %sPolicyAccess {\n'
+ ' const char* policy_key;\n'
+ ' bool per_profile;\n'
+ ' enterprise_management::%sPolicyProto* (*mutable_proto_ptr)(\n'
+ ' enterprise_management::CloudPolicySettings* policy);\n'
+ '};\n' % (protobuf_type, protobuf_type))
+ f.write('extern const std::array<%sPolicyAccess, %d> k%sPolicyAccess;\n\n' %
+ (protobuf_type, len(supported_policies), protobuf_type))
+
+
+# Writes policy_constants.h for use in Chrome OS.
+def _WriteChromeOSPolicyConstantsHeader(policies, policy_atomic_groups,
+ target_platform, f, risk_tags):
+ f.write('#ifndef __BINDINGS_POLICY_CONSTANTS_H_\n'
+ '#define __BINDINGS_POLICY_CONSTANTS_H_\n\n'
+ '#include <array>\n\n')
+
+ # Forward declarations.
+ supported_user_policies = _GetSupportedChromeOSPolicies(policies, 'user')
+ protobuf_types = _GetProtobufTypes()
+ f.write('namespace enterprise_management {\n' 'class CloudPolicySettings;\n')
+ for protobuf_type in protobuf_types:
+ f.write('class %sPolicyProto;\n' % protobuf_type)
+ f.write('} // namespace enterprise_management\n\n')
+
+ f.write('namespace policy {\n\n')
+
+ # Policy keys.
+ all_supported_policies = _GetSupportedChromeOSPolicies(policies, 'both')
+ f.write('// Registry key names for user and device policies.\n'
+ 'namespace key {\n\n')
+ for policy in all_supported_policies:
+ f.write('extern const char k' + policy.name + '[];\n')
+ f.write('\n} // namespace key\n\n')
+
+ # Device policy keys.
+ f.write('// NULL-terminated list of device policy registry key names.\n')
+ f.write('extern const char* kDevicePolicyKeys[];\n\n')
+
+ # User policy proto pointers, one struct for each protobuf type.
+ for protobuf_type in protobuf_types:
+ supported_user_policies = _GetSupportedChromeOSPoliciesForProtobufType(
+ policies, 'user', protobuf_type)
+ _WriteChromeOSPolicyAccessHeader(supported_user_policies, f, protobuf_type)
+
+ f.write('} // namespace policy\n\n'
+ '#endif // __BINDINGS_POLICY_CONSTANTS_H_\n')
+
+
+#------------------ Chrome OS policy constants source --------------#
+
+
+# Writes an array that contains the pointers to the mutable proto field for each
+# policy in |policies| of the given |protobuf_type|.
+def _WriteChromeOSPolicyAccessSource(supported_policies, f, protobuf_type):
+ f.write('const std::array<%sPolicyAccess, %d> k%sPolicyAccess {{\n' %
+ (protobuf_type, len(supported_policies), protobuf_type))
+ for policy in supported_policies:
+ name = policy.name
+ lowercase_name = name.lower()
+
+ chunk_number = _ChunkNumber(policy.id)
+ if chunk_number == 0:
+ mutable_proto_ptr = 'policy->mutable_%s()' % lowercase_name
+ else:
+ mutable_proto_ptr = 'policy->mutable_subproto%d()->mutable_%s()' % (
+ chunk_number, lowercase_name)
+
+ f.write(' {key::k%s,\n'
+ ' %s,\n'
+ ' [](em::CloudPolicySettings* policy)\n'
+ ' -> em::%sPolicyProto* {\n'
+ ' return %s;\n'
+ ' }\n'
+ ' },\n' % (name, str(
+ policy.per_profile).lower(), protobuf_type, mutable_proto_ptr))
+ f.write('}};\n\n')
+
+
+# Writes policy_constants.cc for use in Chrome OS.
+def _WriteChromeOSPolicyConstantsSource(policies, policy_atomic_groups,
+ target_platform, f, risk_tags):
+ f.write('#include "bindings/cloud_policy.pb.h"\n'
+ '#include "bindings/policy_constants.h"\n\n'
+ 'namespace em = enterprise_management;\n\n'
+ 'namespace policy {\n\n')
+
+ # Policy keys.
+ all_supported_policies = _GetSupportedChromeOSPolicies(policies, 'both')
+ f.write('namespace key {\n\n')
+ for policy in all_supported_policies:
+ f.write('const char k{name}[] = "{name}";\n'.format(name=policy.name))
+ f.write('\n} // namespace key\n\n')
+
+ # Device policy keys.
+ supported_device_policies = _GetSupportedChromeOSPolicies(policies, 'device')
+ f.write('const char* kDevicePolicyKeys[] = {\n\n')
+ for policy in supported_device_policies:
+ f.write(' key::k%s,\n' % policy.name)
+ f.write(' nullptr};\n\n')
+
+ # User policy proto pointers, one struct for each protobuf type.
+ protobuf_types = _GetProtobufTypes()
+ for protobuf_type in protobuf_types:
+ supported_user_policies = _GetSupportedChromeOSPoliciesForProtobufType(
+ policies, 'user', protobuf_type)
+ _WriteChromeOSPolicyAccessSource(supported_user_policies, f, protobuf_type)
+
+ f.write('} // namespace policy\n')
+
+
+#------------------ app restrictions -------------------------------#
+
+
+ENROLLMENT_TOKEN_POLICY_NAME = 'CloudManagementEnrollmentToken'
+
+
+def _WriteAppRestrictions(policies, policy_atomic_groups, target_platform, f,
+ risk_tags):
+
+ def WriteRestrictionCommon(key):
+ f.write(' <restriction\n' ' android:key="%s"\n' % key)
+ f.write(' android:title="@string/%sTitle"\n' % key)
+ f.write(' android:description="@string/%sDesc"\n' % key)
+
+ def WriteItemsDefinition(key):
+ f.write(' android:entries="@array/%sEntries"\n' % key)
+ f.write(' android:entryValues="@array/%sValues"\n' % key)
+
+ def WriteAppRestriction(policy):
+ policy_name = policy.name
+ WriteRestrictionCommon(policy_name)
+
+ if policy.items is not None:
+ WriteItemsDefinition(policy_name)
+
+ f.write(' android:restrictionType="%s"/>' % policy.restriction_type)
+ f.write('\n\n')
+
+ def ShouldWriteAppRestriction(policy):
+ return (policy.is_supported and policy.restriction_type != 'invalid'
+ and not policy.is_deprecated and not policy.is_future
+ and not policy.internal_only and not policy.cloud_only)
+
+ # Compare policies by name, considering that `ENROLLMENT_TOKEN_POLICY_NAME`
+ # should come before all other policies in the generate app restrictions file.
+ def Compare(policy1, policy2):
+ if policy1.name == policy2.name:
+ return 0
+ if policy1.name == ENROLLMENT_TOKEN_POLICY_NAME:
+ return -1
+ if policy2.name == ENROLLMENT_TOKEN_POLICY_NAME:
+ return 1
+ if policy1.name < policy2.name:
+ return -1
+ return 1
+
+ # _WriteAppRestrictions body
+ f.write('<restrictions xmlns:android="'
+ 'http://schemas.android.com/apk/res/android">\n\n')
+ for policy in sorted(policies, key=cmp_to_key(Compare)):
+ if ShouldWriteAppRestriction(policy):
+ WriteAppRestriction(policy)
+ f.write('</restrictions>')
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/components/policy/tools/generate_policy_source_test.py b/chromium/components/policy/tools/generate_policy_source_test.py
new file mode 100755
index 00000000000..26282724ca0
--- /dev/null
+++ b/chromium/components/policy/tools/generate_policy_source_test.py
@@ -0,0 +1,473 @@
+#!/usr/bin/env python3
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import codecs
+import unittest
+from unittest.mock import patch, mock_open, call
+from typing import NamedTuple
+
+import generate_policy_source
+import generate_policy_source_test_data as test_data
+
+from generate_policy_source import PolicyDetails
+
+
+class PolicyData(NamedTuple):
+ policy_id: int
+ chunk_number: int
+ field_number: int
+
+
+class PolicyGenerationTest(unittest.TestCase):
+
+ TEMPLATES_JSON = {
+ "risk_tag_definitions": [{
+ "name": "full-admin-access",
+ "description": "full-admin-access-desc",
+ "user-description": "full-admin-access-user-desc"
+ }],
+ "policy_definitions": [{
+ "name": "ExampleStringPolicy",
+ "type": "string",
+ "schema": {
+ "type": "string"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 1,
+ "tags": [],
+ "caption": "ExampleStringPolicy caption",
+ "desc": "ExampleStringPolicy desc"
+ }, {
+ "name": "ExampleBoolPolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 2,
+ "tags": [],
+ "caption": "ExampleBoolPolicy caption",
+ "desc": "ExampleBoolPolicy desc",
+ }, {
+ "name": "ExampleBoolMergeMetapolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "features": {
+ "metapolicy_type": "merge",
+ },
+ "id": 3,
+ "tags": [],
+ "caption": "ExampleBoolMergeMetapolicy caption",
+ "desc": "ExampleBoolMergeMetapolicy desc",
+ }, {
+ "name": "ExampleBoolPrecedenceMetapolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "features": {
+ "metapolicy_type": "precedence",
+ },
+ "id": 4,
+ "tags": [],
+ "caption": "ExampleBoolPrecedenceMetapolicy caption",
+ "desc": "ExampleBoolPrecedenceMetapolicy desc",
+ }, {
+ "name": "CloudOnlyPolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "features": {
+ "cloud_only": True,
+ },
+ "supported_on": ["chrome_os:1-", "android:1-"],
+ "id": 5,
+ "tags": [],
+ "caption": "CloudOnlyPolicy caption",
+ "desc": "CloudOnlyPolicy desc",
+ }, {
+ "name": "CloudManagementEnrollmentToken",
+ "type": "string",
+ "schema": {
+ "type": "string"
+ },
+ "supported_on": ["chrome_os:1-", "android:1-"],
+ "id": 6,
+ "tags": [],
+ "caption": "CloudManagementEnrollmentToken caption",
+ "desc": "CloudManagementEnrollmentToken desc"
+ }, {
+ "name": "ChunkZeroLastFieldBooleanPolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 1015,
+ "tags": [],
+ "caption": "ChunkZeroLastFieldBooleanPolicy caption",
+ "desc": "ChunkZeroLastFieldBooleanPolicy desc.",
+ }, {
+ "name": "ChunkOneFirstFieldBooleanPolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 1016,
+ "tags": [],
+ "caption": "ChunkOneFirstFieldBooleanPolicy caption",
+ "desc": "ChunkOneFirstFieldBooleanPolicy desc.",
+ }, {
+ "name": "ChunkOneLastFieldBooleanPolicy",
+ "type": "main",
+ "schema": {
+ "type": "boolean"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 1815,
+ "tags": [],
+ "caption": "ChunkOneLastFieldBooleanPolicy caption",
+ "desc": "ChunkOneLastFieldBooleanPolicy desc.",
+ }, {
+ "name": "ChunkTwoFirstFieldStringPolicy",
+ "type": "string",
+ "schema": {
+ "type": "string"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 1816,
+ "tags": [],
+ "caption": "ChunkTwoFirstFieldStringPolicy caption",
+ "desc": "ChunkTwoFirstFieldStringPolicy desc"
+ }, {
+ "name": "ChunkTwoLastFieldStringPolicy",
+ "type": "string",
+ "schema": {
+ "type": "string"
+ },
+ "supported_on": ["chrome_os:1-"],
+ "id": 2615,
+ "tags": [],
+ "caption": "ChunkTwoLastFieldStringPolicy caption",
+ "desc": "ChunkTwoLastFieldStringPolicy desc"
+ }],
+ "policy_atomic_group_definitions": []
+ }
+
+ def setUp(self):
+ self.maxDiff = 10000
+ self.chrome_major_version = 94
+ self.target_platform = 'chrome_os'
+ self.all_target_platforms = ['win', 'mac', 'linux', 'chromeos', 'fuchsia']
+ self.risk_tags = generate_policy_source.RiskTags(self.TEMPLATES_JSON)
+ self.policies = [
+ generate_policy_source.PolicyDetails(policy, self.chrome_major_version,
+ self.target_platform,
+ self.risk_tags.GetValidTags())
+ for policy in self.TEMPLATES_JSON['policy_definitions']
+ ]
+ self.risk_tags.ComputeMaxTags(self.policies)
+
+ policy_details_set = list(map((lambda x: x.name), self.policies))
+ policies_already_in_group = set()
+ self.policy_atomic_groups = [
+ generate_policy_source.PolicyAtomicGroup(group, policy_details_set,
+ policies_already_in_group)
+ for group in self.TEMPLATES_JSON['policy_atomic_group_definitions']
+ ]
+
+ def testDefaultValueGeneration(self):
+ """Tests generation of default policy values."""
+ # Bools
+ stmts, expr = generate_policy_source._GenerateDefaultValue(True)
+ self.assertListEqual([], stmts)
+ self.assertEqual('base::Value(true)', expr)
+ stmts, expr = generate_policy_source._GenerateDefaultValue(False)
+ self.assertListEqual([], stmts)
+ self.assertEqual('base::Value(false)', expr)
+
+ # Ints
+ stmts, expr = generate_policy_source._GenerateDefaultValue(33)
+ self.assertListEqual([], stmts)
+ self.assertEqual('base::Value(33)', expr)
+
+ # Strings
+ stmts, expr = generate_policy_source._GenerateDefaultValue('foo')
+ self.assertListEqual([], stmts)
+ self.assertEqual('base::Value("foo")', expr)
+
+ # Empty list
+ stmts, expr = generate_policy_source._GenerateDefaultValue([])
+ self.assertListEqual(
+ ['base::Value default_value(base::Value::Type::LIST);'], stmts)
+ self.assertEqual('std::move(default_value)', expr)
+
+ # List with values
+ stmts, expr = generate_policy_source._GenerateDefaultValue([1, '2'])
+ self.assertListEqual([
+ 'base::Value default_value(base::Value::Type::LIST);',
+ 'default_value.Append(base::Value(1));',
+ 'default_value.Append(base::Value("2"));'
+ ], stmts)
+ self.assertEqual('std::move(default_value)', expr)
+
+ # Recursive lists are not supported.
+ stmts, expr = generate_policy_source._GenerateDefaultValue([1, []])
+ self.assertListEqual([], stmts)
+ self.assertIsNone(expr)
+
+ # Arbitary types are not supported.
+ stmts, expr = generate_policy_source._GenerateDefaultValue(object())
+ self.assertListEqual([], stmts)
+ self.assertIsNone(expr)
+
+ def _assertCallsEqual(self, expected_output, call_args_list):
+ # Convert mocked write calls into actual content that would be written
+ # to the file. Elements of call_args_list are call objects, which are
+ # two-tuples of (positional args, keyword args). With call[0] we first
+ # fetch the positional args, which are an n-tuple, and with call[0][0]
+ # we get the first positional argument, which is the string that is
+ # written into the file.
+ actual_output = ''.join(call[0][0] for call in call_args_list)
+
+ # Strip whitespace from the beginning and end of expected and actual
+ # output and verify that they are equal.
+ self.assertEqual(expected_output.strip(), actual_output.strip())
+
+ def testWriteCloudPolicyProtobuf(self):
+ is_full_runtime_values = [False, True]
+ output_path = 'mock_cloud_policy_proto'
+
+ for is_full_runtime in is_full_runtime_values:
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WriteCloudPolicyProtobuf(
+ self.policies,
+ self.policy_atomic_groups,
+ self.target_platform,
+ f,
+ self.risk_tags,
+ is_full_runtime=is_full_runtime)
+
+ full_runtime_comment = '//' if is_full_runtime else ''
+ full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
+
+ with self.subTest(is_full_runtime=is_full_runtime):
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+
+ expected_formatted = test_data.EXPECTED_CLOUD_POLICY_PROTOBUF % {
+ "full_runtime_comment": full_runtime_comment,
+ "full_runtime_suffix": full_runtime_suffix,
+ }
+
+ self._assertCallsEqual(expected_formatted,
+ mocked_file().write.call_args_list)
+
+ def testWriteChromeSettingsProtobuf(self):
+ is_full_runtime_values = [False, True]
+ output_path = 'mock_chrome_settings_proto'
+
+ for is_full_runtime in is_full_runtime_values:
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WriteChromeSettingsProtobuf(
+ self.policies,
+ self.policy_atomic_groups,
+ self.target_platform,
+ f,
+ self.risk_tags,
+ is_full_runtime=is_full_runtime)
+
+ full_runtime_comment = '//' if is_full_runtime else ''
+ full_runtime_suffix = '_full_runtime' if is_full_runtime else ''
+
+ with self.subTest(is_full_runtime=is_full_runtime):
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+
+ expected_formatted = test_data.EXPECTED_CHROME_SETTINGS_PROTOBUF % {
+ "full_runtime_comment": full_runtime_comment,
+ "full_runtime_suffix": full_runtime_suffix,
+ }
+
+ self._assertCallsEqual(expected_formatted,
+ mocked_file().write.call_args_list)
+
+ def testWritePolicyProto(self):
+ output_path = 'mock_write_policy_proto'
+
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WritePolicyProto(f, self.policies[0])
+
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+ self._assertCallsEqual(test_data.EXPECTED_POLICY_PROTO,
+ mocked_file().write.call_args_list)
+
+ def testGetMetapoliciesOfType(self):
+ merge_metapolicies = generate_policy_source._GetMetapoliciesOfType(
+ self.policies, "merge")
+ self.assertListEqual(["ExampleBoolMergeMetapolicy"], merge_metapolicies)
+ self.assertEqual(1, len(merge_metapolicies))
+
+ precedence_metapolicies = generate_policy_source._GetMetapoliciesOfType(
+ self.policies, "precedence")
+ self.assertListEqual(["ExampleBoolPrecedenceMetapolicy"],
+ precedence_metapolicies)
+ self.assertEqual(1, len(precedence_metapolicies))
+
+ invalid_metapolicies = generate_policy_source._GetMetapoliciesOfType(
+ self.policies, "invalid")
+ self.assertListEqual([], invalid_metapolicies)
+ self.assertEqual(0, len(invalid_metapolicies))
+
+ def testWritePolicyConstantHeader(self):
+ output_path = 'mock_policy_constants_h'
+
+ for target_platform in self.all_target_platforms:
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WritePolicyConstantHeader(
+ self.policies,
+ self.policy_atomic_groups,
+ target_platform,
+ f,
+ self.risk_tags,
+ )
+ with self.subTest(target_platform=target_platform):
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+
+ if target_platform == 'win':
+ windows_only_part = test_data.POLICY_CONSTANTS_HEADER_WIN_ONLY_PART
+ else:
+ windows_only_part = ''
+ expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_HEADER % {
+ "windows_only_part": windows_only_part,
+ }
+
+ self._assertCallsEqual(expected_formatted,
+ mocked_file().write.call_args_list)
+
+ def testWritePolicyConstantSource(self):
+ output_path = 'mock_policy_constants_cc'
+
+ for target_platform in self.all_target_platforms:
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WritePolicyConstantSource(
+ self.policies,
+ self.policy_atomic_groups,
+ target_platform,
+ f,
+ self.risk_tags,
+ )
+ with self.subTest(target_platform=target_platform):
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+
+ if target_platform == 'win':
+ windows_only_part = test_data.POLICY_CONSTANTS_SOURCE_WIN_ONLY_PART
+ else:
+ windows_only_part = ''
+ expected_formatted = test_data.EXPECTED_POLICY_CONSTANTS_SOURCE % {
+ "windows_only_part": windows_only_part,
+ }
+
+ self._assertCallsEqual(expected_formatted,
+ mocked_file().write.call_args_list)
+
+ def testWriteChromeOSPolicyConstantsHeader(self):
+ output_path = 'mock_policy_constants_h'
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WriteChromeOSPolicyConstantsHeader(
+ self.policies,
+ self.policy_atomic_groups,
+ self.target_platform,
+ f,
+ self.risk_tags,
+ )
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+ self._assertCallsEqual(test_data.EXPECTED_CROS_POLICY_CONSTANTS_HEADER,
+ mocked_file().write.call_args_list)
+
+ def testWriteChromeOSPolicyConstantsSource(self):
+ output_path = 'mock_policy_constants_cc'
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WriteChromeOSPolicyConstantsSource(
+ self.policies,
+ self.policy_atomic_groups,
+ self.target_platform,
+ f,
+ self.risk_tags,
+ )
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+ self._assertCallsEqual(test_data.EXPECTED_CROS_POLICY_CONSTANTS_SOURCE,
+ mocked_file().write.call_args_list)
+
+
+ def testWriteAppRestrictions(self):
+ output_path = 'app_restrictions_xml'
+ with patch('codecs.open', mock_open()) as mocked_file:
+ with codecs.open(output_path, 'w', encoding='utf-8') as f:
+ generate_policy_source._WriteAppRestrictions(
+ self.policies,
+ self.policy_atomic_groups,
+ self.target_platform,
+ f,
+ self.risk_tags,
+ )
+ mocked_file.assert_called_once_with(output_path, 'w', encoding='utf-8')
+ self._assertCallsEqual(test_data.EXPECTED_APP_RESTRICTIONS_XML,
+ mocked_file().write.call_args_list)
+
+
+ def testChunkNumberAndFieldNumber(self):
+ test_data = [
+ # Last top-level policy
+ PolicyData(policy_id=1015, chunk_number=0, field_number=1017),
+ # First policy in chunk 1
+ PolicyData(policy_id=1016, chunk_number=1, field_number=1),
+ # Last policy in chunk 1
+ PolicyData(policy_id=1815, chunk_number=1, field_number=800),
+ # First policy in chunk 2
+ PolicyData(policy_id=1816, chunk_number=2, field_number=1),
+ # Last policy in chunk 2
+ PolicyData(policy_id=2615, chunk_number=2, field_number=800),
+ # First policy in chunk 3
+ PolicyData(policy_id=2616, chunk_number=3, field_number=1),
+ # Last policy in chunk 3
+ PolicyData(policy_id=3415, chunk_number=3, field_number=800),
+ # First policy in chunk 501
+ PolicyData(policy_id=401016, chunk_number=501, field_number=1),
+ # Last policy in chunk 501
+ PolicyData(policy_id=401815, chunk_number=501, field_number=800),
+ # First policy in chunk 502
+ PolicyData(policy_id=401816, chunk_number=502, field_number=1),
+ # Last policy in chunk 502
+ PolicyData(policy_id=402615, chunk_number=502, field_number=800),
+ # First policy in chunk 503
+ PolicyData(policy_id=402616, chunk_number=503, field_number=1),
+ # Last policy in chunk 503
+ PolicyData(policy_id=403415, chunk_number=503, field_number=800),
+ ]
+
+ for policy_data in test_data:
+ self.assertEqual(
+ generate_policy_source._ChunkNumber(policy_data.policy_id),
+ policy_data.chunk_number)
+ self.assertEqual(
+ generate_policy_source._FieldNumber(policy_data.policy_id,
+ policy_data.chunk_number),
+ policy_data.field_number)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/generate_policy_source_test_data.py b/chromium/components/policy/tools/generate_policy_source_test_data.py
new file mode 100644
index 00000000000..2bc4db62ca7
--- /dev/null
+++ b/chromium/components/policy/tools/generate_policy_source_test_data.py
@@ -0,0 +1,937 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# pylint: disable=line-too-long
+# Disable this warning because shortening the lines in this file to 80
+# characters will negatively impact readability as the strings will no longer
+# look the same as the output files.
+
+EXPECTED_CLOUD_POLICY_PROTOBUF = '''
+syntax = "proto2";
+
+%(full_runtime_comment)soption optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+import "policy_common_definitions%(full_runtime_suffix)s.proto";
+
+message CloudPolicySubProto1 {
+ optional BooleanPolicyProto ChunkOneFirstFieldBooleanPolicy = 1;
+ optional BooleanPolicyProto ChunkOneLastFieldBooleanPolicy = 800;
+}
+
+message CloudPolicySubProto2 {
+ optional StringPolicyProto ChunkTwoFirstFieldStringPolicy = 1;
+ optional StringPolicyProto ChunkTwoLastFieldStringPolicy = 800;
+}
+
+message CloudPolicySettings {
+ optional StringPolicyProto ExampleStringPolicy = 3;
+ optional BooleanPolicyProto ExampleBoolPolicy = 4;
+ optional BooleanPolicyProto ExampleBoolMergeMetapolicy = 5;
+ optional BooleanPolicyProto ExampleBoolPrecedenceMetapolicy = 6;
+ optional BooleanPolicyProto CloudOnlyPolicy = 7;
+ optional StringPolicyProto CloudManagementEnrollmentToken = 8;
+ optional BooleanPolicyProto ChunkZeroLastFieldBooleanPolicy = 1017;
+ optional CloudPolicySubProto1 subProto1 = 1018;
+ optional CloudPolicySubProto2 subProto2 = 1019;
+}
+'''
+
+EXPECTED_CHROME_SETTINGS_PROTOBUF = """
+syntax = "proto2";
+
+%(full_runtime_comment)soption optimize_for = LITE_RUNTIME;
+
+package enterprise_management;
+
+option go_package="chromium/policy/enterprise_management_proto";
+
+// For StringList and PolicyOptions.
+import "policy_common_definitions%(full_runtime_suffix)s.proto";
+
+// PBs for individual settings.
+
+// ExampleStringPolicy caption
+//
+// ExampleStringPolicy desc
+//
+// Supported on: chrome_os
+message ExampleStringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string ExampleStringPolicy = 2;
+}
+
+// ExampleBoolPolicy caption
+//
+// ExampleBoolPolicy desc
+//
+// Supported on: chrome_os
+message ExampleBoolPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ExampleBoolPolicy = 2;
+}
+
+// ExampleBoolMergeMetapolicy caption
+//
+// ExampleBoolMergeMetapolicy desc
+//
+// Supported on: chrome_os
+message ExampleBoolMergeMetapolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ExampleBoolMergeMetapolicy = 2;
+}
+
+// ExampleBoolPrecedenceMetapolicy caption
+//
+// ExampleBoolPrecedenceMetapolicy desc
+//
+// Supported on: chrome_os
+message ExampleBoolPrecedenceMetapolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ExampleBoolPrecedenceMetapolicy = 2;
+}
+
+// CloudOnlyPolicy caption
+//
+// CloudOnlyPolicy desc
+//
+// Supported on: android, chrome_os
+message CloudOnlyPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool CloudOnlyPolicy = 2;
+}
+
+// CloudManagementEnrollmentToken caption
+//
+// CloudManagementEnrollmentToken desc
+//
+// Supported on: android, chrome_os
+message CloudManagementEnrollmentTokenProto {
+ optional PolicyOptions policy_options = 1;
+ optional string CloudManagementEnrollmentToken = 2;
+}
+
+// ChunkZeroLastFieldBooleanPolicy caption
+//
+// ChunkZeroLastFieldBooleanPolicy desc.
+//
+// Supported on: chrome_os
+message ChunkZeroLastFieldBooleanPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ChunkZeroLastFieldBooleanPolicy = 2;
+}
+
+// ChunkOneFirstFieldBooleanPolicy caption
+//
+// ChunkOneFirstFieldBooleanPolicy desc.
+//
+// Supported on: chrome_os
+message ChunkOneFirstFieldBooleanPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ChunkOneFirstFieldBooleanPolicy = 2;
+}
+
+// ChunkOneLastFieldBooleanPolicy caption
+//
+// ChunkOneLastFieldBooleanPolicy desc.
+//
+// Supported on: chrome_os
+message ChunkOneLastFieldBooleanPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional bool ChunkOneLastFieldBooleanPolicy = 2;
+}
+
+// ChunkTwoFirstFieldStringPolicy caption
+//
+// ChunkTwoFirstFieldStringPolicy desc
+//
+// Supported on: chrome_os
+message ChunkTwoFirstFieldStringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string ChunkTwoFirstFieldStringPolicy = 2;
+}
+
+// ChunkTwoLastFieldStringPolicy caption
+//
+// ChunkTwoLastFieldStringPolicy desc
+//
+// Supported on: chrome_os
+message ChunkTwoLastFieldStringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string ChunkTwoLastFieldStringPolicy = 2;
+}
+
+// --------------------------------------------------
+// PBs for policies with ID > 1015.
+
+message ChromeSettingsSubProto1 {
+ optional ChunkOneFirstFieldBooleanPolicyProto ChunkOneFirstFieldBooleanPolicy = 1;
+ optional ChunkOneLastFieldBooleanPolicyProto ChunkOneLastFieldBooleanPolicy = 800;
+}
+
+message ChromeSettingsSubProto2 {
+ optional ChunkTwoFirstFieldStringPolicyProto ChunkTwoFirstFieldStringPolicy = 1;
+ optional ChunkTwoLastFieldStringPolicyProto ChunkTwoLastFieldStringPolicy = 800;
+}
+
+// --------------------------------------------------
+// Big wrapper PB containing the above groups.
+
+message ChromeSettingsProto {
+ optional ExampleStringPolicyProto ExampleStringPolicy = 3;
+ optional ExampleBoolPolicyProto ExampleBoolPolicy = 4;
+ optional ExampleBoolMergeMetapolicyProto ExampleBoolMergeMetapolicy = 5;
+ optional ExampleBoolPrecedenceMetapolicyProto ExampleBoolPrecedenceMetapolicy = 6;
+ optional CloudOnlyPolicyProto CloudOnlyPolicy = 7;
+ optional CloudManagementEnrollmentTokenProto CloudManagementEnrollmentToken = 8;
+ optional ChunkZeroLastFieldBooleanPolicyProto ChunkZeroLastFieldBooleanPolicy = 1017;
+ optional ChromeSettingsSubProto1 subProto1 = 1018;
+ optional ChromeSettingsSubProto2 subProto2 = 1019;
+}
+"""
+
+EXPECTED_POLICY_PROTO = '''\
+// ExampleStringPolicy caption
+//
+// ExampleStringPolicy desc
+//
+// Supported on: chrome_os
+message ExampleStringPolicyProto {
+ optional PolicyOptions policy_options = 1;
+ optional string ExampleStringPolicy = 2;
+}
+'''
+
+EXPECTED_POLICY_CONSTANTS_HEADER = '''
+#ifndef COMPONENTS_POLICY_POLICY_CONSTANTS_H_
+#define COMPONENTS_POLICY_POLICY_CONSTANTS_H_
+
+#include <cstdint>
+#include <string>
+
+#include "components/policy/core/common/policy_details.h"
+#include "components/policy/core/common/policy_map.h"
+
+namespace enterprise_management {
+class BooleanPolicyProto;
+class CloudPolicySettings;
+class IntegerPolicyProto;
+class StringListPolicyProto;
+class StringPolicyProto;
+}
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace internal {
+struct SchemaData;
+}
+%(windows_only_part)s
+#if BUILDFLAG(IS_CHROMEOS)
+// Sets default profile policies values for enterprise users.
+void SetEnterpriseUsersProfileDefaults(PolicyMap* policy_map);
+// Sets default system-wide policies values for enterprise users.
+void SetEnterpriseUsersSystemWideDefaults(PolicyMap* policy_map);
+// Sets all default values for enterprise users.
+void SetEnterpriseUsersDefaults(PolicyMap* policy_map);
+#endif
+
+// Returns the PolicyDetails for |policy| if |policy| is a known
+// Chrome policy, otherwise returns nullptr.
+const PolicyDetails* GetChromePolicyDetails(
+const std::string& policy);
+
+// Returns the schema data of the Chrome policy schema.
+const internal::SchemaData* GetChromeSchemaData();
+
+// Key names for the policy settings.
+namespace key {
+
+extern const char kExampleStringPolicy[];
+extern const char kExampleBoolPolicy[];
+extern const char kExampleBoolMergeMetapolicy[];
+extern const char kExampleBoolPrecedenceMetapolicy[];
+extern const char kCloudOnlyPolicy[];
+extern const char kCloudManagementEnrollmentToken[];
+extern const char kChunkZeroLastFieldBooleanPolicy[];
+extern const char kChunkOneFirstFieldBooleanPolicy[];
+extern const char kChunkOneLastFieldBooleanPolicy[];
+extern const char kChunkTwoFirstFieldStringPolicy[];
+extern const char kChunkTwoLastFieldStringPolicy[];
+
+} // namespace key
+
+// Group names for the policy settings.
+namespace group {
+
+
+} // namespace group
+
+struct AtomicGroup {
+ const short id;
+ const char* policy_group;
+ const char* const* policies;
+};
+
+extern const AtomicGroup kPolicyAtomicGroupMappings[];
+
+extern const size_t kPolicyAtomicGroupMappingsLength;
+
+// Arrays of metapolicies.
+namespace metapolicy {
+
+extern const char* const kMerge[1];
+extern const char* const kPrecedence[1];
+
+} // namespace metapolicy
+
+enum class StringPolicyType {
+ STRING,
+ JSON,
+ EXTERNAL,
+};
+
+// Read access to the protobufs of all supported boolean user policies.
+struct BooleanPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ bool (*has_proto)(const em::CloudPolicySettings& policy);
+ const em::BooleanPolicyProto& (*get_proto)(
+ const em::CloudPolicySettings& policy);
+};
+extern const std::array<BooleanPolicyAccess, 7> kBooleanPolicyAccess;
+
+// Read access to the protobufs of all supported integer user policies.
+struct IntegerPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ bool (*has_proto)(const em::CloudPolicySettings& policy);
+ const em::IntegerPolicyProto& (*get_proto)(
+ const em::CloudPolicySettings& policy);
+};
+extern const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess;
+
+// Read access to the protobufs of all supported string user policies.
+struct StringPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ bool (*has_proto)(const em::CloudPolicySettings& policy);
+ const em::StringPolicyProto& (*get_proto)(
+ const em::CloudPolicySettings& policy);
+ const StringPolicyType type;
+};
+extern const std::array<StringPolicyAccess, 4> kStringPolicyAccess;
+
+// Read access to the protobufs of all supported stringlist user policies.
+struct StringListPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ bool (*has_proto)(const em::CloudPolicySettings& policy);
+ const em::StringListPolicyProto& (*get_proto)(
+ const em::CloudPolicySettings& policy);
+};
+extern const std::array<StringListPolicyAccess, 0> kStringListPolicyAccess;
+
+constexpr int64_t kDevicePolicyExternalDataResourceCacheSize = 0;
+
+} // namespace policy
+
+#endif // COMPONENTS_POLICY_POLICY_CONSTANTS_H_
+'''
+
+POLICY_CONSTANTS_HEADER_WIN_ONLY_PART = '''
+// The windows registry path where Chrome policy configuration resides.
+extern const wchar_t kRegistryChromePolicyKey[];'''
+
+EXPECTED_POLICY_CONSTANTS_SOURCE = '''\
+#include "components/policy/policy_constants.h"
+
+#include <algorithm>
+#include <climits>
+#include <iterator>
+#include <memory>
+
+#include "base/check_op.h"
+#include "base/values.h"
+#include "build/branding_buildflags.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/core/common/schema_internal.h"
+#include "components/policy/proto/cloud_policy.pb.h"
+#include "components/policy/risk_tag.h"
+
+namespace policy {
+
+[[maybe_unused]] const PolicyDetails kChromePolicyDetails[] = {
+// is_deprecated is_future is_device_policy id max_external_data_size, risk tags
+ // ExampleStringPolicy
+ { false, false, false, 1, 0, { } },
+ // ExampleBoolPolicy
+ { false, false, false, 2, 0, { } },
+ // ExampleBoolMergeMetapolicy
+ { false, false, false, 3, 0, { } },
+ // ExampleBoolPrecedenceMetapolicy
+ { false, false, false, 4, 0, { } },
+ // CloudOnlyPolicy
+ { false, false, false, 5, 0, { } },
+ // CloudManagementEnrollmentToken
+ { false, false, false, 6, 0, { } },
+ // ChunkZeroLastFieldBooleanPolicy
+ { false, false, false, 1015, 0, { } },
+ // ChunkOneFirstFieldBooleanPolicy
+ { false, false, false, 1016, 0, { } },
+ // ChunkOneLastFieldBooleanPolicy
+ { false, false, false, 1815, 0, { } },
+ // ChunkTwoFirstFieldStringPolicy
+ { false, false, false, 1816, 0, { } },
+ // ChunkTwoLastFieldStringPolicy
+ { false, false, false, 2615, 0, { } },
+};
+
+const internal::SchemaNode kSchemas[] = {
+// Type Extra IsSensitiveValue HasSensitiveChildren
+ { base::Value::Type::DICTIONARY, 0, false, false }, // root node
+ { base::Value::Type::BOOLEAN, -1, false, false }, // simple type: boolean
+ { base::Value::Type::STRING, -1, false, false }, // simple type: string
+};
+
+const internal::PropertyNode kPropertyNodes[] = {
+// Property Schema
+ { key::kChunkOneFirstFieldBooleanPolicy, 1 },
+ { key::kChunkOneLastFieldBooleanPolicy, 1 },
+ { key::kChunkTwoFirstFieldStringPolicy, 2 },
+ { key::kChunkTwoLastFieldStringPolicy, 2 },
+ { key::kChunkZeroLastFieldBooleanPolicy, 1 },
+ { key::kCloudManagementEnrollmentToken, 2 },
+ { key::kCloudOnlyPolicy, 1 },
+ { key::kExampleBoolMergeMetapolicy, 1 },
+ { key::kExampleBoolPolicy, 1 },
+ { key::kExampleBoolPrecedenceMetapolicy, 1 },
+ { key::kExampleStringPolicy, 2 },
+};
+
+const internal::PropertiesNode kProperties[] = {
+// Begin End PatternEnd RequiredBegin RequiredEnd Additional Properties
+ { 0, 11, 11, 0, 0, -1 }, // root node
+};
+
+const internal::SchemaData* GetChromeSchemaData() {
+ static const internal::SchemaData kChromeSchemaData = {
+ kSchemas,
+ kPropertyNodes,
+ kProperties,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ -1, // validation_schema root index
+ };
+
+ return &kChromeSchemaData;
+}
+
+
+namespace {
+bool CompareKeys(const internal::PropertyNode& node,
+ const std::string& key) {
+ return node.key < key;
+}
+
+} // namespace
+%(windows_only_part)s
+#if BUILDFLAG(IS_CHROMEOS)
+void SetEnterpriseUsersProfileDefaults(PolicyMap* policy_map) {
+
+}
+
+void SetEnterpriseUsersSystemWideDefaults(PolicyMap* policy_map) {
+
+}
+
+void SetEnterpriseUsersDefaults(PolicyMap* policy_map) {
+ SetEnterpriseUsersProfileDefaults(policy_map);
+ SetEnterpriseUsersSystemWideDefaults(policy_map);
+}
+#endif
+
+const PolicyDetails* GetChromePolicyDetails(const std::string& policy) {
+ // First index in kPropertyNodes of the Chrome policies.
+ static const int begin_index = 0;
+ // One-past-the-end of the Chrome policies in kPropertyNodes.
+ static const int end_index = 11;
+ const internal::PropertyNode* begin =
+ kPropertyNodes + begin_index;
+ const internal::PropertyNode* end = kPropertyNodes + end_index;
+ const internal::PropertyNode* it =
+ std::lower_bound(begin, end, policy, CompareKeys);
+ if (it == end || it->key != policy)
+ return nullptr;
+ // This relies on kPropertyNodes from begin_index to end_index
+ // having exactly the same policies (and in the same order) as
+ // kChromePolicyDetails, so that binary searching on the first
+ // gets the same results as a binary search on the second would.
+ // However, kPropertyNodes has the policy names and
+ // kChromePolicyDetails doesn't, so we obtain the index into
+ // the second array by searching the first to avoid duplicating
+ // the policy name pointers.
+ // Offsetting |it| from |begin| here obtains the index we're
+ // looking for.
+ size_t index = it - begin;
+ CHECK_LT(index, std::size(kChromePolicyDetails));
+ return kChromePolicyDetails + index;
+}
+
+namespace key {
+
+const char kExampleStringPolicy[] = "ExampleStringPolicy";
+const char kExampleBoolPolicy[] = "ExampleBoolPolicy";
+const char kExampleBoolMergeMetapolicy[] = "ExampleBoolMergeMetapolicy";
+const char kExampleBoolPrecedenceMetapolicy[] = "ExampleBoolPrecedenceMetapolicy";
+const char kCloudOnlyPolicy[] = "CloudOnlyPolicy";
+const char kCloudManagementEnrollmentToken[] = "CloudManagementEnrollmentToken";
+const char kChunkZeroLastFieldBooleanPolicy[] = "ChunkZeroLastFieldBooleanPolicy";
+const char kChunkOneFirstFieldBooleanPolicy[] = "ChunkOneFirstFieldBooleanPolicy";
+const char kChunkOneLastFieldBooleanPolicy[] = "ChunkOneLastFieldBooleanPolicy";
+const char kChunkTwoFirstFieldStringPolicy[] = "ChunkTwoFirstFieldStringPolicy";
+const char kChunkTwoLastFieldStringPolicy[] = "ChunkTwoLastFieldStringPolicy";
+
+} // namespace key
+
+namespace group {
+
+
+namespace {
+
+
+} // namespace
+
+} // namespace group
+
+const AtomicGroup kPolicyAtomicGroupMappings[] = {
+};
+
+const size_t kPolicyAtomicGroupMappingsLength = 0;
+
+namespace metapolicy {
+
+const char* const kMerge[1] = {
+ key::kExampleBoolMergeMetapolicy,
+};
+
+const char* const kPrecedence[1] = {
+ key::kExampleBoolPrecedenceMetapolicy,
+};
+
+} // namespace metapolicy
+
+const std::array<BooleanPolicyAccess, 7> kBooleanPolicyAccess {{
+ {key::kExampleBoolPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_exampleboolpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.exampleboolpolicy();
+ }
+ },
+ {key::kExampleBoolMergeMetapolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_exampleboolmergemetapolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.exampleboolmergemetapolicy();
+ }
+ },
+ {key::kExampleBoolPrecedenceMetapolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_exampleboolprecedencemetapolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.exampleboolprecedencemetapolicy();
+ }
+ },
+ {key::kCloudOnlyPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_cloudonlypolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.cloudonlypolicy();
+ }
+ },
+ {key::kChunkZeroLastFieldBooleanPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_chunkzerolastfieldbooleanpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.chunkzerolastfieldbooleanpolicy();
+ }
+ },
+ {key::kChunkOneFirstFieldBooleanPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_subproto1() &&
+ policy.subproto1().has_chunkonefirstfieldbooleanpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.subproto1().chunkonefirstfieldbooleanpolicy();
+ }
+ },
+ {key::kChunkOneLastFieldBooleanPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_subproto1() &&
+ policy.subproto1().has_chunkonelastfieldbooleanpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::BooleanPolicyProto& {
+ return policy.subproto1().chunkonelastfieldbooleanpolicy();
+ }
+ },
+}};
+
+const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess {{
+}};
+
+const std::array<StringPolicyAccess, 4> kStringPolicyAccess {{
+ {key::kExampleStringPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_examplestringpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::StringPolicyProto& {
+ return policy.examplestringpolicy();
+ },
+ StringPolicyType::STRING
+ },
+ {key::kCloudManagementEnrollmentToken,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_cloudmanagementenrollmenttoken();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::StringPolicyProto& {
+ return policy.cloudmanagementenrollmenttoken();
+ },
+ StringPolicyType::STRING
+ },
+ {key::kChunkTwoFirstFieldStringPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_subproto2() &&
+ policy.subproto2().has_chunktwofirstfieldstringpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::StringPolicyProto& {
+ return policy.subproto2().chunktwofirstfieldstringpolicy();
+ },
+ StringPolicyType::STRING
+ },
+ {key::kChunkTwoLastFieldStringPolicy,
+ false,
+ [](const em::CloudPolicySettings& policy) {
+ return policy.has_subproto2() &&
+ policy.subproto2().has_chunktwolastfieldstringpolicy();
+ },
+ [](const em::CloudPolicySettings& policy)
+ -> const em::StringPolicyProto& {
+ return policy.subproto2().chunktwolastfieldstringpolicy();
+ },
+ StringPolicyType::STRING
+ },
+}};
+
+const std::array<StringListPolicyAccess, 0> kStringListPolicyAccess {{
+}};
+
+
+} // namespace policy
+'''
+
+POLICY_CONSTANTS_SOURCE_WIN_ONLY_PART = '''
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+const wchar_t kRegistryChromePolicyKey[] = L"SOFTWARE\\\\Policies\\\\Google\\\\Chrome";
+#else
+const wchar_t kRegistryChromePolicyKey[] = L"SOFTWARE\\\\Policies\\\\Chromium";
+#endif
+'''
+
+EXPECTED_CROS_POLICY_CONSTANTS_HEADER = '''
+#ifndef __BINDINGS_POLICY_CONSTANTS_H_
+#define __BINDINGS_POLICY_CONSTANTS_H_
+
+#include <array>
+
+namespace enterprise_management {
+class CloudPolicySettings;
+class BooleanPolicyProto;
+class IntegerPolicyProto;
+class StringPolicyProto;
+class StringListPolicyProto;
+} // namespace enterprise_management
+
+namespace policy {
+
+// Registry key names for user and device policies.
+namespace key {
+
+extern const char kExampleStringPolicy[];
+extern const char kExampleBoolPolicy[];
+extern const char kExampleBoolMergeMetapolicy[];
+extern const char kExampleBoolPrecedenceMetapolicy[];
+extern const char kCloudOnlyPolicy[];
+extern const char kCloudManagementEnrollmentToken[];
+extern const char kChunkZeroLastFieldBooleanPolicy[];
+extern const char kChunkOneFirstFieldBooleanPolicy[];
+extern const char kChunkOneLastFieldBooleanPolicy[];
+extern const char kChunkTwoFirstFieldStringPolicy[];
+extern const char kChunkTwoLastFieldStringPolicy[];
+
+} // namespace key
+
+// NULL-terminated list of device policy registry key names.
+extern const char* kDevicePolicyKeys[];
+
+// Access to the mutable protobuf function of all supported boolean user
+// policies.
+struct BooleanPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ enterprise_management::BooleanPolicyProto* (*mutable_proto_ptr)(
+ enterprise_management::CloudPolicySettings* policy);
+};
+extern const std::array<BooleanPolicyAccess, 7> kBooleanPolicyAccess;
+
+// Access to the mutable protobuf function of all supported integer user
+// policies.
+struct IntegerPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ enterprise_management::IntegerPolicyProto* (*mutable_proto_ptr)(
+ enterprise_management::CloudPolicySettings* policy);
+};
+extern const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess;
+
+// Access to the mutable protobuf function of all supported string user
+// policies.
+struct StringPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ enterprise_management::StringPolicyProto* (*mutable_proto_ptr)(
+ enterprise_management::CloudPolicySettings* policy);
+};
+extern const std::array<StringPolicyAccess, 4> kStringPolicyAccess;
+
+// Access to the mutable protobuf function of all supported stringlist user
+// policies.
+struct StringListPolicyAccess {
+ const char* policy_key;
+ bool per_profile;
+ enterprise_management::StringListPolicyProto* (*mutable_proto_ptr)(
+ enterprise_management::CloudPolicySettings* policy);
+};
+extern const std::array<StringListPolicyAccess, 0> kStringListPolicyAccess;
+
+} // namespace policy
+
+#endif // __BINDINGS_POLICY_CONSTANTS_H_
+'''
+
+EXPECTED_CROS_POLICY_CONSTANTS_SOURCE = '''
+#include "bindings/cloud_policy.pb.h"
+#include "bindings/policy_constants.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace key {
+
+const char kExampleStringPolicy[] = "ExampleStringPolicy";
+const char kExampleBoolPolicy[] = "ExampleBoolPolicy";
+const char kExampleBoolMergeMetapolicy[] = "ExampleBoolMergeMetapolicy";
+const char kExampleBoolPrecedenceMetapolicy[] = "ExampleBoolPrecedenceMetapolicy";
+const char kCloudOnlyPolicy[] = "CloudOnlyPolicy";
+const char kCloudManagementEnrollmentToken[] = "CloudManagementEnrollmentToken";
+const char kChunkZeroLastFieldBooleanPolicy[] = "ChunkZeroLastFieldBooleanPolicy";
+const char kChunkOneFirstFieldBooleanPolicy[] = "ChunkOneFirstFieldBooleanPolicy";
+const char kChunkOneLastFieldBooleanPolicy[] = "ChunkOneLastFieldBooleanPolicy";
+const char kChunkTwoFirstFieldStringPolicy[] = "ChunkTwoFirstFieldStringPolicy";
+const char kChunkTwoLastFieldStringPolicy[] = "ChunkTwoLastFieldStringPolicy";
+
+} // namespace key
+
+const char* kDevicePolicyKeys[] = {
+
+ nullptr};
+
+const std::array<BooleanPolicyAccess, 7> kBooleanPolicyAccess {{
+ {key::kExampleBoolPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_exampleboolpolicy();
+ }
+ },
+ {key::kExampleBoolMergeMetapolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_exampleboolmergemetapolicy();
+ }
+ },
+ {key::kExampleBoolPrecedenceMetapolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_exampleboolprecedencemetapolicy();
+ }
+ },
+ {key::kCloudOnlyPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_cloudonlypolicy();
+ }
+ },
+ {key::kChunkZeroLastFieldBooleanPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_chunkzerolastfieldbooleanpolicy();
+ }
+ },
+ {key::kChunkOneFirstFieldBooleanPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_subproto1()->mutable_chunkonefirstfieldbooleanpolicy();
+ }
+ },
+ {key::kChunkOneLastFieldBooleanPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::BooleanPolicyProto* {
+ return policy->mutable_subproto1()->mutable_chunkonelastfieldbooleanpolicy();
+ }
+ },
+}};
+
+const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess {{
+}};
+
+const std::array<StringPolicyAccess, 4> kStringPolicyAccess {{
+ {key::kExampleStringPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::StringPolicyProto* {
+ return policy->mutable_examplestringpolicy();
+ }
+ },
+ {key::kCloudManagementEnrollmentToken,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::StringPolicyProto* {
+ return policy->mutable_cloudmanagementenrollmenttoken();
+ }
+ },
+ {key::kChunkTwoFirstFieldStringPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::StringPolicyProto* {
+ return policy->mutable_subproto2()->mutable_chunktwofirstfieldstringpolicy();
+ }
+ },
+ {key::kChunkTwoLastFieldStringPolicy,
+ false,
+ [](em::CloudPolicySettings* policy)
+ -> em::StringPolicyProto* {
+ return policy->mutable_subproto2()->mutable_chunktwolastfieldstringpolicy();
+ }
+ },
+}};
+
+const std::array<StringListPolicyAccess, 0> kStringListPolicyAccess {{
+}};
+
+} // namespace policy
+'''
+
+EXPECTED_APP_RESTRICTIONS_XML = '''
+<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <restriction
+ android:key="CloudManagementEnrollmentToken"
+ android:title="@string/CloudManagementEnrollmentTokenTitle"
+ android:description="@string/CloudManagementEnrollmentTokenDesc"
+ android:restrictionType="string"/>
+
+ <restriction
+ android:key="ChunkOneFirstFieldBooleanPolicy"
+ android:title="@string/ChunkOneFirstFieldBooleanPolicyTitle"
+ android:description="@string/ChunkOneFirstFieldBooleanPolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ChunkOneLastFieldBooleanPolicy"
+ android:title="@string/ChunkOneLastFieldBooleanPolicyTitle"
+ android:description="@string/ChunkOneLastFieldBooleanPolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ChunkTwoFirstFieldStringPolicy"
+ android:title="@string/ChunkTwoFirstFieldStringPolicyTitle"
+ android:description="@string/ChunkTwoFirstFieldStringPolicyDesc"
+ android:restrictionType="string"/>
+
+ <restriction
+ android:key="ChunkTwoLastFieldStringPolicy"
+ android:title="@string/ChunkTwoLastFieldStringPolicyTitle"
+ android:description="@string/ChunkTwoLastFieldStringPolicyDesc"
+ android:restrictionType="string"/>
+
+ <restriction
+ android:key="ChunkZeroLastFieldBooleanPolicy"
+ android:title="@string/ChunkZeroLastFieldBooleanPolicyTitle"
+ android:description="@string/ChunkZeroLastFieldBooleanPolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ExampleBoolMergeMetapolicy"
+ android:title="@string/ExampleBoolMergeMetapolicyTitle"
+ android:description="@string/ExampleBoolMergeMetapolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ExampleBoolPolicy"
+ android:title="@string/ExampleBoolPolicyTitle"
+ android:description="@string/ExampleBoolPolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ExampleBoolPrecedenceMetapolicy"
+ android:title="@string/ExampleBoolPrecedenceMetapolicyTitle"
+ android:description="@string/ExampleBoolPrecedenceMetapolicyDesc"
+ android:restrictionType="bool"/>
+
+ <restriction
+ android:key="ExampleStringPolicy"
+ android:title="@string/ExampleStringPolicyTitle"
+ android:description="@string/ExampleStringPolicyDesc"
+ android:restrictionType="string"/>
+
+</restrictions>'''
diff --git a/chromium/components/policy/tools/make_policy_zip.py b/chromium/components/policy/tools/make_policy_zip.py
new file mode 100755
index 00000000000..c9ab2fd566c
--- /dev/null
+++ b/chromium/components/policy/tools/make_policy_zip.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Creates a zip archive with policy template files.
+"""
+
+import argparse
+import os
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ os.pardir, os.pardir, os.pardir,
+ 'build', 'android', 'gyp'))
+from util import build_utils
+
+
+def main():
+ """Pack a list of files into a zip archive.
+
+ Args:
+ output: The file path of the zip archive.
+ base_dir: Base path of input files.
+ languages: Comma-separated list of languages, e.g. en-US,de.
+ add: List of files to include in the archive. The language placeholder
+ ${lang} is expanded into one file for each language.
+ """
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--output", dest="output")
+ parser.add_argument("--timestamp",
+ type=int,
+ metavar="TIME",
+ help="Unix timestamp to use for files in the archive")
+ parser.add_argument("--base_dir", dest="base_dir")
+ parser.add_argument("--languages", dest="languages")
+ parser.add_argument("--add", action="append", dest="files", default=[])
+ args = parser.parse_args()
+
+ # Process file list, possibly expanding language placeholders.
+ _LANG_PLACEHOLDER = "${lang}"
+ languages = list(filter(bool, args.languages.split(',')))
+ file_list = []
+ for file_to_add in args.files:
+ if (_LANG_PLACEHOLDER in file_to_add):
+ for lang in languages:
+ file_list.append(file_to_add.replace(_LANG_PLACEHOLDER, lang))
+ else:
+ file_list.append(file_to_add)
+
+ with build_utils.AtomicOutput(args.output) as f:
+ build_utils.DoZip(file_list, f, args.base_dir, timestamp=args.timestamp)
+
+
+if '__main__' == __name__:
+ sys.exit(main())
diff --git a/chromium/components/policy/tools/schema_validator.py b/chromium/components/policy/tools/schema_validator.py
new file mode 100644
index 00000000000..5151d309332
--- /dev/null
+++ b/chromium/components/policy/tools/schema_validator.py
@@ -0,0 +1,541 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import re
+
+# Dict with valid schema type names as keys. The values are the allowed
+# attribute names and their expected value types.
+#
+# These are the ONLY supported schema features. For the full schema proposal see
+# https://json-schema.org/understanding-json-schema/index.html.
+#
+# There are also these departures from the proposal:
+# - "additionalProperties": false is not supported. Instead, it is assumed by
+# default. The value of "additionalProperties" has to be a schema.
+ALLOWED_ATTRIBUTES_AND_TYPES = {
+ 'boolean': {
+ 'type': str, # required
+ 'id': str, # optional
+ 'description': str, # optional
+ 'sensitiveValue': bool # optional
+ },
+ 'string': {
+ 'type': str, # required
+ 'id': str, # optional
+ 'description': str, # optional
+ 'enum': list, # optional
+ 'pattern': str, # optional
+ 'sensitiveValue': bool # optional
+ },
+ 'integer': {
+ 'type': str, # required
+ 'id': str, # optional
+ 'description': str, # optional
+ 'enum': list, # optional
+ 'minimum': int, # optional
+ 'maximum': int, # optional
+ 'sensitiveValue': bool # optional
+ },
+ 'array': {
+ 'type': str, # required
+ 'id': str, # optional
+ 'items': dict, # required,
+ 'description': str, # optional
+ 'sensitiveValue': bool # optional
+ },
+ 'object': {
+ 'type': str, # required
+ 'id': str, # optional
+ 'description': str, # optional
+ 'properties': dict, # one of these 3 properties is required
+ 'patternProperties': dict, # one of these 3 properties is required
+ 'additionalProperties': dict, # one of these 3 properties is required
+ 'required': list, # optional
+ 'sensitiveValue': bool # optional
+ }
+}
+
+# Dict of allowed attributes and their expected types for schemas with a $ref.
+ALLOWED_REF_ATTRIBUTES_AND_TYPES = {'$ref': str, 'description': str}
+
+# Dict of human-readable enum types and their python types as values.
+ENUM_ITEM_TYPES = {'integer': int, 'string': str}
+
+
+class SchemaValidator(object):
+ """This class can validate schemas by calling ValidateSchema() or can
+ validate values against their schema by calling ValidateValue().
+ Schemas with an 'id' used as $ref link by another schema have to be passed to
+ ValidateSchema() before the schema with the $ref can be used in
+ ValidateSchema() or ValidateValue().
+ |schemas_by_id| is used as storage for schemas with their 'id' as key and
+ their schemas as value.
+ """
+
+ def __init__(self):
+ self.schemas_by_id = {}
+ self.invalid_ref_ids = set()
+ self.found_ref_ids = set()
+ self.num_errors = 0
+ self.enforce_use_entire_schema = False
+ self.expected_properties = {}
+ self.expected_pattern_properties = {}
+ self.expected_additional_properties = {}
+ self.used_properties = {}
+ self.used_pattern_properties = {}
+ self.used_additional_properties = {}
+
+ def ValidateSchema(self, schema):
+ """Checks if |schema| is a valid schema and only uses valid $ref links.
+
+ See _ValidateSchemaInternal() for a detailed description of the schema
+ validation. This method also checks if all used $ref links are known and
+ valid.
+ Args:
+ schema (dict): The JSON schema.
+ Returns:
+ bool: Whether the schema is valid or not.
+ """
+ self.found_ref_ids.clear()
+ self.num_errors = 0
+
+ self._ValidateSchemaInternal(schema)
+
+ unknown_ref_ids = self.found_ref_ids.difference(self.schemas_by_id.keys())
+ for unknown_ref_id in unknown_ref_ids:
+ if unknown_ref_id in self.invalid_ref_ids:
+ self._Error("$ref to invalid schema '%s'." % unknown_ref_id)
+ else:
+ self._Error("Unknown $ref '%s'." % unknown_ref_id)
+
+ return self.num_errors == 0
+
+ def _ValidateSchemaInternal(self, schema):
+ """Check if |schema| is a valid schema.
+
+ This method checks whether |schema| is a dict, has a valid 'type' property,
+ only has properties and values allowed by its type (see
+ ALLOWED_ATTRIBUTES_AND_TYPES) and calls the appropriate
+ _Validate{Integer,String,Array,Object}Schema() method.
+ If the schema has a $ref, the $ref id is added to |found_ref_ids| so that
+ its existence can be validated later on in ValidateSchema(). They may
+ also not contain other attributes except for '$ref' and 'description' (see
+ ALLOWED_REF_ATTRIBUTES_AND_TYPES).
+ If the schema has an id, the id has to be unique and the schema is stored
+ for later re-use if it is valid.
+ Args:
+ schema (dict): The JSON schema.
+ """
+ num_errors_before = self.num_errors
+ # Check that schema is of type dict.
+ if not isinstance(schema, dict):
+ self._Error("Schema must be a dict.")
+
+ # Validate $ref links. All '$ref' links are gathered in |found_ref_links| so
+ # that their existence can be validated later on in ValidateSchema(schema).
+ if '$ref' in schema:
+ ref_id = schema['$ref']
+ for name, value in schema.items():
+ if name not in ALLOWED_REF_ATTRIBUTES_AND_TYPES:
+ self._Error("Attribute '%s' is not allowed for schema with $ref '%s'."
+ % (name, ref_id))
+ expected_type = ALLOWED_REF_ATTRIBUTES_AND_TYPES[name]
+ if not isinstance(value, expected_type):
+ self._Error(
+ ("Attribute value for '%s' (%s) has incorrect type (Expected "
+ "type: '%s'; actual type: '%s')") % (name, value, expected_type,
+ type(value)))
+ self.found_ref_ids.add(ref_id)
+ return
+
+ # Every schema (non-ref) must have a type.
+ if 'type' not in schema:
+ self._Error("Missing attribute 'type'.")
+ schema_type = schema['type']
+
+ # Check that the type is valid.
+ if schema_type not in ALLOWED_ATTRIBUTES_AND_TYPES:
+ self._Error("Unknown type: %s" % schema_type)
+
+ # Check that each schema only contains attributes that are allowed in their
+ # respective type and that their values are of correct type.
+ allowed_attributes = ALLOWED_ATTRIBUTES_AND_TYPES[schema_type]
+ for attribute_name, attribute_value in schema.items():
+ if attribute_name in allowed_attributes:
+ expected_type = allowed_attributes[attribute_name]
+ if not isinstance(attribute_value, expected_type):
+ self._Error(
+ ("Attribute '%s' has incorrect type (Expected type: '%s'; actual "
+ "type: '%s').") % (attribute_name, expected_type,
+ type(attribute_value)))
+ else:
+ self._Error("Attribute '%s' is not allowed for type '%s'." %
+ (attribute_name, schema_type))
+
+ # Validate schemas depending on 'type'.
+ if schema_type == 'string':
+ self._ValidateStringSchema(schema)
+ elif schema_type == 'integer':
+ self._ValidateIntegerSchema(schema)
+ elif schema_type == 'array':
+ self._ValidateArraySchema(schema)
+ elif schema_type == 'object':
+ self._ValidateObjectSchema(schema)
+
+ # If the schema has an 'id', ensure that the id is unique and store the
+ # schema for later reference.
+ if 'id' in schema:
+ ref_id = schema['id']
+ if ref_id in self.schemas_by_id:
+ self._Error("ID '%s' is not unique." % ref_id)
+ if self.num_errors == num_errors_before:
+ self.schemas_by_id[ref_id] = schema
+ else:
+ self.invalid_ref_ids.add(ref_id)
+
+ def _ValidateStringSchema(self, schema):
+ """Validates a |schema| with type 'string'.
+
+ Validates the 'enum' (see _ValidateEnum()) and/or 'pattern' property (see
+ _ValidatePattern()) if existing.
+ Args:
+ schema (dict): The JSON schema.
+ """
+ if 'enum' in schema:
+ self._ValidateEnum(schema['enum'], 'string')
+ if 'pattern' in schema:
+ self._ValidatePattern(schema['pattern'])
+
+ def _ValidateIntegerSchema(self, schema):
+ """Validates a |schema| with type 'integer'.
+
+ Validates the 'enum' property (see _ValidateEnum()) if existing. This
+ method also ensures that the specified minimum value is smaller or equal to
+ the specified maximum value, if both exist.
+ Args:
+ schema (dict): The JSON schema.
+ """
+ if 'enum' in schema:
+ self._ValidateEnum(schema['enum'], 'integer')
+ if ('minimum' in schema and 'maximum' in schema and
+ schema['minimum'] > schema['maximum']):
+ self._Error("Invalid range specified: [%s; %s]" % (schema['minimum'],
+ schema['maximum']))
+
+ def _ValidateArraySchema(self, schema):
+ """Validates a |schema| with type 'array'.
+
+ Validates that the 'items' attribute exists and its value is a valid schema.
+ Args:
+ schema (dict): The JSON schema.
+ """
+ if 'items' in schema:
+ self._ValidateSchemaInternal(schema['items'])
+ else:
+ self._Error("Schema of type 'array' must have an 'items' attribute.")
+
+ def _ValidateObjectSchema(self, schema):
+ """Validates a schema of type 'object'.
+
+ If |schema| has a 'required' attribute, this method validates that it is not
+ empty, only contains strings and only contains property names of properties
+ defined in the 'properties' attribute.
+ This method also ensures that at least one of 'properties',
+ 'patternProperties' or 'additionalProperties' is defined.
+ If 'properties' are defined, they must have non-empty string names and
+ contain a valid schema.
+ If 'patternProperties' are defined, they must be valid regex patterns and
+ contain a valid schema.
+ If 'additionalProperties is defined, it must contain a valid schema.
+ Args:
+ schema (dict): The JSON schema.
+ '"""
+ # Validate 'required' attribute.
+ if 'required' in schema:
+ required_properties = schema['required']
+ if not required_properties:
+ self._Error("Attribute 'required' may not be empty (omit it if empty).")
+ if not all(
+ isinstance(required_property, str)
+ for required_property in required_properties):
+ self._Error("Attribute 'required' may only contain strings.")
+ properties = schema.get('properties', {})
+ unknown_properties = [
+ property_name for property_name in required_properties
+ if property_name not in properties
+ ]
+ if unknown_properties:
+ self._Error("Unknown properties in 'required': %s" % unknown_properties)
+ # Validate '*properties' attributes.
+ has_any_properties = False
+ if 'properties' in schema:
+ has_any_properties = True
+ properties = schema['properties']
+ for property_name, property_schema in properties.items():
+ if not isinstance(property_name, str):
+ self._Error("Property name must be a string.")
+ if not property_name:
+ self._Error("Property name may not be empty.")
+ self._ValidateSchemaInternal(property_schema)
+ if 'patternProperties' in schema:
+ has_any_properties = True
+ pattern_properties = schema['patternProperties']
+ for property_pattern, property_schema in pattern_properties.items():
+ self._ValidatePattern(property_pattern)
+ self._ValidateSchemaInternal(property_schema)
+ if 'additionalProperties' in schema:
+ has_any_properties = True
+ additional_properties = schema['additionalProperties']
+ self._ValidateSchemaInternal(additional_properties)
+ if not has_any_properties:
+ self._Error(
+ "Schema of type 'object' must have at least one of the following "
+ "attributes: ['properties', 'patternProperties' or "
+ "'additionalProperties'].")
+
+ def _ValidateEnum(self, enum, schema_type):
+ """Validates an |enum| of type |schema_type|.
+
+ Validates that |enum| is not empty and its elements have the correct type
+ according to |schema_type| (see ENUM_ITEM_TYPES).
+ Args:
+ enum (list): The list of enum values.
+ schema_type (str): The schema type in which the enum is used.
+ """
+ if not enum:
+ self._Error("Attribute 'enum' may not be empty.")
+ item_type = ENUM_ITEM_TYPES[schema_type]
+ if not all(isinstance(enum_value, item_type) for enum_value in enum):
+ self._Error(("Attribute 'enum' for type '%s' may only contain elements of"
+ " type %s: %s") % (schema_type, item_type, enum))
+
+ def _ValidatePattern(self, pattern):
+ """Validates a regex |pattern|.
+
+ Validates that |pattern| is a string and can be used as regex pattern.
+ Args:
+ pattern (str): The regex pattern.
+ """
+ if not isinstance(pattern, str):
+ self._Error("Pattern must be a string: %s" % pattern)
+ try:
+ re.compile(pattern)
+ except re.error:
+ self._Error("Pattern is not a valid regex: %s" % pattern)
+
+ def ValidateValue(self, schema, value, enforce_use_entire_schema=False):
+ """Validates that |value| complies to |schema|.
+
+ See _ValidateValueInternal(schema, value) for a detailed description of the
+ value validation. If |enforce_use_entire_schema| is enabled, each value and
+ its sub-values have to use every property of each used schema at least once
+ and values with schema type 'array' may not be empty.
+ Args:
+ schema (dict): The JSON schema.
+ value (any): The value being validated.
+ enforce_use_entire_schema (bool): Whether each property hsa to be used at
+ least once.
+ Returns:
+ bool: Whether the value is valid or not.
+ """
+ self.enforce_use_entire_schema = enforce_use_entire_schema
+ self.expected_properties = {}
+ self.expected_pattern_properties = {}
+ self.expected_additional_properties = {}
+ self.used_properties = {}
+ self.used_pattern_properties = {}
+ self.used_additional_properties = {}
+ self.num_errors = 0
+
+ self._ValidateValueInternal(schema, value)
+
+ # Check that all properties, patternProperties and additionalProperties were
+ # used at least once for each schema.
+ if self.enforce_use_entire_schema:
+ if self.expected_properties != self.used_properties:
+ for schema_id, expected_properties \
+ in self.expected_properties.items():
+ used_properties = self.used_properties.get(schema_id, set())
+ unused_properties = expected_properties.difference(used_properties)
+ if unused_properties:
+ self._Error("Unused properties: %s" % unused_properties)
+ if self.expected_pattern_properties != self.used_pattern_properties:
+ for schema_id, expected_properties \
+ in self.expected_pattern_properties.items():
+ used_properties = self.used_pattern_properties.get(schema_id, set())
+ unused_properties = expected_properties.difference(used_properties)
+ if unused_properties:
+ self._Error("Unused pattern properties: %s" % unused_properties)
+ if self.expected_additional_properties != self.used_additional_properties:
+ self._Error("Unused additional properties.")
+
+ return self.num_errors == 0
+
+ def _ValidateValueInternal(self, schema, value):
+ """Validates that |value| complies to |schema|.
+
+ This method checks if the |value|'s type is correct according to type
+ expected in |schema| and calls the associated
+ _Validate{Integer,String,Array,Object}ValueInternal(schema, value).
+ Args:
+ schema (dict): The JSON schema.
+ value (any): The value being validated.
+ """
+ # Load schema from store if it has '$ref'.
+ if '$ref' in schema:
+ ref_id = schema['$ref']
+ if ref_id not in self.schemas_by_id:
+ self._Error("Unknown $ref id: %s" % ref_id)
+ schema = self.schemas_by_id[ref_id]
+
+ schema_type = schema.get('type')
+ if schema_type == 'boolean' and isinstance(value, bool):
+ pass # Boolean doesn't need any validation.
+ elif schema_type == 'integer' and isinstance(value, int):
+ self.ValidateIntegerValue(schema, value)
+ elif schema_type == 'string' and isinstance(value, (bytes, str)):
+ self.ValidateStringValue(schema, value)
+ elif schema_type == 'array' and isinstance(value, list):
+ self.ValidateArrayValue(schema, value)
+ elif schema_type == 'object' and isinstance(value, dict):
+ self.ValidateObjectValue(schema, value)
+ else:
+ # Type mismatch or unknown type.
+ self._Error(
+ "Type mismatch or unknown (schema_type: %s; value_type: %s): %s" %
+ (schema_type, type(value), value))
+
+ def ValidateIntegerValue(self, schema, value):
+ """Validates an integer |value| according to |schema|.
+
+ If the |schema| has an enum of possible values, check whether |value| is one
+ of them.
+ If 'minimum' and/or 'maximum' are defined in |schema|, check that
+ minimum <= value <= maximum.
+ Args:
+ schema (dict): The JSON schema.
+ value (int): The value being validated.
+ """
+ if 'enum' in schema:
+ self._ValidateEnumValue(schema['enum'], value)
+ if (('minimum' in schema and value < schema['minimum']) or
+ ('maximum' in schema and value > schema['maximum'])):
+ self._Error(
+ "Value %s not in range [%s,%s]." %
+ (value, schema.get('minimum', '-inf'), schema.get('maximum', '+inf')))
+
+ def ValidateStringValue(self, schema, value):
+ """Validates a string |value| according to |schema|.
+
+ If the |schema| has an enum of possible values, check whether |value| is one
+ of them.
+ If the |schema| has a 'pattern' attribute, check whether |value| matches the
+ pattern.
+ Args:
+ schema (dict): The JSON schema.
+ value (str): The value being validated.
+ """
+ if 'enum' in schema:
+ self._ValidateEnumValue(schema['enum'], value)
+ if 'pattern' in schema:
+ pattern = schema['pattern']
+ if not re.search(pattern, value):
+ self._Error(
+ "String value '%s' does not match pattern '%s'." % (value, pattern))
+
+ def ValidateArrayValue(self, schema, child_values):
+ """Validates an array |child_values| according to |schema|.
+
+ Validates each item in |child_values| (see _ValidateValueInternal()).
+ If |enforce_use_entire_schema| is enabled, the value must contain at least
+ one element.
+ Args:
+ schema (dict): The JSON schema.
+ child_values (list): The list of children being validated.
+ """
+ child_schema = schema.get('items')
+ for child_value in child_values:
+ self._ValidateValueInternal(child_schema, child_value)
+ if self.enforce_use_entire_schema and not child_values:
+ self._Error("Array must contain at least one item.")
+
+ def ValidateObjectValue(self, schema, value):
+ """Validates an object |value| according to |schema|.
+
+ Validates each property in |value| according to its matching schema out of
+ the |schema|'s 'properties', 'patternProperties' or 'additionalProperties'
+ properties. Also adds the property to the set of |used_*properties|.
+ If the |schema| is used for the first time in this validation, the
+ |expected_*properties| are initialized to the |schema|'s properties.
+ Args:
+ schema (dict): The JSON schema.
+ value (dict): The value being validated.
+ """
+ # Get allowed properties.
+ properties = schema.get('properties', {})
+ pattern_properties = schema.get('patternProperties', {})
+ additional_properties = schema.get('additionalProperties', {})
+
+ # If the schema hasn't been used before, store sets of expected properties,
+ # patternProperties and a bool whether we expect additionalProperties to be
+ # used. Also initialize the list of used properties and patternProperties to
+ # empty sets.
+ schema_id = id(schema)
+ if schema_id not in self.expected_properties:
+ self.expected_properties[schema_id] = set(properties.keys())
+ self.expected_pattern_properties[schema_id] = set(
+ pattern_properties.keys())
+ self.expected_additional_properties[schema_id] = (
+ 'additionalProperties' in schema)
+ self.used_properties[schema_id] = set()
+ self.used_pattern_properties[schema_id] = set()
+ self.used_additional_properties[schema_id] = False
+
+ for property_key, property_value in value.items():
+ # Find property schema from either properties, patternProperties or
+ # additionalProperties.
+ property_schema = {}
+ if properties and property_key in properties:
+ property_schema = properties[property_key]
+ self.used_properties[schema_id].add(property_key)
+ elif pattern_properties:
+ matched_pattern = next((pattern
+ for pattern in pattern_properties.keys()
+ if re.search(pattern, property_key)), "")
+ property_schema = pattern_properties.get(matched_pattern, {})
+ self.used_pattern_properties[schema_id].add(matched_pattern)
+ if not property_schema and additional_properties:
+ property_schema = additional_properties
+ self.used_additional_properties[schema_id] = True
+
+ if not property_schema:
+ self._Error("Unknown property: %s" % property_key)
+ self._ValidateValueInternal(property_schema, property_value)
+
+ # Check that all 'required' properties are existing.
+ if 'required' in schema:
+ missing_required = [
+ required_key for required_key in schema['required']
+ if required_key not in value
+ ]
+ if missing_required:
+ self._Error("Required property missing: %s" % missing_required)
+
+ def _ValidateEnumValue(self, enum, value):
+ """Validates that |value| is in |enum|.
+
+ Args:
+ enum (list): The list of allowed values.
+ value (any): The value being validated.
+ """
+ if value not in enum:
+ self._Error("Unknown enum value: %s (expected one of %s)" % (value, enum))
+
+ def _Error(self, message):
+ """Captures an error.
+
+ Logs the error |message| and increases the error count |num_errors| by one.
+ Args:
+ message (str): The error message."""
+ self.num_errors += 1
+ print(message)
diff --git a/chromium/components/policy/tools/syntax_check_policy_template_json.py b/chromium/components/policy/tools/syntax_check_policy_template_json.py
new file mode 100755
index 00000000000..18f53cd4c68
--- /dev/null
+++ b/chromium/components/policy/tools/syntax_check_policy_template_json.py
@@ -0,0 +1,2021 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''
+Checks a policy_templates.json file for conformity to its syntax specification.
+'''
+
+import argparse
+import ast
+import json
+import os
+import re
+import sys
+from schema_validator import SchemaValidator
+
+LEADING_WHITESPACE = re.compile('^([ \t]*)')
+TRAILING_WHITESPACE = re.compile('.*?([ \t]+)$')
+# Matches all non-empty strings that contain no whitespaces.
+NO_WHITESPACE = re.compile('[^\s]+$')
+
+SOURCE_DIR = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+
+# List of boolean policies that have been introduced with negative polarity in
+# the past and should not trigger the negative polarity check.
+LEGACY_INVERTED_POLARITY_ALLOWLIST = [
+ 'DeveloperToolsDisabled',
+ 'DeviceAutoUpdateDisabled',
+ 'Disable3DAPIs',
+ 'DisableAuthNegotiateCnameLookup',
+ 'DisablePluginFinder',
+ 'DisablePrintPreview',
+ 'DisableSafeBrowsingProceedAnyway',
+ 'DisableScreenshots',
+ 'DisableSpdy',
+ 'DisableSSLRecordSplitting',
+ 'DriveDisabled',
+ 'DriveDisabledOverCellular',
+ 'ExternalStorageDisabled',
+ 'SavingBrowserHistoryDisabled',
+ 'SyncDisabled',
+]
+
+# List of policies where the 'string' part of the schema is actually a JSON
+# string which has its own schema.
+LEGACY_EMBEDDED_JSON_ALLOWLIST = [
+ 'ArcPolicy',
+ 'AutoSelectCertificateForUrls',
+ 'DefaultPrinterSelection',
+ 'DeviceAppPack',
+ 'DeviceLoginScreenAutoSelectCertificateForUrls',
+ 'DeviceOpenNetworkConfiguration',
+ 'NativePrinters',
+ 'Printers',
+ 'OpenNetworkConfiguration',
+ 'RemoteAccessHostDebugOverridePolicies',
+ # NOTE: Do not add any new policies to this list! Do not store policies with
+ # complex schemas using stringified JSON - instead, store them as dicts.
+]
+
+# List of 'integer' policies that allow a negative 'minimum' value.
+LEGACY_NEGATIVE_MINIMUM_ALLOWED = [
+ 'PrintJobHistoryExpirationPeriod',
+ 'GaiaOfflineSigninTimeLimitDays',
+ 'SAMLOfflineSigninTimeLimit',
+ 'GaiaLockScreenOfflineSigninTimeLimitDays',
+ 'SamlLockScreenOfflineSigninTimeLimitDays',
+]
+
+# List of policies where not all properties are required to be presented in the
+# example value. This could be useful e.g. in case of mutually exclusive fields.
+# See crbug.com/1068257 for the details.
+OPTIONAL_PROPERTIES_POLICIES_ALLOWLIST = ['ProxySettings']
+
+# 100 MiB upper limit on the total device policy external data max size limits
+# due to the security reasons.
+# You can increase this limit if you're introducing new external data type
+# device policy, but be aware that too heavy policies could result in user
+# profiles not having enough space on the device.
+TOTAL_DEVICE_POLICY_EXTERNAL_DATA_MAX_SIZE = 1024 * 1024 * 100
+
+# Each policy must have a description message shorter than 4096 characters in
+# all its translations (ADM format limitation). However, translations of the
+# description might exceed this limit, so a lower limit of is used instead.
+POLICY_DESCRIPTION_LENGTH_SOFT_LIMIT = 3500
+
+# Dictionaries that define how the checks can determine if a change to a policy
+# value are backwards compatible.
+
+# Defines specific keys in specific types that have custom validation functions
+# for checking if a change to the value is a backwards compatible change.
+# For instance increasing the 'maxmimum' value for an integer is less
+# restrictive than decreasing it.
+CUSTOM_VALUE_CHANGE_VALIDATION_PER_TYPE = {
+ 'integer': {
+ 'minimum': lambda old_value, new_value: new_value <= old_value,
+ 'maximum': lambda old_value, new_value: new_value >= old_value
+ }
+}
+
+# Defines keys per type that can simply be removed in a newer version of a
+# policy. For example, removing a 'required' field makes a policy schema less
+# restrictive.
+# This dictionary allows us to state that the given key can be totally removed
+# when checking for a particular type. Or if the key usually represents an
+# array of values, it states that entries in the array can be removed. Normally
+# no array value can be removed in a policy change if we want to keep it
+# backwards compatible.
+REMOVABLE_SCHEMA_VALUES_PER_TYPE = {
+ 'integer': ['minimum', 'maximum'],
+ 'string': ['pattern'],
+ 'object': ['required']
+}
+
+# Defines keys per type that that can be changed in any way without affecting
+# policy compatibility (for example we can change, remove or add a 'description'
+# to a policy schema without causing incompatibilities).
+MODIFIABLE_SCHEMA_KEYS_PER_TYPE = {
+ 'integer': ['description', 'sensitiveValue'],
+ 'string': ['description', 'sensitiveValue'],
+ 'object': ['description', 'sensitiveValue'],
+ 'boolean': ['description']
+}
+
+# Defines keys per type that themselves define a further dictionary of
+# properties each with their own schemas. For example, 'object' types define
+# a 'properties' key that list all the possible keys in the object.
+KEYS_DEFINING_PROPERTY_DICT_SCHEMAS_PER_TYPE = {
+ 'object': ['properties', 'patternProperties']
+}
+
+# Defines keys per type that themselves define a schema. For example, 'array'
+# types define an 'items' key defines the schema for each item in the array.
+KEYS_DEFINING_SCHEMAS_PER_TYPE = {
+ 'object': ['additionalProperties'],
+ 'array': ['items']
+}
+
+# The list of platforms policy could support.
+ALL_SUPPORTED_PLATFORMS = [
+ 'chrome_frame', 'chrome_os', 'android', 'webview_android', 'ios',
+ 'chrome.win', 'chrome.win7', 'chrome.linux', 'chrome.mac', 'chrome.*'
+]
+
+# The list of platforms that chrome.* represents.
+CHROME_STAR_PLATFORMS = ['chrome.win', 'chrome.mac', 'chrome.linux']
+
+# List of supported metapolicy types.
+METAPOLICY_TYPES = ['merge', 'precedence']
+
+
+# Helper function to determine if a given type defines a key in a dictionary
+# that is used to condition certain backwards compatibility checks.
+def IsKeyDefinedForTypeInDictionary(type, key, key_per_type_dict):
+ return type in key_per_type_dict and key in key_per_type_dict[type]
+
+
+# Helper function that expand chrome.* in the |platforms| list or dict.
+def ExpandChromeStar(platforms):
+ if platforms and 'chrome.*' in platforms:
+ if isinstance(platforms, list):
+ index = platforms.index('chrome.*')
+ platforms[index:index + 1] = CHROME_STAR_PLATFORMS
+ elif isinstance(platforms, dict):
+ value = platforms.pop('chrome.*')
+ for chrome_star_platform in CHROME_STAR_PLATFORMS:
+ # copy reference here as the value shouldn't be changed.
+ platforms[chrome_star_platform] = value
+ return platforms
+
+
+def _GetSupportedVersionPlatformAndRange(supported_on):
+ (supported_on_platform, supported_on_versions) = supported_on.split(':')
+
+ (supported_on_from, supported_on_to) = supported_on_versions.split('-')
+
+ return supported_on_platform, (int(supported_on_from) if supported_on_from
+ else None), (int(supported_on_to)
+ if supported_on_to else None)
+
+
+def _GetPolicyValueType(policy_type):
+ if policy_type == 'main':
+ return bool
+ elif policy_type in ('string', 'string-enum'):
+ return str
+ elif policy_type in ('int', 'int-enum'):
+ return int
+ elif policy_type in ('list', 'string-enum-list'):
+ return list
+ elif policy_type == 'external':
+ return dict
+ elif policy_type == 'dict':
+ return [dict, list]
+ else:
+ raise NotImplementedError('Unknown value type for policy type: %s' %
+ policy_type)
+
+
+def _GetPolicyItemType(policy_type):
+ if policy_type == 'main':
+ return bool
+ elif policy_type in ('string-enum', 'string-enum-list'):
+ return str
+ elif policy_type in ('int-enum'):
+ return int
+ else:
+ raise NotImplementedError('Unknown item type for policy type: %s' %
+ policy_type)
+
+
+def MergeDict(*dicts):
+ result = {}
+ for dictionary in dicts:
+ result.update(dictionary)
+ return result
+
+
+class DuplicateKeyVisitor(ast.NodeVisitor):
+ def visit_Dict(self, node):
+ seen_keys = set()
+ for i, node_key in enumerate(node.keys):
+ key = ast.literal_eval(node_key)
+ if key in seen_keys:
+ raise ValueError("Duplicate key '%s' in line %d found." %
+ (key, node.values[i].lineno))
+ seen_keys.add(key)
+
+ # Recursively check for all nested objects.
+ self.generic_visit(node)
+
+
+class PolicyTypeProvider():
+ def __init__(self):
+ # TODO(crbug.com/1171839): Persist the deduced schema types into a separate
+ # file to further speed up the presubmit scripts.
+ self._policy_types = {}
+ # List of policies which are type 'dict' but should be type 'external'
+ # according to their schema. There are several reasons for such exceptions:
+ # - The file being downloaded is large (on the order of GB)
+ # - The downloaded file shouldn't be publicly accessible
+ self._external_type_mismatch_allowlist = ['PluginVmImage']
+
+ def GetPolicyType(self, policy):
+ # Policies may have the same name as the groups they belong to, so caching
+ # would not work. Instead, first check if the policy is a group; if it's
+ # not, go ahead with caching.
+ if self._IsGroup(policy):
+ return 'group'
+
+ policy_name = policy.get('name')
+ if not policy_name or policy_name not in self._policy_types:
+ return self._policy_types.setdefault(
+ policy_name, self._GetPolicyTypeFromSchema(policy))
+ return self._policy_types[policy_name]
+
+ def _IsGroup(self, policy):
+ return policy.get('type') == 'group'
+
+ def _GetPolicyTypeFromSchema(self, policy):
+ schema = policy.get('schema')
+ if not schema:
+ raise NotImplementedError(
+ 'Policy %s does not have a schema. A schema must be implemented for '
+ 'all non-group type policies.' % policy.get('name'))
+
+ schema_type = schema.get('type')
+ if schema_type == 'boolean':
+ return 'main'
+ elif schema_type == 'integer':
+ items = policy.get('items')
+ if items and all([
+ item.get('name') and item.get('value') is not None for item in items
+ ]):
+ return 'int-enum'
+ return 'int'
+ elif schema_type == 'string':
+ items = policy.get('items')
+ if items and all([
+ item.get('name') and item.get('value') is not None for item in items
+ ]):
+ return 'string-enum'
+ return 'string'
+ elif schema_type == 'array':
+ schema_items = schema.get('items')
+ if schema_items.get('type') == 'string' and schema_items.get('enum'):
+ return 'string-enum-list'
+ elif schema_items.get('type') == 'object' and schema_items.get(
+ 'properties'):
+ return 'dict'
+ return 'list'
+ elif schema_type == 'object':
+ schema_properties = schema.get('properties')
+ if schema_properties and schema_properties.get(
+ 'url') and schema_properties.get('hash') and policy.get(
+ 'name') not in self._external_type_mismatch_allowlist:
+ return 'external'
+ return 'dict'
+
+
+class PolicyTemplateChecker(object):
+
+ def __init__(self):
+ self.error_count = 0
+ self.warning_count = 0
+ self.num_policies = 0
+ self.num_groups = 0
+ self.num_policies_in_groups = 0
+ self.options = None
+ self.features = []
+ self.schema_validator = SchemaValidator()
+ self.has_schema_error = False
+ self.policy_type_provider = PolicyTypeProvider()
+
+ def _Warning(self, message):
+ self.warning_count += 1
+ print(message)
+
+ def _Error(self,
+ message,
+ parent_element=None,
+ identifier=None,
+ offending_snippet=None):
+ self.error_count += 1
+ error = ''
+ if identifier is not None and parent_element is not None:
+ error += 'In %s %s: ' % (parent_element, identifier)
+ print(error + 'Error: ' + message)
+ if offending_snippet is not None:
+ print(' Offending:', json.dumps(offending_snippet, indent=2))
+
+ def _CheckContains(self,
+ container,
+ key,
+ value_type,
+ optional=False,
+ parent_element='policy',
+ container_name=None,
+ identifier=None,
+ offending='__CONTAINER__',
+ regexp_check=None):
+ '''
+ Checks |container| for presence of |key| with value of type |value_type|.
+ If |value_type| is string and |regexp_check| is specified, then an error is
+ reported when the value does not match the regular expression object.
+
+ |value_type| can also be a list, if more than one type is supported.
+
+ The other parameters are needed to generate, if applicable, an appropriate
+ human-readable error message of the following form:
+
+ In |parent_element| |identifier|:
+ (if the key is not present):
+ Error: |container_name| must have a |value_type| named |key|.
+ Offending snippet: |offending| (if specified; defaults to |container|)
+ (if the value does not have the required type):
+ Error: Value of |key| must be a |value_type|.
+ Offending snippet: |container[key]|
+
+ Returns: |container[key]| if the key is present and there are no errors,
+ None otherwise.
+ '''
+ if identifier is None:
+ try:
+ identifier = container.get('name')
+ except:
+ self._Error('Cannot access container name of "%s".' % container_name)
+ return None
+ if container_name is None:
+ container_name = parent_element
+ if offending == '__CONTAINER__':
+ offending = container
+ if key not in container:
+ if optional:
+ return
+ else:
+ self._Error(
+ '%s must have a %s "%s".' % (container_name.title(),
+ value_type.__name__, key),
+ container_name, identifier, offending)
+ return None
+ value = container[key]
+ value_types = value_type if isinstance(value_type, list) else [value_type]
+ if not any(isinstance(value, type) for type in value_types):
+ self._Error(
+ 'Value of "%s" must be one of [ %s ].' % (key, ', '.join(
+ [type.__name__ for type in value_types])), container_name,
+ identifier, value)
+ return None
+ if str in value_types and regexp_check and not regexp_check.match(value):
+ self._Error(
+ 'Value of "%s" must match "%s".' % (key, regexp_check.pattern),
+ container_name, identifier, value)
+ return None
+ return value
+
+ def _AddPolicyID(self, id, policy_ids, policy, deleted_policy_ids):
+ '''
+ Adds |id| to |policy_ids|. Generates an error message if the
+ |id| exists already; |policy| is needed for this message.
+ '''
+ if id in policy_ids:
+ self._Error('Duplicate id', 'policy', policy.get('name'), id)
+ elif id in deleted_policy_ids:
+ self._Error('Deleted id', 'policy', policy.get('name'), id)
+ else:
+ policy_ids.add(id)
+
+ def _CheckPolicyIDs(self, policy_ids, deleted_policy_ids):
+ '''
+ Checks a set of policy_ids to make sure it contains a continuous range
+ of entries (i.e. no holes).
+ Holes would not be a technical problem, but we want to ensure that nobody
+ accidentally omits IDs.
+ '''
+ policy_count = len(policy_ids) + len(deleted_policy_ids)
+ for i in range(policy_count):
+ if (i + 1) not in policy_ids and (i + 1) not in deleted_policy_ids:
+ self._Error('No policy with id: %s' % (i + 1))
+
+ def _CheckHighestId(self, policy_ids, highest_id):
+ '''
+ Checks that the 'highest_id_currently_used' value is actually set to the
+ highest id in use by any policy.
+ '''
+ highest_id_in_policies = max(policy_ids)
+ if highest_id != highest_id_in_policies:
+ self._Error(("'highest_id_currently_used' must be set to the highest"
+ "policy id in use, which is currently %s (vs %s).") %
+ (highest_id_in_policies, highest_id))
+
+ def _CheckPolicySchema(self, policy, policy_type):
+ '''Checks that the 'schema' field matches the 'type' field.'''
+ self.has_schema_error = False
+
+ if policy_type == 'group':
+ self._Error('Schema should not be defined for group type policy %s.' %
+ policy.get('name'))
+ self.has_schema_error = True
+ return
+
+ schema = self._CheckContains(policy, 'schema', dict)
+ if not schema:
+ # Schema must be defined for all non-group type policies. An appropriate
+ # |_Error| message is populated in the |_CheckContains| call above, so it
+ # is not repeated here.
+ self.has_schema_error = True
+ return
+
+ policy_type_legacy = policy.get('type')
+ # TODO(crbug.com/1310258): Remove this check once 'type' is removed from
+ # policy_templates.json.
+ if policy_type != policy_type_legacy:
+ self._Error(
+ ('Type \"%s\" was expected instead of \"%s\" based on the schema ' +
+ 'for policy %s.') %
+ (policy_type, policy_type_legacy, policy.get('name')))
+
+ if not self.schema_validator.ValidateSchema(schema):
+ self._Error('Schema is invalid for policy %s' % policy.get('name'))
+ self.has_schema_error = True
+
+ if 'validation_schema' in policy:
+ validation_schema = policy.get('validation_schema')
+ if not self.schema_validator.ValidateSchema(validation_schema):
+ self._Error(
+ 'Validation schema is invalid for policy %s' % policy.get('name'))
+ self.has_schema_error = True
+
+ # Checks that boolean policies are not negated (which makes them harder to
+ # reason about).
+ if (policy_type == 'main' and 'disable' in policy.get('name').lower()
+ and policy.get('name') not in LEGACY_INVERTED_POLARITY_ALLOWLIST):
+ self._Error(('Boolean policy %s uses negative polarity, please make ' +
+ 'new boolean policies follow the XYZEnabled pattern. ' +
+ 'See also http://crbug.com/85687') % policy.get('name'))
+
+ # Checks that the policy doesn't have a validation_schema - the whole
+ # schema should be defined in 'schema'- unless listed as legacy.
+ if ('validation_schema' in policy
+ and policy.get('name') not in LEGACY_EMBEDDED_JSON_ALLOWLIST):
+ self._Error(('"validation_schema" is defined for new policy %s - ' +
+ 'entire schema data should be contained in "schema"') %
+ policy.get('name'))
+
+ # Try to make sure that any policy with a complex schema is storing it as
+ # a 'dict', not embedding it inside JSON strings - unless listed as legacy.
+ if (self._AppearsToContainEmbeddedJson(policy.get('example_value'))
+ and policy.get('name') not in LEGACY_EMBEDDED_JSON_ALLOWLIST):
+ self._Error(('Example value for new policy %s looks like JSON. Do ' +
+ 'not store complex data as stringified JSON - instead, ' +
+ 'store it in a dict and define it in "schema".') %
+ policy.get('name'))
+
+ # Checks that integer policies do not allow negative values.
+ if (policy_type == 'int' and schema.get('minimum', 0) < 0
+ and policy.get('name') not in LEGACY_NEGATIVE_MINIMUM_ALLOWED):
+ self._Error(f"Integer policy {policy.get('name')} allows negative values "
+ f"('minimum' is {schema.get('minimum')}). Negative values "
+ "are forbidden and could silently be replaced with zeros "
+ "when using them. See also https://crbug.com/1115976")
+
+ def _CheckTotalDevicePolicyExternalDataMaxSize(self, policy_definitions):
+ total_device_policy_external_data_max_size = 0
+ for policy in policy_definitions:
+ if (policy.get('device_only', False)
+ and self.policy_type_provider.GetPolicyType(policy) == 'external'):
+ total_device_policy_external_data_max_size += self._CheckContains(
+ policy, 'max_size', int)
+ if (total_device_policy_external_data_max_size >
+ TOTAL_DEVICE_POLICY_EXTERNAL_DATA_MAX_SIZE):
+ self._Error(
+ ('Total sum of device policy external data maximum size limits ' +
+ 'should not exceed %d bytes, current sum is %d bytes.') %
+ (TOTAL_DEVICE_POLICY_EXTERNAL_DATA_MAX_SIZE,
+ total_device_policy_external_data_max_size))
+
+ # Returns True if the example value for a policy seems to contain JSON
+ # embedded inside a string. Simply checks if strings start with '{', so it
+ # doesn't flag numbers (which are valid JSON) but it does flag both JSON
+ # objects and python objects (regardless of the type of quotes used).
+ def _AppearsToContainEmbeddedJson(self, example_value):
+ if isinstance(example_value, str):
+ return example_value.strip().startswith('{')
+ elif isinstance(example_value, list):
+ return any(self._AppearsToContainEmbeddedJson(v) for v in example_value)
+ elif isinstance(example_value, dict):
+ return any(
+ self._AppearsToContainEmbeddedJson(v) for v in example_value.values())
+
+ # Checks that there are no duplicate proto paths in device_policy_proto_map.
+ def _CheckDevicePolicyProtoMappingUniqueness(self, device_policy_proto_map,
+ legacy_device_policy_proto_map):
+ # Check that device_policy_proto_map does not have duplicate values.
+ proto_paths = set()
+ for proto_path in device_policy_proto_map.values():
+ if proto_path in proto_paths:
+ self._Error(
+ "Duplicate proto path '%s' in device_policy_proto_map. Did you set "
+ "the right path for your device policy?" % proto_path)
+ proto_paths.add(proto_path)
+
+ # Check that legacy_device_policy_proto_map only contains pairs
+ # [policy_name, proto_path] and does not have duplicate proto_paths.
+ for policy_and_path in legacy_device_policy_proto_map:
+ if len(policy_and_path) != 2 or not isinstance(
+ policy_and_path[0], str) or not isinstance(policy_and_path[1], str):
+ self._Error(
+ "Every entry in legacy_device_policy_proto_map must be an array of "
+ "two strings, but found '%s'" % policy_and_path)
+ if policy_and_path[1] != '' and policy_and_path[1] in proto_paths:
+ self._Error(
+ "Duplicate proto path '%s' in legacy_device_policy_proto_map. Did "
+ "you set the right path for your device policy?" %
+ policy_and_path[1])
+ proto_paths.add(policy_and_path[1])
+
+ # If 'device only' field is true, the policy must be mapped to its proto
+ # field in device_policy_proto_map.json.
+ def _CheckDevicePolicyProtoMappingDeviceOnly(
+ self, policy, device_policy_proto_map, legacy_device_policy_proto_map):
+ if not policy.get('device_only', False):
+ return
+
+ name = policy.get('name')
+ if not name in device_policy_proto_map and not any(
+ name == policy_and_path[0]
+ for policy_and_path in legacy_device_policy_proto_map):
+ self._Error(
+ "Please add '%s' to device_policy_proto_map and map it to "
+ "the corresponding field in chrome_device_policy.proto." % name)
+ return
+
+ # Performs a quick check whether all fields in |device_policy_proto_map| are
+ # actually present in the device policy proto at |device_policy_proto_path|.
+ # Note that this presubmit check can't compile the proto to pb2.py easily (or
+ # can it?).
+ def _CheckDevicePolicyProtoMappingExistence(self, device_policy_proto_map,
+ device_policy_proto_path):
+ with open(device_policy_proto_path, 'r', encoding='utf-8') as file:
+ device_policy_proto = file.read()
+
+ for policy, proto_path in device_policy_proto_map.items():
+ fields = proto_path.split(".")
+ for field in fields:
+ if field not in device_policy_proto:
+ self._Error("Bad device_policy_proto_map for policy '%s': "
+ "Field '%s' not present in device policy proto." %
+ (policy, field))
+
+ def _NeedsDefault(self, policy):
+ return self.policy_type_provider.GetPolicyType(policy) in ('int', 'main',
+ 'string-enum',
+ 'int-enum')
+
+ def _CheckDefault(self, policy, current_version):
+ if not self._NeedsDefault(policy):
+ return
+
+ # If a policy should have a default but it is no longer supported, we can
+ # safely ignore this error.
+ if ('default' not in policy
+ and not self._SupportedPolicy(policy, current_version)):
+ return
+
+ # Only validate the default when present.
+ # TODO(crbug.com/1139046): Always validate the default for types that
+ # should have it.
+ if 'default' not in policy:
+ return
+
+ policy_type = self.policy_type_provider.GetPolicyType(policy)
+ default = policy.get('default')
+ if policy_type == 'int':
+ # A default value of None is acceptable when the default case is
+ # equivalent to the policy being unset and there is no numeric equivalent.
+ if default is None:
+ return
+
+ default = self._CheckContains(policy, 'default', int)
+ if default is None or default < 0:
+ self._Error(
+ ('Default for policy %s of type int should be an int >= 0 or None, '
+ 'got %s') % (policy.get('name'), default))
+ return
+
+ if policy_type == 'main':
+ # TODO(crbug.com/1139306): Query the acceptable values from items
+ # once that is used for policy type main.
+ acceptable_values = (True, False, None)
+ elif policy_type in ('string-enum', 'int-enum'):
+ acceptable_values = [None] + [x['value'] for x in policy['items']]
+ else:
+ raise NotImplementedError('Unimplemented policy type: %s' % policy_type)
+
+ if default not in acceptable_values:
+ self._Error(
+ ('Default for policy %s of type %s should be one of %s, got %s') %
+ (policy.get('name'), policy_type, acceptable_values, default))
+
+ def _NeedsItems(self, policy):
+ return self.policy_type_provider.GetPolicyType(policy) in (
+ 'main', 'int-enum', 'string-enum', 'string-enum-list')
+
+ def _CheckItems(self, policy, current_version):
+ if not self._NeedsItems(policy):
+ return
+
+ # If a policy should have items, but it is no longer supported, we
+ # can safely ignore this error.
+ if 'items' not in policy and not self._SupportedPolicy(
+ policy, current_version):
+ return
+
+ # TODO(crbug.com/1139306): Remove this check once all main policies
+ # have specified their items field.
+ policy_type = self.policy_type_provider.GetPolicyType(policy)
+ if policy_type == 'main' and 'items' not in policy:
+ return
+
+ items = self._CheckContains(policy, 'items', list)
+ if items is None:
+ return
+
+ if len(items) < 1:
+ self._Error('"items" must not be empty.', 'policy', policy, items)
+ return
+
+ # Ensure all items have valid captions.
+ for item in items:
+ self._CheckContains(item,
+ 'caption',
+ str,
+ container_name='item',
+ identifier=policy.get('name'))
+
+ if policy_type == 'main':
+ # Main (bool) policies must contain a list of items to clearly
+ # indicate what the states mean.
+ required_values = [True, False]
+
+ # The unset item can only appear if the default is None, since
+ # there is no other way for it to be set.
+ if 'default' in policy and policy['default'] == None:
+ required_values.append(None)
+
+ # Since the item captions don't appear everywhere the description does,
+ # try and ensure the items are still described in the descriptions.
+ value_to_names = {
+ None: {'None', 'Unset', 'unset', 'not set', 'not configured'},
+ True: {'true', 'enable'},
+ False: {'false', 'disable'},
+ }
+ for value in required_values:
+ names = value_to_names[value]
+ if not any(name in policy['desc'].lower() for name in names):
+ self._Warning(
+ ('Policy %s doesn\'t seem to describe what happens when it is '
+ 'set to %s. If possible update the description to describe this '
+ 'while using at least one of %s') %
+ (policy.get('name'), value, names))
+
+ values_seen = set()
+ for item in items:
+ # Bool items shouldn't have names, since it's the same information
+ # as the value field.
+ if 'name' in item:
+ self._Error(
+ ('Policy %s has item %s with an unexpected name field, '
+ 'please delete the name field.') % (policy.get('name'), item))
+
+ # Each item must have a value.
+ if 'value' not in item:
+ self._Error(
+ ('Policy %s has item %s which is missing the value field') %
+ (policy.get('name'), item))
+ else:
+ value = item['value']
+ if value in values_seen:
+ self._Error(
+ ('Policy %s has multiple items with the same value (%s), each '
+ 'value should only appear once') % (policy.get('name'), value))
+ else:
+ values_seen.add(value)
+ if value not in required_values:
+ self._Error(
+ ('Policy %s of type main has an item with a value %s, value '
+ 'must be one of %s') %
+ (policy.get('name'), value, required_values))
+
+ if not values_seen.issuperset(required_values):
+ self._Error(
+ ('Policy %s is missing some required values, found "%s", requires '
+ '"%s"') % (policy.get('name'), list(values_seen), required_values))
+
+ if policy_type in ('int-enum', 'string-enum', 'string-enum-list'):
+ for item in items:
+ # Each item must have a name.
+ self._CheckContains(item,
+ 'name',
+ str,
+ container_name='item',
+ identifier=policy.get('name'),
+ regexp_check=NO_WHITESPACE)
+
+ # Each item must have a value of the correct type.
+ self._CheckContains(item,
+ 'value',
+ _GetPolicyItemType(policy_type),
+ container_name='item',
+ identifier=policy.get('name'))
+
+ def _CheckOwners(self, policy):
+ owners = self._CheckContains(policy, 'owners', list)
+ if not owners:
+ return
+
+ for owner in owners:
+ FILE_PREFIX = 'file://'
+ if owner.startswith(FILE_PREFIX):
+ file_path = owner[len(FILE_PREFIX):]
+ full_file_path = os.path.join(SOURCE_DIR, file_path)
+ if not (os.path.exists(full_file_path)):
+ self._Warning(
+ 'Policy %s lists non-existant owners files, %s, as an owner. '
+ 'Please either add the owners file or remove it from this list.' %
+ (policy.get('name'), full_file_path))
+ elif '@' in owner:
+ # TODO(pastarmovj): Validate the email is a committer's.
+ pass
+ else:
+ self._Error('Policy %s has an unexpected owner, %s, all owners should '
+ 'be committer emails or file:// paths' %
+ (policy.get('name'), owner))
+
+ def _SupportedPolicy(self, policy, current_version):
+ # If a policy has any future_on platforms, it is still supported.
+ if len(policy.get('future_on', [])) > 0:
+ return True
+
+ for s in policy.get('supported_on', []):
+ _, _, supported_on_to = _GetSupportedVersionPlatformAndRange(s)
+
+ # If supported_on_to isn't given, this policy is still supported.
+ if supported_on_to is None:
+ return True
+
+ # If supported_on_to is equal or greater than the current version, it's
+ # still supported.
+ if current_version <= int(supported_on_to):
+ return True
+
+ return False
+
+ def _CheckPolicy(self, policy, is_in_group, policy_ids, deleted_policy_ids,
+ current_version):
+ if not isinstance(policy, dict):
+ self._Error('Each policy must be a dictionary.', 'policy', None, policy)
+ return
+
+ # There should not be any unknown keys in |policy|.
+ for key in policy:
+ if key not in (
+ 'name',
+ 'owners',
+ 'type',
+ 'caption',
+ 'desc',
+ 'device_only',
+ 'supported_on',
+ 'label',
+ 'policies',
+ 'items',
+ 'example_value',
+ 'features',
+ 'deprecated',
+ 'future_on',
+ 'id',
+ 'schema',
+ 'validation_schema',
+ 'description_schema',
+ 'url_schema',
+ 'max_size',
+ 'tags',
+ 'default',
+ 'default_for_enterprise_users',
+ 'default_for_managed_devices_doc_only',
+ 'arc_support',
+ 'supported_chrome_os_management',
+ ):
+ self._Error('In policy %s: Unknown key: %s' % (policy.get('name'), key))
+
+ # Each policy must have a name.
+ self._CheckContains(policy, 'name', str, regexp_check=NO_WHITESPACE)
+
+ # Each policy must have a type.
+ policy_types = ('group', 'main', 'string', 'int', 'list', 'int-enum',
+ 'string-enum', 'string-enum-list', 'dict', 'external')
+ policy_type = self.policy_type_provider.GetPolicyType(policy)
+ if policy_type not in policy_types:
+ self._Error('Policy type must be one of: ' + ', '.join(policy_types),
+ 'policy', policy.get('name'), policy_type)
+ return # Can't continue for unsupported type.
+
+ # Each policy must have a caption message.
+ self._CheckContains(policy, 'caption', str)
+
+ # Each policy's description should be within the limit.
+ desc = self._CheckContains(policy, 'desc', str)
+ if len(desc) > POLICY_DESCRIPTION_LENGTH_SOFT_LIMIT:
+ self._Error(
+ 'Length of description is more than %d characters, which might '
+ 'exceed the limit of 4096 characters in one of its '
+ 'translations. If there is no alternative to reducing the length '
+ 'of the description, it is recommended to add a page under %s '
+ 'instead and provide a link to it.' %
+ (POLICY_DESCRIPTION_LENGTH_SOFT_LIMIT,
+ 'https://www.chromium.org/administrators'), 'policy',
+ policy.get('name'))
+
+ # If 'label' is present, it must be a string.
+ self._CheckContains(policy, 'label', str, True)
+
+ # If 'deprecated' is present, it must be a bool.
+ self._CheckContains(policy, 'deprecated', bool, True)
+
+ # If 'arc_support' is present, it must be a string.
+ self._CheckContains(policy, 'arc_support', str, True)
+
+ if policy_type == 'group':
+ # Groups must not be nested.
+ if is_in_group:
+ self._Error('Policy groups must not be nested.', 'policy', policy)
+
+ # Each policy group must have a list of policies.
+ policies = self._CheckContains(policy, 'policies', list)
+
+ # Policy list should not be empty
+ if isinstance(policies, list) and len(policies) == 0:
+ self._Error('Policy list should not be empty.', 'policies', None,
+ policy)
+
+ # Groups must not have an |id|.
+ if 'id' in policy:
+ self._Error('Policies of type "group" must not have an "id" field.',
+ 'policy', policy)
+
+ # Statistics.
+ self.num_groups += 1
+
+ else: # policy_type != group
+ # Each policy must have a protobuf ID.
+ id = self._CheckContains(policy, 'id', int)
+ self._AddPolicyID(id, policy_ids, policy, deleted_policy_ids)
+
+ # Each policy must have an owner.
+ self._CheckOwners(policy)
+
+ # Each policy must have a tag list.
+ self._CheckContains(policy, 'tags', list)
+
+ # 'schema' is the new 'type'.
+ # TODO(crbug.com/1310258): remove 'type' from policy_templates.json and
+ # all supporting files (including this one), and exclusively use 'schema'.
+ self._CheckPolicySchema(policy, policy_type)
+
+ # Each policy must have a supported_on list.
+ supported_on = self._CheckContains(policy,
+ 'supported_on',
+ list,
+ optional=True)
+ supported_platforms = []
+ if supported_on:
+ for s in supported_on:
+ (
+ supported_on_platform,
+ supported_on_from,
+ supported_on_to,
+ ) = _GetSupportedVersionPlatformAndRange(s)
+
+ supported_platforms.append(supported_on_platform)
+ if not isinstance(supported_on_platform,
+ str) or not supported_on_platform:
+ self._Error(
+ 'Entries in "supported_on" must have a valid target before the '
+ '":".', 'policy', policy, supported_on)
+ elif not isinstance(supported_on_from, int):
+ self._Error(
+ 'Entries in "supported_on" must have a valid starting '
+ 'supported version after the ":".', 'policy', policy,
+ supported_on)
+ elif isinstance(supported_on_to,
+ int) and supported_on_to < supported_on_from:
+ self._Error(
+ 'Entries in "supported_on" that have an ending '
+ 'supported version must have a version larger than the '
+ 'starting supported version.', 'policy', policy, supported_on)
+
+ if (not self._SupportedPolicy(policy, current_version)
+ and not policy.get('deprecated', False)):
+ self._Error(
+ 'Policy %s is marked as no longer supported (%s), but isn\'t '
+ 'marked as deprecated. Unsupported policies must be marked as '
+ '"deprecated": True' % (policy.get('name'), supported_on))
+
+ supported_platforms = ExpandChromeStar(supported_platforms)
+ future_on = ExpandChromeStar(
+ self._CheckContains(policy, 'future_on', list, optional=True))
+
+ self._CheckPlatform(supported_platforms, 'supported_on',
+ policy.get('name'))
+ self._CheckPlatform(future_on, 'future_on', policy.get('name'))
+
+ if not supported_platforms and not future_on:
+ self._Error(
+ 'The policy needs to be supported now or in the future on at '
+ 'least one platform.', 'policy', policy.get('name'))
+
+ if supported_on == []:
+ self._Warning("Policy %s: supported_on' is empty." %
+ (policy.get('name')))
+
+ if future_on == []:
+ self._Warning("Policy %s: 'future_on' is empty." % (policy.get('name')))
+
+ if future_on:
+ for platform in set(supported_platforms).intersection(future_on):
+ self._Error(
+ "Platform %s is marked as 'supported_on' and 'future_on'. Only "
+ "put released platform in 'supported_on' field" % (platform),
+ 'policy', policy.get('name'))
+
+
+ # Each policy must have a 'features' dict.
+ features = self._CheckContains(policy, 'features', dict)
+
+ # All the features must have a documenting message.
+ if features:
+ for feature in features:
+ if not feature in self.features:
+ self._Error(
+ 'Unknown feature "%s". Known features must have a '
+ 'documentation string in the messages dictionary.' % feature,
+ 'policy', policy.get('name', policy))
+
+ # All user policies must have a per_profile feature flag.
+ if (not policy.get('device_only', False)
+ and not policy.get('deprecated', False)
+ and not 'chrome_frame' in supported_platforms):
+ self._CheckContains(
+ features,
+ 'per_profile',
+ bool,
+ container_name='features',
+ identifier=policy.get('name'))
+
+ # If 'device only' policy is on, feature 'per_profile' shouldn't exist.
+ if (policy.get('device_only', False) and
+ features.get('per_profile', False)):
+ self._Error('per_profile attribute should not be set '
+ 'for policies with device_only=True')
+
+ # If 'device only' policy is on, 'default_for_enterprise_users' shouldn't
+ # exist.
+ if (policy.get('device_only', False) and
+ 'default_for_enterprise_users' in policy):
+ self._Error('default_for_enteprise_users should not be set '
+ 'for policies with device_only=True. Please use '
+ 'default_for_managed_devices_doc_only to document a'
+ 'differing default value for enrolled devices. Please note '
+ 'that default_for_managed_devices_doc_only is for '
+ 'documentation only - it has no side effects, so you will '
+ ' still have to implement the enrollment-dependent default '
+ 'value handling yourself in all places where the device '
+ 'policy proto is evaluated. This will probably include '
+ 'device_policy_decoder.cc for chrome, but could '
+ 'also have to done in other components if they read the '
+ 'proto directly. Details: crbug.com/809653')
+
+ if (not policy.get('device_only', False) and
+ 'default_for_managed_devices_doc_only' in policy):
+ self._Error('default_for_managed_devices_doc_only should only be used '
+ 'with policies that have device_only=True.')
+
+ # All policies must declare whether they allow changes at runtime.
+ self._CheckContains(
+ features,
+ 'dynamic_refresh',
+ bool,
+ container_name='features',
+ identifier=policy.get('name'))
+
+ # 'cloud_only' feature must be an optional boolean flag.
+ cloud_only = self._CheckContains(
+ features,
+ 'cloud_only',
+ bool,
+ optional=True,
+ container_name='features')
+
+ # 'platform_only' feature must be an optional boolean flag.
+ platform_only = self._CheckContains(
+ features,
+ 'platform_only',
+ bool,
+ optional=True,
+ container_name='features')
+
+ # 'internal_only' feature must be an optional boolean flag.
+ internal_only = self._CheckContains(features,
+ 'internal_only',
+ bool,
+ optional=True,
+ container_name='features')
+
+ # 'private' feature must be an optional boolean flag.
+ is_unlisted = self._CheckContains(features,
+ 'unlisted',
+ bool,
+ optional=True,
+ container_name='features')
+
+ # 'metapolicy_type' feature must be one of the supported types.
+ metapolicy_type = self._CheckContains(features,
+ 'metapolicy_type',
+ str,
+ optional=True,
+ container_name='features')
+ if metapolicy_type and metapolicy_type not in METAPOLICY_TYPES:
+ self._Error(
+ 'The value entered for metapolicy_type is not supported. '
+ 'Please use one of [%s].' % ", ".join(METAPOLICY_TYPES), 'policy',
+ policy.get('name'), metapolicy_type)
+
+ if cloud_only and platform_only:
+ self._Error(
+ 'cloud_only and platfrom_only must not be true at the same '
+ 'time.', 'policy', policy.get('name'))
+
+ if is_unlisted and not cloud_only:
+ self._Error('unlisted can only be used by cloud_only policy.', 'policy',
+ policy.get('name'))
+
+
+ # Chrome OS policies may have a non-empty supported_chrome_os_management
+ # list with either 'active_directory' or 'google_cloud' or both.
+ supported_chrome_os_management = self._CheckContains(
+ policy, 'supported_chrome_os_management', list, True)
+ if supported_chrome_os_management is not None:
+ # Must be on Chrome OS.
+ if (supported_on is not None and
+ not any('chrome_os:' in str for str in supported_on)):
+ self._Error(
+ '"supported_chrome_os_management" is only supported on '
+ 'Chrome OS', 'policy', policy, supported_on)
+ # Must be non-empty.
+ if len(supported_chrome_os_management) == 0:
+ self._Error('"supported_chrome_os_management" must be non-empty',
+ 'policy', policy)
+ # Must be either 'active_directory' or 'google_cloud'.
+ if (any(str != 'google_cloud' and str != 'active_directory'
+ for str in supported_chrome_os_management)):
+ self._Error(
+ 'Values in "supported_chrome_os_management" must be '
+ 'either "active_directory" or "google_cloud"', 'policy', policy,
+ supported_chrome_os_management)
+
+ # Each policy must have an 'example_value' of appropriate type.
+ self._CheckContains(policy, 'example_value',
+ _GetPolicyValueType(policy_type))
+
+ # Verify that the example complies with the schema and that all properties
+ # are used at least once, so the examples are as useful as possible for
+ # admins.
+ schema = policy.get('schema')
+ example = policy.get('example_value')
+ enforce_use_entire_schema = policy.get(
+ 'name') not in OPTIONAL_PROPERTIES_POLICIES_ALLOWLIST
+ if not self.has_schema_error:
+ if not self.schema_validator.ValidateValue(schema, example,
+ enforce_use_entire_schema):
+ self._Error(('Example for policy %s does not comply to the policy\'s '
+ 'schema or does not use all properties at least once.') %
+ policy.get('name'))
+ if 'validation_schema' in policy and 'description_schema' in policy:
+ self._Error(('validation_schema and description_schema both defined '
+ 'for policy %s.') % policy.get('name'))
+ secondary_schema = policy.get('validation_schema',
+ policy.get('description_schema'))
+ if secondary_schema:
+ real_example = {}
+ if policy_type == 'string':
+ real_example = json.loads(example)
+ elif policy_type == 'list':
+ real_example = [json.loads(entry) for entry in example]
+ else:
+ self._Error('Unsupported type for legacy embedded json policy.')
+ if not self.schema_validator.ValidateValue(
+ secondary_schema, real_example, enforce_use_entire_schema=True):
+ self._Error(('Example for policy %s does not comply to the ' +
+ 'policy\'s validation_schema') % policy.get('name'))
+
+ self._CheckDefault(policy, current_version)
+
+ # Statistics.
+ self.num_policies += 1
+ if is_in_group:
+ self.num_policies_in_groups += 1
+
+ self._CheckItems(policy, current_version)
+
+ if policy_type == 'external':
+ # Each policy referencing external data must specify a maximum data
+ # size.
+ self._CheckContains(policy, 'max_size', int)
+
+ def _CheckPlatform(self, platforms, field_name, policy_name):
+ ''' Verifies the |platforms| list. Records any error with |field_name| and
+ |policy_name|. '''
+ if not platforms:
+ return
+
+ duplicated = set()
+ for platform in platforms:
+ if platform not in ALL_SUPPORTED_PLATFORMS:
+ self._Error(
+ 'Platform %s is not supported in %s. Valid platforms are %s.' %
+ (platform, field_name, ', '.join(ALL_SUPPORTED_PLATFORMS)),
+ 'policy', policy_name)
+ if platform in duplicated:
+ self._Error(
+ 'platform %s appears more than once in %s.' %
+ (platform, field_name), 'policy', policy_name)
+ duplicated.add(platform)
+
+ def _CheckMessage(self, key, value):
+ # |key| must be a string, |value| a dict.
+ if not isinstance(key, str):
+ self._Error('Each message key must be a string.', 'message', key, key)
+ return
+
+ if not isinstance(value, dict):
+ self._Error('Each message must be a dictionary.', 'message', key, value)
+ return
+
+ # Each message must have a desc.
+ self._CheckContains(
+ value, 'desc', str, parent_element='message', identifier=key)
+
+ # Each message must have a text.
+ self._CheckContains(
+ value, 'text', str, parent_element='message', identifier=key)
+
+ # There should not be any unknown keys in |value|.
+ for vkey in value:
+ if vkey not in ('desc', 'text'):
+ self._Warning('In message %s: Warning: Unknown key: %s' % (key, vkey))
+
+ def _GetReleasedPlatforms(self, policy, current_version):
+ '''
+ Returns a dictionary that contains released platforms and their released
+ version. Returns empty dictionary if policy is None or policy.future is
+ True.
+
+ Args:
+ policy: A dictionary contains all policy data from policy_templates.json.
+ current_version: A integer represents the current major milestone.
+
+ Returns:
+ released_platforms: A dictionary contains all platforms that have been
+ released to stable and their released version.
+ rolling_out_platform: A dictionary contains all platforms that have been
+ released but haven't reached stable.
+ Example:
+ {
+ 'chrome.win' : 10,
+ 'chrome_os': '10,
+ }, {
+ 'chrome.mac': 15,
+ }
+ '''
+
+ released_platforms = {}
+ rolling_out_platform = {}
+ for supported_on in policy.get('supported_on', []):
+ (supported_platform, supported_from,
+ _) = _GetSupportedVersionPlatformAndRange(supported_on)
+ if supported_from < current_version - 1:
+ released_platforms[supported_platform] = supported_from
+ else:
+ rolling_out_platform[supported_platform] = supported_from
+
+ released_platforms = ExpandChromeStar(released_platforms)
+ rolling_out_platform = ExpandChromeStar(rolling_out_platform)
+
+ return released_platforms, rolling_out_platform
+
+ def _CheckSingleSchemaValueIsCompatible(self, old_schema_value,
+ new_schema_value,
+ custom_value_validation):
+ '''
+ Checks if a |new_schema_value| in a schema is compatible with an
+ |old_schema_value| in a schema. The check will either use the provided
+ |custom_value_validation| if any or do a normal equality comparison.
+ '''
+ return (custom_value_validation == None
+ and old_schema_value == new_schema_value) or (
+ custom_value_validation != None
+ and custom_value_validation(old_schema_value, new_schema_value))
+
+ def _CheckSchemaValueIsCompatible(self, schema_key_path, old_schema_value,
+ new_schema_value, only_removals_allowed,
+ custom_value_validation):
+ '''
+ Checks if two leaf schema values defined by |old_schema_value| and
+ |new_schema_value| are compatible with each other given certain conditions
+ concerning removal (|only_removals_allowed|) and also for custom
+ compatibility validation (|custom_value_validation|). The leaf schema should
+ never be a dictionary type.
+
+ |schema_key_path|: Used for error reporting, this is the current path in the
+ policy schema that we are processing represented as a list of paths.
+ |old_schema_value|: The value of the schema property in the original policy
+ templates file.
+ |new_schema_value|: The value of the schema property in the modified policy
+ templates file.
+ |only_removals_allowed|: Specifies whether the schema value can be removed
+ in the modified policy templates file. For list type schema values, this
+ flag will also allow removing some entries in the list while keeping other
+ parts.
+ |custom_value_validation|: Custom validation function used to compare the
+ old and new values to see if they are compatible. If None is provided then
+ an equality comparison is used.
+ '''
+ current_schema_key = '/'.join(schema_key_path)
+
+ # If there is no new value but an old one exists, generally this is
+ # considered an incompatibility and should be reported unless removals are
+ # allowed for this value.
+ if (new_schema_value == None):
+ if not only_removals_allowed:
+ self._Error(
+ 'Value in policy schema path \'%s\' was removed in new schema '
+ 'value.' % (current_schema_key))
+ return
+
+ # Both old and new values must be of the same type.
+ if type(old_schema_value) != type(new_schema_value):
+ self._Error(
+ 'Value in policy schema path \'%s\' is of type \'%s\' but value in '
+ 'schema is of type \'%s\'.' %
+ (current_schema_key, type(old_schema_value).__name__,
+ type(new_schema_value).__name__))
+
+ # We are checking a leaf schema key and do not expect to ever get a
+ # dictionary value at this level.
+ if (type(old_schema_value) is dict):
+ self._Error(
+ 'Value in policy schema path \'%s\' had an unexpected type: \'%s\'.' %
+ (current_schema_key, type(old_schema_value).__name__))
+ # We have a list type schema value. In general additions to the list are
+ # allowed (e.g. adding a new enum value) but removals from the lists are
+ # not allowed. Also additions to the list must only occur at the END of the
+ # old list and not in the middle.
+ elif (type(old_schema_value) is list):
+ # If only removal from the list is allowed check that there are no new
+ # values and that only old values are removed. Since we are enforcing
+ # strict ordering we can check the lists sequentially for this condition.
+ if only_removals_allowed:
+ j = 0
+ i = 0
+
+ # For every old value, check that it either exists in the new value in
+ # the same order or was removed. This loop only iterates sequentially
+ # on both lists.
+ while i < len(old_schema_value) and j < len(new_schema_value):
+ # Keep looking in the old value until we find a matching new_value at
+ # our current position in the list or until we reach the end of the
+ # old values.
+ while not self._CheckSingleSchemaValueIsCompatible(
+ old_schema_value[i], new_schema_value[j],
+ custom_value_validation):
+ i += 1
+ if i >= len(old_schema_value):
+ break
+
+ # Here either we've found the matching old value so that we can say
+ # the new value matches and move to the next new value (j += 1) and
+ # the next old value (i += 1) to check, or we have exhausted the old
+ # value list and can exit the loop.
+ if i < len(old_schema_value):
+ j += 1
+ i += 1
+ # Everything we have not processed in the new value list is in error
+ # because only allow removal in this list.
+ while j < len(new_schema_value):
+ self._Error(
+ 'Value \'%s\' in policy schema path \'%s/[%s]\' was added which '
+ 'is not allowed.' %
+ (str(new_schema_value[j]), current_schema_key, j))
+ j += 1
+ else:
+ # If removals are not allowed we should be able to add to the list, but
+ # only at the end. We only need to check that all the old values appear
+ # in the same order in the new value as in the old value. Everything
+ # added after the end of the old value list is allowed.
+ # If the new value list is shorter than the old value list we will end
+ # up with calls to _CheckSchemaValueIsCompatible where
+ # new_schema_value == None and this will raise an error on the first
+ # check in the function.
+ for i in range(len(old_schema_value)):
+ self._CheckSchemaValueIsCompatible(
+ schema_key_path + ['[' + str(i) + ']'], old_schema_value[i],
+ new_schema_value[i] if len(new_schema_value) > i else None,
+ only_removals_allowed, custom_value_validation)
+ # For non list values, we compare the two values against each other with
+ # the custom_value_validation or standard equality comparisons.
+ elif not self._CheckSingleSchemaValueIsCompatible(
+ old_schema_value, new_schema_value, custom_value_validation):
+ self._Error(
+ 'Value in policy schema path \'%s\' was changed from \'%s\' to '
+ '\'%s\' which is not allowed.' %
+ (current_schema_key, str(old_schema_value), str(new_schema_value)))
+
+ def _CheckSchemasAreCompatible(self, schema_key_path, old_schema, new_schema):
+ current_schema_key = '/'.join(schema_key_path)
+ '''
+ Checks if two given schemas are compatible with each other.
+
+ This function will raise errors if it finds any incompatibilities between
+ the |old_schema| and |new_schema|.
+
+ |schema_key_path|: Used for error reporting, this is the current path in the
+ policy schema that we are processing represented as a list of paths.
+ |old_schema|: The full contents of the schema as found in the original
+ policy templates file.
+ |new_schema|: The full contents of the new schema as found (if any) in the
+ modified policy templates file.
+ '''
+
+ # If the old schema was present and the new one is no longer present, this
+ # is an error. This case can occur while we are recursing through various
+ # 'object' type schemas.
+ if (new_schema is None):
+ self._Error(
+ 'Policy schema path \'%s\' in old schema was removed in newer '
+ 'version.' % (current_schema_key))
+ return
+
+ # Both old and new schema information must be in dict format.
+ if type(old_schema) is not dict:
+ self._Error(
+ 'Policy schema path \'%s\' in old policy is of type \'%s\', it must '
+ 'be dict type.' % (current_schema_key, type(old_schema)))
+
+ if type(new_schema) is not dict:
+ self._Error(
+ 'Policy schema path \'%s\' in new policy is of type \'%s\', it must '
+ 'be dict type.' % (current_schema_key, type(new_schema)))
+
+ # Both schemas must either have a 'type' key or not. This covers the case
+ # where the schema is merely a '$ref'
+ if ('type' in old_schema) != ('type' in new_schema):
+ self._Error(
+ 'Mismatch in type definition for old schema and new schema for '
+ 'policy schema path \'%s\'. One schema defines a type while the other'
+ ' does not.' %
+ (current_schema_key, old_schema['type'], new_schema['type']))
+ return
+
+ # For schemas that define a 'type', make sure they match.
+ schema_type = None
+ if ('type' in old_schema):
+ if (old_schema['type'] != new_schema['type']):
+ self._Error(
+ 'Policy schema path \'%s\' in old schema is of type \'%s\' but '
+ 'new schema is of type \'%s\'.' %
+ (current_schema_key, old_schema['type'], new_schema['type']))
+ return
+ schema_type = old_schema['type']
+
+ # If a schema does not have 'type' we will simply end up comparing every
+ # key/value pair for exact matching (the final else in this loop). This will
+ # ensure that '$ref' type schemas match.
+ for old_key, old_value in old_schema.items():
+ # 'type' key was already checked above.
+ if (old_key == 'type'):
+ continue
+
+ # If the schema key is marked as modifiable (e.g. 'description'), then
+ # no validation is needed. Anything can be done to it including removal.
+ if IsKeyDefinedForTypeInDictionary(schema_type, old_key,
+ MODIFIABLE_SCHEMA_KEYS_PER_TYPE):
+ continue
+
+ # If a key was removed in the new schema, check if the removal was
+ # allowed. If not this is an error. The removal of some schema keys make
+ # the schema less restrictive (e.g. removing 'required' keys in
+ # dictionaries or removing 'minimum' in integer schemas).
+ if old_key not in new_schema:
+ if not IsKeyDefinedForTypeInDictionary(
+ schema_type, old_key, REMOVABLE_SCHEMA_VALUES_PER_TYPE):
+ self._Error(
+ 'Key \'%s\' in old policy schema path \'%s\' was removed in '
+ 'newer version.' % (old_key, current_schema_key))
+ continue
+
+ # For a given type that has a key that can define dictionaries of schemas
+ # (e.g. 'object' types), we need to validate the schema of each individual
+ # property that is defined. We also need to validate that no old
+ # properties were removed. Any new properties can be added.
+ if IsKeyDefinedForTypeInDictionary(
+ schema_type, old_key, KEYS_DEFINING_PROPERTY_DICT_SCHEMAS_PER_TYPE):
+ if type(old_value) is not dict:
+ self._Error(
+ 'Unexpected type \'%s\' at policy schema path \'%s\'. It must be '
+ 'dict' % (type(old_value).__name__, ))
+ continue
+
+ # Make sure that all old properties exist and are compatible. Everything
+ # else that is new requires no validation.
+ new_schema_value = new_schema[old_key]
+ for sub_key in old_value.keys():
+ self._CheckSchemasAreCompatible(
+ schema_key_path + [old_key, sub_key], old_value[sub_key],
+ new_schema_value[sub_key]
+ if sub_key in new_schema_value else None)
+ # For types that have a key that themselves define a schema (e.g. 'items'
+ # schema in an 'array' type), we need to validate the schema defined in
+ # the key.
+ elif IsKeyDefinedForTypeInDictionary(schema_type, old_key,
+ KEYS_DEFINING_SCHEMAS_PER_TYPE):
+ self._CheckSchemasAreCompatible(
+ schema_key_path + [old_key], old_value,
+ new_schema[old_key] if old_key in new_schema else None)
+ # For any other key, we just check if the two values of the key are
+ # compatible with each other, possibly allowing removal of entries in
+ # array values if needed (e.g. removing 'required' fields makes the schema
+ # less restrictive).
+ else:
+ self._CheckSchemaValueIsCompatible(
+ schema_key_path + [old_key], old_value, new_schema[old_key],
+ IsKeyDefinedForTypeInDictionary(schema_type, old_key,
+ REMOVABLE_SCHEMA_VALUES_PER_TYPE),
+ CUSTOM_VALUE_CHANGE_VALIDATION_PER_TYPE[schema_type][old_key]
+ if IsKeyDefinedForTypeInDictionary(
+ schema_type, old_key,
+ CUSTOM_VALUE_CHANGE_VALIDATION_PER_TYPE) else None)
+
+ for new_key in (old_key for old_key in new_schema.keys()
+ if not old_key in old_schema.keys()):
+ self._Error(
+ 'Key \'%s\' was added to policy schema path \'%s\' in new schema.' %
+ (new_key, current_schema_key))
+
+ def _CheckPolicyDefinitionChangeCompatibility(self, original_policy,
+ original_released_platforms,
+ new_policy,
+ new_released_platforms,
+ current_version):
+ '''
+ Checks if the new policy definition is compatible with the original policy
+ definition.
+
+ Args:
+ original_policy: The policy definition as it was in the original policy
+ templates file.
+ original_released_platforms: A dictionary contains a released platforms
+ and their release version in the original
+ policy template files.
+ new_policy: The policy definition as it is (if any) in the modified policy
+ templates file.
+ new_released_platforms: A dictionary contains a released platforms and
+ their release version in the modified policy
+ template files.
+ current_version: The current major version of the branch as stored in
+ chrome/VERSION.
+
+ '''
+ # 1. Check if the supported_on versions are valid.
+
+ # All starting versions in supported_on in the original policy must also
+ # appear in the changed policy. The only thing that can be added is an
+ # ending version.
+ for platform in original_released_platforms:
+ if platform not in new_released_platforms:
+ self._Error('Released platform %s has been removed.' % (platform),
+ 'policy', original_policy['name'])
+ elif original_released_platforms[platform] < new_released_platforms[
+ platform]:
+ self._Error(
+ 'Supported version of released platform %s is changed to a later '
+ 'version %d from %d.' % (platform, new_released_platforms[platform],
+ original_released_platforms[platform]),
+ 'policy', original_policy['name'])
+
+ # 2. Check if the policy has changed its device_only value
+ original_device_only = ('device_only' in original_policy
+ and original_policy['device_only'])
+ if (('device_only' in new_policy
+ and original_device_only != new_policy['device_only'])
+ or ('device_only' not in new_policy and original_device_only)):
+ self._Error(
+ 'Cannot change the device_only status of released policy \'%s\'' %
+ (new_policy['name']))
+
+ # 3. Check schema changes for compatibility.
+ self._CheckSchemasAreCompatible([original_policy['name']],
+ original_policy['schema'],
+ new_policy['schema'])
+
+ def _CheckNewReleasedPlatforms(self, original_platforms, new_platforms,
+ current_version, policy_name):
+ '''If released version has changed, it should be the current version unless
+ there is a special reason.'''
+ for platform in new_platforms:
+ new_version = new_platforms[platform]
+ if new_version == original_platforms.get(platform):
+ continue
+ if new_version == current_version - 1:
+ self._Warning(
+ 'Policy %s on %s will be released in %d which has passed the '
+ 'branch point. Please merge it into Beta or change the version to '
+ '%d.' % (policy_name, platform, new_version, current_version))
+ elif new_version < current_version - 1:
+ self.non_compatibility_error_count += 1
+ self._Error(
+ 'Version %d has been released to Stable already. Please use '
+ 'version %d instead for platform %s.' %
+ (new_version, current_version, platform), 'policy', policy_name)
+
+ # Checks if the new policy definitions are compatible with the policy
+ # definitions coming from the original_file_contents.
+ def _CheckPolicyDefinitionsChangeCompatibility(self, policy_definitions,
+ original_file_contents,
+ current_version):
+ '''
+ Checks if all the |policy_definitions| in the modified policy templates file
+ are compatible with the policy definitions defined in the original policy
+ templates file with |original_file_contents| .
+
+ |policy_definitions|: The policy definition as it is in the modified policy
+ templates file.
+ |original_file_contents|: The full contents of the original policy templates
+ file.
+ |current_version|: The current major version of the branch as stored in
+ chrome/VERSION.
+ '''
+ try:
+ original_container = eval(original_file_contents)
+ except:
+ import traceback
+ traceback.print_exc(file=sys.stdout)
+ self._Error('Invalid Python/JSON syntax in original file.')
+ return
+
+ if original_container == None:
+ self._Error('Invalid Python/JSON syntax in original file.')
+ return
+
+ original_policy_definitions = self._CheckContains(
+ original_container,
+ 'policy_definitions',
+ list,
+ parent_element=None,
+ optional=True,
+ container_name='The root element',
+ offending=None)
+
+ if original_policy_definitions is None:
+ return
+
+ # Sort the new policies by name for faster searches.
+ policy_definitions_dict = {
+ policy['name']: policy
+ for policy in policy_definitions
+ if self.policy_type_provider.GetPolicyType(policy) != 'group'
+ }
+
+ original_policy_name_set = {
+ policy['name']
+ for policy in original_policy_definitions
+ if self.policy_type_provider.GetPolicyType(policy) != 'group'
+ }
+
+ for original_policy in original_policy_definitions:
+ # Check change compatibility for all non-group policy definitions.
+ if self.policy_type_provider.GetPolicyType(original_policy) == 'group':
+ continue
+
+ (original_released_platforms,
+ original_rolling_out_platforms) = self._GetReleasedPlatforms(
+ original_policy, current_version)
+
+ new_policy = policy_definitions_dict.get(original_policy['name'])
+
+ # A policy that has at least one released platform cannot be removed.
+ if new_policy is None:
+ name = original_policy['name']
+ if original_released_platforms:
+ self._Error(f'Released policy {name} has been removed.')
+ else:
+ self._Warning(f'Unreleased Policy {name} has been removed. If the '
+ 'policy is available in Beta, please cleanup the Beta '
+ 'branch as well.')
+ continue
+
+ (new_released_platforms,
+ new_rolling_out_platform) = self._GetReleasedPlatforms(
+ new_policy, current_version)
+
+ # Check policy compatibility if there is at least one released platform.
+ if original_released_platforms:
+ self._CheckPolicyDefinitionChangeCompatibility(
+ original_policy, original_released_platforms, new_policy,
+ new_released_platforms, current_version)
+
+ # New released platforms should always use the current version unless they
+ # are going to be merged into previous milestone.
+ if new_released_platforms or new_rolling_out_platform:
+ self._CheckNewReleasedPlatforms(
+ MergeDict(original_released_platforms,
+ original_rolling_out_platforms),
+ MergeDict(new_released_platforms, new_rolling_out_platform),
+ current_version, original_policy['name'])
+
+ # Check brand new policies:
+ for new_policy_name in set(
+ policy_definitions_dict.keys()) - original_policy_name_set:
+ new_policy = policy_definitions_dict[new_policy_name]
+ (new_released_platforms,
+ new_rolling_out_platform) = self._GetReleasedPlatforms(
+ new_policy, current_version)
+ if new_released_platforms or new_rolling_out_platform:
+ self._CheckNewReleasedPlatforms({},
+ MergeDict(new_released_platforms,
+ new_rolling_out_platform),
+ current_version, new_policy_name)
+ # TODO(crbug.com/1139046): This default check should apply to all
+ # policies instead of just new ones.
+ if self._NeedsDefault(new_policy) and not 'default' in new_policy:
+ self._Error("Definition of policy %s must include a 'default'"
+ " field." % (new_policy_name))
+
+ # TODO(crbug.com/1139306): This item check should apply to all policies
+ # instead of just new ones.
+ if self._NeedsItems(new_policy) and new_policy.get('items', None) == None:
+ self._Error(('Missing items field for policy %s') % (new_policy_name))
+
+ def _LeadingWhitespace(self, line):
+ match = LEADING_WHITESPACE.match(line)
+ if match:
+ return match.group(1)
+ return ''
+
+ def _TrailingWhitespace(self, line):
+ match = TRAILING_WHITESPACE.match(line)
+ if match:
+ return match.group(1)
+ return ''
+
+ def _LineError(self, message, line_number):
+ self.error_count += 1
+ print('In line %d: Error: %s' % (line_number, message))
+
+ def _LineWarning(self, message, line_number):
+ self._Warning('In line %d: Warning: Automatically fixing formatting: %s' %
+ (line_number, message))
+
+ def _CheckFormat(self, filename):
+ if self.options.fix:
+ fixed_lines = []
+ # Three quotes open and close multiple lines strings. Odd means currently
+ # inside a multiple line strings. We don't change indentation for those
+ # strings. It changes hash of the string and grit can't find translation in
+ # the file.
+ three_quotes_cnt = 0
+ with open(filename, encoding='utf-8') as f:
+ indent = 0
+ line_number = 0
+ for line in f:
+ line_number += 1
+ line = line.rstrip('\n')
+ # Check for trailing whitespace.
+ trailing_whitespace = self._TrailingWhitespace(line)
+ if len(trailing_whitespace) > 0:
+ if self.options.fix:
+ line = line.rstrip()
+ self._LineWarning('Trailing whitespace.', line_number)
+ else:
+ self._LineError('Trailing whitespace.', line_number)
+ if self.options.fix:
+ if len(line) == 0:
+ fixed_lines += ['\n']
+ continue
+ else:
+ if line == trailing_whitespace:
+ # This also catches the case of an empty line.
+ continue
+ # Check for correct amount of leading whitespace.
+ leading_whitespace = self._LeadingWhitespace(line)
+ if leading_whitespace.count('\t') > 0:
+ if self.options.fix:
+ leading_whitespace = leading_whitespace.replace('\t', ' ')
+ line = leading_whitespace + line.lstrip()
+ self._LineWarning('Tab character found.', line_number)
+ else:
+ self._LineError('Tab character found.', line_number)
+ if line[len(leading_whitespace)] in (']', '}'):
+ indent -= 2
+ # Ignore 0-indented comments and multiple string literals.
+ if line[0] != '#' and three_quotes_cnt % 2 == 0:
+ if len(leading_whitespace) != indent:
+ if self.options.fix:
+ line = ' ' * indent + line.lstrip()
+ self._LineWarning(
+ 'Indentation should be ' + str(indent) + ' spaces.',
+ line_number)
+ else:
+ self._LineError(
+ 'Bad indentation. Should be ' + str(indent) + ' spaces.',
+ line_number)
+ three_quotes_cnt += line.count("'''")
+ if line[-1] in ('[', '{'):
+ indent += 2
+ if self.options.fix:
+ fixed_lines.append(line + '\n')
+
+ assert three_quotes_cnt % 2 == 0
+ # If --fix is specified: backup the file (deleting any existing backup),
+ # then write the fixed version with the old filename.
+ if self.options.fix:
+ if self.options.backup:
+ backupfilename = filename + '.bak'
+ if os.path.exists(backupfilename):
+ os.remove(backupfilename)
+ os.rename(filename, backupfilename)
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.writelines(fixed_lines)
+
+ def _ValidatePolicyAtomicGroups(self, atomic_groups, max_id, deleted_ids):
+ ids = [x['id'] for x in atomic_groups]
+ actual_highest_id = max(ids)
+ if actual_highest_id != max_id:
+ self._Error(
+ ("'highest_atomic_group_id_currently_used' must be set to the "
+ "highest atomic group id in use, which is currently %s (vs %s).") %
+ (actual_highest_id, max_id))
+ return
+
+ ids_set = set()
+ for i in range(len(ids)):
+ if (ids[i] in ids_set):
+ self._Error('Duplicate atomic group id %s' % (ids[i]))
+ return
+ ids_set.add(ids[i])
+ if i > 0 and ids[i - 1] + 1 != ids[i]:
+ for delete_id in range(ids[i - 1] + 1, ids[i]):
+ if delete_id not in deleted_ids:
+ self._Error('Missing atomic group id %s' % (delete_id))
+ return
+
+ def Main(self, filename, options, original_file_contents, current_version,
+ skip_compability_check):
+ try:
+ with open(filename, 'rb') as f:
+ raw_data = f.read().decode('UTF-8')
+ data = eval(raw_data)
+ DuplicateKeyVisitor().visit(ast.parse(raw_data))
+ except ValueError as e:
+ self._Error(str(e))
+ return 1
+ except:
+ import traceback
+ traceback.print_exc(file=sys.stdout)
+ self._Error('Invalid Python/JSON syntax.')
+ return 1
+ if data == None:
+ self._Error('Invalid Python/JSON syntax.')
+ return 1
+ self.options = options
+
+ # First part: check JSON structure.
+
+ # Check (non-policy-specific) message definitions.
+ messages = self._CheckContains(
+ data,
+ 'messages',
+ dict,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ if messages is not None:
+ for message in messages:
+ self._CheckMessage(message, messages[message])
+ if message.startswith('doc_feature_'):
+ self.features.append(message[12:])
+
+ # Check policy definitions.
+ policy_definitions = self._CheckContains(
+ data,
+ 'policy_definitions',
+ list,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ deleted_policy_ids = self._CheckContains(
+ data,
+ 'deleted_policy_ids',
+ list,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ deleted_atomic_policy_group_ids = self._CheckContains(
+ data,
+ 'deleted_atomic_policy_group_ids',
+ list,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ highest_id = self._CheckContains(
+ data,
+ 'highest_id_currently_used',
+ int,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ highest_atomic_group_id = self._CheckContains(
+ data,
+ 'highest_atomic_group_id_currently_used',
+ int,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ device_policy_proto_map = self._CheckContains(
+ data,
+ 'device_policy_proto_map',
+ dict,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ legacy_device_policy_proto_map = self._CheckContains(
+ data,
+ 'legacy_device_policy_proto_map',
+ list,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+ policy_atomic_group_definitions = self._CheckContains(
+ data,
+ 'policy_atomic_group_definitions',
+ list,
+ parent_element=None,
+ container_name='The root element',
+ offending=None)
+
+ self._ValidatePolicyAtomicGroups(policy_atomic_group_definitions,
+ highest_atomic_group_id,
+ deleted_atomic_policy_group_ids)
+ self._CheckDevicePolicyProtoMappingUniqueness(
+ device_policy_proto_map, legacy_device_policy_proto_map)
+ self._CheckDevicePolicyProtoMappingExistence(
+ device_policy_proto_map, options.device_policy_proto_path)
+
+ if policy_definitions is not None:
+ policy_ids = set()
+ for policy in policy_definitions:
+ self._CheckPolicy(policy, False, policy_ids, deleted_policy_ids,
+ current_version)
+ self._CheckDevicePolicyProtoMappingDeviceOnly(
+ policy, device_policy_proto_map, legacy_device_policy_proto_map)
+ self._CheckPolicyIDs(policy_ids, deleted_policy_ids)
+ if highest_id is not None:
+ self._CheckHighestId(policy_ids, highest_id)
+ self._CheckTotalDevicePolicyExternalDataMaxSize(policy_definitions)
+
+ # Made it as a dict (policy_name -> True) to reuse _CheckContains.
+ policy_names = {
+ policy['name']: True
+ for policy in policy_definitions
+ if self.policy_type_provider.GetPolicyType(policy) != 'group'
+ }
+ policy_in_groups = set()
+ for group in [
+ policy for policy in policy_definitions
+ if self.policy_type_provider.GetPolicyType(policy) == 'group'
+ ]:
+ for policy_name in group['policies']:
+ self._CheckContains(
+ policy_names,
+ policy_name,
+ bool,
+ parent_element='policy_definitions')
+ if policy_name in policy_in_groups:
+ self._Error('Policy %s defined in several groups.' % (policy_name))
+ else:
+ policy_in_groups.add(policy_name)
+
+ policy_in_atomic_groups = set()
+ for group in policy_atomic_group_definitions:
+ for policy_name in group['policies']:
+ self._CheckContains(
+ policy_names,
+ policy_name,
+ bool,
+ parent_element='policy_definitions')
+ if policy_name in policy_in_atomic_groups:
+ self._Error('Policy %s defined in several atomic policy groups.' %
+ (policy_name))
+ else:
+ policy_in_atomic_groups.add(policy_name)
+
+ # Second part: check formatting.
+ self._CheckFormat(filename)
+
+ # Third part: if the original file contents are available, try to check
+ # if the new policy definitions are compatible with the original policy
+ # definitions (if the original file contents have not raised any syntax
+ # errors).
+ self.non_compatibility_error_count = self.error_count
+ if (not self.non_compatibility_error_count
+ and original_file_contents is not None and not skip_compability_check):
+ self._CheckPolicyDefinitionsChangeCompatibility(
+ policy_definitions, original_file_contents, current_version)
+
+ if self.non_compatibility_error_count != self.error_count:
+ print(
+ '\nThere were compatibility validation errors in the change. You may '
+ 'bypass this validation by adding "BYPASS_POLICY_COMPATIBILITY_CHECK='
+ '<justification>" to your changelist description. If you believe '
+ 'that this validation is a bug, please file a crbug against '
+ '"Enterprise" and add a link to the bug as '
+ 'justification. Otherwise, please provide an explanation for the '
+ 'change. For more information please refer to: '
+ 'https://bit.ly/33qr3ZV.')
+
+ # Fourth part: summary and exit.
+ print('Finished checking %s. %d errors, %d warnings.' %
+ (filename, self.error_count, self.warning_count))
+ if self.options.stats:
+ if self.num_groups > 0:
+ print('%d policies, %d of those in %d groups (containing on '
+ 'average %.1f policies).' %
+ (self.num_policies, self.num_policies_in_groups, self.num_groups,
+ (1.0 * self.num_policies_in_groups / self.num_groups)))
+ else:
+ print(self.num_policies, 'policies, 0 policy groups.')
+ if self.error_count > 0:
+ return 1
+ return 0
+
+ def Run(self,
+ argv,
+ filename=None,
+ original_file_contents=None,
+ current_version=None,
+ skip_compability_check=False):
+ parser = argparse.ArgumentParser(
+ usage='usage: %prog [options] filename',
+ description='Syntax check a policy_templates.json file.')
+ parser.add_argument(
+ '--device_policy_proto_path',
+ help='[REQUIRED] File path of the device policy proto file.')
+ parser.add_argument(
+ '--fix', action='store_true', help='Automatically fix formatting.')
+ parser.add_argument(
+ '--backup',
+ action='store_true',
+ help='Create backup of original file (before fixing).')
+ parser.add_argument(
+ '--stats', action='store_true', help='Generate statistics.')
+ args = parser.parse_args(argv)
+ if filename is None:
+ print('Error: Filename not specified.')
+ return 1
+ if args.device_policy_proto_path is None:
+ print('Error: Missing --device_policy_proto_path argument.')
+ return 1
+ return self.Main(filename, args, original_file_contents, current_version,
+ skip_compability_check)
diff --git a/chromium/components/policy/tools/template_writers/PRESUBMIT.py b/chromium/components/policy/tools/template_writers/PRESUBMIT.py
new file mode 100755
index 00000000000..e159b0d3b7a
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/PRESUBMIT.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Template writers unittests presubmit script.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into gcl.
+"""
+
+
+USE_PYTHON3 = True
+
+
+def RunUnittests(input_api, output_api):
+ return input_api.canned_checks.RunPythonUnitTests(input_api, output_api,
+ ['test_suite_all'])
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return RunUnittests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return RunUnittests(input_api, output_api)
diff --git a/chromium/components/policy/tools/template_writers/__init__.py b/chromium/components/policy/tools/template_writers/__init__.py
new file mode 100755
index 00000000000..abc93655ae4
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/__init__.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Module template_writers
+'''
+
+pass
diff --git a/chromium/components/policy/tools/template_writers/policy_template_generator.py b/chromium/components/policy/tools/template_writers/policy_template_generator.py
new file mode 100755
index 00000000000..99a9e03c89f
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/policy_template_generator.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import copy
+import os
+import re
+import sys
+
+sys.path.insert(
+ 0,
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ os.pardir, 'third_party', 'six', 'src'))
+
+import six
+
+
+def IsGroupOrAtomicGroup(policy):
+ return policy['type'] == 'group' or policy['type'] == 'atomic_group'
+
+
+class PolicyTemplateGenerator:
+ '''Generates template text for a particular platform.
+
+ This class is used to traverse a JSON structure from a .json template
+ definition metafile and merge GUI message string definitions that come
+ from a .grd resource tree onto it. After this, it can be used to output
+ this data to policy template files using TemplateWriter objects.
+ '''
+
+ def _ImportMessage(self, msg_txt):
+ msg_txt = six.ensure_text(msg_txt)
+ lines = msg_txt.split('\n')
+
+ # Strip any extra leading spaces, but keep useful indentation:
+ min_leading_spaces = min(list(self._IterateLeadingSpaces(lines)) or [0])
+ if min_leading_spaces > 0:
+ lstrip_pattern = re.compile('^[ ]{1,%s}' % min_leading_spaces)
+ lines = [lstrip_pattern.sub('', line) for line in lines]
+ # Strip all trailing spaces:
+ lines = [line.rstrip() for line in lines]
+ return "\n".join(lines)
+
+ def _IterateLeadingSpaces(self, lines):
+ '''Yields the number of leading spaces on each line, skipping lines which
+ have no leading spaces.'''
+ for line in lines:
+ match = re.search('^[ ]+', line)
+ if match:
+ yield len(match.group(0))
+
+ def __init__(self, config, policy_data):
+ '''Initializes this object with all the data necessary to output a
+ policy template.
+
+ Args:
+ config: Writer configuration.
+ policy_data: The list of defined policies and groups, as parsed from the
+ policy metafile. See
+ components/policy/resources/policy_templates.json
+ for description and content.
+ '''
+ # List of all the policies. Create a copy since the data is modified.
+ self._policy_data = copy.deepcopy(policy_data)
+ # Localized messages to be inserted to the policy_definitions structure:
+ self._messages = self._policy_data['messages']
+ self._config = config
+ for key in self._messages.keys():
+ self._messages[key]['text'] = self._ImportMessage(
+ self._messages[key]['text'])
+ self._AddGroups(self._policy_data['policy_definitions'])
+ self._AddAtomicGroups(self._policy_data['policy_definitions'],
+ self._policy_data['policy_atomic_group_definitions'])
+ self._policy_data[
+ 'policy_atomic_group_definitions'] = self._ExpandAtomicGroups(
+ self._policy_data['policy_definitions'],
+ self._policy_data['policy_atomic_group_definitions'])
+ self._ProcessPolicyList(
+ self._policy_data['policy_atomic_group_definitions'])
+ self._policy_data['policy_definitions'] = self._ExpandGroups(
+ self._policy_data['policy_definitions'])
+ self._policy_definitions = self._policy_data['policy_definitions']
+ self._ProcessPolicyList(self._policy_definitions)
+
+ def _ProcessProductPlatformString(self, product_platform_string):
+ '''Splits the |product_platform_string| string to product and a list of
+ platforms.'''
+ if '.' in product_platform_string:
+ product, platform = product_platform_string.split('.')
+ if platform == '*':
+ # e.g.: 'chrome.*:8-10'
+ platforms = ['linux', 'mac', 'win']
+ else:
+ # e.g.: 'chrome.win:-10'
+ platforms = [platform]
+ else:
+ # e.g.: 'chrome_frame:7-'
+ product, platform = {
+ 'android': ('chrome', 'android'),
+ 'webview_android': ('webview', 'android'),
+ 'ios': ('chrome', 'ios'),
+ 'chrome_os': ('chrome_os', 'chrome_os'),
+ 'chrome_frame': ('chrome_frame', 'win'),
+ }[product_platform_string]
+ platforms = [platform]
+ return product, platforms
+
+ def _ProcessSupportedOn(self, supported_on):
+ '''Parses and converts the string items of the list of supported platforms
+ into dictionaries.
+
+ Args:
+ supported_on: The list of supported platforms. E.g.:
+ ['chrome.win:8-10', 'chrome_frame:10-']
+
+ Returns:
+ supported_on: The list with its items converted to dictionaries. E.g.:
+ [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '8',
+ 'until_version': '10'
+ }, {
+ 'product': 'chrome_frame',
+ 'platform': 'win',
+ 'since_version': '10',
+ 'until_version': ''
+ }]
+ '''
+ result = []
+ for supported_on_item in supported_on:
+ product_platform_part, version_part = supported_on_item.split(':')
+ product, platforms = self._ProcessProductPlatformString(
+ product_platform_part)
+
+ since_version, until_version = version_part.split('-')
+ for platform in platforms:
+ result.append({
+ 'product': product,
+ 'platform': platform,
+ 'since_version': since_version,
+ 'until_version': until_version
+ })
+ return result
+
+ def _ProcessFutureOn(self, future_on):
+ '''Parses and converts the |future_on| strings into a list of dictionaries
+ contain product and platform string pair.
+
+ Args:
+ future_on: A list of platform strings. E.g.:
+ ['chrome.win', 'chromeos']
+ Returns:
+ future_on: A list of dictionaries. E.g.:
+ [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ },{
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ }]
+ '''
+ result = []
+ for future in future_on:
+ product, platforms = self._ProcessProductPlatformString(future)
+ for platform in platforms:
+ result.append({
+ 'product': product,
+ 'platform': platform,
+ })
+ return result
+
+ def _ProcessPolicy(self, policy):
+ '''Processes localized message strings in a policy or a group.
+ Also breaks up the content of 'supported_on' attribute into a list.
+
+ Args:
+ policy: The data structure of the policy or group, that will get message
+ strings here.
+ '''
+ if policy['type'] != 'atomic_group':
+ policy['desc'] = self._ImportMessage(policy['desc'])
+ policy['caption'] = self._ImportMessage(policy['caption'])
+ if 'label' in policy:
+ policy['label'] = self._ImportMessage(policy['label'])
+ if 'arc_support' in policy:
+ policy['arc_support'] = self._ImportMessage(policy['arc_support'])
+
+ if IsGroupOrAtomicGroup(policy):
+ self._ProcessPolicyList(policy['policies'])
+ elif policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'):
+ # Iterate through all the items of an enum-type policy, and add captions.
+ for item in policy['items']:
+ item['caption'] = self._ImportMessage(item['caption'])
+ if 'supported_on' in item:
+ item['supported_on'] = self._ProcessSupportedOn(item['supported_on'])
+ if not IsGroupOrAtomicGroup(policy):
+ if not 'label' in policy:
+ # If 'label' is not specified, then it defaults to 'caption':
+ policy['label'] = policy['caption']
+ policy['supported_on'] = self._ProcessSupportedOn(
+ policy.get('supported_on', []))
+ policy['future_on'] = self._ProcessFutureOn(policy.get('future_on', []))
+
+ def _ProcessPolicyList(self, policy_list):
+ '''Adds localized message strings to each item in a list of policies and
+ groups. Also breaks up the content of 'supported_on' attributes into lists
+ of dictionaries.
+
+ Args:
+ policy_list: A list of policies and groups. Message strings will be added
+ for each item and to their child items, recursively.
+ '''
+ for policy in policy_list:
+ self._ProcessPolicy(policy)
+
+ def GetTemplateText(self, template_writer):
+ '''Generates the text of the template from the arguments given
+ to the constructor, using a given TemplateWriter.
+
+ Args:
+ template_writer: An object implementing TemplateWriter. Its methods
+ are called here for each item of self._policy_data.
+
+ Returns:
+ The text of the generated template.
+ '''
+ # Create a copy, so that writers can't screw up subsequent writers.
+ policy_data_copy = copy.deepcopy(self._policy_data)
+ return template_writer.WriteTemplate(policy_data_copy)
+
+
+ def _AddGroups(self, policy_list):
+ '''Adds a 'group' field, which is set to be the group's name, to the
+ policies that are part of a group.
+
+ Args:
+ policy_list: A list of policies and groups whose policies will have a
+ 'group' field added.
+ '''
+ groups = [policy for policy in policy_list if policy['type'] == 'group']
+ policy_lookup = {
+ policy['name']: policy
+ for policy in policy_list
+ if not IsGroupOrAtomicGroup(policy)
+ }
+ for group in groups:
+ for policy_name in group['policies']:
+ policy_lookup[policy_name]['group'] = group['name']
+
+ def _AddAtomicGroups(self, policy_list, policy_atomic_groups):
+ '''Adds an 'atomic_group' field to the policies that are part of an atomic
+ group.
+
+ Args:
+ policy_list: A list of policies and groups.
+ policy_atomic_groups: A list of policy atomic groups
+ '''
+ policy_lookup = {
+ policy['name']: policy
+ for policy in policy_list
+ if not IsGroupOrAtomicGroup(policy)
+ }
+ for group in policy_atomic_groups:
+ for policy_name in group['policies']:
+ policy_lookup[policy_name]['atomic_group'] = group['name']
+ break
+
+ def _ExpandAtomicGroups(self, policy_list, policy_atomic_groups):
+ '''Replaces policies names inside atomic group definitions for actual
+ policies definitions.
+
+ Args:
+ policy_list: A list of policies and groups.
+
+ Returns:
+ Modified policy_list
+ '''
+ policies = [
+ policy for policy in policy_list if not IsGroupOrAtomicGroup(policy)
+ ]
+ for group in policy_atomic_groups:
+ group['type'] = 'atomic_group'
+ expanded = self._ExpandGroups(policies + policy_atomic_groups)
+ expanded = [policy for policy in expanded if IsGroupOrAtomicGroup(policy)]
+ return copy.deepcopy(expanded)
+
+ def _ExpandGroups(self, policy_list):
+ '''Replaces policies names inside group definitions for actual policies
+ definitions. If policy does not belong to any group, leave it as is.
+
+ Args:
+ policy_list: A list of policies and groups.
+
+ Returns:
+ Modified policy_list
+ '''
+ groups = [policy for policy in policy_list if IsGroupOrAtomicGroup(policy)]
+ policies = {
+ policy['name']: policy
+ for policy in policy_list
+ if not IsGroupOrAtomicGroup(policy)
+ }
+ policies_in_groups = set()
+ result_policies = []
+ for group in groups:
+ group_policies = group['policies']
+ expanded_policies = [
+ policies[policy_name] for policy_name in group_policies
+ ]
+ assert policies_in_groups.isdisjoint(group_policies)
+ policies_in_groups.update(group_policies)
+ group['policies'] = expanded_policies
+ result_policies.append(group)
+
+ result_policies.extend([
+ policy for policy in policy_list if not IsGroupOrAtomicGroup(policy) and
+ policy['name'] not in policies_in_groups
+ ])
+ return result_policies
diff --git a/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py b/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py
new file mode 100755
index 00000000000..5beb6082a4d
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/policy_template_generator_unittest.py
@@ -0,0 +1,654 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
+
+import unittest
+
+import policy_template_generator
+from writers import mock_writer
+from writers import template_writer
+
+
+class PolicyTemplateGeneratorUnittest(unittest.TestCase):
+ '''Unit tests for policy_template_generator.py.'''
+
+ TEST_CONFIG = {
+ 'app_name': '_app_name',
+ 'frame_name': '_frame_name',
+ 'os_name': '_os_name',
+ }
+
+ TEST_POLICY_DATA = {
+ 'messages': {},
+ 'placeholders': [],
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ }
+
+ def do_test(self, policy_data, writer):
+ '''Executes a test case.
+
+ Creates and invokes an instance of PolicyTemplateGenerator with
+ the given arguments.
+
+ Notice: Plain comments are used in test methods instead of docstrings,
+ so that method names do not get overridden by the docstrings in the
+ test output.
+
+ Args:
+ policy_data: The list of policies and groups as it would be
+ loaded from policy_templates.json.
+ writer: A writer used for this test. It is usually derived from
+ mock_writer.MockWriter.
+ '''
+ writer.tester = self
+
+ policy_data = dict(self.TEST_POLICY_DATA, **policy_data)
+ policy_generator = policy_template_generator.PolicyTemplateGenerator(
+ self.TEST_CONFIG, policy_data)
+ res = policy_generator.GetTemplateText(writer)
+ writer.Test()
+ return res
+
+ def testSequence(self):
+ # Test the sequence of invoking the basic PolicyWriter operations,
+ # in case of empty input data structures.
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self):
+ self.log = 'init;'
+
+ def Init(self):
+ self.log += 'prepare;'
+
+ def BeginTemplate(self):
+ self.log += 'begin;'
+
+ def EndTemplate(self):
+ self.log += 'end;'
+
+ def GetTemplateText(self):
+ self.log += 'get_text;'
+ return 'writer_result_string'
+
+ def Test(self):
+ self.tester.assertEquals(self.log, 'init;prepare;begin;end;get_text;')
+
+ result = self.do_test({}, LocalMockWriter())
+ self.assertEquals(result, 'writer_result_string')
+
+ def testEmptyGroups(self):
+ # Test that empty policy groups are not passed to the writer.
+ policies_mock = {
+ 'policy_definitions': [
+ {
+ 'name': 'Group1',
+ 'type': 'group',
+ 'policies': [],
+ 'desc': '',
+ 'caption': ''
+ },
+ {
+ 'name': 'Group2',
+ 'type': 'group',
+ 'policies': [],
+ 'desc': '',
+ 'caption': ''
+ },
+ {
+ 'name': 'Group3',
+ 'type': 'group',
+ 'policies': [],
+ 'desc': '',
+ 'caption': ''
+ },
+ ]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self):
+ self.log = ''
+
+ def BeginPolicyGroup(self, group):
+ self.log += '['
+
+ def EndPolicyGroup(self):
+ self.log += ']'
+
+ def Test(self):
+ self.tester.assertEquals(self.log, '')
+
+ self.do_test(policies_mock, LocalMockWriter())
+
+ def testGroups(self):
+ # Test that policy groups are passed to the writer in the correct order.
+ policies_mock = {
+ 'policy_definitions': [
+ {
+ 'name': 'Group1',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['TAG1'],
+ },
+ {
+ 'name': 'Group2',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['TAG2',],
+ },
+ {
+ 'name': 'Group3',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['TAG3'],
+ },
+ {
+ 'name': 'TAG1',
+ 'type': 'mock',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ {
+ 'name': 'TAG2',
+ 'type': 'mock',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ {
+ 'name': 'TAG3',
+ 'type': 'mock',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ ]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self):
+ self.log = ''
+
+ def BeginPolicyGroup(self, group):
+ self.log += '[' + group['policies'][0]['name']
+
+ def EndPolicyGroup(self):
+ self.log += ']'
+
+ def Test(self):
+ self.tester.assertEquals(self.log, '[TAG1][TAG2][TAG3]')
+
+ self.do_test(policies_mock, LocalMockWriter())
+
+ def testPolicies(self):
+ # Test that policies are passed to the writer in the correct order.
+ policy_defs_mock = {
+ 'policy_definitions': [
+ {
+ 'name': 'Group1',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['Group1Policy1', 'Group1Policy2'],
+ },
+ {
+ 'name': 'Group2',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['Group2Policy3'],
+ },
+ {
+ 'name': 'Group1Policy1',
+ 'type': 'string',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ {
+ 'name': 'Group1Policy2',
+ 'type': 'string',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ {
+ 'name': 'Group2Policy3',
+ 'type': 'string',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ ]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self):
+ self.policy_name = None
+ self.policy_list = []
+
+ def BeginPolicyGroup(self, group):
+ self.group = group
+
+ def EndPolicyGroup(self):
+ self.group = None
+
+ def WritePolicy(self, policy):
+ self.tester.assertEquals(policy['name'][0:6], self.group['name'])
+ self.policy_list.append(policy['name'])
+
+ def Test(self):
+ self.tester.assertEquals(
+ self.policy_list,
+ ['Group1Policy1', 'Group1Policy2', 'Group2Policy3'])
+
+ self.do_test(policy_defs_mock, LocalMockWriter())
+
+ def testIntEnumTexts(self):
+ # Test that GUI messages are assigned correctly to int-enums
+ # (aka dropdown menus).
+ policy_defs_mock = {
+ 'policy_definitions': [{
+ 'name':
+ 'Policy1',
+ 'type':
+ 'int-enum',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': [],
+ 'items': [
+ {
+ 'name': 'item1',
+ 'value': 0,
+ 'caption': 'string1',
+ 'desc': ''
+ },
+ {
+ 'name': 'item2',
+ 'value': 1,
+ 'caption': 'string2',
+ 'desc': ''
+ },
+ {
+ 'name': 'item3',
+ 'value': 3,
+ 'caption': 'string3',
+ 'desc': ''
+ },
+ ]
+ }]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def WritePolicy(self, policy):
+ self.tester.assertEquals(policy['items'][0]['caption'], 'string1')
+ self.tester.assertEquals(policy['items'][1]['caption'], 'string2')
+ self.tester.assertEquals(policy['items'][2]['caption'], 'string3')
+
+ self.do_test(policy_defs_mock, LocalMockWriter())
+
+ def testStringEnumTexts(self):
+ # Test that GUI messages are assigned correctly to string-enums
+ # (aka dropdown menus).
+ policy_data_mock = {
+ 'policy_definitions': [{
+ 'name':
+ 'Policy1',
+ 'type':
+ 'string-enum',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': [],
+ 'items': [
+ {
+ 'name': 'item1',
+ 'value': 'one',
+ 'caption': 'string1',
+ 'desc': ''
+ },
+ {
+ 'name': 'item2',
+ 'value': 'two',
+ 'caption': 'string2',
+ 'desc': ''
+ },
+ {
+ 'name': 'item3',
+ 'value': 'three',
+ 'caption': 'string3',
+ 'desc': ''
+ },
+ ]
+ }]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def WritePolicy(self, policy):
+ self.tester.assertEquals(policy['items'][0]['caption'], 'string1')
+ self.tester.assertEquals(policy['items'][1]['caption'], 'string2')
+ self.tester.assertEquals(policy['items'][2]['caption'], 'string3')
+
+ self.do_test(policy_data_mock, LocalMockWriter())
+
+ def testStringEnumTexts(self):
+ # Test that GUI messages are assigned correctly to string-enums
+ # (aka dropdown menus).
+ policy_data_mock = {
+ 'policy_definitions': [{
+ 'name':
+ 'Policy1',
+ 'type':
+ 'string-enum-list',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': [],
+ 'items': [
+ {
+ 'name': 'item1',
+ 'value': 'one',
+ 'caption': 'string1',
+ 'desc': ''
+ },
+ {
+ 'name': 'item2',
+ 'value': 'two',
+ 'caption': 'string2',
+ 'desc': ''
+ },
+ {
+ 'name': 'item3',
+ 'value': 'three',
+ 'caption': 'string3',
+ 'desc': ''
+ },
+ ]
+ }]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def WritePolicy(self, policy):
+ self.tester.assertEquals(policy['items'][0]['caption'], 'string1')
+ self.tester.assertEquals(policy['items'][1]['caption'], 'string2')
+ self.tester.assertEquals(policy['items'][2]['caption'], 'string3')
+
+ self.do_test(policy_data_mock, LocalMockWriter())
+
+ def testWin7OnlyPolicy(self):
+ # Test that Win7 only policy is marked as windows policy with speicial flag.
+ policy_data_mock = {
+ 'policy_definitions': [{
+ 'name':
+ 'Policy1',
+ 'type':
+ 'string-enum-list',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': ['chrome.win7:2-'],
+ 'items': [{
+ 'name': 'item1',
+ 'value': 'one',
+ 'caption': 'string1',
+ 'desc': '',
+ 'supported_on': ['chrome.win7:2-'],
+ },]
+ }]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def WritePolicy(self, policy):
+ self.tester.assertEquals(policy['supported_on'][0]['platform'], 'win7')
+ self.tester.assertEquals(
+ policy['items'][0]['supported_on'][0]['platform'], 'win7')
+
+ self.do_test(policy_data_mock, LocalMockWriter())
+
+ def testFutures(self):
+ # Test that 'future_on' tag has been processed successfully.
+ policy_data_mock = {
+ 'policy_definitions': [{
+ 'name': 'UnrelasedPolicy',
+ 'type': 'string',
+ 'caption': '',
+ 'desc': '',
+ 'future_on': ['chrome.*', 'chrome_os']
+ }, {
+ 'name':
+ 'PartiallyReleasedPolicy',
+ 'type':
+ 'string',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': ['chrome.win:2-', 'chrome.mac:2-', 'chrome_os:4-'],
+ 'future_on': ['chrome.linux', 'chrome_os'],
+ }, {
+ 'name': 'ReleasedPolicy',
+ 'type': 'string',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome.*:2-', 'chrome_os:4-'],
+ }]
+ }
+
+ expected_future_on = {
+ 'UnrelasedPolicy': [{
+ 'product': 'chrome',
+ 'platform': 'linux'
+ }, {
+ 'product': 'chrome',
+ 'platform': 'mac'
+ }, {
+ 'product': 'chrome',
+ 'platform': 'win'
+ }, {
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os'
+ }],
+ 'PartiallyReleasedPolicy': [{
+ 'product': 'chrome',
+ 'platform': 'linux'
+ }, {
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os'
+ }],
+ 'ReleasedPolicy': [],
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+ def WritePolicy(self, policy):
+ self.tester.assertTrue(isinstance(policy['supported_on'], list))
+ self.tester.assertEquals(policy['future_on'],
+ expected_future_on[policy['name']])
+
+ self.do_test(policy_data_mock, LocalMockWriter())
+
+ def testPolicyFiltering(self):
+ # Test that policies are filtered correctly based on their annotations.
+ policy_data_mock = {
+ 'policy_definitions': [
+ {
+ 'name': 'Group1',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['Group1Policy1', 'Group1Policy2'],
+ },
+ {
+ 'name': 'Group2',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['Group2Policy3'],
+ },
+ {
+ 'name': 'SinglePolicy',
+ 'type': 'int',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome.eee:8-']
+ },
+ {
+ 'name':
+ 'Group1Policy1',
+ 'type':
+ 'string',
+ 'caption':
+ '',
+ 'desc':
+ '',
+ 'supported_on': [
+ 'chrome.aaa:8-', 'chrome.bbb:8-', 'chrome.ccc:8-'
+ ]
+ },
+ {
+ 'name': 'Group1Policy2',
+ 'type': 'string',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome.ddd:8-']
+ },
+ {
+ 'name': 'Group2Policy3',
+ 'type': 'string',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome.eee:8-']
+ },
+ ]
+ }
+
+ # This writer accumulates the list of policies it is asked to write.
+ # This list is stored in the result_list member variable and can
+ # be used later for assertions.
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self, platforms):
+ super(LocalMockWriter, self).__init__(platforms)
+ self.policy_name = None
+ self.result_list = []
+
+ def BeginPolicyGroup(self, group):
+ self.group = group
+ self.result_list.append('begin_' + group['name'])
+
+ def EndPolicyGroup(self):
+ self.result_list.append('end_group')
+ self.group = None
+
+ def WritePolicy(self, policy):
+ self.result_list.append(policy['name'])
+
+ def IsPolicySupported(self, policy):
+ # Call the original (non-mock) implementation of this method.
+ return template_writer.TemplateWriter.IsPolicySupported(self, policy)
+
+ local_mock_writer = LocalMockWriter(['eee'])
+ self.do_test(policy_data_mock, local_mock_writer)
+ # Test that only policies of platform 'eee' were written:
+ self.assertEquals(
+ local_mock_writer.result_list,
+ ['begin_Group2', 'Group2Policy3', 'end_group', 'SinglePolicy'])
+
+ local_mock_writer = LocalMockWriter(['ddd', 'bbb'])
+ self.do_test(policy_data_mock, local_mock_writer)
+ # Test that only policies of platforms 'ddd' and 'bbb' were written:
+ self.assertEquals(
+ local_mock_writer.result_list,
+ ['begin_Group1', 'Group1Policy1', 'Group1Policy2', 'end_group'])
+
+ def testSortingInvoked(self):
+ # Tests that policy-sorting happens before passing policies to the writer.
+ policy_data = {
+ 'policy_definitions': [
+ {
+ 'name': 'zp',
+ 'type': 'string',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ {
+ 'name': 'ap',
+ 'type': 'string',
+ 'supported_on': [],
+ 'caption': '',
+ 'desc': ''
+ },
+ ]
+ }
+
+ class LocalMockWriter(mock_writer.MockWriter):
+
+ def __init__(self):
+ self.result_list = []
+
+ def WritePolicy(self, policy):
+ self.result_list.append(policy['name'])
+
+ def Test(self):
+ self.tester.assertEquals(self.result_list, ['ap', 'zp'])
+
+ self.do_test(policy_data, LocalMockWriter())
+
+ def testImportMessage_noIndentation(self):
+ message = '''
+Simple policy:
+
+Description of simple policy'''
+
+ policy_generator = policy_template_generator.PolicyTemplateGenerator(
+ self.TEST_CONFIG, self.TEST_POLICY_DATA)
+ self.assertEquals(message, policy_generator._ImportMessage(message))
+
+ def testImportMessage_withIndentation(self):
+ message = '''JSON policy:
+
+ JSON spec:
+ {
+ "key": {
+ "key2": "value"
+ }
+ }'''
+ imported_message = '''JSON policy:
+
+JSON spec:
+{
+ "key": {
+ "key2": "value"
+ }
+}'''
+
+ policy_generator = policy_template_generator.PolicyTemplateGenerator(
+ self.TEST_CONFIG, self.TEST_POLICY_DATA)
+ self.assertEquals(imported_message,
+ policy_generator._ImportMessage(message))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/template_formatter.py b/chromium/components/policy/tools/template_writers/template_formatter.py
new file mode 100755
index 00000000000..9af88164b66
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/template_formatter.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Takes translated policy_template.json files as input, applies template
+writers and emits various template and doc files (admx, html, json etc.).
+'''
+
+import argparse
+import codecs
+import collections
+import json
+import os
+import re
+import sys
+
+sys.path.insert(
+ 0,
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+ os.pardir, 'third_party', 'six', 'src'))
+
+import six
+
+import writer_configuration
+import policy_template_generator
+
+from writers import adm_writer, adml_writer, admx_writer, \
+ chromeos_admx_writer, chromeos_adml_writer, \
+ google_admx_writer, google_adml_writer, \
+ android_policy_writer, reg_writer, doc_writer, \
+ doc_atomic_groups_writer , json_writer, plist_writer, \
+ plist_strings_writer, ios_app_config_writer, jamf_writer
+
+
+def MacLanguageMap(lang):
+ '''Handles slightly different path naming convention for Macs:
+ - 'en-US' -> 'en'
+ - '-' -> '_'
+
+ Args:
+ lang: Language, e.g. 'en-US'.
+ '''
+ return 'en' if lang == 'en-US' else lang.replace('-', '_')
+
+
+'''Template writer descriptors.
+
+Members:
+ type: Writer type, e.g. 'admx'
+ is_per_language: Whether one file per language should be emitted.
+ encoding: Encoding of the output file.
+ language_map: Optional language mapping for file paths.
+ force_windows_line_ending: Forces output file to use Windows line ending.
+'''
+WriterDesc = collections.namedtuple('WriterDesc', [
+ 'type', 'is_per_language', 'encoding', 'language_map',
+ 'force_windows_line_ending'
+])
+
+_WRITER_DESCS = [
+ WriterDesc('adm', True, 'utf-16', None, True),
+ WriterDesc('adml', True, 'utf-16', None, True),
+ WriterDesc('admx', False, 'utf-16', None, True),
+ WriterDesc('google_adml', True, 'utf-8', None, True),
+ WriterDesc('google_admx', False, 'utf-8', None, True),
+ WriterDesc('chromeos_adml', True, 'utf-8', None, True),
+ WriterDesc('chromeos_admx', False, 'utf-8', None, True),
+ WriterDesc('android_policy', False, 'utf-8', None, False),
+ WriterDesc('reg', False, 'utf-16', None, False),
+ WriterDesc('doc', True, 'utf-8', None, False),
+ WriterDesc('doc_atomic_groups', True, 'utf-8', None, False),
+ WriterDesc('json', False, 'utf-8', None, False),
+ WriterDesc('plist', False, 'utf-8', None, False),
+ WriterDesc('plist_strings', True, 'utf-8', MacLanguageMap, False),
+ WriterDesc('jamf', False, 'utf-8', None, False),
+ WriterDesc('ios_app_config', False, 'utf-8', None, False),
+]
+
+# Template writers that are not per-language use policy_templates.json from
+# this language.
+_DEFAULT_LANGUAGE = 'en-US'
+
+
+def GetWriter(writer_type, config):
+ '''Returns the template writer for the given writer type.
+
+ Args:
+ writer_type: Writer type, e.g. 'admx'.
+ config: Writer configuration, see writer_configuration.py.
+ '''
+ return eval(writer_type + '_writer.GetWriter(config)')
+
+
+def _GetWriterConfiguration(grit_defines):
+ '''Returns the writer configuration based on grit defines.
+
+ Args:
+ grit_defines: Array of grit defines, see grit_rule.gni.
+ '''
+ # Build a dictionary from grit defines, which can be plain DEFs or KEY=VALUEs.
+ grit_defines_dict = {}
+ for define in grit_defines:
+ parts = define.split('=', 1)
+ grit_defines_dict[parts[0]] = parts[1] if len(parts) > 1 else 1
+ return writer_configuration.GetConfigurationForBuild(grit_defines_dict)
+
+
+def _ParseVersionFile(version_path):
+ '''Parse version file, return the version if it exists.
+
+ Args:
+ version_path: The path of Chrome VERSION file containing the major version
+ number.
+ '''
+ version = {}
+ with open(version_path) as fp:
+ for line in fp:
+ key, _, value = line.partition('=')
+ if key.strip() == 'MAJOR':
+ version['major'] = value.strip()
+ elif key.strip() == 'MINOR':
+ version['minor'] = value.strip()
+ elif key.strip() == 'BUILD':
+ version['build'] = value.strip()
+ elif key.strip() == 'PATCH':
+ version['patch'] = value.strip()
+
+ version_found = len(version) == 4
+ return version if version_found else None
+
+
+def _JsonToUtf8Encoding(data, ignore_dicts=False):
+ if six.PY2 and isinstance(data, unicode):
+ return data.encode('utf-8')
+ elif isinstance(data, list):
+ return [_JsonToUtf8Encoding(item, False) for item in data]
+ elif isinstance(data, dict):
+ return {
+ _JsonToUtf8Encoding(key): _JsonToUtf8Encoding(value)
+ for key, value in data.items()
+ }
+ return data
+
+
+def main():
+ '''Main policy template conversion script.
+ Usage: template_formatter
+ --translations <translations_path>
+ --languages <language_list>
+ [--adm <adm_path>]
+ ...
+ [--android_policy <android_policy_path>]
+ -D <grit_define>
+ -E <grit_env_variable>
+ -t <grit_target_platform>
+
+ Args:
+ translations: Absolute path of the translated policy_template.json
+ files. Must contain a ${lang} placeholder for the language.
+ languages: Comma-separated list of languages. Trailing commas are fine, e.g.
+ 'en,de,'
+ adm, adml, google_adml, doc, plist_string: Absolute path of the
+ corresponding file types. Must contain a ${lang} placeholder.
+ admx, google_admx, android_policy, reg, json, plist: Absolute path of the
+ corresponding file types. Must NOT contain a ${lang} placeholder. There
+ is only one output file, not one per language.
+ D: List of grit defines, used to assemble writer configuration.
+ E, t: Grit environment variables and target platform. Unused, but
+ grit_rule.gni adds them, so ArgumentParser must handle them.
+ '''
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--translations', dest='translations')
+ parser.add_argument('--languages', dest='languages')
+ parser.add_argument('--version_path', dest='version_path')
+ parser.add_argument('--adm', action='append', dest='adm')
+ parser.add_argument('--adml', action='append', dest='adml')
+ parser.add_argument('--admx', action='append', dest='admx')
+ parser.add_argument('--chromeos_adml', action='append', dest='chromeos_adml')
+ parser.add_argument('--chromeos_admx', action='append', dest='chromeos_admx')
+ parser.add_argument('--google_adml', action='append', dest='google_adml')
+ parser.add_argument('--google_admx', action='append', dest='google_admx')
+ parser.add_argument('--reg', action='append', dest='reg')
+ parser.add_argument('--doc', action='append', dest='doc')
+ parser.add_argument(
+ '--doc_atomic_groups', action='append', dest='doc_atomic_groups')
+ parser.add_argument('--local',
+ action='store_true',
+ help='If set, the documentation will be built so '
+ 'that links work locally in the generated path.')
+ parser.add_argument('--json', action='append', dest='json')
+ parser.add_argument('--plist', action='append', dest='plist')
+ parser.add_argument('--plist_strings', action='append', dest='plist_strings')
+ parser.add_argument('--jamf', action='append', dest='jamf')
+ parser.add_argument(
+ '--android_policy', action='append', dest='android_policy')
+ parser.add_argument(
+ '--ios_app_config', action='append', dest='ios_app_config')
+ parser.add_argument('-D', action='append', dest='grit_defines')
+ parser.add_argument('-E', action='append', dest='grit_build_env')
+ parser.add_argument('-t', action='append', dest='grit_target')
+ args = parser.parse_args()
+
+ _LANG_PLACEHOLDER = "${lang}"
+ assert _LANG_PLACEHOLDER in args.translations
+
+ languages = list(filter(bool, args.languages.split(',')))
+ assert _DEFAULT_LANGUAGE in languages
+
+ config = _GetWriterConfiguration(args.grit_defines)
+
+ version = _ParseVersionFile(args.version_path)
+ if version != None:
+ config['major_version'] = int(version['major'])
+ config['version'] = '.'.join([
+ version['major'], version['minor'], version['build'], version['patch']
+ ])
+ config['local'] = args.local
+
+ # For each language, load policy data once and run all writers on it.
+ for lang in languages:
+ # Load the policy data.
+ policy_templates_json_path = args.translations.replace(
+ _LANG_PLACEHOLDER, lang)
+ # Loads the localized policy json file which must be a valid json file
+ # encoded in utf-8.
+ with codecs.open(policy_templates_json_path, 'r', 'utf-8') as policy_file:
+ policy_data = json.loads(
+ policy_file.read(), object_hook=_JsonToUtf8Encoding)
+
+ # Preprocess the policy data.
+ policy_generator = policy_template_generator.PolicyTemplateGenerator(
+ config, policy_data)
+
+ for writer_desc in _WRITER_DESCS:
+ # For writer types that are not per language (e.g. admx), only do it once.
+ if (not writer_desc.is_per_language and lang != _DEFAULT_LANGUAGE):
+ continue
+
+ # Was the current writer type passed as argument, e.g. --admx <path>?
+ # Note that all paths are arrays and we loop over all of them.
+ output_paths = getattr(args, writer_desc.type, '')
+ if (not output_paths):
+ continue
+ for output_path in output_paths:
+ # Substitute language placeholder in output file.
+ if (writer_desc.is_per_language):
+ assert _LANG_PLACEHOLDER in output_path
+ mapped_lang = writer_desc.language_map(
+ lang) if writer_desc.language_map else lang
+ output_path = output_path.replace(_LANG_PLACEHOLDER, mapped_lang)
+ else:
+ assert _LANG_PLACEHOLDER not in output_path
+
+ # Run the template writer on th policy data.
+ writer = GetWriter(writer_desc.type, config)
+ output_data = policy_generator.GetTemplateText(writer)
+ # Make sure the file uses Windows line endings if needed. This is
+ # important here because codecs.open() opens files in binary more and
+ # will not do line ending conversion.
+ if writer_desc.force_windows_line_ending:
+ output_data = re.sub(r'([^\r])\n', r'\1\r\n', output_data)
+
+ # Make output directory if it doesn't exist yet.
+ output_dir = os.path.split(output_path)[0]
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+
+ # Write output file.
+ with codecs.open(output_path, 'w', writer_desc.encoding) as output_file:
+ output_file.write(output_data)
+
+
+if '__main__' == __name__:
+ sys.exit(main())
diff --git a/chromium/components/policy/tools/template_writers/test_suite_all.py b/chromium/components/policy/tools/template_writers/test_suite_all.py
new file mode 100755
index 00000000000..8ca43de4571
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/test_suite_all.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit test suite that collects template_writer tests.'''
+
+import os
+import sys
+import unittest
+
+
+class TestSuiteAll(unittest.TestSuite):
+
+ def __init__(self):
+ super(TestSuiteAll, self).__init__()
+ # Imports placed here to prevent circular imports.
+ # pylint: disable-msg=C6204
+ import policy_template_generator_unittest
+ import writers.adm_writer_unittest
+ import writers.adml_writer_unittest
+ import writers.admx_writer_unittest
+ import writers.android_policy_writer_unittest
+ import writers.chromeos_adml_writer_unittest
+ import writers.chromeos_admx_writer_unittest
+ import writers.doc_writer_unittest
+ import writers.google_adml_writer_unittest
+ import writers.google_admx_writer_unittest
+ import writers.ios_app_config_writer_unittest
+ import writers.jamf_writer_unittest
+ import writers.json_writer_unittest
+ import writers.plist_strings_writer_unittest
+ import writers.plist_writer_unittest
+ import writers.reg_writer_unittest
+ import writers.template_writer_unittest
+ import writers.xml_writer_base_unittest
+
+ test_classes = [
+ policy_template_generator_unittest.PolicyTemplateGeneratorUnittest,
+ writers.adm_writer_unittest.AdmWriterUnittest,
+ writers.adml_writer_unittest.AdmlWriterUnittest,
+ writers.admx_writer_unittest.AdmxWriterUnittest,
+ writers.android_policy_writer_unittest.AndroidPolicyWriterUnittest,
+ writers.chromeos_adml_writer_unittest.ChromeOsAdmlWriterUnittest,
+ writers.chromeos_admx_writer_unittest.ChromeOsAdmxWriterUnittest,
+ writers.doc_writer_unittest.DocWriterUnittest,
+ writers.google_adml_writer_unittest.GoogleAdmlWriterUnittest,
+ writers.google_admx_writer_unittest.GoogleAdmxWriterUnittest,
+ writers.ios_app_config_writer_unittest.IOSAppConfigWriterUnitTests,
+ writers.jamf_writer_unittest.JamfWriterUnitTests,
+ writers.json_writer_unittest.JsonWriterUnittest,
+ writers.plist_strings_writer_unittest.PListStringsWriterUnittest,
+ writers.plist_writer_unittest.PListWriterUnittest,
+ writers.reg_writer_unittest.RegWriterUnittest,
+ writers.template_writer_unittest.TemplateWriterUnittests,
+ writers.xml_writer_base_unittest.XmlWriterBaseTest,
+ # add test classes here, in alphabetical order...
+ ]
+
+ for test_class in test_classes:
+ self.addTest(unittest.makeSuite(test_class))
+
+
+if __name__ == '__main__':
+ test_result = unittest.TextTestRunner(verbosity=2).run(TestSuiteAll())
+ sys.exit(len(test_result.errors) + len(test_result.failures))
diff --git a/chromium/components/policy/tools/template_writers/writer_configuration.py b/chromium/components/policy/tools/template_writers/writer_configuration.py
new file mode 100755
index 00000000000..43cb02dde07
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writer_configuration.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+def GetConfigurationForBuild(defines):
+ '''Returns a configuration dictionary for the given build that contains
+ build-specific settings and information.
+
+ Args:
+ defines: Definitions coming from the build system.
+
+ Raises:
+ Exception: If 'defines' contains an unknown build-type.
+ '''
+ # The prefix of key names in config determines which writer will use their
+ # corresponding values:
+ # win: Both ADM and ADMX.
+ # mac: Only plist.
+ # admx: Only ADMX.
+ # adm: Only ADM.
+ # none/other: Used by all the writers.
+ # Google:Cat_Google references the external google.admx file.
+ # category_path_strings strings in curly braces are looked up from localized
+ # 'messages' in policy_templates.json.
+ if '_chromium' in defines:
+ config = {
+ 'build': 'chromium',
+ 'app_name': 'Chromium',
+ 'doc_url': 'https://chromeenterprise.google/policies/',
+ 'frame_name': 'Chromium Frame',
+ 'os_name': 'ChromiumOS',
+ 'webview_name': 'Chromium WebView',
+ 'win_config': {
+ 'win': {
+ 'reg_mandatory_key_name': 'Software\\Policies\\Chromium',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\Chromium\\Recommended',
+ 'mandatory_category_path': ['chromium'],
+ 'recommended_category_path': ['chromium_recommended'],
+ 'category_path_strings': {
+ 'chromium': 'Chromium',
+ 'chromium_recommended': 'Chromium - {doc_recommended}',
+ },
+ 'namespace': 'Chromium.Policies.Chromium',
+ },
+ 'chrome_os': {
+ 'reg_mandatory_key_name': 'Software\\Policies\\ChromiumOS',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\ChromiumOS\\Recommended',
+ 'mandatory_category_path': ['chromium_os'],
+ 'recommended_category_path': ['chromium_os_recommended'],
+ 'category_path_strings': {
+ 'chromium_os': 'ChromiumOS',
+ 'chromium_os_recommended': 'ChromiumOS - {doc_recommended}',
+ },
+ 'namespace': 'Chromium.Policies.ChromiumOS'
+ },
+ },
+ 'admx_prefix': 'chromium',
+ 'linux_policy_path': '/etc/chromium/policies/',
+ 'bundle_id': 'org.chromium',
+ }
+ elif '_google_chrome' in defines:
+ config = {
+ 'build': 'chrome',
+ 'app_name': 'Google Chrome',
+ 'doc_url': 'https://chromeenterprise.google/policies/',
+ 'frame_name': 'Google Chrome Frame',
+ 'os_name': 'Google ChromeOS',
+ 'webview_name': 'Android System WebView',
+ 'win_config': {
+ 'win': {
+ 'reg_mandatory_key_name':
+ 'Software\\Policies\\Google\\Chrome',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\Google\\Chrome\\Recommended',
+ 'mandatory_category_path':
+ ['Google:Cat_Google', 'googlechrome'],
+ 'recommended_category_path':
+ ['Google:Cat_Google', 'googlechrome_recommended'],
+ 'category_path_strings': {
+ 'googlechrome': 'Google Chrome',
+ 'googlechrome_recommended':
+ 'Google Chrome - {doc_recommended}'
+ },
+ 'namespace':
+ 'Google.Policies.Chrome',
+ },
+ 'chrome_os': {
+ 'reg_mandatory_key_name':
+ 'Software\\Policies\\Google\\ChromeOS',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\Google\\ChromeOS\\Recommended',
+ 'mandatory_category_path':
+ ['Google:Cat_Google', 'googlechromeos'],
+ 'recommended_category_path':
+ ['Google:Cat_Google', 'googlechromeos_recommended'],
+ 'category_path_strings': {
+ 'googlechromeos':
+ 'Google ChromeOS',
+ 'googlechromeos_recommended':
+ 'Google ChromeOS - {doc_recommended}'
+ },
+ 'namespace':
+ 'Google.Policies.ChromeOS',
+ },
+ },
+ # The string 'Google' is defined in google.adml for ADMX, but ADM
+ # doesn't support external references, so we define this map here.
+ 'adm_category_path_strings': {
+ 'Google:Cat_Google': 'Google'
+ },
+ 'admx_prefix': 'chrome',
+ 'admx_using_namespaces': {
+ 'Google': 'Google.Policies' # prefix: namespace
+ },
+ 'linux_policy_path': '/etc/opt/chrome/policies/',
+ 'bundle_id': 'com.google.chrome.ios',
+ }
+ else:
+ raise Exception('Unknown build')
+ if 'version' in defines:
+ config['version'] = defines['version']
+ if 'major_version' in defines:
+ config['major_version'] = defines['major_version']
+ config['win_supported_os'] = 'SUPPORTED_WIN7'
+ config['win_supported_os_win7'] = 'SUPPORTED_WIN7_ONLY'
+ if 'mac_bundle_id' in defines:
+ config['mac_bundle_id'] = defines['mac_bundle_id']
+ config['android_webview_restriction_prefix'] = 'com.android.browser:'
+ return config
diff --git a/chromium/components/policy/tools/template_writers/writers/__init__.py b/chromium/components/policy/tools/template_writers/writers/__init__.py
new file mode 100755
index 00000000000..fb050d1f24f
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/__init__.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Module template_writers.writers
+'''
+
+pass
diff --git a/chromium/components/policy/tools/template_writers/writers/adm_writer.py b/chromium/components/policy/tools/template_writers/writers/adm_writer.py
new file mode 100755
index 00000000000..7c20a5a8e78
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/adm_writer.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import gpo_editor_writer
+import re
+
+NEWLINE = '\r\n'
+POLICY_LIST_URL = '''https://cloud.google.com/docs/chrome-enterprise/policies/?policy='''
+
+
+def GetWriter(config):
+ '''Factory method for creating AdmWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return AdmWriter(['win', 'win7'], config)
+
+
+class IndentedStringBuilder:
+ '''Utility class for building text with indented lines.'''
+
+ def __init__(self):
+ self.lines = []
+ self.indent = ''
+
+ def AddLine(self, string='', indent_diff=0):
+ '''Appends a string with indentation and a linebreak to |self.lines|.
+
+ Args:
+ string: The string to print.
+ indent_diff: the difference of indentation of the printed line,
+ compared to the next/previous printed line. Increment occurs
+ after printing the line, while decrement occurs before that.
+ '''
+ indent_diff *= 2
+ if indent_diff < 0:
+ self.indent = self.indent[(-indent_diff):]
+ if string != '':
+ self.lines.append(self.indent + string)
+ else:
+ self.lines.append('')
+ if indent_diff > 0:
+ self.indent += ''.ljust(indent_diff)
+
+ def AddLines(self, other):
+ '''Appends the content of another |IndentedStringBuilder| to |self.lines|.
+ Indentation of the added lines will be the sum of |self.indent| and
+ their original indentation.
+
+ Args:
+ other: The buffer from which lines are copied.
+ '''
+ for line in other.lines:
+ self.AddLine(line)
+
+ def ToString(self):
+ '''Returns |self.lines| as text string.'''
+ return NEWLINE.join(self.lines)
+
+
+class AdmWriter(gpo_editor_writer.GpoEditorWriter):
+ '''Class for generating policy templates in Windows ADM format.
+ It is used by PolicyTemplateGenerator to write ADM files.
+ '''
+
+ TYPE_TO_INPUT = {
+ 'string': 'EDITTEXT',
+ 'int': 'NUMERIC',
+ 'string-enum': 'DROPDOWNLIST',
+ 'int-enum': 'DROPDOWNLIST',
+ 'list': 'LISTBOX',
+ 'string-enum-list': 'LISTBOX',
+ 'dict': 'EDITTEXT',
+ 'external': 'EDITTEXT'
+ }
+
+ def _Escape(self, string):
+ return string.replace('.', '_')
+
+ def _AddGuiString(self, name, value):
+ # The |name| must be escaped.
+ assert name == self._Escape(name)
+ # Escape newlines in the value.
+ value = value.replace('\n', '\\n')
+ if name in self.strings_seen:
+ err = ('%s was added as "%s" and now added again as "%s"' %
+ (name, self.strings_seen[name], value))
+ assert value == self.strings_seen[name], err
+ else:
+ self.strings_seen[name] = value
+ line = '%s="%s"' % (name, value)
+ self.strings.AddLine(line)
+
+ def _WriteSupported(self, builder, is_win7_only):
+ builder.AddLine('#if version >= 4', 1)
+ key = 'win_supported_os_win7' if is_win7_only else 'win_supported_os'
+ supported_on_text = self.config[key]
+ builder.AddLine('SUPPORTED !!' + supported_on_text)
+ builder.AddLine('#endif', -1)
+
+ def _WritePart(self, policy, key_name, builder):
+ '''Writes the PART ... END PART section of a policy.
+
+ Args:
+ policy: The policy to write to the output.
+ key_name: The registry key backing the policy.
+ builder: Builder to append lines to.
+ '''
+ policy_part_name = self._Escape(policy['name'] + '_Part')
+ self._AddGuiString(policy_part_name, policy['label'])
+
+ # Print the PART ... END PART section:
+ builder.AddLine()
+ adm_type = self.TYPE_TO_INPUT[policy['type']]
+ builder.AddLine('PART !!%s %s' % (policy_part_name, adm_type), 1)
+ if policy['type'] in ('list', 'string-enum-list'):
+ # Note that the following line causes FullArmor ADMX Migrator to create
+ # corrupt ADMX files. Please use admx_writer to get ADMX files.
+ builder.AddLine('KEYNAME "%s\\%s"' % (key_name, policy['name']))
+ builder.AddLine('VALUEPREFIX ""')
+ else:
+ builder.AddLine('VALUENAME "%s"' % policy['name'])
+ if policy['type'] == 'int':
+ # The default max for NUMERIC values is 9999 which is too small for us.
+ max = 2000000000
+ min = 0
+ if self.PolicyHasRestrictions(policy):
+ schema = policy['schema']
+ if 'minimum' in schema:
+ min = schema['minimum']
+ if 'maximum' in schema:
+ max = schema['maximum']
+ builder.AddLine('MIN %d MAX %d' % (min, max))
+ if policy['type'] in ('string', 'dict', 'external'):
+ # The default max for EDITTEXT values is 1023, which is too small for
+ # big JSON blobs and other string policies.
+ builder.AddLine('MAXLEN 1000000')
+ if policy['type'] in ('int-enum', 'string-enum'):
+ builder.AddLine('ITEMLIST', 1)
+ for item in policy['items']:
+ if policy['type'] == 'int-enum':
+ value_text = 'NUMERIC ' + str(item['value'])
+ else:
+ value_text = '"' + item['value'] + '"'
+ string_id = self._Escape(policy['name'] + '_' + item['name'] +
+ '_DropDown')
+ builder.AddLine('NAME !!%s VALUE %s' % (string_id, value_text))
+ self._AddGuiString(string_id, item['caption'])
+ builder.AddLine('END ITEMLIST', -1)
+ builder.AddLine('END PART', -1)
+
+ def PolicyHasRestrictions(self, policy):
+ if 'schema' in policy:
+ return any(keyword in policy['schema'] \
+ for keyword in ['minimum', 'maximum'])
+ return False
+
+ def _WritePolicy(self, policy, key_name, builder):
+ policy_name = self._Escape(policy['name'] + '_Policy')
+ self._AddGuiString(policy_name, policy['caption'])
+ builder.AddLine('POLICY !!%s' % policy_name, 1)
+ self._WriteSupported(builder, self.IsPolicyOnWin7Only(policy))
+ policy_explain_name = self._Escape(policy['name'] + '_Explain')
+ policy_explain = self._GetPolicyExplanation(policy)
+ self._AddGuiString(policy_explain_name, policy_explain)
+ builder.AddLine('EXPLAIN !!' + policy_explain_name)
+
+ if policy['type'] == 'main':
+ builder.AddLine('VALUENAME "%s"' % policy['name'])
+ builder.AddLine('VALUEON NUMERIC 1')
+ builder.AddLine('VALUEOFF NUMERIC 0')
+ else:
+ self._WritePart(policy, key_name, builder)
+
+ builder.AddLine('END POLICY', -1)
+ builder.AddLine()
+
+ def _GetPolicyExplanation(self, policy):
+ '''Returns the explanation for a given policy.
+ Includes a link to the relevant documentation on chromium.org.
+ '''
+ policy_desc = policy.get('desc')
+ reference_url = POLICY_LIST_URL + policy['name']
+ reference_link_text = self.GetLocalizedMessage('reference_link')
+ reference_link_text = reference_link_text.replace('$6', reference_url)
+
+ if policy_desc is not None:
+ policy_desc += '\n\n'
+ if (not policy.get('deprecated', False) and
+ not self._IsRemovedPolicy(policy)):
+ policy_desc += reference_link_text
+ return policy_desc
+ else:
+ return reference_link_text
+
+ def WriteComment(self, comment):
+ self.lines.AddLine('; ' + comment)
+
+ def WritePolicy(self, policy):
+ if self.CanBeMandatory(policy):
+ self._WritePolicy(policy, self.winconfig['reg_mandatory_key_name'],
+ self.policies)
+
+ def WriteRecommendedPolicy(self, policy):
+ self._WritePolicy(policy, self.winconfig['reg_recommended_key_name'],
+ self.recommended_policies)
+
+ def BeginPolicyGroup(self, group):
+ category_name = self._Escape(group['name'] + '_Category')
+ self._AddGuiString(category_name, group['caption'])
+ self.policies.AddLine('CATEGORY !!' + category_name, 1)
+
+ def EndPolicyGroup(self):
+ self.policies.AddLine('END CATEGORY', -1)
+ self.policies.AddLine('')
+
+ def BeginRecommendedPolicyGroup(self, group):
+ category_name = self._Escape(group['name'] + '_Category')
+ self._AddGuiString(category_name, group['caption'])
+ self.recommended_policies.AddLine('CATEGORY !!' + category_name, 1)
+
+ def EndRecommendedPolicyGroup(self):
+ self.recommended_policies.AddLine('END CATEGORY', -1)
+ self.recommended_policies.AddLine('')
+
+ def _CreateTemplate(self, category_path, key_name, policies):
+ '''Creates the whole ADM template except for the [Strings] section, and
+ returns it as an |IndentedStringBuilder|.
+
+ Args:
+ category_path: List of strings representing the category path.
+ key_name: Main registry key backing the policies.
+ policies: ADM code for all the policies in an |IndentedStringBuilder|.
+ '''
+ lines = IndentedStringBuilder()
+ for part in category_path:
+ lines.AddLine('CATEGORY !!' + part, 1)
+ lines.AddLine('KEYNAME "%s"' % key_name)
+ lines.AddLine()
+
+ lines.AddLines(policies)
+
+ for part in category_path:
+ lines.AddLine('END CATEGORY', -1)
+ lines.AddLine()
+
+ return lines
+
+ def BeginTemplate(self):
+ if self._GetChromiumVersionString() is not None:
+ self.WriteComment(self.config['build'] + ' version: ' + \
+ self._GetChromiumVersionString())
+ self._AddGuiString(self.config['win_supported_os'],
+ self.messages['win_supported_all']['text'])
+ self._AddGuiString(self.config['win_supported_os_win7'],
+ self.messages['win_supported_win7']['text'])
+ categories = self.winconfig['mandatory_category_path'] + \
+ self.winconfig['recommended_category_path']
+ strings = self.winconfig['category_path_strings'].copy()
+ if 'adm_category_path_strings' in self.config:
+ strings.update(self.config['adm_category_path_strings'])
+ for category in categories:
+ if (category in strings):
+ # Replace {...} by localized messages.
+ string = re.sub(r"\{(\w+)\}", \
+ lambda m: self.messages[m.group(1)]['text'], \
+ strings[category])
+ self._AddGuiString(category, string)
+ # All the policies will be written into self.policies.
+ # The final template text will be assembled into self.lines by
+ # self.EndTemplate().
+
+ def EndTemplate(self):
+ # Copy policies into self.lines.
+ policy_class = self.GetClass().upper()
+ for class_name in ['MACHINE', 'USER']:
+ if policy_class != 'BOTH' and policy_class != class_name:
+ continue
+ self.lines.AddLine('CLASS ' + class_name, 1)
+ self.lines.AddLines(
+ self._CreateTemplate(self.winconfig['mandatory_category_path'],
+ self.winconfig['reg_mandatory_key_name'],
+ self.policies))
+ self.lines.AddLines(
+ self._CreateTemplate(self.winconfig['recommended_category_path'],
+ self.winconfig['reg_recommended_key_name'],
+ self.recommended_policies))
+ self.lines.AddLine('', -1)
+ # Copy user strings into self.lines.
+ self.lines.AddLine('[Strings]')
+ self.lines.AddLines(self.strings)
+
+ def Init(self):
+ # String buffer for building the whole ADM file.
+ self.lines = IndentedStringBuilder()
+ # String buffer for building the strings section of the ADM file.
+ self.strings = IndentedStringBuilder()
+ # Map of strings seen, to avoid duplicates.
+ self.strings_seen = {}
+ # String buffer for building the policies of the ADM file.
+ self.policies = IndentedStringBuilder()
+ # String buffer for building the recommended policies of the ADM file.
+ self.recommended_policies = IndentedStringBuilder()
+ # Shortcut to platform-specific ADMX/ADM specific configuration.
+ assert len(self.platforms) == 2
+ self.winconfig = self.config['win_config'][self.platforms[0]]
+
+ def GetTemplateText(self):
+ return self.lines.ToString()
+
+ def GetClass(self):
+ return 'Both'
diff --git a/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py
new file mode 100755
index 00000000000..9d2b86d9e3a
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/adm_writer_unittest.py
@@ -0,0 +1,1470 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.adm_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import writer_unittest_common
+
+MESSAGES = '''
+ {
+ 'win_supported_all': {
+ 'text': 'Microsoft Windows 7 or later', 'desc': 'blah'
+ },
+ 'win_supported_win7': {
+ 'text': 'Microsoft Windows 7', 'desc': 'blah'
+ },
+
+ 'doc_recommended': {
+ 'text': 'Recommended', 'desc': 'bleh'
+ },
+ 'doc_reference_link': {
+ 'text': 'Reference: $6', 'desc': 'bleh'
+ },
+ 'deprecated_policy_group_caption': {
+ 'text': 'Deprecated policies', 'desc': 'bleh'
+ },
+ 'deprecated_policy_group_desc': {
+ 'desc': 'bleh',
+ 'text': 'These policies are included here to make them easy to remove.'
+ },
+ 'deprecated_policy_desc': {
+ 'desc': 'bleh',
+ 'text': 'This policy is deprecated. blah blah blah'
+ },
+ 'removed_policy_group_caption': {
+ 'text': 'Removed policies', 'desc': 'bleh'
+ },
+ 'removed_policy_group_desc': {
+ 'desc': 'bleh',
+ 'text': 'These policies are included here to make them easy to remove.'
+ },
+ 'removed_policy_desc': {
+ 'desc': 'bleh',
+ 'text': 'This policy is removed. blah blah blah'
+ },
+ }'''
+
+
+class AdmWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for AdmWriter.'''
+
+ def ConstructOutput(self, classes, body, strings):
+ result = []
+ for clazz in classes:
+ result.append('CLASS ' + clazz)
+ result.append(body)
+ result.append(strings)
+ return ''.join(result)
+
+ def CompareOutputs(self, output, expected_output):
+ '''Compares the output of the adm_writer with its expected output.
+
+ Args:
+ output: The output of the adm writer.
+ expected_output: The expected output.
+
+ Raises:
+ AssertionError: if the two strings are not equivalent.
+ '''
+ self.assertEquals(output.strip(),
+ expected_output.strip().replace('\n', '\r\n'))
+
+ def testEmpty(self):
+ # Test PListWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ }, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"''')
+ self.CompareOutputs(output, expected_output)
+
+ def testVersionAnnotation(self):
+ # Test PListWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'version': '39.0.0.0'
+ }, 'adm')
+ expected_output = '; chromium version: 39.0.0.0\n' + \
+ self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"''')
+ self.CompareOutputs(output, expected_output)
+
+ def testMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'caption': 'Caption of main.',
+ 'desc': 'Description of main.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ POLICY !!MainPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!MainPolicy_Explain
+ VALUENAME "MainPolicy"
+ VALUEON NUMERIC 1
+ VALUEOFF NUMERIC 0
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ POLICY !!MainPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!MainPolicy_Explain
+ VALUENAME "MainPolicy"
+ VALUEON NUMERIC 1
+ VALUEOFF NUMERIC 0
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+MainPolicy_Policy="Caption of main."
+MainPolicy_Explain="Description of main.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''')
+ self.CompareOutputs(output, expected_output)
+
+ def testMainPolicyRecommendedOnly(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': {
+ 'can_be_recommended': True,
+ 'can_be_mandatory': False
+ },
+ 'caption': 'Caption of main.',
+ 'desc': 'Description of main.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ POLICY !!MainPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!MainPolicy_Explain
+ VALUENAME "MainPolicy"
+ VALUEON NUMERIC 1
+ VALUEOFF NUMERIC 0
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+MainPolicy_Policy="Caption of main."
+MainPolicy_Explain="Description of main.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringPolicy(self):
+ # Tests a policy group with a single policy of type 'string'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'StringPolicy',
+ 'type': 'string',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'desc': """Description of group.
+With a newline.""",
+ 'caption': 'Caption of policy.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!StringPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!StringPolicy_Explain
+
+ PART !!StringPolicy_Part EDITTEXT
+ VALUENAME "StringPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!StringPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!StringPolicy_Explain
+
+ PART !!StringPolicy_Part EDITTEXT
+ VALUENAME "StringPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+StringPolicy_Policy="Caption of policy."
+StringPolicy_Explain="Description of group.\\nWith a newline.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=StringPolicy"
+StringPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntPolicy(self):
+ # Tests a policy group with a single policy of type 'int'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'IntPolicy',
+ 'type': 'int',
+ 'caption': 'Caption of policy.',
+ 'features': { 'can_be_recommended': True },
+ 'desc': 'Description of policy.',
+ 'supported_on': ['chrome.win:8-']
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 0 MAX 2000000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 0 MAX 2000000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+IntPolicy_Policy="Caption of policy."
+IntPolicy_Explain="Description of policy.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy"
+IntPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntPolicyWithWin7(self):
+ # Tests a policy group with a single policy of type 'int' that is supported
+ # on Windows 7 only.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'IntPolicy',
+ 'type': 'int',
+ 'caption': 'Caption of policy.',
+ 'features': { 'can_be_recommended': True },
+ 'desc': 'Description of policy.',
+ 'supported_on': ['chrome.win7:8-'],
+ },
+ ],
+ 'placeholders': [],
+ 'policy_atomic_group_definitions': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7_ONLY
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 0 MAX 2000000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7_ONLY
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 0 MAX 2000000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+IntPolicy_Policy="Caption of policy."
+IntPolicy_Explain="Description of policy.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy"
+IntPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntPolicyWithRange(self):
+ # Tests a policy group with a single policy of type 'int' with a min and
+ # max value.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'IntPolicy',
+ 'type': 'int',
+ 'schema': { 'type': 'integer', 'minimum': 5, 'maximum': 10 },
+ 'caption': 'Caption of policy.',
+ 'features': { 'can_be_recommended': True },
+ 'desc': 'Description of policy.',
+ 'supported_on': ['chrome.win:8-']
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 5 MAX 10
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!IntPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!IntPolicy_Explain
+
+ PART !!IntPolicy_Part NUMERIC
+ VALUENAME "IntPolicy"
+ MIN 5 MAX 10
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+IntPolicy_Policy="Caption of policy."
+IntPolicy_Explain="Description of policy.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=IntPolicy"
+IntPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'int-enum',
+ 'items': [
+ {
+ 'name': 'ProxyServerDisabled',
+ 'value': 0,
+ 'caption': 'Option1',
+ },
+ {
+ 'name': 'ProxyServerAutoDetect',
+ 'value': 1,
+ 'caption': 'Option2',
+ },
+ ],
+ 'desc': 'Description of policy.',
+ 'caption': 'Caption of policy.',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ POLICY !!EnumPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_Explain
+
+ PART !!EnumPolicy_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy"
+ ITEMLIST
+ NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE NUMERIC 0
+ NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE NUMERIC 1
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ POLICY !!EnumPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_Explain
+
+ PART !!EnumPolicy_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy"
+ ITEMLIST
+ NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE NUMERIC 0
+ NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE NUMERIC 1
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+EnumPolicy_Policy="Caption of policy."
+EnumPolicy_Explain="Description of policy.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy"
+EnumPolicy_Part="Caption of policy."
+EnumPolicy_ProxyServerDisabled_DropDown="Option1"
+EnumPolicy_ProxyServerAutoDetect_DropDown="Option2"
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'string-enum',
+ 'caption': 'Caption of policy.',
+ 'desc': 'Description of policy.',
+ 'items': [
+ {'name': 'ProxyServerDisabled', 'value': 'one',
+ 'caption': 'Option1'},
+ {'name': 'ProxyServerAutoDetect', 'value': 'two',
+ 'caption': 'Option2'},
+ ],
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ POLICY !!EnumPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_Explain
+
+ PART !!EnumPolicy_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy"
+ ITEMLIST
+ NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE "one"
+ NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE "two"
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ POLICY !!EnumPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_Explain
+
+ PART !!EnumPolicy_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy"
+ ITEMLIST
+ NAME !!EnumPolicy_ProxyServerDisabled_DropDown VALUE "one"
+ NAME !!EnumPolicy_ProxyServerAutoDetect_DropDown VALUE "two"
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+EnumPolicy_Policy="Caption of policy."
+EnumPolicy_Explain="Description of policy.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy"
+EnumPolicy_Part="Caption of policy."
+EnumPolicy_ProxyServerDisabled_DropDown="Option1"
+EnumPolicy_ProxyServerAutoDetect_DropDown="Option2"
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testListPolicy(self):
+ # Tests a policy group with a single policy of type 'list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ListPolicy',
+ 'type': 'list',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'desc': """Description of list policy.
+With a newline.""",
+ 'caption': 'Caption of list policy.',
+ 'label': 'Label of list policy.'
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s,
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!ListPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ListPolicy_Explain
+
+ PART !!ListPolicy_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\ListPolicy"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!ListPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ListPolicy_Explain
+
+ PART !!ListPolicy_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\Recommended\\ListPolicy"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+ListPolicy_Policy="Caption of list policy."
+ListPolicy_Explain="Description of list policy.\\nWith a newline.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ListPolicy"
+ListPolicy_Part="Label of list policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringEnumListPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum-list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ListPolicy',
+ 'type': 'string-enum-list',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'desc': """Description of list policy.
+With a newline.""",
+ 'items': [
+ {'name': 'ProxyServerDisabled', 'value': 'one',
+ 'caption': 'Option1'},
+ {'name': 'ProxyServerAutoDetect', 'value': 'two',
+ 'caption': 'Option2'},
+ ],
+ 'caption': 'Caption of list policy.',
+ 'label': 'Label of list policy.'
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!ListPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ListPolicy_Explain
+
+ PART !!ListPolicy_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\ListPolicy"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!ListPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ListPolicy_Explain
+
+ PART !!ListPolicy_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\Recommended\\ListPolicy"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+ListPolicy_Policy="Caption of list policy."
+ListPolicy_Explain="Description of list policy.\\nWith a newline.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ListPolicy"
+ListPolicy_Part="Label of list policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testDictionaryPolicy(self):
+ # Tests a policy group with a single policy of type 'dict'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'DictionaryPolicy',
+ 'type': 'dict',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'desc': 'Description of group.',
+ 'caption': 'Caption of policy.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!DictionaryPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!DictionaryPolicy_Explain
+
+ PART !!DictionaryPolicy_Part EDITTEXT
+ VALUENAME "DictionaryPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!DictionaryPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!DictionaryPolicy_Explain
+
+ PART !!DictionaryPolicy_Part EDITTEXT
+ VALUENAME "DictionaryPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+DictionaryPolicy_Policy="Caption of policy."
+DictionaryPolicy_Explain="Description of group.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=DictionaryPolicy"
+DictionaryPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testExternalPolicy(self):
+ # Tests a policy group with a single policy of type 'external'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ExternalPolicy',
+ 'type': 'external',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'desc': 'Description of group.',
+ 'caption': 'Caption of policy.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ POLICY !!ExternalPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ExternalPolicy_Explain
+
+ PART !!ExternalPolicy_Part EDITTEXT
+ VALUENAME "ExternalPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ POLICY !!ExternalPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!ExternalPolicy_Explain
+
+ PART !!ExternalPolicy_Part EDITTEXT
+ VALUENAME "ExternalPolicy"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+ExternalPolicy_Policy="Caption of policy."
+ExternalPolicy_Explain="Description of group.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalPolicy"
+ExternalPolicy_Part="Caption of policy."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testNonSupportedPolicy(self):
+ # Tests a policy that is not supported on Windows, so it shouldn't
+ # be included in the ADM file.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'NonWinGroup',
+ 'type': 'group',
+ 'policies': ['NonWinPolicy'],
+ 'caption': 'Group caption.',
+ 'desc': 'Group description.',
+ },
+ {
+ 'name': 'NonWinPolicy',
+ 'type': 'list',
+ 'supported_on': ['chrome.linux:8-', 'chrome.mac:8-'],
+ 'caption': 'Caption of list policy.',
+ 'desc': 'Desc of list policy.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testNonRecommendedPolicy(self):
+ # Tests a policy that is not recommended, so it should be included.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'supported_on': ['chrome.win:8-'],
+ 'caption': 'Caption of main.',
+ 'desc': 'Description of main.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ POLICY !!MainPolicy_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!MainPolicy_Explain
+ VALUENAME "MainPolicy"
+ VALUEON NUMERIC 1
+ VALUEOFF NUMERIC 0
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+MainPolicy_Policy="Caption of main."
+MainPolicy_Explain="Description of main.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=MainPolicy"''')
+ self.CompareOutputs(output, expected_output)
+
+ def testPolicyGroup(self):
+ # Tests a policy group that has more than one policies.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'Group1',
+ 'type': 'group',
+ 'desc': 'Description of group.',
+ 'caption': 'Caption of group.',
+ 'policies': ['Policy1', 'Policy2'],
+ },
+ {
+ 'name': 'Policy1',
+ 'type': 'list',
+ 'supported_on': ['chrome.win:8-'],
+ 'features': { 'can_be_recommended': True },
+ 'caption': 'Caption of policy1.',
+ 'desc': """Description of policy1.
+With a newline."""
+ },
+ {
+ 'name': 'Policy2',
+ 'type': 'string',
+ 'supported_on': ['chrome.win:8-'],
+ 'caption': 'Caption of policy2.',
+ 'desc': """Description of policy2.
+With a newline."""
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ CATEGORY !!Group1_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\Policy1"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ POLICY !!Policy2_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy2_Explain
+
+ PART !!Policy2_Part EDITTEXT
+ VALUENAME "Policy2"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ CATEGORY !!Group1_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part LISTBOX
+ KEYNAME "Software\\Policies\\Chromium\\Recommended\\Policy1"
+ VALUEPREFIX ""
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+Group1_Category="Caption of group."
+Policy1_Policy="Caption of policy1."
+Policy1_Explain="Description of policy1.\\nWith a newline.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=Policy1"
+Policy1_Part="Caption of policy1."
+Policy2_Policy="Caption of policy2."
+Policy2_Explain="Description of policy2.\\nWith a newline.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=Policy2"
+Policy2_Part="Caption of policy2."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testDuplicatedStringEnumPolicy(self):
+ # Verifies that duplicated enum constants with different descriptions are
+ # allowed.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumPolicy.A',
+ 'type': 'string-enum',
+ 'caption': 'Caption of policy A.',
+ 'desc': 'Description of policy A.',
+ 'items': [
+ {'name': 'tls1.2', 'value': 'tls1.2', 'caption': 'tls1.2' },
+ ],
+ 'supported_on': ['chrome.win:39-'],
+ },
+ {
+ 'name': 'EnumPolicy.B',
+ 'type': 'string-enum',
+ 'caption': 'Caption of policy B.',
+ 'desc': 'Description of policy B.',
+ 'items': [
+ {'name': 'tls1.2', 'value': 'tls1.2', 'caption': 'tls1.2' },
+ ],
+ 'supported_on': ['chrome.win:39-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome
+ KEYNAME "Software\\Policies\\Google\\Chrome"
+
+ POLICY !!EnumPolicy_A_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_A_Explain
+
+ PART !!EnumPolicy_A_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy.A"
+ ITEMLIST
+ NAME !!EnumPolicy_A_tls1_2_DropDown VALUE "tls1.2"
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ POLICY !!EnumPolicy_B_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!EnumPolicy_B_Explain
+
+ PART !!EnumPolicy_B_Part DROPDOWNLIST
+ VALUENAME "EnumPolicy.B"
+ ITEMLIST
+ NAME !!EnumPolicy_B_tls1_2_DropDown VALUE "tls1.2"
+ END ITEMLIST
+ END PART
+ END POLICY
+
+ END CATEGORY
+ END CATEGORY
+
+ CATEGORY !!Google:Cat_Google
+ CATEGORY !!googlechrome_recommended
+ KEYNAME "Software\\Policies\\Google\\Chrome\\Recommended"
+
+ END CATEGORY
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+Google:Cat_Google="Google"
+googlechrome="Google Chrome"
+googlechrome_recommended="Google Chrome - Recommended"
+EnumPolicy_A_Policy="Caption of policy A."
+EnumPolicy_A_Explain="Description of policy A.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy.A"
+EnumPolicy_A_Part="Caption of policy A."
+EnumPolicy_A_tls1_2_DropDown="tls1.2"
+EnumPolicy_B_Policy="Caption of policy B."
+EnumPolicy_B_Explain="Description of policy B.\\n\\n\
+Reference: \
+https://cloud.google.com/docs/chrome-enterprise/policies/?policy=EnumPolicy.B"
+EnumPolicy_B_Part="Caption of policy B."
+EnumPolicy_B_tls1_2_DropDown="tls1.2"
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testDeprecatedPolicy(self):
+ # Tests that a deprecated policy gets placed in the special
+ # 'DeprecatedPolicies' group.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'Policy1',
+ 'type': 'string',
+ 'deprecated': True,
+ 'features': { 'can_be_recommended': True },
+ 'supported_on': ['chrome.win:8-'],
+ 'caption': 'Caption of policy1.',
+ 'desc': """Description of policy1."""
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ CATEGORY !!DeprecatedPolicies_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part EDITTEXT
+ VALUENAME "Policy1"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ CATEGORY !!DeprecatedPolicies_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part EDITTEXT
+ VALUENAME "Policy1"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+DeprecatedPolicies_Category="Deprecated policies"
+Policy1_Policy="Caption of policy1."
+Policy1_Explain="This policy is deprecated. blah blah blah\\n\\n"
+Policy1_Part="Caption of policy1."
+''')
+ self.CompareOutputs(output, expected_output)
+
+ def testRemovedPolicy(self):
+ # Tests that a deprecated policy gets placed in the special
+ # 'RemovedPolicies' group.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'Policy1',
+ 'type': 'string',
+ 'deprecated': True,
+ 'features': { 'can_be_recommended': True },
+ 'supported_on': ['chrome.win:40-83'],
+ 'caption': 'Caption of policy1.',
+ 'desc': """Description of policy1."""
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': %s
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1',
+ 'major_version': 84}, 'adm')
+ expected_output = self.ConstructOutput(['MACHINE', 'USER'], '''
+ CATEGORY !!chromium
+ KEYNAME "Software\\Policies\\Chromium"
+
+ CATEGORY !!RemovedPolicies_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part EDITTEXT
+ VALUENAME "Policy1"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+ CATEGORY !!chromium_recommended
+ KEYNAME "Software\\Policies\\Chromium\\Recommended"
+
+ CATEGORY !!RemovedPolicies_Category
+ POLICY !!Policy1_Policy
+ #if version >= 4
+ SUPPORTED !!SUPPORTED_WIN7
+ #endif
+ EXPLAIN !!Policy1_Explain
+
+ PART !!Policy1_Part EDITTEXT
+ VALUENAME "Policy1"
+ MAXLEN 1000000
+ END PART
+ END POLICY
+
+ END CATEGORY
+
+ END CATEGORY
+
+
+''', '''[Strings]
+SUPPORTED_WIN7="Microsoft Windows 7 or later"
+SUPPORTED_WIN7_ONLY="Microsoft Windows 7"
+chromium="Chromium"
+chromium_recommended="Chromium - Recommended"
+RemovedPolicies_Category="Removed policies"
+Policy1_Policy="Caption of policy1."
+Policy1_Explain="This policy is removed. blah blah blah\\n\\n"
+Policy1_Part="Caption of policy1."
+''')
+ self.CompareOutputs(output, expected_output)
+
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/adml_writer.py b/chromium/components/policy/tools/template_writers/writers/adml_writer.py
new file mode 100755
index 00000000000..6f7233a0366
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/adml_writer.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from xml.dom import minidom
+from writers import gpo_editor_writer, xml_formatted_writer
+from writers.admx_writer import AdmxElementType
+import json
+import re
+
+
+def GetWriter(config):
+ '''Factory method for instanciating the ADMLWriter. Every Writer needs a
+ GetWriter method because the TemplateFormatter uses this method to
+ instantiate a Writer.
+ '''
+ return ADMLWriter(['win', 'win7'], config)
+
+
+class ADMLWriter(xml_formatted_writer.XMLFormattedWriter,
+ gpo_editor_writer.GpoEditorWriter):
+ ''' Class for generating an ADML policy template. It is used by the
+ PolicyTemplateGenerator to write the ADML file.
+ '''
+
+ # DOM root node of the generated ADML document.
+ _doc = None
+
+ # The string-table contains all ADML "string" elements.
+ _string_table_elem = None
+
+ # The presentation-table is the container for presentation elements, that
+ # describe the presentation of Policy-Groups and Policies.
+ _presentation_table_elem = None
+
+ def _AddString(self, id, text):
+ ''' Adds an ADML "string" element to _string_table_elem. The following
+ ADML snippet contains an example:
+
+ <string id="$(id)">$(text)</string>
+
+ Args:
+ id: ID of the newly created "string" element.
+ text: Value of the newly created "string" element.
+ '''
+ id = id.replace('.', '_')
+ if id in self.strings_seen:
+ assert text == self.strings_seen[id]
+ else:
+ self.strings_seen[id] = text
+ string_elem = self.AddElement(self._string_table_elem, 'string',
+ {'id': id})
+ string_elem.appendChild(self._doc.createTextNode(text))
+
+ def _GetAdmxElementType(self, policy):
+ '''Returns the ADMX element type for a particular Policy.'''
+ return AdmxElementType.GetType(policy, allow_multi_strings=False)
+
+ def WritePolicy(self, policy):
+ '''Generates the ADML elements for a Policy.
+ <stringTable>
+ ...
+ <string id="$(policy_group_name)">$(caption)</string>
+ <string id="$(policy_group_name)_Explain">$(description)</string>
+ </stringTable>
+
+ <presentationTables>
+ ...
+ <presentation id=$(policy_group_name)/>
+ </presentationTables>
+
+ Args:
+ policy: The Policy to generate ADML elements for.
+ '''
+ policy_name = policy['name']
+ policy_caption = policy.get('caption', policy_name)
+ policy_label = policy.get('label', policy_name)
+
+ policy_desc = policy.get('desc')
+ example_value_text = self._GetExampleValueText(policy)
+
+ if policy_desc is not None and self.HasExpandedPolicyDescription(policy):
+ policy_desc += '\n' + self.GetExpandedPolicyDescription(policy) + '\n'
+
+ if (policy_desc is not None and example_value_text is not None and
+ not self._IsRemovedPolicy(policy)):
+ policy_explain = policy_desc + '\n\n' + example_value_text
+ elif policy_desc is not None:
+ policy_explain = policy_desc
+ elif example_value_text is not None:
+ policy_explain = example_value_text
+ else:
+ # No explanation found at all.
+ policy_explain = policy_name
+
+ self._AddString(policy_name, policy_caption)
+ self._AddString(policy_name + '_Explain', policy_explain)
+ presentation_elem = self.AddElement(self._presentation_table_elem,
+ 'presentation', {'id': policy_name})
+
+ admx_element_type = self._GetAdmxElementType(policy)
+ if admx_element_type == AdmxElementType.MAIN:
+ pass
+ elif admx_element_type == AdmxElementType.STRING:
+ textbox_elem = self.AddElement(presentation_elem, 'textBox',
+ {'refId': policy_name})
+ label_elem = self.AddElement(textbox_elem, 'label')
+ label_elem.appendChild(self._doc.createTextNode(policy_label))
+ elif admx_element_type == AdmxElementType.MULTI_STRING:
+ # We currently also show a single-line textbox - see http://crbug/829328
+ textbox_elem = self.AddElement(presentation_elem, 'textBox',
+ {'refId': policy_name + '_Legacy'})
+ label_elem = self.AddElement(textbox_elem, 'label')
+ legacy_label = self._GetLegacySingleLineLabel(policy_label)
+ self._AddString(policy_name + '_Legacy', legacy_label)
+ label_elem.appendChild(self._doc.createTextNode(legacy_label))
+ # New multi-line textbox, easier to use than old single-line textbox:
+ multitextbox_elem = self.AddElement(presentation_elem, 'multiTextBox', {
+ 'refId': policy_name,
+ 'defaultHeight': '8'
+ })
+ multitextbox_elem.appendChild(self._doc.createTextNode(policy_label))
+ elif admx_element_type == AdmxElementType.INT:
+ textbox_elem = self.AddElement(presentation_elem, 'decimalTextBox',
+ {'refId': policy_name})
+ textbox_elem.appendChild(self._doc.createTextNode(policy_label + ':'))
+ elif admx_element_type == AdmxElementType.ENUM:
+ for item in policy['items']:
+ self._AddString(policy_name + "_" + item['name'], item['caption'])
+ dropdownlist_elem = self.AddElement(presentation_elem, 'dropdownList',
+ {'refId': policy_name})
+ dropdownlist_elem.appendChild(self._doc.createTextNode(policy_label))
+ elif admx_element_type == AdmxElementType.LIST:
+ self._AddString(policy_name + 'Desc', policy_caption)
+ listbox_elem = self.AddElement(presentation_elem, 'listBox',
+ {'refId': policy_name + 'Desc'})
+ listbox_elem.appendChild(self._doc.createTextNode(policy_label))
+ elif admx_element_type == AdmxElementType.GROUP:
+ pass
+ else:
+ raise Exception('Unknown element type %s.' % admx_element_type)
+
+ def BeginPolicyGroup(self, group):
+ '''Generates ADML elements for a Policy-Group. For each Policy-Group two
+ ADML "string" elements are added to the string-table. One contains the
+ caption of the Policy-Group and the other a description. A Policy-Group also
+ requires an ADML "presentation" element that must be added to the
+ presentation-table. The "presentation" element is the container for the
+ elements that define the visual presentation of the Policy-Goup's Policies.
+ The following ADML snippet shows an example:
+
+ Args:
+ group: The Policy-Group to generate ADML elements for.
+ '''
+ # Add ADML "string" elements to the string-table that are required by a
+ # Policy-Group.
+ self._AddString(group['name'] + '_group', group['caption'])
+
+ def _AddBaseStrings(self):
+ ''' Adds ADML "string" elements to the string-table that are referenced by
+ the ADMX file but not related to any specific Policy-Group or Policy.
+ '''
+ self._AddString(self.config['win_supported_os'],
+ self.messages['win_supported_all']['text'])
+ self._AddString(self.config['win_supported_os_win7'],
+ self.messages['win_supported_win7']['text'])
+ categories = self.winconfig['mandatory_category_path'] + \
+ self.winconfig['recommended_category_path']
+ strings = self.winconfig['category_path_strings']
+ for category in categories:
+ if (category in strings):
+ # Replace {...} by localized messages.
+ string = re.sub(r"\{(\w+)\}", \
+ lambda m: self.messages[m.group(1)]['text'], \
+ strings[category])
+ self._AddString(category, string)
+
+ def _GetExampleValueText(self, policy):
+ '''Generates a string that describes the example value, if needed.
+ Returns None if no string is needed. For instance, if the setting is a
+ boolean, the user can only select true or false, so example text is not
+ useful.'''
+ example_value = policy.get('example_value')
+ # If there is no example_value, we show nothing.
+ if not example_value:
+ return None
+
+ # Strings are simple - just return them as-is, on the same line.
+ if isinstance(example_value, str):
+ return self.GetLocalizedMessage('example_value') + ' ' + example_value
+
+ # Dicts are pretty simple - json.dumps them onto multiple lines.
+ if isinstance(example_value, dict):
+ value_as_text = json.dumps(example_value, indent=2)
+ return self.GetLocalizedMessage('example_value') + '\n\n' + value_as_text
+
+ # Lists are the more complicated - the example value we show the user
+ # depends on if they need to enter the list into a textbox (using JSON
+ # array syntax) or into a listbox (which doesn't need JSON array syntax,
+ # but does need exactly one entry per line).
+ if isinstance(example_value, list):
+ policy_type = policy.get('type')
+ if policy_type == 'dict':
+ # If the policy type is dict, that means they get to enter in the
+ # whole policy as JSON, including the JSON array square brackets:
+ value_as_text = json.dumps(example_value, indent=2)
+
+ elif policy_type is not None and 'list' in policy_type:
+ # But if the policy type is list, then they get to enter each item
+ # into a listbox, one item per line.
+ if isinstance(example_value[0], str):
+ # Items are strings. These don't need quotes when in a listbox.
+ value_as_text = '\n'.join([str(v) for v in example_value])
+ else:
+ # Items are dicts. We dump each item onto a single line, since the
+ # user has to enter one item per line into the listbox.
+ value_as_text = '\n'.join([json.dumps(v) for v in example_value])
+
+ else:
+ # Lists should be type 'dict', 'list', or something like '...enum-list'
+ raise Exception(
+ 'Unexpected policy type with list example value: %s' % policy_type)
+
+ return self.GetLocalizedMessage('example_value') + '\n\n' + value_as_text
+
+ # Other types - mostly booleans - we don't show example values.
+ return None
+
+ def _GetLegacySingleLineLabel(self, policy_label):
+ '''Generates a label for a legacy single-line textbox.'''
+ return (self.GetLocalizedMessage('legacy_single_line_label').replace(
+ '$6', policy_label))
+
+ def BeginTemplate(self):
+ dom_impl = minidom.getDOMImplementation('')
+ self._doc = dom_impl.createDocument(None, 'policyDefinitionResources', None)
+ if self._GetChromiumVersionString() is not None:
+ self.AddComment(self._doc.documentElement, self.config['build'] + \
+ ' version: ' + self._GetChromiumVersionString())
+ policy_definitions_resources_elem = self._doc.documentElement
+ policy_definitions_resources_elem.attributes['revision'] = '1.0'
+ policy_definitions_resources_elem.attributes['schemaVersion'] = '1.0'
+
+ self.AddElement(policy_definitions_resources_elem, 'displayName')
+ self.AddElement(policy_definitions_resources_elem, 'description')
+ resources_elem = self.AddElement(policy_definitions_resources_elem,
+ 'resources')
+ self._string_table_elem = self.AddElement(resources_elem, 'stringTable')
+ self._AddBaseStrings()
+ self._presentation_table_elem = self.AddElement(resources_elem,
+ 'presentationTable')
+
+ def Init(self):
+ # Map of all strings seen.
+ self.strings_seen = {}
+ # Shortcut to platform-specific ADMX/ADM specific configuration.
+ assert len(self.platforms) <= 2
+ self.winconfig = self.config['win_config'][self.platforms[0]]
+
+ def GetTemplateText(self):
+ # Using "toprettyxml()" confuses the Windows Group Policy Editor
+ # (gpedit.msc) because it interprets whitespace characters in text between
+ # the "string" tags. This prevents gpedit.msc from displaying the category
+ # names correctly.
+ return self._doc.toxml()
diff --git a/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py
new file mode 100755
index 00000000000..2f1afd14502
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/adml_writer_unittest.py
@@ -0,0 +1,521 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.adml_writer."""
+
+import os
+import sys
+import unittest
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+from writers import adml_writer
+from writers import xml_writer_base_unittest
+
+
+class AdmlWriterUnittest(xml_writer_base_unittest.XmlWriterBaseTest):
+
+ def setUp(self):
+ config = {
+ 'app_name': 'test',
+ 'build': 'test',
+ 'win_supported_os': 'SUPPORTED_TESTOS',
+ 'win_supported_os_win7': 'SUPPORTED_TESTOS_2',
+ 'win_config': {
+ 'win': {
+ 'mandatory_category_path': ['test_category'],
+ 'recommended_category_path': ['test_category_recommended'],
+ 'category_path_strings': {
+ 'test_category': 'TestCategory',
+ 'test_category_recommended': 'TestCategory - recommended',
+ },
+ },
+ 'chrome_os': {
+ 'mandatory_category_path': ['cros_test_category'],
+ 'recommended_category_path': ['cros_test_category_recommended'],
+ 'category_path_strings': {
+ 'cros_test_category':
+ 'CrOSTestCategory',
+ 'cros_test_category_recommended':
+ 'CrOSTestCategory - recommended',
+ },
+ },
+ },
+ }
+ self.writer = self._GetWriter(config)
+ self.writer.messages = {
+ 'win_supported_all': {
+ 'text': 'Supported on Test OS or higher',
+ 'desc': 'blah'
+ },
+ 'win_supported_win7': {
+ 'text': 'Supported on Test OS',
+ 'desc': 'blah'
+ },
+ 'doc_recommended': {
+ 'text': 'Recommended',
+ 'desc': 'bleh'
+ },
+ 'doc_example_value': {
+ 'text': 'Example value:',
+ 'desc': 'bluh'
+ },
+ 'doc_legacy_single_line_label': {
+ 'text': '$6 (deprecated)',
+ },
+ 'doc_schema_description_link': {
+ 'text': '''See $6'''
+ },
+ }
+ self.writer.Init()
+
+ def _GetWriter(self, config):
+ return adml_writer.GetWriter(config)
+
+ def GetCategory(self):
+ return "test_category"
+
+ def GetCategoryString(self):
+ return "TestCategory"
+
+ def _InitWriterForAddingPolicyGroups(self, writer):
+ '''Initialize the writer for adding policy groups. This method must be
+ called before the method "BeginPolicyGroup" can be called. It initializes
+ attributes of the writer.
+ '''
+ writer.BeginTemplate()
+
+ def _InitWriterForAddingPolicies(self, writer, policy):
+ '''Initialize the writer for adding policies. This method must be
+ called before the method "WritePolicy" can be called. It initializes
+ attributes of the writer.
+ '''
+ self._InitWriterForAddingPolicyGroups(writer)
+ policy_group = {
+ 'name': 'PolicyGroup',
+ 'caption': 'Test Caption',
+ 'desc': 'This is the test description of the test policy group.',
+ 'policies': policy,
+ }
+ writer.BeginPolicyGroup(policy_group)
+
+ string_elements = \
+ self.writer._string_table_elem.getElementsByTagName('string')
+ for elem in string_elements:
+ self.writer._string_table_elem.removeChild(elem)
+
+ def testEmpty(self):
+ self.writer.BeginTemplate()
+ self.writer.EndTemplate()
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?><policyDefinitionResources'
+ ' revision="1.0" schemaVersion="1.0"><displayName/><description/>'
+ '<resources><stringTable><string id="SUPPORTED_TESTOS">Supported on'
+ ' Test OS or higher</string>'
+ '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>'
+ '<string id="' + self.GetCategory() + '">' + \
+ self.GetCategoryString() + '</string>'
+ '<string id="' + self.GetCategory() + '_recommended">' + \
+ self.GetCategoryString() + ' - recommended</string>'
+ '</stringTable><presentationTable/>'
+ '</resources></policyDefinitionResources>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testVersionAnnotation(self):
+ self.writer.config['version'] = '39.0.0.0'
+ self.writer.BeginTemplate()
+ self.writer.EndTemplate()
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?><policyDefinitionResources'
+ ' revision="1.0" schemaVersion="1.0"><!--test version: 39.0.0.0-->'
+ '<displayName/><description/><resources><stringTable>'
+ '<string id="SUPPORTED_TESTOS">Supported on'
+ ' Test OS or higher</string>'
+ '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>'
+ '<string id="' + self.GetCategory() + '">' + \
+ self.GetCategoryString() + '</string>'
+ '<string id="' + self.GetCategory() + '_recommended">' + \
+ self.GetCategoryString() + ' - recommended</string>'
+ '</stringTable><presentationTable/>'
+ '</resources></policyDefinitionResources>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testPolicyGroup(self):
+ empty_policy_group = {
+ 'name':
+ 'PolicyGroup',
+ 'caption':
+ 'Test Group Caption',
+ 'desc':
+ 'This is the test description of the test policy group.',
+ 'policies': [
+ {
+ 'name': 'PolicyStub2',
+ 'type': 'main'
+ },
+ {
+ 'name': 'PolicyStub1',
+ 'type': 'main'
+ },
+ ],
+ }
+ self._InitWriterForAddingPolicyGroups(self.writer)
+ self.writer.BeginPolicyGroup(empty_policy_group)
+ self.writer.EndPolicyGroup()
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="SUPPORTED_TESTOS">'
+ 'Supported on Test OS or higher</string>\n' + \
+ '<string id="SUPPORTED_TESTOS_2">Supported on Test OS</string>\n' + \
+ '<string id="' + self.GetCategory() + '">' + \
+ self.GetCategoryString() + '</string>\n'
+ '<string id="' + self.GetCategory() + '_recommended">' + \
+ self.GetCategoryString() + ' - recommended</string>\n'
+ '<string id="PolicyGroup_group">Test Group Caption</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ''
+ self.AssertXMLEquals(output, expected_output)
+
+ def testMainPolicy(self):
+ main_policy = {
+ 'name': 'DummyMainPolicy',
+ 'type': 'main',
+ 'caption': 'Main policy caption',
+ 'desc': 'Main policy test description.'
+ }
+ self._InitWriterForAddingPolicies(self.writer, main_policy)
+ self.writer.WritePolicy(main_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="DummyMainPolicy">Main policy caption</string>\n'
+ '<string id="DummyMainPolicy_Explain">'
+ 'Main policy test description.</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = '<presentation id="DummyMainPolicy"/>'
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringPolicy(self):
+ string_policy = {
+ 'name': 'StringPolicyStub',
+ 'type': 'string',
+ 'caption': 'String policy caption',
+ 'label': 'String policy label',
+ 'desc': 'This is a test description.',
+ 'supported_on': [{'platform': 'win'}, {'platform': 'chrome_os'}],
+ 'example_value': '01:23:45:67:89:ab',
+ }
+ self._InitWriterForAddingPolicies(self.writer, string_policy)
+ self.writer.WritePolicy(string_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="StringPolicyStub">String policy caption</string>\n'
+ '<string id="StringPolicyStub_Explain">'
+ 'This is a test description.\n\n'
+ 'Example value: 01:23:45:67:89:ab</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="StringPolicyStub">\n'
+ ' <textBox refId="StringPolicyStub">\n'
+ ' <label>String policy label</label>\n'
+ ' </textBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testIntPolicy(self):
+ int_policy = {
+ 'name': 'IntPolicyStub',
+ 'type': 'int',
+ 'caption': 'Int policy caption',
+ 'label': 'Int policy label',
+ 'desc': 'This is a test description.',
+ }
+ self._InitWriterForAddingPolicies(self.writer, int_policy)
+ self.writer.WritePolicy(int_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="IntPolicyStub">Int policy caption</string>\n'
+ '<string id="IntPolicyStub_Explain">'
+ 'This is a test description.</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="IntPolicyStub">\n'
+ ' <decimalTextBox refId="IntPolicyStub">'
+ 'Int policy label:</decimalTextBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testIntEnumPolicy(self):
+ enum_policy = {
+ 'name':
+ 'EnumPolicyStub',
+ 'type':
+ 'int-enum',
+ 'caption':
+ 'Enum policy caption',
+ 'label':
+ 'Enum policy label',
+ 'desc':
+ 'This is a test description.',
+ 'items': [
+ {
+ 'name': 'item 1',
+ 'value': 1,
+ 'caption': 'Caption Item 1',
+ },
+ {
+ 'name': 'item 2',
+ 'value': 2,
+ 'caption': 'Caption Item 2',
+ },
+ ],
+ }
+ self._InitWriterForAddingPolicies(self.writer, enum_policy)
+ self.writer.WritePolicy(enum_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="EnumPolicyStub">Enum policy caption</string>\n'
+ '<string id="EnumPolicyStub_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="EnumPolicyStub_item 1">Caption Item 1</string>\n'
+ '<string id="EnumPolicyStub_item 2">Caption Item 2</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="EnumPolicyStub">\n'
+ ' <dropdownList refId="EnumPolicyStub">'
+ 'Enum policy label</dropdownList>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringEnumPolicy(self):
+ enum_policy = {
+ 'name':
+ 'EnumPolicyStub',
+ 'type':
+ 'string-enum',
+ 'caption':
+ 'Enum policy caption',
+ 'label':
+ 'Enum policy label',
+ 'desc':
+ 'This is a test description.',
+ 'items': [
+ {
+ 'name': 'item 1',
+ 'value': 'value 1',
+ 'caption': 'Caption Item 1',
+ },
+ {
+ 'name': 'item 2',
+ 'value': 'value 2',
+ 'caption': 'Caption Item 2',
+ },
+ ],
+ }
+ self._InitWriterForAddingPolicies(self.writer, enum_policy)
+ self.writer.WritePolicy(enum_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="EnumPolicyStub">Enum policy caption</string>\n'
+ '<string id="EnumPolicyStub_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="EnumPolicyStub_item 1">Caption Item 1</string>\n'
+ '<string id="EnumPolicyStub_item 2">Caption Item 2</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="EnumPolicyStub">\n'
+ ' <dropdownList refId="EnumPolicyStub">'
+ 'Enum policy label</dropdownList>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testListPolicy(self):
+ list_policy = {
+ 'name': 'ListPolicyStub',
+ 'type': 'list',
+ 'caption': 'List policy caption',
+ 'label': 'List policy label',
+ 'desc': 'This is a test description.',
+ }
+ self._InitWriterForAddingPolicies(self.writer, list_policy)
+ self.writer.WritePolicy(list_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="ListPolicyStub">List policy caption</string>\n'
+ '<string id="ListPolicyStub_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="ListPolicyStubDesc">List policy caption</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = (
+ '<presentation id="ListPolicyStub">\n'
+ ' <listBox refId="ListPolicyStubDesc">List policy label</listBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringEnumListPolicy(self):
+ list_policy = {
+ 'name':
+ 'ListPolicyStub',
+ 'type':
+ 'string-enum-list',
+ 'caption':
+ 'List policy caption',
+ 'label':
+ 'List policy label',
+ 'desc':
+ 'This is a test description.',
+ 'items': [
+ {
+ 'name': 'item 1',
+ 'value': 'value 1',
+ 'caption': 'Caption Item 1',
+ },
+ {
+ 'name': 'item 2',
+ 'value': 'value 2',
+ 'caption': 'Caption Item 2',
+ },
+ ],
+ }
+ self._InitWriterForAddingPolicies(self.writer, list_policy)
+ self.writer.WritePolicy(list_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="ListPolicyStub">List policy caption</string>\n'
+ '<string id="ListPolicyStub_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="ListPolicyStubDesc">List policy caption</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = (
+ '<presentation id="ListPolicyStub">\n'
+ ' <listBox refId="ListPolicyStubDesc">List policy label</listBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testDictionaryPolicy(self, is_external=False):
+ dict_policy = {
+ 'name': 'DictionaryPolicyStub',
+ 'type': 'external' if is_external else 'dict',
+ 'caption': 'Dictionary policy caption',
+ 'label': 'Dictionary policy label',
+ 'desc': 'This is a test description.',
+ }
+ self._InitWriterForAddingPolicies(self.writer, dict_policy)
+ self.writer.WritePolicy(dict_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="DictionaryPolicyStub">Dictionary policy caption</string>\n'
+ '<string id="DictionaryPolicyStub_Explain">'
+ 'This is a test description.\n'
+ 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy='
+ 'DictionaryPolicyStub\n</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="DictionaryPolicyStub">\n'
+ ' <textBox refId="DictionaryPolicyStub">\n'
+ ' <label>Dictionary policy label</label>\n'
+ ' </textBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testExternalPolicy(self):
+ self.testDictionaryPolicy(is_external=True)
+
+ def testPlatform(self):
+ # Test that the writer correctly chooses policies of platform Windows.
+ self.assertTrue(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+ self.assertFalse(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'mac',
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+
+ def testStringEncodings(self):
+ enum_policy_a = {
+ 'name': 'EnumPolicy.A',
+ 'type': 'string-enum',
+ 'caption': 'Enum policy A caption',
+ 'label': 'Enum policy A label',
+ 'desc': 'This is a test description.',
+ 'items': [{
+ 'name': 'same_item',
+ 'value': '1',
+ 'caption': 'caption_a',
+ }],
+ }
+ enum_policy_b = {
+ 'name': 'EnumPolicy.B',
+ 'type': 'string-enum',
+ 'caption': 'Enum policy B caption',
+ 'label': 'Enum policy B label',
+ 'desc': 'This is a test description.',
+ 'items': [{
+ 'name': 'same_item',
+ 'value': '2',
+ 'caption': 'caption_b',
+ }],
+ }
+ self._InitWriterForAddingPolicies(self.writer, enum_policy_a)
+ self.writer.WritePolicy(enum_policy_a)
+ self.writer.WritePolicy(enum_policy_b)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="EnumPolicy_A">Enum policy A caption</string>\n'
+ '<string id="EnumPolicy_A_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="EnumPolicy_A_same_item">caption_a</string>\n'
+ '<string id="EnumPolicy_B">Enum policy B caption</string>\n'
+ '<string id="EnumPolicy_B_Explain">'
+ 'This is a test description.</string>\n'
+ '<string id="EnumPolicy_B_same_item">caption_b</string>\n')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = ('<presentation id="EnumPolicy.A">\n'
+ ' <dropdownList refId="EnumPolicy.A">'
+ 'Enum policy A label</dropdownList>\n'
+ '</presentation>\n'
+ '<presentation id="EnumPolicy.B">\n'
+ ' <dropdownList refId="EnumPolicy.B">'
+ 'Enum policy B label</dropdownList>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/admx_writer.py b/chromium/components/policy/tools/template_writers/writers/admx_writer.py
new file mode 100755
index 00000000000..d6dbf4fbc99
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/admx_writer.py
@@ -0,0 +1,500 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from xml.dom import minidom
+from writers import gpo_editor_writer, xml_formatted_writer
+
+
+class AdmxElementType:
+ '''The different types of ADMX elements that can be used to display a policy.
+ This is related to the 'type' field in policy_templates.json, but there isn't
+ a perfect 1:1 mapping. This class is also used when writing ADML files, to
+ ensure that the ADML generated from policy_templates.json is compatible with
+ the ADMX generated from policy_templates.json"""
+ '''
+ MAIN = 1
+ STRING = 2
+ MULTI_STRING = 3
+ INT = 4
+ ENUM = 5
+ LIST = 6
+ GROUP = 7
+
+ @staticmethod
+ def GetType(policy, allow_multi_strings=False):
+ '''Returns the ADMX element type that should be used for the given policy.
+ This logic is shared between the ADMX writer and the ADML writer, to ensure
+ that the ADMX and ADML generated from policy_tempates.json are compatible.
+
+ Args:
+ policy: A dict describing the policy, as found in policy_templates.json.
+ allow_multi_strings: If true, the use of multi-line textbox elements is
+ allowed, so this function will sometimes return MULTI_STRING. If false
+ it falls back to single-line textboxes instead by returning STRING.
+
+ Returns:
+ One of the enum values of AdmxElementType.
+
+ Raises:
+ Exception: If policy['type'] is not recognized.
+ '''
+ policy_type = policy['type']
+ policy_example = policy.get('example_value')
+
+ # TODO(olsen): Some policies are defined in policy_templates.json as type
+ # string, but the string is actually a JSON object. We should change the
+ # schema so they are 'dict' or similar, but until then, we use this
+ # heuristic to decide whether they are actually JSON and so could benefit
+ # from being displayed to the user as a multi-line string:
+ if (policy_type == 'string' and allow_multi_strings and
+ policy_example is not None and policy_example.strip().startswith('{')):
+ return AdmxElementType.MULTI_STRING
+
+ admx_element_type = AdmxElementType._POLICY_TYPE_MAP.get(policy_type)
+ if admx_element_type is None:
+ raise Exception('Unknown policy type %s.' % policy_type)
+
+ if (admx_element_type == AdmxElementType.MULTI_STRING and
+ not allow_multi_strings):
+ return AdmxElementType.STRING
+
+ return admx_element_type
+
+
+AdmxElementType._POLICY_TYPE_MAP = {
+ 'main': AdmxElementType.MAIN,
+ 'string': AdmxElementType.STRING,
+ 'dict': AdmxElementType.MULTI_STRING,
+ 'external': AdmxElementType.MULTI_STRING,
+ 'int': AdmxElementType.INT,
+ 'int-enum': AdmxElementType.ENUM,
+ 'string-enum': AdmxElementType.ENUM,
+ 'list': AdmxElementType.LIST,
+ 'string-enum-list': AdmxElementType.LIST,
+ 'group': AdmxElementType.GROUP
+}
+
+
+def GetWriter(config):
+ '''Factory method for instanciating the ADMXWriter. Every Writer needs a
+ GetWriter method because the TemplateFormatter uses this method to
+ instantiate a Writer.
+ '''
+ return ADMXWriter(['win', 'win7'], config)
+
+
+class ADMXWriter(xml_formatted_writer.XMLFormattedWriter,
+ gpo_editor_writer.GpoEditorWriter):
+ '''Class for generating an ADMX policy template. It is used by the
+ PolicyTemplateGenerator to write the admx file.
+ '''
+
+ # DOM root node of the generated ADMX document.
+ _doc = None
+
+ # The ADMX "policies" element that contains the ADMX "policy" elements that
+ # are generated.
+ _active_policies_elem = None
+
+ def Init(self):
+ # Shortcut to platform-specific ADMX/ADM specific configuration.
+ assert len(self.platforms) <= 2
+ self.winconfig = self.config['win_config'][self.platforms[0]]
+
+ def _AdmlString(self, name):
+ '''Creates a reference to the named string in an ADML file.
+ Args:
+ name: Name of the referenced ADML string.
+ '''
+ name = name.replace('.', '_')
+ return '$(string.' + name + ')'
+
+ def _AdmlStringExplain(self, name):
+ '''Creates a reference to the named explanation string in an ADML file.
+ Args:
+ name: Name of the referenced ADML explanation.
+ '''
+ name = name.replace('.', '_')
+ return '$(string.' + name + '_Explain)'
+
+ def _AdmlPresentation(self, name):
+ '''Creates a reference to the named presentation element in an ADML file.
+ Args:
+ name: Name of the referenced ADML presentation element.
+ '''
+ return '$(presentation.' + name + ')'
+
+ def _AddPolicyNamespaces(self, parent, prefix, namespace):
+ '''Generates the ADMX "policyNamespace" element and adds the elements to the
+ passed parent element. The namespace of the generated ADMX document is
+ define via the ADMX "target" element. Used namespaces are declared with an
+ ADMX "using" element. ADMX "target" and "using" elements are children of the
+ ADMX "policyNamespace" element.
+
+ Args:
+ parent: The parent node to which all generated elements are added.
+ prefix: A logical name that can be used in the generated ADMX document to
+ refere to this namespace.
+ namespace: Namespace of the generated ADMX document.
+ '''
+ policy_namespaces_elem = self.AddElement(parent, 'policyNamespaces')
+ attributes = {
+ 'prefix': prefix,
+ 'namespace': namespace,
+ }
+ self.AddElement(policy_namespaces_elem, 'target', attributes)
+ if 'admx_using_namespaces' in self.config:
+ prefix_namespace_map = self.config['admx_using_namespaces']
+ for prefix in prefix_namespace_map:
+ attributes = {
+ 'prefix': prefix,
+ 'namespace': prefix_namespace_map[prefix],
+ }
+ self.AddElement(policy_namespaces_elem, 'using', attributes)
+ attributes = {
+ 'prefix': 'windows',
+ 'namespace': 'Microsoft.Policies.Windows',
+ }
+ self.AddElement(policy_namespaces_elem, 'using', attributes)
+
+ def _AddCategory(self, parent, name, display_name, parent_category_name=None):
+ '''Adds an ADMX category element to the passed parent node. The following
+ snippet shows an example of a category element where "chromium" is the value
+ of the parameter name:
+
+ <category displayName="$(string.chromium)" name="chromium"/>
+
+ Each parent node can have only one category with a given name. Adding the
+ same category again with the same attributes is ignored, but adding it
+ again with different attributes is an error.
+
+ Args:
+ parent: The parent node to which all generated elements are added.
+ name: Name of the category.
+ display_name: Display name of the category.
+ parent_category_name: Name of the parent category. Defaults to None.
+ '''
+ existing = list(
+ filter(lambda e: e.getAttribute('name') == name,
+ parent.getElementsByTagName('category')))
+ if existing:
+ assert len(existing) == 1
+ assert existing[0].getAttribute('name') == name
+ assert existing[0].getAttribute('displayName') == display_name
+ return
+ attributes = {
+ 'name': name,
+ 'displayName': display_name,
+ }
+ category_elem = self.AddElement(parent, 'category', attributes)
+ if parent_category_name:
+ attributes = {'ref': parent_category_name}
+ self.AddElement(category_elem, 'parentCategory', attributes)
+
+ def _AddCategories(self, categories):
+ '''Generates the ADMX "categories" element and adds it to the categories
+ main node. The "categories" element defines the category for the policies
+ defined in this ADMX document. Here is an example of an ADMX "categories"
+ element:
+
+ <categories>
+ <category displayName="$(string.googlechrome)" name="googlechrome">
+ <parentCategory ref="Google:Cat_Google"/>
+ </category>
+ </categories>
+
+ Args:
+ categories_path: The categories path e.g. ['google', 'googlechrome']. For
+ each level in the path a "category" element will be generated, unless
+ the level contains a ':', in which case it is treated as external
+ references and no element is generated. Except for the root level, each
+ level refers to its parent. Since the root level category has no parent
+ it does not require a parent reference.
+ '''
+ category_name = None
+ for category in categories:
+ parent_category_name = category_name
+ category_name = category
+ if (":" not in category_name):
+ self._AddCategory(self._categories_elem, category_name,
+ self._AdmlString(category_name), parent_category_name)
+
+ def _AddSupportedOn(self, parent, supported_os_list):
+ '''Generates the "supportedOn" ADMX element and adds it to the passed
+ parent node. The "supportedOn" element contains information about supported
+ Windows OS versions. The following code snippet contains an example of a
+ "supportedOn" element:
+
+ <supportedOn>
+ <definitions>
+ <definition name="$(supported_os)"
+ displayName="$(string.$(supported_os))"/>
+ </definitions>
+ ...
+ </supportedOn>
+
+ Args:
+ parent: The parent element to which all generated elements are added.
+ supported_os: List with all supported Win OSes.
+ '''
+ supported_on_elem = self.AddElement(parent, 'supportedOn')
+ definitions_elem = self.AddElement(supported_on_elem, 'definitions')
+ for supported_os in supported_os_list:
+ attributes = {
+ 'name': supported_os,
+ 'displayName': self._AdmlString(supported_os)
+ }
+ self.AddElement(definitions_elem, 'definition', attributes)
+
+ def _AddStringPolicy(self, parent, name, id=None):
+ '''Generates ADMX elements for a String-Policy and adds them to the
+ passed parent node.
+ '''
+ attributes = {
+ 'id': id or name,
+ 'valueName': name,
+ 'maxLength': '1000000',
+ }
+ self.AddElement(parent, 'text', attributes)
+
+ def _AddMultiStringPolicy(self, parent, name):
+ '''Generates ADMX elements for a multi-line String-Policy and adds them to
+ the passed parent node.
+ '''
+ # We currently also show a single-line textbox - see http://crbug/829328
+ self._AddStringPolicy(parent, name, id=name + '_Legacy')
+ attributes = {
+ 'id': name,
+ 'valueName': name,
+ 'maxLength': '1000000',
+ }
+ self.AddElement(parent, 'multiText', attributes)
+
+ def _AddIntPolicy(self, parent, policy):
+ '''Generates ADMX elements for an Int-Policy and adds them to the passed
+ parent node.
+ '''
+ #default max value for an integer
+ max = 2000000000
+ min = 0
+ if self.PolicyHasRestrictions(policy):
+ schema = policy['schema']
+ if 'minimum' in schema and schema['minimum'] >= 0:
+ min = schema['minimum']
+ if 'maximum' in schema and schema['maximum'] >= 0:
+ max = schema['maximum']
+ assert type(min) == int
+ assert type(max) == int
+ attributes = {
+ 'id': policy['name'],
+ 'valueName': policy['name'],
+ 'maxValue': str(max),
+ 'minValue': str(min),
+ }
+ self.AddElement(parent, 'decimal', attributes)
+
+ def _AddEnumPolicy(self, parent, policy):
+ '''Generates ADMX elements for an Enum-Policy and adds them to the
+ passed parent element.
+ '''
+ name = policy['name']
+ items = policy['items']
+ attributes = {
+ 'id': name,
+ 'valueName': name,
+ }
+ enum_elem = self.AddElement(parent, 'enum', attributes)
+ for item in items:
+ attributes = {'displayName': self._AdmlString(name + "_" + item['name'])}
+ item_elem = self.AddElement(enum_elem, 'item', attributes)
+ value_elem = self.AddElement(item_elem, 'value')
+ value_string = str(item['value'])
+ if policy['type'] == 'int-enum':
+ self.AddElement(value_elem, 'decimal', {'value': value_string})
+ else:
+ self.AddElement(value_elem, 'string', {}, value_string)
+
+ def _AddListPolicy(self, parent, key, name):
+ '''Generates ADMX XML elements for a List-Policy and adds them to the
+ passed parent element.
+ '''
+ attributes = {
+ # The ID must be in sync with ID of the corresponding element in the
+ # ADML file.
+ 'id': name + 'Desc',
+ 'valuePrefix': '',
+ 'key': key + '\\' + name,
+ }
+ self.AddElement(parent, 'list', attributes)
+
+ def _AddMainPolicy(self, parent):
+ '''Generates ADMX elements for a Main-Policy amd adds them to the
+ passed parent element.
+ '''
+ enabled_value_elem = self.AddElement(parent, 'enabledValue')
+ self.AddElement(enabled_value_elem, 'decimal', {'value': '1'})
+ disabled_value_elem = self.AddElement(parent, 'disabledValue')
+ self.AddElement(disabled_value_elem, 'decimal', {'value': '0'})
+
+ def PolicyHasRestrictions(self, policy):
+ if 'schema' in policy:
+ return any(keyword in policy['schema'] \
+ for keyword in ['minimum', 'maximum'])
+ return False
+
+ def _GetElements(self, policy_group_elem):
+ '''Returns the ADMX "elements" child from an ADMX "policy" element. If the
+ "policy" element has no "elements" child yet, a new child is created.
+
+ Args:
+ policy_group_elem: The ADMX "policy" element from which the child element
+ "elements" is returned.
+
+ Raises:
+ Exception: The policy_group_elem does not contain a ADMX "policy" element.
+ '''
+ if policy_group_elem.tagName != 'policy':
+ raise Exception('Expected a "policy" element but got a "%s" element' %
+ policy_group_elem.tagName)
+ elements_list = policy_group_elem.getElementsByTagName('elements')
+ if len(elements_list) == 0:
+ return self.AddElement(policy_group_elem, 'elements')
+ elif len(elements_list) == 1:
+ return elements_list[0]
+ else:
+ raise Exception('There is supposed to be only one "elements" node but'
+ ' there are %s.' % str(len(elements_list)))
+
+ def _GetAdmxElementType(self, policy):
+ '''Returns the ADMX element type for a particular Policy.'''
+ return AdmxElementType.GetType(policy, allow_multi_strings=False)
+
+ def _WritePolicy(self, policy, name, key, parent):
+ '''Generates ADMX elements for a Policy.'''
+ policies_elem = self._active_policies_elem
+ policy_name = policy['name']
+ attributes = {
+ 'name': name,
+ 'class': self.GetClass(policy),
+ 'displayName': self._AdmlString(policy_name),
+ 'explainText': self._AdmlStringExplain(policy_name),
+ 'presentation': self._AdmlPresentation(policy_name),
+ 'key': key,
+ }
+ is_win7_only = self.IsPolicyOnWin7Only(policy)
+ supported_key = ('win_supported_os_win7'
+ if is_win7_only else 'win_supported_os')
+ supported_on_text = self.config[supported_key]
+
+ # Store the current "policy" AMDX element in self for later use by the
+ # WritePolicy method.
+ policy_elem = self.AddElement(policies_elem, 'policy', attributes)
+ self.AddElement(policy_elem, 'parentCategory', {'ref': parent})
+ self.AddElement(policy_elem, 'supportedOn', {'ref': supported_on_text})
+
+ element_type = self._GetAdmxElementType(policy)
+ if element_type == AdmxElementType.MAIN:
+ self.AddAttribute(policy_elem, 'valueName', policy_name)
+ self._AddMainPolicy(policy_elem)
+ elif element_type == AdmxElementType.STRING:
+ parent = self._GetElements(policy_elem)
+ self._AddStringPolicy(parent, policy_name)
+ elif element_type == AdmxElementType.MULTI_STRING:
+ parent = self._GetElements(policy_elem)
+ self._AddMultiStringPolicy(parent, policy_name)
+ elif element_type == AdmxElementType.INT:
+ parent = self._GetElements(policy_elem)
+ self._AddIntPolicy(parent, policy)
+ elif element_type == AdmxElementType.ENUM:
+ parent = self._GetElements(policy_elem)
+ self._AddEnumPolicy(parent, policy)
+ elif element_type == AdmxElementType.LIST:
+ parent = self._GetElements(policy_elem)
+ self._AddListPolicy(parent, key, policy_name)
+ elif element_type == AdmxElementType.GROUP:
+ pass
+ else:
+ raise Exception('Unknown element type %s.' % element_type)
+
+ def WritePolicy(self, policy):
+ if self.CanBeMandatory(policy):
+ self._WritePolicy(policy, policy['name'],
+ self.winconfig['reg_mandatory_key_name'],
+ self._active_mandatory_policy_group_name)
+
+ def WriteRecommendedPolicy(self, policy):
+ self._WritePolicy(policy, policy['name'] + '_recommended',
+ self.winconfig['reg_recommended_key_name'],
+ self._active_recommended_policy_group_name)
+
+ def _BeginPolicyGroup(self, group, name, parent):
+ '''Generates ADMX elements for a Policy-Group.
+ '''
+ attributes = {
+ 'name': name,
+ 'displayName': self._AdmlString(group['name'] + '_group'),
+ }
+ category_elem = self.AddElement(self._categories_elem, 'category',
+ attributes)
+ attributes = {'ref': parent}
+ self.AddElement(category_elem, 'parentCategory', attributes)
+
+ def BeginPolicyGroup(self, group):
+ self._BeginPolicyGroup(group, group['name'],
+ self.winconfig['mandatory_category_path'][-1])
+ self._active_mandatory_policy_group_name = group['name']
+
+ def EndPolicyGroup(self):
+ self._active_mandatory_policy_group_name = \
+ self.winconfig['mandatory_category_path'][-1]
+
+ def BeginRecommendedPolicyGroup(self, group):
+ self._BeginPolicyGroup(group, group['name'] + '_recommended',
+ self.winconfig['recommended_category_path'][-1])
+ self._active_recommended_policy_group_name = group['name'] + '_recommended'
+
+ def EndRecommendedPolicyGroup(self):
+ self._active_recommended_policy_group_name = \
+ self.winconfig['recommended_category_path'][-1]
+
+ def BeginTemplate(self):
+ '''Generates the skeleton of the ADMX template. An ADMX template contains
+ an ADMX "PolicyDefinitions" element with four child nodes: "policies"
+ "policyNamspaces", "resources", "supportedOn" and "categories"
+ '''
+ dom_impl = minidom.getDOMImplementation('')
+ self._doc = dom_impl.createDocument(None, 'policyDefinitions', None)
+ if self._GetChromiumVersionString() is not None:
+ self.AddComment(self._doc.documentElement, self.config['build'] + \
+ ' version: ' + self._GetChromiumVersionString())
+ policy_definitions_elem = self._doc.documentElement
+
+ policy_definitions_elem.attributes['revision'] = '1.0'
+ policy_definitions_elem.attributes['schemaVersion'] = '1.0'
+
+ self._AddPolicyNamespaces(policy_definitions_elem,
+ self.config['admx_prefix'],
+ self.winconfig['namespace'])
+ self.AddElement(policy_definitions_elem, 'resources',
+ {'minRequiredRevision': '1.0'})
+ self._AddSupportedOn(
+ policy_definitions_elem,
+ [self.config['win_supported_os'], self.config['win_supported_os_win7']])
+ self._categories_elem = self.AddElement(policy_definitions_elem,
+ 'categories')
+ self._AddCategories(self.winconfig['mandatory_category_path'])
+ self._AddCategories(self.winconfig['recommended_category_path'])
+ self._active_policies_elem = self.AddElement(policy_definitions_elem,
+ 'policies')
+ self._active_mandatory_policy_group_name = \
+ self.winconfig['mandatory_category_path'][-1]
+ self._active_recommended_policy_group_name = \
+ self.winconfig['recommended_category_path'][-1]
+
+ def GetTemplateText(self):
+ return self.ToPrettyXml(self._doc)
+
+ def GetClass(self, policy):
+ return 'Both'
diff --git a/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py
new file mode 100755
index 00000000000..1543a016bb9
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/admx_writer_unittest.py
@@ -0,0 +1,700 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.admx_writer."""
+
+import os
+import sys
+import unittest
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+from writers import admx_writer
+from writers import xml_writer_base_unittest
+from xml.dom import minidom
+
+
+class AdmxWriterUnittest(xml_writer_base_unittest.XmlWriterBaseTest):
+
+ def _CreateDocumentElement(self):
+ dom_impl = minidom.getDOMImplementation('')
+ doc = dom_impl.createDocument(None, 'root', None)
+ return doc.documentElement
+
+ def setUp(self):
+ # Writer configuration. This dictionary contains parameter used by the ADMX
+ # Writer
+ config = {
+ 'win_supported_os': 'SUPPORTED_TESTOS',
+ 'win_supported_os_win7': 'SUPPORTED_TESTOS_2',
+ 'win_config': {
+ 'win': {
+ 'reg_mandatory_key_name':
+ 'Software\\Policies\\Test',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\Test\\Recommended',
+ 'mandatory_category_path': ['test_category'],
+ 'recommended_category_path': ['test_recommended_category'],
+ 'category_path_strings': {
+ 'test_category': 'TestCategory',
+ 'test_recommended_category': 'TestCategory - recommended',
+ },
+ 'namespace':
+ 'ADMXWriter.Test.Namespace',
+ },
+ 'chrome_os': {
+ 'reg_mandatory_key_name':
+ 'Software\\Policies\\CrOSTest',
+ 'reg_recommended_key_name':
+ 'Software\\Policies\\CrOSTest\\Recommended',
+ 'mandatory_category_path': ['cros_test_category'],
+ 'recommended_category_path': ['cros_test_recommended_category'],
+ 'category_path_strings': {
+ 'cros_test_category':
+ 'CrOSTestCategory',
+ 'cros_test_recommended_category':
+ 'CrOSTestCategory - recommended',
+ },
+ 'namespace':
+ 'ADMXWriter.Test.Namespace.ChromeOS',
+ },
+ },
+ 'admx_prefix': 'test_prefix',
+ 'build': 'test_product',
+ }
+ self.writer = self._GetWriter(config)
+ self.writer.Init()
+
+ def _GetWriter(self, config):
+ return admx_writer.GetWriter(config)
+
+ def _GetKey(self):
+ return "Test"
+
+ def _GetCategory(self):
+ return "test_category"
+
+ def _GetCategoryRec(self):
+ return "test_recommended_category"
+
+ def _GetNamespace(self):
+ return "ADMXWriter.Test.Namespace"
+
+ def _GetPoliciesElement(self, doc):
+ node_list = doc.getElementsByTagName('policies')
+ self.assertTrue(node_list.length == 1)
+ return node_list.item(0)
+
+ def _GetCategoriesElement(self, doc):
+ node_list = doc.getElementsByTagName('categories')
+ self.assertTrue(node_list.length == 1)
+ return node_list.item(0)
+
+ def testEmpty(self):
+ self.writer.BeginTemplate()
+ self.writer.EndTemplate()
+
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?>\n'
+ '<policyDefinitions revision="1.0" schemaVersion="1.0">\n'
+ ' <policyNamespaces>\n'
+ ' <target namespace="' + self._GetNamespace() + '"'
+ ' prefix="test_prefix"/>\n'
+ ' <using namespace="Microsoft.Policies.Windows" prefix="windows"/>\n'
+ ' </policyNamespaces>\n'
+ ' <resources minRequiredRevision="1.0"/>\n'
+ ' <supportedOn>\n'
+ ' <definitions>\n'
+ ' <definition displayName="'
+ '$(string.SUPPORTED_TESTOS)" name="SUPPORTED_TESTOS"/>\n'
+ ' <definition displayName="'
+ '$(string.SUPPORTED_TESTOS_2)" name="SUPPORTED_TESTOS_2"/>\n'
+ ' </definitions>\n'
+ ' </supportedOn>\n'
+ ' <categories>\n'
+ ' <category displayName="$(string.' + self._GetCategory() + ')"'
+ ' name="' + self._GetCategory() + '"/>\n'
+ ' <category displayName="$(string.' + self._GetCategoryRec() + ')"'
+ ' name="' + self._GetCategoryRec() + '"/>\n'
+ ' </categories>\n'
+ ' <policies/>\n'
+ '</policyDefinitions>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testEmptyVersion(self):
+ self.writer.config['version'] = '39.0.0.0'
+ self.writer.BeginTemplate()
+ self.writer.EndTemplate()
+
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?>\n'
+ '<policyDefinitions revision="1.0" schemaVersion="1.0">\n'
+ ' <!--test_product version: 39.0.0.0-->\n'
+ ' <policyNamespaces>\n'
+ ' <target namespace="' + self._GetNamespace() + '"'
+ ' prefix="test_prefix"/>\n'
+ ' <using namespace="Microsoft.Policies.Windows" prefix="windows"/>\n'
+ ' </policyNamespaces>\n'
+ ' <resources minRequiredRevision="1.0"/>\n'
+ ' <supportedOn>\n'
+ ' <definitions>\n'
+ ' <definition displayName="'
+ '$(string.SUPPORTED_TESTOS)" name="SUPPORTED_TESTOS"/>\n'
+ ' <definition displayName="'
+ '$(string.SUPPORTED_TESTOS_2)" name="SUPPORTED_TESTOS_2"/>\n'
+ ' </definitions>\n'
+ ' </supportedOn>\n'
+ ' <categories>\n'
+ ' <category displayName="$(string.' + self._GetCategory() + ')"'
+ ' name="' + self._GetCategory() + '"/>\n'
+ ' <category displayName="$(string.' + self._GetCategoryRec() + ')"'
+ ' name="' + self._GetCategoryRec() + '"/>\n'
+ ' </categories>\n'
+ ' <policies/>\n'
+ '</policyDefinitions>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testEmptyPolicyGroup(self):
+ empty_policy_group = {'name': 'PolicyGroup', 'policies': []}
+ # Initialize writer to write a policy group.
+ self.writer.BeginTemplate()
+ # Write policy group
+ self.writer.BeginPolicyGroup(empty_policy_group)
+ self.writer.EndPolicyGroup()
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = ''
+ self.AssertXMLEquals(output, expected_output)
+
+ output = self.GetXMLOfChildren(self._GetCategoriesElement(self.writer._doc))
+ expected_output = (
+ '<category displayName="$(string.' + self._GetCategory() + ')"'
+ ' name="' + self._GetCategory() + '"/>\n'
+ '<category displayName="$(string.' + self._GetCategoryRec() + ')"'
+ ' name="' + self._GetCategoryRec() + '"/>\n'
+ '<category displayName="$(string.PolicyGroup_group)"'
+ ' name="PolicyGroup">\n'
+ ' <parentCategory ref="' + self._GetCategory() + '"/>\n'
+ '</category>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testPolicyGroup(self):
+ empty_policy_group = {
+ 'name':
+ 'PolicyGroup',
+ 'policies': [
+ {
+ 'name': 'PolicyStub2',
+ 'type': 'main'
+ },
+ {
+ 'name': 'PolicyStub1',
+ 'type': 'main'
+ },
+ ]
+ }
+ # Initialize writer to write a policy group.
+ self.writer.BeginTemplate()
+ # Write policy group
+ self.writer.BeginPolicyGroup(empty_policy_group)
+ self.writer.EndPolicyGroup()
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = ''
+ self.AssertXMLEquals(output, expected_output)
+
+ output = self.GetXMLOfChildren(self._GetCategoriesElement(self.writer._doc))
+ expected_output = (
+ '<category displayName="$(string.' + self._GetCategory() + ')"'
+ ' name="' + self._GetCategory() + '"/>\n'
+ '<category displayName="$(string.' + self._GetCategoryRec() + ')"'
+ ' name="' + self._GetCategoryRec() + '"/>\n'
+ '<category displayName="$(string.PolicyGroup_group)"'
+ ' name="PolicyGroup">\n'
+ ' <parentCategory ref="' + self._GetCategory() + '"/>\n'
+ '</category>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def _initWriterForPolicy(self, writer, policy):
+ '''Initializes the writer to write the given policy next.
+ '''
+ policy_group = {'name': 'PolicyGroup', 'policies': [policy]}
+ writer.BeginTemplate()
+ writer.BeginPolicyGroup(policy_group)
+
+ def testMainPolicy(self):
+ main_policy = {
+ 'name': 'DummyMainPolicy',
+ 'type': 'main',
+ }
+
+ self._initWriterForPolicy(self.writer, main_policy)
+
+ self.writer.WritePolicy(main_policy)
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(main_policy) + '"'
+ ' displayName="$(string.DummyMainPolicy)"'
+ ' explainText="$(string.DummyMainPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="DummyMainPolicy"'
+ ' presentation="$(presentation.DummyMainPolicy)"'
+ ' valueName="DummyMainPolicy">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <enabledValue>\n'
+ ' <decimal value="1"/>\n'
+ ' </enabledValue>\n'
+ ' <disabledValue>\n'
+ ' <decimal value="0"/>\n'
+ ' </disabledValue>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testRecommendedPolicy(self):
+ main_policy = {
+ 'name': 'DummyMainPolicy',
+ 'type': 'main',
+ }
+
+ policy_group = {
+ 'name': 'PolicyGroup',
+ 'policies': [main_policy],
+ }
+ self.writer.BeginTemplate()
+ self.writer.BeginRecommendedPolicyGroup(policy_group)
+
+ self.writer.WriteRecommendedPolicy(main_policy)
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(main_policy) + '"'
+ ' displayName="$(string.DummyMainPolicy)"'
+ ' explainText="$(string.DummyMainPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '\\Recommended"'
+ ' name="DummyMainPolicy_recommended"'
+ ' presentation="$(presentation.DummyMainPolicy)"'
+ ' valueName="DummyMainPolicy">\n'
+ ' <parentCategory ref="PolicyGroup_recommended"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <enabledValue>\n'
+ ' <decimal value="1"/>\n'
+ ' </enabledValue>\n'
+ ' <disabledValue>\n'
+ ' <decimal value="0"/>\n'
+ ' </disabledValue>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testRecommendedOnlyPolicy(self):
+ main_policy = {
+ 'name': 'DummyMainPolicy',
+ 'type': 'main',
+ 'features': {
+ 'can_be_recommended': True,
+ 'can_be_mandatory': False,
+ }
+ }
+
+ policy_group = {
+ 'name': 'PolicyGroup',
+ 'policies': [main_policy],
+ }
+ self.writer.BeginTemplate()
+ self.writer.BeginRecommendedPolicyGroup(policy_group)
+
+ self.writer.WritePolicy(main_policy)
+ self.writer.WriteRecommendedPolicy(main_policy)
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(main_policy) + '"'
+ ' displayName="$(string.DummyMainPolicy)"'
+ ' explainText="$(string.DummyMainPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '\\Recommended"'
+ ' name="DummyMainPolicy_recommended"'
+ ' presentation="$(presentation.DummyMainPolicy)"'
+ ' valueName="DummyMainPolicy">\n'
+ ' <parentCategory ref="PolicyGroup_recommended"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <enabledValue>\n'
+ ' <decimal value="1"/>\n'
+ ' </enabledValue>\n'
+ ' <disabledValue>\n'
+ ' <decimal value="0"/>\n'
+ ' </disabledValue>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringPolicy(self):
+ string_policy = {
+ 'name': 'SampleStringPolicy',
+ 'type': 'string',
+ }
+ self._initWriterForPolicy(self.writer, string_policy)
+
+ self.writer.WritePolicy(string_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(string_policy) + '"'
+ ' displayName="$(string.SampleStringPolicy)"'
+ ' explainText="$(string.SampleStringPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleStringPolicy"'
+ ' presentation="$(presentation.SampleStringPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <text id="SampleStringPolicy" maxLength="1000000"'
+ ' valueName="SampleStringPolicy"/>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testIntPolicy(self):
+ int_policy = {
+ 'name': 'SampleIntPolicy',
+ 'type': 'int',
+ }
+ self._initWriterForPolicy(self.writer, int_policy)
+
+ self.writer.WritePolicy(int_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(int_policy) + '"'
+ ' displayName="$(string.SampleIntPolicy)"'
+ ' explainText="$(string.SampleIntPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleIntPolicy"'
+ ' presentation="$(presentation.SampleIntPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <decimal id="SampleIntPolicy" maxValue="2000000000" minValue="0" '
+ 'valueName="SampleIntPolicy"/>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testIntPolicyWithWin7Only(self):
+ int_policy = {
+ 'name': 'SampleIntPolicy',
+ 'type': 'int',
+ 'supported_on': [{
+ 'platform': 'win7',
+ }]
+ }
+ self._initWriterForPolicy(self.writer, int_policy)
+
+ self.writer.WritePolicy(int_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(int_policy) + '"'
+ ' displayName="$(string.SampleIntPolicy)"'
+ ' explainText="$(string.SampleIntPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleIntPolicy"'
+ ' presentation="$(presentation.SampleIntPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS_2"/>\n'
+ ' <elements>\n'
+ ' <decimal id="SampleIntPolicy" maxValue="2000000000" minValue="0" '
+ 'valueName="SampleIntPolicy"/>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+
+ def testIntEnumPolicy(self):
+ enum_policy = {
+ 'name':
+ 'SampleEnumPolicy',
+ 'type':
+ 'int-enum',
+ 'items': [
+ {
+ 'name': 'item_1',
+ 'value': 0
+ },
+ {
+ 'name': 'item_2',
+ 'value': 1
+ },
+ ]
+ }
+
+ self._initWriterForPolicy(self.writer, enum_policy)
+ self.writer.WritePolicy(enum_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(enum_policy) + '"'
+ ' displayName="$(string.SampleEnumPolicy)"'
+ ' explainText="$(string.SampleEnumPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleEnumPolicy"'
+ ' presentation="$(presentation.SampleEnumPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <enum id="SampleEnumPolicy" valueName="SampleEnumPolicy">\n'
+ ' <item displayName="$(string.SampleEnumPolicy_item_1)">\n'
+ ' <value>\n'
+ ' <decimal value="0"/>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' <item displayName="$(string.SampleEnumPolicy_item_2)">\n'
+ ' <value>\n'
+ ' <decimal value="1"/>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' </enum>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringEnumPolicy(self):
+ enum_policy = {
+ 'name':
+ 'SampleEnumPolicy',
+ 'type':
+ 'string-enum',
+ 'items': [
+ {
+ 'name': 'item_1',
+ 'value': 'one'
+ },
+ {
+ 'name': 'item_2',
+ 'value': 'two'
+ },
+ ]
+ }
+
+ # This test is different than the others because it also tests that space
+ # usage inside <string> nodes is correct.
+ dom_impl = minidom.getDOMImplementation('')
+ self.writer._doc = dom_impl.createDocument(None, 'policyDefinitions', None)
+ self.writer._active_policies_elem = self.writer._doc.documentElement
+ self.writer._active_mandatory_policy_group_name = 'PolicyGroup'
+ self.writer.WritePolicy(enum_policy)
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?>\n'
+ '<policyDefinitions>\n'
+ ' <policy class="' + self.writer.GetClass(enum_policy) + '"'
+ ' displayName="$(string.SampleEnumPolicy)"'
+ ' explainText="$(string.SampleEnumPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleEnumPolicy"'
+ ' presentation="$(presentation.SampleEnumPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <enum id="SampleEnumPolicy" valueName="SampleEnumPolicy">\n'
+ ' <item displayName="$(string.SampleEnumPolicy_item_1)">\n'
+ ' <value>\n'
+ ' <string>one</string>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' <item displayName="$(string.SampleEnumPolicy_item_2)">\n'
+ ' <value>\n'
+ ' <string>two</string>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' </enum>\n'
+ ' </elements>\n'
+ ' </policy>\n'
+ '</policyDefinitions>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testListPolicy(self):
+ list_policy = {
+ 'name': 'SampleListPolicy',
+ 'type': 'list',
+ }
+ self._initWriterForPolicy(self.writer, list_policy)
+ self.writer.WritePolicy(list_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(list_policy) + '"'
+ ' displayName="$(string.SampleListPolicy)"'
+ ' explainText="$(string.SampleListPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleListPolicy"'
+ ' presentation="$(presentation.SampleListPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <list id="SampleListPolicyDesc"'
+ ' key="Software\Policies\\' + self._GetKey() + '\SampleListPolicy"'
+ ' valuePrefix=""/>\n'
+ ' </elements>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testStringEnumListPolicy(self):
+ list_policy = {
+ 'name':
+ 'SampleListPolicy',
+ 'type':
+ 'string-enum-list',
+ 'items': [
+ {
+ 'name': 'item_1',
+ 'value': 'one'
+ },
+ {
+ 'name': 'item_2',
+ 'value': 'two'
+ },
+ ]
+ }
+ self._initWriterForPolicy(self.writer, list_policy)
+ self.writer.WritePolicy(list_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(list_policy) + '"'
+ ' displayName="$(string.SampleListPolicy)"'
+ ' explainText="$(string.SampleListPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleListPolicy"'
+ ' presentation="$(presentation.SampleListPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <list id="SampleListPolicyDesc"'
+ ' key="Software\Policies\\' + self._GetKey() + '\SampleListPolicy"'
+ ' valuePrefix=""/>\n'
+ ' </elements>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testDictionaryPolicy(self, is_external=False):
+ dict_policy = {
+ 'name': 'SampleDictionaryPolicy',
+ 'type': 'external' if is_external else 'dict',
+ }
+ self._initWriterForPolicy(self.writer, dict_policy)
+
+ self.writer.WritePolicy(dict_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(dict_policy) + '"'
+ ' displayName="$(string.SampleDictionaryPolicy)"'
+ ' explainText="$(string.SampleDictionaryPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleDictionaryPolicy"'
+ ' presentation="$(presentation.SampleDictionaryPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <text id="SampleDictionaryPolicy" maxLength="1000000"'
+ ' valueName="SampleDictionaryPolicy"/>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+ def testExternalPolicy(self):
+ self.testDictionaryPolicy(is_external=True)
+
+ def testPlatform(self):
+ # Test that the writer correctly chooses policies of platform Windows.
+ self.assertTrue(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+ self.assertFalse(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'mac'
+ }, {
+ 'platform': 'aaa'
+ }, {
+ 'platform': 'linux'
+ }]
+ }))
+
+ def testStringEncodings(self):
+ enum_policy_a = {
+ 'name': 'SampleEnumPolicy.A',
+ 'type': 'string-enum',
+ 'items': [{
+ 'name': 'tls1.2',
+ 'value': 'tls1.2'
+ }]
+ }
+ enum_policy_b = {
+ 'name': 'SampleEnumPolicy.B',
+ 'type': 'string-enum',
+ 'items': [{
+ 'name': 'tls1.2',
+ 'value': 'tls1.2'
+ }]
+ }
+
+ dom_impl = minidom.getDOMImplementation('')
+ self.writer._doc = dom_impl.createDocument(None, 'policyDefinitions', None)
+ self.writer._active_policies_elem = self.writer._doc.documentElement
+ self.writer._active_mandatory_policy_group_name = 'PolicyGroup'
+ self.writer.WritePolicy(enum_policy_a)
+ self.writer.WritePolicy(enum_policy_b)
+ output = self.writer.GetTemplateText()
+ expected_output = (
+ '<?xml version="1.0" ?>\n'
+ '<policyDefinitions>\n'
+ ' <policy class="' + self.writer.GetClass(enum_policy_a) + '"'
+ ' displayName="$(string.SampleEnumPolicy_A)"'
+ ' explainText="$(string.SampleEnumPolicy_A_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleEnumPolicy.A"'
+ ' presentation="$(presentation.SampleEnumPolicy.A)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <enum id="SampleEnumPolicy.A" valueName="SampleEnumPolicy.A">\n'
+ ' <item displayName="$(string.SampleEnumPolicy_A_tls1_2)">\n'
+ ' <value>\n'
+ ' <string>tls1.2</string>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' </enum>\n'
+ ' </elements>\n'
+ ' </policy>\n'
+ ' <policy class="' + self.writer.GetClass(enum_policy_b) + '"'
+ ' displayName="$(string.SampleEnumPolicy_B)"'
+ ' explainText="$(string.SampleEnumPolicy_B_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleEnumPolicy.B"'
+ ' presentation="$(presentation.SampleEnumPolicy.B)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <enum id="SampleEnumPolicy.B" valueName="SampleEnumPolicy.B">\n'
+ ' <item displayName="$(string.SampleEnumPolicy_B_tls1_2)">\n'
+ ' <value>\n'
+ ' <string>tls1.2</string>\n'
+ ' </value>\n'
+ ' </item>\n'
+ ' </enum>\n'
+ ' </elements>\n'
+ ' </policy>\n'
+ '</policyDefinitions>')
+ self.AssertXMLEquals(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py b/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py
new file mode 100755
index 00000000000..5536a41fca3
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/android_policy_writer.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import xml_formatted_writer
+from xml.dom import minidom
+from xml.sax import saxutils as xml_escape
+
+
+def GetWriter(config):
+ '''Factory method for creating AndroidPolicyWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return AndroidPolicyWriter(['android'], config)
+
+
+def _EscapeResource(resource):
+ '''Escape the resource for usage in an Android resource XML file.
+ This includes standard XML escaping as well as those specific to Android.
+ '''
+ if resource == None or type(resource) in (int, bool):
+ return str(resource)
+ return xml_escape.escape(
+ resource,
+ {
+ # Written order is matter to prevent "'" becomes "\\\\'" instead of
+ # "\\'".
+ "\\": "\\\\",
+ "'": "\\'",
+ '"': '\\"',
+ })
+
+
+class AndroidPolicyWriter(xml_formatted_writer.XMLFormattedWriter):
+ '''Outputs localized Android Resource XML files.
+ The policy strings are localized and exposed as string resources for
+ consumption through Android's App restriction Schema.
+ '''
+
+ # DOM root node of the generated XML document.
+ _doc = None
+ # The resources node contains all resource 'string' and 'string-array'
+ # elements.
+ _resources = None
+
+ def AddStringResource(self, name, string):
+ '''Add a string resource of the given name.
+ '''
+ string_node = self._doc.createElement('string')
+ string_node.setAttribute('name', name)
+ string_node.appendChild(self._doc.createTextNode(_EscapeResource(string)))
+ self._resources.appendChild(string_node)
+
+ def AddStringArrayResource(self, name, string_items):
+ '''Add a string-array resource of the given name and
+ elements from string_items.
+ '''
+ string_array_node = self._doc.createElement('string-array')
+ string_array_node.setAttribute('name', name)
+ self._resources.appendChild(string_array_node)
+ for item in string_items:
+ string_node = self._doc.createElement('item')
+ string_node.appendChild(self._doc.createTextNode(_EscapeResource(item)))
+ string_array_node.appendChild(string_node)
+
+ def PreprocessPolicies(self, policy_list):
+ return self.FlattenGroupsAndSortPolicies(policy_list)
+
+ def CanBeRecommended(self, policy):
+ return False
+
+ def WritePolicy(self, policy):
+ name = policy['name']
+ self.AddStringResource(name + 'Title', policy['caption'])
+
+ # Get the policy description.
+ description = policy['desc']
+ self.AddStringResource(name + 'Desc', description)
+
+ items = policy.get('items')
+ if items is not None:
+ items = [
+ item for item in items
+ if ('supported_on' not in item or
+ self.IsPolicyOrItemSupportedOnPlatform(item, 'android'))
+ ]
+ entries = [item['caption'] for item in items]
+ values = [item['value'] for item in items]
+ self.AddStringArrayResource(name + 'Entries', entries)
+ self.AddStringArrayResource(name + 'Values', values)
+
+ def BeginTemplate(self):
+ comment_text = 'DO NOT MODIFY THIS FILE DIRECTLY!\n' \
+ 'IT IS GENERATED FROM policy_templates.json.'
+ if self._GetChromiumVersionString():
+ comment_text += '\n' + self.config['build'] + ' version: '\
+ + self._GetChromiumVersionString()
+ comment_node = self._doc.createComment(comment_text)
+ self._doc.insertBefore(comment_node, self._resources)
+
+ def Init(self):
+ impl = minidom.getDOMImplementation()
+ self._doc = impl.createDocument(None, 'resources', None)
+ self._resources = self._doc.documentElement
+
+ def GetTemplateText(self):
+ return self.ToPrettyXml(self._doc)
diff --git a/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py
new file mode 100755
index 00000000000..a282bc5aab8
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/android_policy_writer_unittest.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.android_policy_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+from xml.dom import minidom
+
+from writers import writer_unittest_common
+from writers import android_policy_writer
+
+
+class AndroidPolicyWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests to test assumptions in Android Policy Writer'''
+
+ def testPolicyWithoutItems(self):
+ # Test an example policy without items.
+ policy = {
+ 'name': '_policy_name',
+ 'caption': '_policy_caption',
+ 'desc': 'This is a long policy caption. More than one sentence '
+ 'in a single line because it is very important.\n'
+ 'Second line, also important'
+ }
+ writer = android_policy_writer.GetWriter({})
+ writer.Init()
+ writer.BeginTemplate()
+ writer.WritePolicy(policy)
+ self.assertEquals(
+ writer._resources.toxml(), '<resources>'
+ '<string name="_policy_nameTitle">_policy_caption</string>'
+ '<string name="_policy_nameDesc">This is a long policy caption. More '
+ 'than one sentence in a single line because it is very '
+ 'important.\nSecond line, also important'
+ '</string>'
+ '</resources>')
+
+ def testPolicyWithItems(self):
+ # Test an example policy without items.
+ policy = {
+ 'name':
+ '_policy_name',
+ 'caption':
+ '_policy_caption',
+ 'desc':
+ '_policy_desc_first.\nadditional line',
+ 'items': [{
+ 'caption': '_caption1',
+ 'value': '_value1',
+ }, {
+ 'caption': '_caption2',
+ 'value': '_value2',
+ },
+ {
+ 'caption': '_caption3',
+ 'value': '_value3',
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'win7'
+ }]
+ },
+ {
+ 'caption':
+ '_caption4',
+ 'value':
+ '_value4',
+ 'supported_on': [{
+ 'platform': 'android'
+ }, {
+ 'platform': 'win7'
+ }]
+ }]
+ }
+ writer = android_policy_writer.GetWriter({})
+ writer.Init()
+ writer.BeginTemplate()
+ writer.WritePolicy(policy)
+ self.assertEquals(
+ writer._resources.toxml(), '<resources>'
+ '<string name="_policy_nameTitle">_policy_caption</string>'
+ '<string name="_policy_nameDesc">_policy_desc_first.\n'
+ 'additional line</string>'
+ '<string-array name="_policy_nameEntries">'
+ '<item>_caption1</item>'
+ '<item>_caption2</item>'
+ '<item>_caption4</item>'
+ '</string-array>'
+ '<string-array name="_policy_nameValues">'
+ '<item>_value1</item>'
+ '<item>_value2</item>'
+ '<item>_value4</item>'
+ '</string-array>'
+ '</resources>')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py
new file mode 100755
index 00000000000..72e4dfc5832
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+
+from writers import adml_writer
+from writers.admx_writer import AdmxElementType
+
+
+def GetWriter(config):
+ '''Factory method for creating ADMLWriter objects for the Chrome OS platform.
+ See the constructor of TemplateWriter for description of arguments.
+ '''
+ return ChromeOSADMLWriter(['chrome_os'], config)
+
+
+class ChromeOSADMLWriter(adml_writer.ADMLWriter):
+ ''' Class for generating Chrome OS ADML policy templates. It is used by the
+ PolicyTemplateGenerator to write the ADML file.
+ '''
+
+ # Overridden.
+ # These ADML files are used to generate GPO for Active Directory managed
+ # Chrome OS devices.
+ def IsPolicySupported(self, policy):
+ return self.IsCrOSManagementSupported(policy, 'active_directory') and \
+ super(ChromeOSADMLWriter, self).IsPolicySupported(policy)
+
+ # Overridden.
+ def _GetAdmxElementType(self, policy):
+ return AdmxElementType.GetType(policy, allow_multi_strings=True)
diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py
new file mode 100755
index 00000000000..f07d299270d
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/chromeos_adml_writer_unittest.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.chromeos_adml_writer."""
+
+import os
+import sys
+import unittest
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+from writers import chromeos_adml_writer
+from writers import adml_writer_unittest
+from writers.admx_writer import AdmxElementType
+
+
+class ChromeOsAdmlWriterUnittest(adml_writer_unittest.AdmlWriterUnittest):
+
+ # Overridden.
+ def _GetWriter(self, config):
+ return chromeos_adml_writer.GetWriter(config)
+
+ # Overridden
+ def GetCategory(self):
+ return "cros_test_category"
+
+ # Overridden
+ def GetCategoryString(self):
+ return "CrOSTestCategory"
+
+ # Overridden.
+ def testPlatform(self):
+ # Test that the writer correctly chooses policies of platform Chrome OS.
+ self.assertTrue(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'chrome_os'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+ self.assertFalse(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+
+ def testOnlySupportsAdPolicies(self):
+ # Tests whether only Active Directory managed policies are supported (Google
+ # cloud only managed polices are not put in the ADMX file).
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'supported_on': [{
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ }
+ self.assertTrue(self.writer.IsPolicySupported(policy))
+
+ policy['supported_chrome_os_management'] = ['google_cloud']
+ self.assertFalse(self.writer.IsPolicySupported(policy))
+
+ policy['supported_chrome_os_management'] = ['active_directory']
+ self.assertTrue(self.writer.IsPolicySupported(policy))
+
+ # Overridden.
+ def testDictionaryPolicy(self, is_external=False):
+ dict_policy = {
+ 'name': 'DictionaryPolicyStub',
+ 'type': 'external' if is_external else 'dict',
+ 'caption': 'Dictionary policy caption',
+ 'label': 'Dictionary policy label',
+ 'desc': 'This is a test description.',
+ }
+ self._InitWriterForAddingPolicies(self.writer, dict_policy)
+ self.writer.WritePolicy(dict_policy)
+ # Assert generated string elements.
+ output = self.GetXMLOfChildren(self.writer._string_table_elem)
+ expected_output = (
+ '<string id="DictionaryPolicyStub">Dictionary policy caption</string>\n'
+ '<string id="DictionaryPolicyStub_Explain">'
+ 'This is a test description.\n'
+ 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy='
+ 'DictionaryPolicyStub\n</string>\n'
+ '<string id="DictionaryPolicyStub_Legacy">'
+ 'Dictionary policy label (deprecated)</string>')
+ self.AssertXMLEquals(output, expected_output)
+ # Assert generated presentation elements.
+ output = self.GetXMLOfChildren(self.writer._presentation_table_elem)
+ expected_output = (
+ '<presentation id="DictionaryPolicyStub">\n'
+ ' <textBox refId="DictionaryPolicyStub_Legacy">\n'
+ ' <label>Dictionary policy label (deprecated)</label>\n'
+ ' </textBox>\n'
+ ' <multiTextBox defaultHeight="8" refId="DictionaryPolicyStub">'
+ 'Dictionary policy label</multiTextBox>\n'
+ '</presentation>')
+ self.AssertXMLEquals(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py
new file mode 100755
index 00000000000..c9f1c2c5052
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+
+from writers import admx_writer
+from writers.admx_writer import AdmxElementType
+
+
+def GetWriter(config):
+ '''Factory method for creating ADMXWriter objects for the Chrome OS platform
+ See the constructor of TemplateWriter for description of arguments.
+ '''
+ return ChromeOSADMXWriter(['chrome_os'], config)
+
+
+class ChromeOSADMXWriter(admx_writer.ADMXWriter):
+ '''Class for generating Chrome OS policy templates in the ADMX format.
+ It is used by PolicyTemplateGenerator to write ADMX files.
+ '''
+
+ # Overridden.
+ def GetClass(self, policy):
+ is_device_only = 'device_only' in policy and policy['device_only']
+ return 'Machine' if is_device_only else 'User'
+
+ # Overridden.
+ # These ADMX templates are used to generate GPO for Active Directory managed
+ # Chrome OS devices.
+ def IsPolicySupported(self, policy):
+ return self.IsCrOSManagementSupported(policy, 'active_directory') and \
+ super(ChromeOSADMXWriter, self).IsPolicySupported(policy)
+
+ # Overridden.
+ def _GetAdmxElementType(self, policy):
+ return AdmxElementType.GetType(policy, allow_multi_strings=True)
diff --git a/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py
new file mode 100755
index 00000000000..b6b4f83a426
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/chromeos_admx_writer_unittest.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.chromeos_admx_writer."""
+
+import os
+import sys
+import unittest
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+from writers import chromeos_admx_writer
+from writers import admx_writer_unittest
+from writers.admx_writer import AdmxElementType
+
+
+class ChromeOsAdmxWriterUnittest(admx_writer_unittest.AdmxWriterUnittest):
+
+ # Overridden.
+ def _GetWriter(self, config):
+ return chromeos_admx_writer.GetWriter(config)
+
+ # Overridden.
+ def _GetKey(self):
+ return "CrOSTest"
+
+ # Overridden.
+ def _GetCategory(self):
+ return "cros_test_category"
+
+ # Overridden.
+ def _GetCategoryRec(self):
+ return "cros_test_recommended_category"
+
+ # Overridden.
+ def _GetNamespace(self):
+ return "ADMXWriter.Test.Namespace.ChromeOS"
+
+ # Overridden.
+ def testPlatform(self):
+ # Test that the writer correctly chooses policies of platform Chrome OS.
+ self.assertTrue(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'chrome_os'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+ self.assertFalse(
+ self.writer.IsPolicySupported({
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'aaa'
+ }]
+ }))
+
+ def testUserPolicy(self):
+ self.doTestUserOrDevicePolicy(False)
+
+ def testDevicePolicy(self):
+ self.doTestUserOrDevicePolicy(True)
+
+ def doTestUserOrDevicePolicy(self, is_device_only):
+ # Tests whether CLASS attribute is 'User' for user policies and 'Machine'
+ # for device policies.
+ main_policy = {
+ 'name': 'DummyMainPolicy',
+ 'type': 'main',
+ 'device_only': is_device_only,
+ }
+
+ expected_class = 'Machine' if is_device_only else 'User'
+
+ self._initWriterForPolicy(self.writer, main_policy)
+ self.writer.WritePolicy(main_policy)
+
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = ('<policy class="' + expected_class + '"'
+ ' displayName="$(string.DummyMainPolicy)"'
+ ' explainText="$(string.DummyMainPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="DummyMainPolicy"'
+ ' presentation="$(presentation.DummyMainPolicy)"'
+ ' valueName="DummyMainPolicy">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <enabledValue>\n'
+ ' <decimal value="1"/>\n'
+ ' </enabledValue>\n'
+ ' <disabledValue>\n'
+ ' <decimal value="0"/>\n'
+ ' </disabledValue>\n'
+ '</policy>')
+
+ self.AssertXMLEquals(output, expected_output)
+
+ def testOnlySupportsAdPolicies(self):
+ # Tests whether only Active Directory managed policies are supported (Google
+ # cloud only managed polices are not put in the ADMX file).
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'supported_on': [{
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ }
+ self.assertTrue(self.writer.IsPolicySupported(policy))
+
+ policy['supported_chrome_os_management'] = ['google_cloud']
+ self.assertFalse(self.writer.IsPolicySupported(policy))
+
+ policy['supported_chrome_os_management'] = ['active_directory']
+ self.assertTrue(self.writer.IsPolicySupported(policy))
+
+ #Overridden
+ def testDictionaryPolicy(self, is_external=False):
+ dict_policy = {
+ 'name': 'SampleDictionaryPolicy',
+ 'type': 'external' if is_external else 'dict',
+ }
+ self._initWriterForPolicy(self.writer, dict_policy)
+
+ self.writer.WritePolicy(dict_policy)
+ output = self.GetXMLOfChildren(self._GetPoliciesElement(self.writer._doc))
+ expected_output = (
+ '<policy class="' + self.writer.GetClass(dict_policy) + '"'
+ ' displayName="$(string.SampleDictionaryPolicy)"'
+ ' explainText="$(string.SampleDictionaryPolicy_Explain)"'
+ ' key="Software\\Policies\\' + self._GetKey() + '"'
+ ' name="SampleDictionaryPolicy"'
+ ' presentation="$(presentation.SampleDictionaryPolicy)">\n'
+ ' <parentCategory ref="PolicyGroup"/>\n'
+ ' <supportedOn ref="SUPPORTED_TESTOS"/>\n'
+ ' <elements>\n'
+ ' <text id="SampleDictionaryPolicy_Legacy" maxLength="1000000"'
+ ' valueName="SampleDictionaryPolicy"/>\n'
+ ' <multiText id="SampleDictionaryPolicy" maxLength="1000000"'
+ ' valueName="SampleDictionaryPolicy"/>\n'
+ ' </elements>\n'
+ '</policy>')
+ self.AssertXMLEquals(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py b/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
new file mode 100755
index 00000000000..9f92347a6cb
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import doc_writer
+
+
+def GetWriter(config):
+ '''Factory method for creating DocAtomicGroupsWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return DocAtomicGroupsWriter(['*'], config)
+
+
+class DocAtomicGroupsWriter(doc_writer.DocWriter):
+ '''Class for generating atomic policy group templates in HTML format.
+ The intended use of the generated file is to upload it on
+ https://www.chromium.org, therefore its format has some limitations:
+ - No HTML and body tags.
+ - Restricted set of element attributes: for example no 'class'.
+ Because of the latter the output is styled using the 'style'
+ attributes of HTML elements. This is supported by the dictionary
+ self._STYLES[] and the method self._AddStyledElement(), they try
+ to mimic the functionality of CSS classes. (But without inheritance.)
+
+ This class is invoked by PolicyTemplateGenerator to create the HTML
+ files.
+ '''
+
+ def _AddPolicyRow(self, parent, policy):
+ '''Adds a row for the policy in the summary table.
+
+ Args:
+ parent: The DOM node of the summary table.
+ policy: The data structure of the policy.
+ '''
+ tr = self._AddStyledElement(parent, 'tr', ['tr'])
+ indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14)
+ if policy['type'] != 'group':
+ # Normal policies get two columns with name and caption.
+ name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
+ {'style': indent})
+ policy_ref = './'
+ if self.config.get('local', False):
+ policy_ref = './chrome_policy_list.html'
+ self.AddElement(name_td, 'a', {'href': policy_ref + '#' + policy['name']},
+ policy['name'])
+ self._AddStyledElement(tr, 'td', ['td', 'td.right'], {},
+ policy['caption'])
+ else:
+ # Groups get one column with caption.
+ name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], {
+ 'style': indent,
+ 'colspan': '2'
+ })
+ self.AddElement(name_td, 'a', {'name': policy['name']}, policy['caption'])
+
+ #
+ # Implementation of abstract methods of TemplateWriter:
+ #
+
+ def WritePolicy(self, policy):
+ self._AddPolicyRow(self._summary_tbody, policy)
+
+ def BeginTemplate(self):
+ self._BeginTemplate('group_intro', 'banner')
+
+ def WriteTemplate(self, template):
+ '''Writes the given template definition.
+
+ Args:
+ template: Template definition to write.
+
+ Returns:
+ Generated output for the passed template definition.
+ '''
+ self.messages = template['messages']
+ self.Init()
+
+ policies = self.PreprocessPolicies(
+ template['policy_atomic_group_definitions'])
+
+ self.BeginTemplate()
+ for policy in policies:
+ if policy['type'] != 'atomic_group':
+ continue
+ self.BeginPolicyGroup(policy)
+ for child_policy in policy['policies']:
+ # Nesting of groups is currently not supported.
+ self.WritePolicy(child_policy)
+ self.EndPolicyGroup()
+ self.EndTemplate()
+
+ return self.GetTemplateText()
diff --git a/chromium/components/policy/tools/template_writers/writers/doc_writer.py b/chromium/components/policy/tools/template_writers/writers/doc_writer.py
new file mode 100755
index 00000000000..c5a0104bc9e
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/doc_writer.py
@@ -0,0 +1,883 @@
+#!/usr/bin/env python3
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import re
+from xml.dom import minidom
+from xml.sax.saxutils import escape
+from writers import xml_formatted_writer
+
+
+def GetWriter(config):
+ '''Factory method for creating DocWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return DocWriter(['*'], config)
+
+
+class DocWriter(xml_formatted_writer.XMLFormattedWriter):
+ '''Class for generating policy templates in HTML format.
+ The intended use of the generated file is to upload it on
+ http://dev.chromium.org, therefore its format has some limitations:
+ - No HTML and body tags.
+ - Restricted set of element attributes: for example no 'class'.
+ Because of the latter the output is styled using the 'style'
+ attributes of HTML elements. This is supported by the dictionary
+ self._STYLES[] and the method self._AddStyledElement(), they try
+ to mimic the functionality of CSS classes. (But without inheritance.)
+
+ This class is invoked by PolicyTemplateGenerator to create the HTML
+ files.
+ '''
+
+ def _AddTextWithLinks(self, parent, text):
+ '''Parse a string for URLs and add it to a DOM node with the URLs replaced
+ with <a> HTML links.
+
+ Args:
+ parent: The DOM node to which the text will be added.
+ text: The string to be added.
+ '''
+ # A simple regexp to search for URLs. It is enough for now.
+ url_matcher = re.compile('(https?://[^\\s]*[^\\s\\.\\)\\"])')
+
+ # Iterate through all the URLs and replace them with links.
+ while True:
+ # Look for the first URL.
+ res = url_matcher.search(text)
+ if not res:
+ break
+ # Calculate positions of the substring of the URL.
+ url = res.group(0)
+ start = res.start(0)
+ end = res.end(0)
+ # Add the text prior to the URL.
+ self.AddText(parent, text[:start])
+ # Add a link for the URL.
+ self.AddElement(parent, 'a', {'href': url}, url)
+ # Drop the part of text that is added.
+ text = text[end:]
+ self.AddText(parent, text)
+
+ def _AddParagraphs(self, parent, text):
+ '''Break description into paragraphs and replace URLs with links.
+
+ Args:
+ parent: The DOM node to which the text will be added.
+ text: The string to be added.
+ '''
+ # Split text into list of paragraphs.
+ entries = text.split('\n\n')
+ for entry in entries:
+ # Create a new paragraph node.
+ paragraph = self.AddElement(parent, 'p')
+ # Insert text to the paragraph with processing the URLs.
+ self._AddTextWithLinks(paragraph, entry)
+
+ def _AddStyledElement(self, parent, name, style_ids, attrs=None, text=None):
+ '''Adds an XML element to a parent, with CSS style-sheets included.
+
+ Args:
+ parent: The parent DOM node.
+ name: Name of the element to add.
+ style_ids: A list of CSS style strings from self._STYLE[].
+ attrs: Dictionary of attributes for the element.
+ text: Text content for the element.
+ '''
+ if attrs == None:
+ attrs = {}
+
+ style = ''.join([self._STYLE[x] for x in style_ids])
+ if style != '':
+ # Apply the style specified by style_ids.
+ attrs['style'] = style + attrs.get('style', '')
+ return self.AddElement(parent, name, attrs, text)
+
+ def _AddDescription(self, parent, policy):
+ '''Adds a string containing the description of the policy. URLs are
+ replaced with links and the possible choices are enumerated in case
+ of 'string-enum' and 'int-enum' type policies.
+
+ Args:
+ parent: The DOM node for which the feature list will be added.
+ policy: The data structure of a policy.
+ '''
+ # Add description by paragraphs (URLs will be substituted by links).
+ self._AddParagraphs(parent, policy['desc'])
+ # Add list of enum items.
+ if policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'):
+ ul = self.AddElement(parent, 'ul')
+ for item in policy['items']:
+ if policy['type'] == 'int-enum':
+ value_string = str(item['value'])
+ else:
+ value_string = '"%s"' % item['value']
+ self.AddElement(ul, 'li', {},
+ '%s = %s' % (value_string, item['caption']))
+
+ def _AddSchema(self, parent, schema):
+ '''Adds a schema to a DOM node.
+
+ Args:
+ parent: The DOM node for which the schema will be added.
+ schema: The schema of a policy.
+ '''
+ dd = self._AddPolicyAttribute(parent, 'schema', None,
+ ['.monospace', '.pre-wrap'])
+ # Explicitly specify separators since defaults depend on python version.
+ schema_json = json.dumps(schema,
+ indent=2,
+ sort_keys=True,
+ separators=(", ", ": "))
+ self.AddText(dd, schema_json)
+
+ def _AddFeatures(self, parent, policy):
+ '''Adds a string containing the list of supported features of a policy
+ to a DOM node. The text will look like as:
+ Feature_X: Yes, Feature_Y: No
+
+ Args:
+ parent: The DOM node for which the feature list will be added.
+ policy: The data structure of a policy.
+ '''
+ features = []
+ # The sorting is to make the order well-defined for testing.
+ keys = sorted(policy['features'].keys())
+ for key in keys:
+ key_name = self._FEATURE_MAP[key]
+ if policy['features'][key]:
+ value_name = self.GetLocalizedMessage('supported')
+ else:
+ value_name = self.GetLocalizedMessage('not_supported')
+ features.append('%s: %s' % (key_name, value_name))
+ self.AddText(parent, ', '.join(features))
+
+ def _AddListExampleMac(self, parent, policy):
+ '''Adds an example value for Mac of a 'list' policy to a DOM node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'list', for which the Mac example value
+ is generated.
+ '''
+ example_value = policy['example_value']
+ self.AddElement(parent, 'dt', {}, 'Mac:')
+ mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+
+ mac_text = ['<array>']
+ for item in example_value:
+ mac_text.append(' <string>%s</string>' % item)
+ mac_text.append('</array>')
+ self.AddText(mac, '\n'.join(mac_text))
+
+ def _AddListExampleWindowsChromeOS(self, parent, policy, is_win):
+ '''Adds an example value for Windows or Chromium/Google Chrome OS of a
+ 'list' policy to a DOM node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'list', for which the Windows example value
+ is generated.
+ is_win: True for Windows, False for Chromium/Google Chrome OS.
+ '''
+ example_value = policy['example_value']
+ os_header = self.GetLocalizedMessage('win_example_value') if is_win else \
+ self.GetLocalizedMessage('chrome_os_example_value')
+ self.AddElement(parent, 'dt', {}, os_header)
+ element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+ element_text = []
+ cnt = 1
+ key_name = self._GetRegistryKeyName(policy, is_win)
+ for item in example_value:
+ element_text.append(
+ '%s\\%s\\%d = "%s"' % (key_name, policy['name'], cnt, item))
+ cnt = cnt + 1
+ self.AddText(element, '\n'.join(element_text))
+
+ def _GetRegistryKeyName(self, policy, is_win):
+ use_recommended_key = self.CanBeRecommended(policy) and not \
+ self.CanBeMandatory(policy)
+ platform = 'win' if is_win else 'chrome_os'
+ key = 'reg_recommended_key_name' if use_recommended_key else \
+ 'reg_mandatory_key_name'
+ return self.config['win_config'][platform][key]
+
+ def _GetOmaUriPath(self, policy):
+ product = 'googlechrome' if self.config['build'] == 'chrome' else 'chromium'
+ group = '~' + policy['group'] if 'group' in policy else ''
+ return '.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~%s%s\\%s' % (
+ product, group, policy['name'])
+
+ def _AddListExampleAndroidLinux(self, parent, policy):
+ '''Adds an example value for Android/Linux of a 'list' policy to a DOM node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'list', for which the Android/Linux example value
+ is generated.
+ '''
+ example_value = policy['example_value']
+ self.AddElement(parent, 'dt', {}, 'Android/Linux:')
+ element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+ self.AddText(
+ element,
+ '[\n%s\n]' % ',\n'.join(' "%s"' % item for item in example_value))
+
+ def _AddListExample(self, parent, policy):
+ r'''Adds the example value of a 'list' policy to a DOM node. Example output:
+ <dl>
+ <dt>Windows (Windows clients):</dt>
+ <dd>
+ Software\Policies\Chromium\URLAllowlist\0 = "www.example.com"
+ Software\Policies\Chromium\URLAllowlist\1 = "www.google.com"
+ </dd>
+ <dt>Windows (Chromium OS clients):</dt>
+ <dd>
+ Software\Policies\ChromiumOS\URLAllowlist\0 = "www.example.com"
+ Software\Policies\ChromiumOS\URLAllowlist\1 = "www.google.com"
+ </dd>
+ <dt>Android/Linux:</dt>
+ <dd>
+ [
+ "www.example.com",
+ "www.google.com"
+ ]
+ </dd>
+ <dt>Mac:</dt>
+ <dd>
+ <array>
+ <string>www.example.com</string>
+ <string>www.google.com</string>
+ </array>
+ </dd>
+ </dl>
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: The data structure of a policy.
+ '''
+ examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
+ if self.IsPolicySupportedOnWindows(policy):
+ self._AddListExampleWindowsChromeOS(examples, policy, True)
+ if self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'chrome_os', management='active_directory'):
+ self._AddListExampleWindowsChromeOS(examples, policy, False)
+ if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux')):
+ self._AddListExampleAndroidLinux(examples, policy)
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'):
+ self._AddListExampleMac(examples, policy)
+
+ def _PythonObjectToPlist(self, obj, indent=''):
+ '''Converts a python object to an equivalent XML plist.
+
+ Returns a list of lines.'''
+ obj_type = type(obj)
+ if obj_type == bool:
+ return ['%s<%s/>' % (indent, 'true' if obj else 'false')]
+ elif obj_type == int:
+ return ['%s<integer>%s</integer>' % (indent, obj)]
+ elif obj_type == str:
+ return ['%s<string>%s</string>' % (indent, escape(obj))]
+ elif obj_type == list:
+ result = ['%s<array>' % indent]
+ for item in obj:
+ result += self._PythonObjectToPlist(item, indent + ' ')
+ result.append('%s</array>' % indent)
+ return result
+ elif obj_type == dict:
+ result = ['%s<dict>' % indent]
+ for key in sorted(obj.keys()):
+ result.append('%s<key>%s</key>' % (indent + ' ', key))
+ result += self._PythonObjectToPlist(obj[key], indent + ' ')
+ result.append('%s</dict>' % indent)
+ return result
+ else:
+ raise Exception('Invalid object to convert: %s' % obj)
+
+ def _AddDictionaryExampleMac(self, parent, policy):
+ '''Adds an example value for Mac of a 'dict' or 'external' policy to a DOM
+ node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'dict', for which the Mac example value
+ is generated.
+ '''
+ example_value = policy['example_value']
+ self.AddElement(parent, 'dt', {}, 'Mac:')
+ mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+ mac_text = ['<key>%s</key>' % (policy['name'])]
+ mac_text += self._PythonObjectToPlist(example_value)
+ self.AddText(mac, '\n'.join(mac_text))
+
+ def _AddDictionaryExampleWindowsChromeOS(self, parent, policy, is_win):
+ '''Adds an example value for Windows of a 'dict' or 'external' policy to a
+ DOM node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'dict', for which the Windows example value
+ is generated.
+ '''
+ os_header = self.GetLocalizedMessage('win_example_value') if is_win else \
+ self.GetLocalizedMessage('chrome_os_example_value')
+ self.AddElement(parent, 'dt', {}, os_header)
+ element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+ key_name = self._GetRegistryKeyName(policy, is_win)
+ # Explicitly specify separators since defaults depend on python version.
+ example = json.dumps(policy['example_value'],
+ indent=2,
+ sort_keys=True,
+ separators=(", ", ": "))
+ self.AddText(element, '%s\\%s = %s' % (key_name, policy['name'], example))
+
+ def _AddDictionaryExampleAndroidLinux(self, parent, policy):
+ '''Adds an example value for Android/Linux of a 'dict' or 'external' policy
+ to a DOM node.
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: A policy of type 'dict', for which the Android/Linux example value
+ is generated.
+ '''
+ self.AddElement(parent, 'dt', {}, 'Android/Linux:')
+ element = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre-wrap'])
+ # Explicitly specify separators since defaults depend on python version.
+ example = json.dumps(policy['example_value'],
+ indent=2,
+ sort_keys=True,
+ separators=(", ", ": "))
+ self.AddText(element, '%s: %s' % (policy['name'], example))
+
+ def _AddDictionaryExample(self, parent, policy):
+ '''Adds the example value of a 'dict' or 'external' policy to a DOM node.
+
+ Example output:
+ <dl>
+ <dt>Windows (Windows clients):</dt>
+ <dd>
+ Software\Policies\Chromium\ProxySettings = {
+ "ProxyMode": "direct"
+ }
+ </dd>
+ <dt>Windows (Chromium OS clients):</dt>
+ <dd>
+ Software\Policies\ChromiumOS\ProxySettings = {
+ "ProxyMode": "direct"
+ }
+ </dd>
+ <dt>Android/Linux:</dt>
+ <dd>
+ ProxySettings: {
+ "ProxyMode": "direct"
+ }
+ </dd>
+ <dt>Mac:</dt>
+ <dd>
+ <key>ProxySettings</key>
+ <dict>
+ <key>ProxyMode</key>
+ <string>direct</string>
+ </dict>
+ </dd>
+ </dl>
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: The data structure of a policy.
+ '''
+ examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
+ if self.IsPolicySupportedOnWindows(policy):
+ self._AddDictionaryExampleWindowsChromeOS(examples, policy, True)
+ if self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'chrome_os', management='active_directory'):
+ self._AddDictionaryExampleWindowsChromeOS(examples, policy, False)
+ if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux')):
+ self._AddDictionaryExampleAndroidLinux(examples, policy)
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'):
+ self._AddDictionaryExampleMac(examples, policy)
+
+ def _AddIntuneExample(self, parent, policy):
+ example_value = policy['example_value']
+ policy_type = policy['type']
+
+ container = self._AddStyledElement(parent, 'dl', [])
+ self.AddElement(container, 'dt', {}, 'Windows (Intune):')
+
+ if policy_type == 'main':
+ self._AddStyledElement(
+ container,
+ 'dd', ['.monospace', '.pre-wrap'],
+ text='<enabled/>' if example_value else '<disabled/>')
+ return
+
+ self._AddStyledElement(
+ container, 'dd', ['.monospace', '.pre-wrap'], text='<enabled/>')
+ if policy_type == 'list':
+ values = [
+ '%s&#xF000;%s' % (index, value)
+ for index, value in enumerate(example_value, start=1)
+ ]
+ self._AddStyledElement(
+ container,
+ 'dd', ['.monospace', '.pre-wrap'],
+ text='<data id="%s" value="%s"/>' % (policy['name'] + 'Desc',
+ '&#xF000;'.join(values)))
+ return
+ elif policy_type == 'int' or policy_type == 'int-enum':
+ self._AddStyledElement(
+ container,
+ 'dd', ['.monospace', '.pre-wrap'],
+ text='<data id="%s" value="%s"/>' % (policy['name'], example_value))
+ else:
+ self._AddStyledElement(
+ container,
+ 'dd', ['.monospace', '.pre-wrap'],
+ text='<data id="%s" value="%s"/>' % (policy['name'],
+ json.dumps(example_value)[1:-1]))
+ return
+
+ def _AddExample(self, parent, policy):
+ '''Adds the HTML DOM representation of the example value of a policy to
+ a DOM node. It is simple text for boolean policies, like
+ '0x00000001 (Windows), true (Linux), true (Android), <true /> (Mac)'
+ in case of boolean policies, but it may also contain other HTML elements.
+ (See method _AddListExample.)
+
+ Args:
+ parent: The DOM node for which the example will be added.
+ policy: The data structure of a policy.
+
+ Raises:
+ Exception: If the type of the policy is unknown or the example value
+ of the policy is out of its expected range.
+ '''
+ example_value = policy['example_value']
+ policy_type = policy['type']
+ if policy_type == 'main':
+ pieces = []
+ if self.IsPolicySupportedOnWindows(policy) or \
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'chrome_os',
+ management='active_directory'):
+ value = '0x00000001' if example_value else '0x00000000'
+ pieces.append(value + ' (Windows)')
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux'):
+ value = 'true' if example_value else 'false'
+ pieces.append(value + ' (Linux)')
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'android'):
+ value = 'true' if example_value else 'false'
+ pieces.append(value + ' (Android)')
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'):
+ value = '<true />' if example_value else '<false />'
+ pieces.append(value + ' (Mac)')
+ self.AddText(parent, ', '.join(pieces))
+ elif policy_type == 'string':
+ self.AddText(parent, '"%s"' % example_value)
+ elif policy_type in ('int', 'int-enum'):
+ pieces = []
+ if self.IsPolicySupportedOnWindows(policy) or \
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'chrome_os',
+ management='active_directory'):
+ pieces.append('0x%08x (Windows)' % example_value)
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux'):
+ pieces.append('%d (Linux)' % example_value)
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'android'):
+ pieces.append('%d (Android)' % example_value)
+ if self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac'):
+ pieces.append('%d (Mac)' % example_value)
+ self.AddText(parent, ', '.join(pieces))
+ elif policy_type == 'string-enum':
+ self.AddText(parent, '"%s"' % (example_value))
+ elif policy_type in ('list', 'string-enum-list'):
+ self._AddListExample(parent, policy)
+ elif policy_type in ('dict', 'external'):
+ self._AddDictionaryExample(parent, policy)
+ else:
+ raise Exception('Unknown policy type: ' + policy_type)
+
+ if self.IsPolicySupportedOnWindows(policy):
+ self._AddIntuneExample(parent, policy)
+
+ def _AddPolicyAttribute(self,
+ dl,
+ term_id,
+ definition=None,
+ definition_style=None):
+ '''Adds a term-definition pair to a HTML DOM <dl> node. This method is
+ used by _AddPolicyDetails. Its result will have the form of:
+ <dt style="...">...</dt>
+ <dd style="...">...</dd>
+
+ Args:
+ dl: The DOM node of the <dl> list.
+ term_id: A key to self._STRINGS[] which specifies the term of the pair.
+ definition: The text of the definition. (Optional.)
+ definition_style: List of references to values self._STYLE[] that specify
+ the CSS stylesheet of the <dd> (definition) element.
+
+ Returns:
+ The DOM node representing the definition <dd> element.
+ '''
+ # Avoid modifying the default value of definition_style.
+ if definition_style == None:
+ definition_style = []
+ term = self.GetLocalizedMessage(term_id)
+ self._AddStyledElement(dl, 'dt', ['dt'], {}, term)
+ return self._AddStyledElement(dl, 'dd', definition_style, {}, definition)
+
+ def _AddSupportedOnList(self, parent, supported_on_list):
+ '''Creates a HTML list containing the platforms, products and versions
+ that are specified in the list of supported_on.
+
+ Args:
+ parent: The DOM node for which the list will be added.
+ supported_on_list: The list of supported products, as a list of
+ dictionaries.
+ '''
+ ul = self._AddStyledElement(parent, 'ul', ['ul'])
+ for supported_on in supported_on_list:
+ text = []
+ product = supported_on['product']
+ platform = supported_on['platform']
+ text.append(self._PRODUCT_MAP[product])
+ text.append('(%s)' % (self._PLATFORM_MAP[platform]))
+ if supported_on['since_version']:
+ since_version = self.GetLocalizedMessage('since_version')
+ text.append(since_version.replace('$6', supported_on['since_version']))
+ if supported_on['until_version']:
+ until_version = self.GetLocalizedMessage('until_version')
+ text.append(until_version.replace('$6', supported_on['until_version']))
+ # Add the list element:
+ self.AddElement(ul, 'li', {}, ' '.join(text))
+
+ def _AddRangeRestrictionsList(self, parent, schema):
+ '''Creates a HTML list containing range restrictions for an integer type
+ policy.
+
+ Args:
+ parent: The DOM node for which the list will be added.
+ schema: The schema of the policy.
+ '''
+ ul = self._AddStyledElement(parent, 'ul', ['ul'])
+ if 'minimum' in schema:
+ text_min = self.GetLocalizedMessage('range_minimum')
+ self.AddElement(ul, 'li', {}, text_min + str(schema['minimum']))
+ if 'maximum' in schema:
+ text_max = self.GetLocalizedMessage('range_maximum')
+ self.AddElement(ul, 'li', {}, text_max + str(schema['maximum']))
+
+ def _AddPolicyDetails(self, parent, policy):
+ '''Adds the list of attributes of a policy to the HTML DOM node parent.
+ It will have the form:
+ <dl>
+ <dt>Attribute:</dt><dd>Description</dd>
+ ...
+ </dl>
+
+ Args:
+ parent: A DOM element for which the list will be added.
+ policy: The data structure of the policy.
+ '''
+
+ dl = self.AddElement(parent, 'dl')
+ data_type = [self._TYPE_MAP[policy['type']]]
+ qualified_types = []
+ is_complex_policy = False
+ if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') and
+ self._RESTRICTION_TYPE_MAP.get(policy['type'], None)):
+ qualified_types.append(
+ 'Android:%s' % self._RESTRICTION_TYPE_MAP[policy['type']])
+ if policy['type'] in ('dict', 'external', 'list'):
+ is_complex_policy = True
+ if ((self.IsPolicySupportedOnWindows(policy) or
+ self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'chrome_os', management='active_directory')) and
+ self._REG_TYPE_MAP.get(policy['type'], None)):
+ qualified_types.append('Windows:%s' % self._REG_TYPE_MAP[policy['type']])
+ if policy['type'] in ('dict', 'external'):
+ is_complex_policy = True
+ if qualified_types:
+ data_type.append('[%s]' % ', '.join(qualified_types))
+ if is_complex_policy:
+ data_type.append(
+ '(%s)' % self.GetLocalizedMessage('complex_policies_on_windows'))
+ self._AddPolicyAttribute(dl, 'data_type', ' '.join(data_type))
+ if self.IsPolicySupportedOnWindows(policy):
+ registry_key_name = self._GetRegistryKeyName(policy, True)
+ self._AddPolicyAttribute(dl, 'win_reg_loc',
+ registry_key_name + '\\' + policy['name'],
+ ['.monospace'])
+ self._AddPolicyAttribute(dl, 'oma_uri', self._GetOmaUriPath(policy),
+ ['.monospace'])
+
+ if self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'chrome_os', management='active_directory'):
+ key_name = self._GetRegistryKeyName(policy, False)
+ self._AddPolicyAttribute(dl, 'chrome_os_reg_loc',
+ key_name + '\\' + policy['name'], ['.monospace'])
+ if (self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux') or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac')):
+ self._AddPolicyAttribute(dl, 'mac_linux_pref_name', policy['name'],
+ ['.monospace'])
+ if self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'android', product='chrome'):
+ self._AddPolicyAttribute(dl, 'android_restriction_name', policy['name'],
+ ['.monospace'])
+ if self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'android', product='webview'):
+ restriction_prefix = self.config['android_webview_restriction_prefix']
+ self._AddPolicyAttribute(dl, 'android_webview_restriction_name',
+ restriction_prefix + policy['name'],
+ ['.monospace'])
+ dd = self._AddPolicyAttribute(dl, 'supported_on')
+ self._AddSupportedOnList(dd, policy['supported_on'])
+ dd = self._AddPolicyAttribute(dl, 'supported_features')
+ self._AddFeatures(dd, policy)
+ dd = self._AddPolicyAttribute(dl, 'description')
+ self._AddDescription(dd, policy)
+ if 'schema' in policy:
+ if self.SchemaHasRangeRestriction(policy['schema']):
+ dd = self._AddPolicyAttribute(dl, 'policy_restriction')
+ self._AddRangeRestrictionsList(dd, policy['schema'])
+ if 'arc_support' in policy:
+ dd = self._AddPolicyAttribute(dl, 'arc_support')
+ self._AddParagraphs(dd, policy['arc_support'])
+ if policy['type'] in ('dict', 'external') and 'schema' in policy:
+ self._AddSchema(dl, policy['schema'])
+ if 'validation_schema' in policy:
+ self._AddSchema(dl, policy['validation_schema'])
+ if 'description_schema' in policy:
+ self._AddSchema(dl, policy['description_schema'])
+ if 'url_schema' in policy:
+ dd = self._AddPolicyAttribute(dl, 'url_schema')
+ self._AddTextWithLinks(dd, policy['url_schema'])
+ if (self.IsPolicySupportedOnWindows(policy) or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'linux') or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'android') or
+ self.IsPolicyOrItemSupportedOnPlatform(policy, 'mac') or
+ self.IsPolicyOrItemSupportedOnPlatform(
+ policy, 'chrome_os', management='active_directory')):
+ # Don't add an example for Google cloud managed ChromeOS policies.
+ dd = self._AddPolicyAttribute(dl, 'example_value')
+ self._AddExample(dd, policy)
+ if 'atomic_group' in policy:
+ dd = self._AddPolicyAttribute(dl, 'policy_atomic_group')
+ policy_group_ref = './policy-list-3/atomic_groups'
+ if 'local' in self.config and self.config['local']:
+ policy_group_ref = './chrome_policy_atomic_groups_list.html'
+ self.AddText(dd, self.GetLocalizedMessage('policy_in_atomic_group') + ' ')
+ self.AddElement(dd, 'a',
+ {'href': policy_group_ref + '#' + policy['atomic_group']},
+ policy['atomic_group'])
+
+ def _AddPolicyRow(self, parent, policy):
+ '''Adds a row for the policy in the summary table.
+
+ Args:
+ parent: The DOM node of the summary table.
+ policy: The data structure of the policy.
+ '''
+ tr = self._AddStyledElement(parent, 'tr', ['tr'])
+ indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14)
+ if policy['type'] != 'group':
+ # Normal policies get two columns with name and caption.
+ name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
+ {'style': indent})
+ self.AddElement(name_td, 'a', {'href': '#' + policy['name']},
+ policy['name'])
+ self._AddStyledElement(tr, 'td', ['td', 'td.right'], {},
+ policy['caption'])
+ else:
+ # Groups get one column with caption.
+ name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'], {
+ 'style': indent,
+ 'colspan': '2'
+ })
+ self.AddElement(name_td, 'a', {'href': '#' + policy['name']},
+ policy['caption'])
+
+ def _AddPolicySection(self, parent, policy):
+ '''Adds a section about the policy in the detailed policy listing.
+
+ Args:
+ parent: The DOM node of the <div> of the detailed policy list.
+ policy: The data structure of the policy.
+ '''
+ # Set style according to group nesting level.
+ indent = 'margin-left: %dpx' % (self._indent_level * 28)
+ if policy['type'] == 'group':
+ heading = 'h2'
+ else:
+ heading = 'h3'
+ parent2 = self.AddElement(parent, 'div', {'style': indent})
+
+ h2 = self.AddElement(parent2, heading)
+ self.AddElement(h2, 'a', {'name': policy['name']})
+ if policy['type'] != 'group':
+ # Normal policies get a full description.
+ policy_name_text = policy['name']
+ if 'deprecated' in policy and policy['deprecated'] == True:
+ policy_name_text += " ("
+ policy_name_text += self.GetLocalizedMessage('deprecated') + ")"
+ self.AddText(h2, policy_name_text)
+ self.AddElement(parent2, 'span', {}, policy['caption'])
+ self._AddPolicyDetails(parent2, policy)
+ else:
+ # Groups get a more compact description.
+ self.AddText(h2, policy['caption'])
+ self._AddStyledElement(parent2, 'div', ['div.group_desc'], {},
+ policy['desc'])
+ self.AddElement(parent2, 'a', {'href': '#top'},
+ self.GetLocalizedMessage('back_to_top'))
+
+ def SchemaHasRangeRestriction(self, schema):
+ if 'maximum' in schema:
+ return True
+ if 'minimum' in schema:
+ return schema['minimum'] != 0
+ return False
+
+ def _BeginTemplate(self, intro_message_id, banner_message_id):
+ # Add a <div> for the summary section.
+ if self._GetChromiumVersionString() is not None:
+ self.AddComment(self._main_div, self.config['build'] + \
+ ' version: ' + self._GetChromiumVersionString())
+
+ banner_div = self._AddStyledElement(self._main_div, 'div', ['div.banner'],
+ {}, '')
+ self._AddParagraphs(banner_div, self.GetLocalizedMessage(banner_message_id))
+ summary_div = self.AddElement(self._main_div, 'div')
+ self.AddElement(summary_div, 'a', {'name': 'top'})
+ self.AddElement(summary_div, 'br')
+ self._AddParagraphs(summary_div, self.GetLocalizedMessage(intro_message_id))
+ self.AddElement(summary_div, 'br')
+ self.AddElement(summary_div, 'br')
+ self.AddElement(summary_div, 'br')
+ # Add the summary table of policies.
+ summary_table = self._AddStyledElement(summary_div, 'table', ['table'])
+ # Add the first row.
+ thead = self.AddElement(summary_table, 'thead')
+ tr = self._AddStyledElement(thead, 'tr', ['tr'])
+ self._AddStyledElement(tr, 'td', ['td', 'td.left', 'thead td'], {},
+ self.GetLocalizedMessage('name_column_title'))
+ self._AddStyledElement(tr, 'td', ['td', 'td.right', 'thead td'], {},
+ self.GetLocalizedMessage('description_column_title'))
+ self._summary_tbody = self.AddElement(summary_table, 'tbody')
+
+ # Add a <div> for the detailed policy listing.
+ self._details_div = self.AddElement(self._main_div, 'div')
+
+ #
+ # Implementation of abstract methods of TemplateWriter:
+ #
+
+ def IsDeprecatedPolicySupported(self, policy):
+ return True
+
+ def WritePolicy(self, policy):
+ self._AddPolicyRow(self._summary_tbody, policy)
+ self._AddPolicySection(self._details_div, policy)
+
+ def BeginPolicyGroup(self, group):
+ self.WritePolicy(group)
+ self._indent_level += 1
+
+ def EndPolicyGroup(self):
+ self._indent_level -= 1
+
+ def BeginTemplate(self):
+ self._BeginTemplate('intro', 'banner')
+
+ def Init(self):
+ dom_impl = minidom.getDOMImplementation('')
+ self._doc = dom_impl.createDocument(None, 'html', None)
+ body = self.AddElement(self._doc.documentElement, 'body')
+ self._main_div = self.AddElement(body, 'div')
+ self._indent_level = 0
+
+ # Human-readable names of supported platforms.
+ self._PLATFORM_MAP = {
+ 'win': 'Windows',
+ 'mac': 'Mac',
+ 'linux': 'Linux',
+ 'chrome_os': self.config['os_name'],
+ 'android': 'Android',
+ 'win7': 'Windows 7',
+ 'ios': 'iOS',
+ }
+ # Human-readable names of supported products.
+ self._PRODUCT_MAP = {
+ 'chrome': self.config['app_name'],
+ 'chrome_frame': self.config['frame_name'],
+ 'chrome_os': self.config['os_name'],
+ 'webview': self.config['webview_name'],
+ }
+ # Human-readable names of supported features. Each supported feature has
+ # a 'doc_feature_X' entry in |self.messages|.
+ self._FEATURE_MAP = {}
+ for message in self.messages:
+ if message.startswith('doc_feature_'):
+ self._FEATURE_MAP[message[12:]] = self.messages[message]['text']
+ # Human-readable names of types.
+ self._TYPE_MAP = {
+ 'string': 'String',
+ 'int': 'Integer',
+ 'main': 'Boolean',
+ 'int-enum': 'Integer',
+ 'string-enum': 'String',
+ 'list': 'List of strings',
+ 'string-enum-list': 'List of strings',
+ 'dict': 'Dictionary',
+ 'external': 'External data reference',
+ }
+ self._REG_TYPE_MAP = {
+ 'string': 'REG_SZ',
+ 'int': 'REG_DWORD',
+ 'main': 'REG_DWORD',
+ 'int-enum': 'REG_DWORD',
+ 'string-enum': 'REG_SZ',
+ 'dict': 'REG_SZ',
+ 'external': 'REG_SZ',
+ }
+ self._RESTRICTION_TYPE_MAP = {
+ 'int-enum': 'choice',
+ 'string-enum': 'choice',
+ 'list': 'string',
+ 'string-enum-list': 'multi-select',
+ 'dict': 'string',
+ 'external': 'string',
+ }
+ # The CSS style-sheet used for the document. It will be used in Google
+ # Sites, which strips class attributes from HTML tags. To work around this,
+ # the style-sheet is a dictionary and the style attributes will be added
+ # "by hand" for each element.
+ self._STYLE = {
+ 'div.banner': 'background-color: rgb(244,204,204); font-size: x-large; '
+ 'border: 1px solid red; padding: 20px; '
+ 'text-align: center;',
+ 'table': 'border-style: none; border-collapse: collapse;',
+ 'tr': 'height: 0px;',
+ 'td': 'border: 1px dotted rgb(170, 170, 170); padding: 7px; '
+ 'vertical-align: top; width: 236px; height: 15px;',
+ 'thead td': 'font-weight: bold;',
+ 'td.left': 'width: 200px;',
+ 'td.right': 'width: 100%;',
+ 'dt': 'font-weight: bold;',
+ 'dd dl': 'margin-top: 0px; margin-bottom: 0px;',
+ '.monospace': 'font-family: monospace;',
+ '.pre-wrap': 'white-space: pre-wrap;',
+ 'div.note': 'border: 2px solid black; padding: 5px; margin: 5px;',
+ 'div.group_desc': 'margin-top: 20px; margin-bottom: 20px;',
+ 'ul': 'padding-left: 0px; margin-left: 0px;'
+ }
+
+ def GetTemplateText(self):
+ # Return the text representation of the main <div> tag.
+ return self._main_div.toxml()
+ # To get a complete HTML file, use the following.
+ # return self._doc.toxml()
diff --git a/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py
new file mode 100755
index 00000000000..8be4dd376c9
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/doc_writer_unittest.py
@@ -0,0 +1,1835 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.doc_writer'''
+
+import json
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+from xml.dom import minidom
+
+from writers import writer_unittest_common
+from writers import doc_writer
+
+
+class MockMessageDictionary:
+ '''A mock dictionary passed to a writer as the dictionary of
+ localized messages.
+ '''
+
+ # Dictionary of messages.
+ msg_dict = {}
+
+
+class DocWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for DocWriter.'''
+
+ def setUp(self):
+ # Create a writer for the tests.
+ self.writer = doc_writer.GetWriter(
+ config={
+ 'app_name': 'Chrome',
+ 'frame_name': 'Chrome Frame',
+ 'os_name': 'Chrome OS',
+ 'webview_name': 'WebView',
+ 'android_webview_restriction_prefix': 'mock.prefix:',
+ 'win_config': {
+ 'win': {
+ 'reg_mandatory_key_name': 'MockKey',
+ 'reg_recommended_key_name': 'MockKeyRec',
+ },
+ 'chrome_os': {
+ 'reg_mandatory_key_name': 'MockKeyCrOS',
+ 'reg_recommended_key_name': 'MockKeyCrOSRec',
+ },
+ },
+ 'build': 'test_product',
+ })
+ self.writer.messages = {
+ 'doc_back_to_top': {
+ 'text': '_test_back_to_top'
+ },
+ 'doc_complex_policies_on_windows': {
+ 'text': '_test_complex_policies_win'
+ },
+ 'doc_data_type': {
+ 'text': '_test_data_type'
+ },
+ 'doc_description': {
+ 'text': '_test_description'
+ },
+ 'doc_schema': {
+ 'text': '_test_schema'
+ },
+ 'doc_url_schema': {
+ 'text': '_test_url_schema'
+ },
+ 'doc_arc_support': {
+ 'text': '_test_arc_support'
+ },
+ 'doc_description_column_title': {
+ 'text': '_test_description_column_title'
+ },
+ 'doc_example_value': {
+ 'text': '_test_example_value'
+ },
+ 'doc_win_example_value': {
+ 'text': '_test_example_value_win'
+ },
+ 'doc_chrome_os_example_value': {
+ 'text': '_test_example_value_chrome_os'
+ },
+ 'doc_feature_dynamic_refresh': {
+ 'text': '_test_feature_dynamic_refresh'
+ },
+ 'doc_feature_can_be_recommended': {
+ 'text': '_test_feature_recommended'
+ },
+ 'doc_feature_can_be_mandatory': {
+ 'text': '_test_feature_mandatory'
+ },
+ 'doc_banner': {
+ 'text': '_test_banner'
+ },
+ 'doc_intro': {
+ 'text': '_test_intro'
+ },
+ 'doc_mac_linux_pref_name': {
+ 'text': '_test_mac_linux_pref_name'
+ },
+ 'doc_android_restriction_name': {
+ 'text': '_test_android_restriction_name'
+ },
+ 'doc_android_webview_restriction_name': {
+ 'text': '_test_android_webview_restriction_name'
+ },
+ 'doc_note': {
+ 'text': '_test_note'
+ },
+ 'doc_name_column_title': {
+ 'text': '_test_name_column_title'
+ },
+ 'doc_not_supported': {
+ 'text': '_test_not_supported'
+ },
+ 'doc_since_version': {
+ 'text': '..$6..'
+ },
+ 'doc_supported': {
+ 'text': '_test_supported'
+ },
+ 'doc_supported_features': {
+ 'text': '_test_supported_features'
+ },
+ 'doc_supported_on': {
+ 'text': '_test_supported_on'
+ },
+ 'doc_win_reg_loc': {
+ 'text': '_test_win_reg_loc'
+ },
+ 'doc_oma_uri': {
+ 'text': '_test_oma_uri'
+ },
+ 'doc_chrome_os_reg_loc': {
+ 'text': '_test_chrome_os_reg_loc'
+ },
+ 'doc_bla': {
+ 'text': '_test_bla'
+ },
+ 'doc_policy_atomic_group': {
+ 'text': '_test_policy_atomic_group'
+ },
+ 'doc_policy_in_atomic_group': {
+ 'text': '_test_policy_in_atomic_group'
+ }
+ }
+ self.writer.Init()
+
+ # It is not worth testing the exact content of style attributes.
+ # Therefore we override them here with shorter texts.
+ for key in self.writer._STYLE.keys():
+ self.writer._STYLE[key] = 'style_%s;' % key
+ # Add some more style attributes for additional testing.
+ self.writer._STYLE['key1'] = 'style1;'
+ self.writer._STYLE['key2'] = 'style2;'
+
+ # Create a DOM document for the tests.
+ dom_impl = minidom.getDOMImplementation('')
+ self.doc = dom_impl.createDocument(None, 'root', None)
+ self.doc_root = self.doc.documentElement
+
+ def testSkeleton(self):
+ # Test if DocWriter creates the skeleton of the document correctly.
+ self.writer.BeginTemplate()
+ self.assertEquals(
+ self.writer._main_div.toxml(), '<div>'
+ '<div style="style_div.banner;"><p>_test_banner</p></div>'
+ '<div>'
+ '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>'
+ '<table style="style_table;">'
+ '<thead><tr style="style_tr;">'
+ '<td style="style_td;style_td.left;style_thead td;">'
+ '_test_name_column_title'
+ '</td>'
+ '<td style="style_td;style_td.right;style_thead td;">'
+ '_test_description_column_title'
+ '</td>'
+ '</tr></thead>'
+ '<tbody/>'
+ '</table>'
+ '</div>'
+ '<div/>'
+ '</div>')
+
+ def testVersionAnnotation(self):
+ # Test if DocWriter creates the skeleton of the document correctly.
+ self.writer.config['version'] = '39.0.0.0'
+ self.writer.BeginTemplate()
+ self.assertEquals(
+ self.writer._main_div.toxml(), '<div>'
+ '<!--test_product version: 39.0.0.0-->'
+ '<div style="style_div.banner;"><p>_test_banner</p></div>'
+ '<div>'
+ '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>'
+ '<table style="style_table;">'
+ '<thead><tr style="style_tr;">'
+ '<td style="style_td;style_td.left;style_thead td;">'
+ '_test_name_column_title'
+ '</td>'
+ '<td style="style_td;style_td.right;style_thead td;">'
+ '_test_description_column_title'
+ '</td>'
+ '</tr></thead>'
+ '<tbody/>'
+ '</table>'
+ '</div>'
+ '<div/>'
+ '</div>')
+
+ def testGetLocalizedMessage(self):
+ # Test if localized messages are retrieved correctly.
+ self.writer.messages = {'doc_hello_world': {'text': 'hello, vilag!'}}
+ self.assertEquals(
+ self.writer.GetLocalizedMessage('hello_world'), 'hello, vilag!')
+
+ def testAddStyledElement(self):
+ # Test function DocWriter.AddStyledElement()
+
+ # Test the case of zero style.
+ e1 = self.writer._AddStyledElement(self.doc_root, 'z', [], {'a': 'b'},
+ 'text')
+ self.assertEquals(e1.toxml(), '<z a="b">text</z>')
+
+ # Test the case of one style.
+ e2 = self.writer._AddStyledElement(self.doc_root, 'z', ['key1'], {'a': 'b'},
+ 'text')
+ self.assertEquals(e2.toxml(), '<z a="b" style="style1;">text</z>')
+
+ # Test the case of two styles.
+ e3 = self.writer._AddStyledElement(self.doc_root, 'z', ['key1', 'key2'],
+ {'a': 'b'}, 'text')
+ self.assertEquals(e3.toxml(), '<z a="b" style="style1;style2;">text</z>')
+
+ def testAddDescriptionIntEnum(self):
+ # Test if URLs are replaced and choices of 'int-enum' policies are listed
+ # correctly.
+ policy = {
+ 'type':
+ 'int-enum',
+ 'items': [
+ {
+ 'value': 0,
+ 'caption': 'Disable foo'
+ },
+ {
+ 'value': 2,
+ 'caption': 'Solve your problem'
+ },
+ {
+ 'value': 5,
+ 'caption': 'Enable bar'
+ },
+ ],
+ 'desc':
+ '''This policy disables foo, except in case of bar.
+See http://policy-explanation.example.com for more details.
+'''
+ }
+ self.writer._AddDescription(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '''<root><p>This policy disables foo, except in case of bar.
+See <a href="http://policy-explanation.example.com">http://policy-explanation.example.com</a> for more details.
+</p><ul><li>0 = Disable foo</li><li>2 = Solve your problem</li><li>5 = Enable bar</li></ul></root>'''
+ )
+
+ def testAddDescriptionStringEnum(self):
+ # Test if URLs are replaced and choices of 'int-enum' policies are listed
+ # correctly.
+ policy = {
+ 'type':
+ 'string-enum',
+ 'items': [
+ {
+ 'value': "one",
+ 'caption': 'Disable foo'
+ },
+ {
+ 'value': "two",
+ 'caption': 'Solve your problem'
+ },
+ {
+ 'value': "three",
+ 'caption': 'Enable bar'
+ },
+ ],
+ 'desc':
+ '''This policy disables foo, except in case of bar.
+See http://policy-explanation.example.com for more details.
+'''
+ }
+ self.writer._AddDescription(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '''<root><p>This policy disables foo, except in case of bar.
+See <a href="http://policy-explanation.example.com">http://policy-explanation.example.com</a> for more details.
+</p><ul><li>&quot;one&quot; = Disable foo</li><li>&quot;two&quot; = Solve your problem</li><li>&quot;three&quot; = Enable bar</li></ul></root>'''
+ )
+
+ def testAddSchema(self):
+ # Test if the schema of a policy is handled correctly.
+ policy = {
+ 'type': 'dict',
+ 'schema': {
+ 'properties': {
+ 'foo': {
+ 'type': 'integer'
+ }
+ },
+ 'type': 'object'
+ }
+ }
+ self.writer._AddSchema(self.doc_root, policy['schema'])
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<dt style="style_dt;">_test_schema</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">{\n'
+ ' &quot;properties&quot;: {\n'
+ ' &quot;foo&quot;: {\n'
+ ' &quot;type&quot;: &quot;integer&quot;\n'
+ ' }\n'
+ ' }, \n'
+ ' &quot;type&quot;: &quot;object&quot;\n'
+ '}</dd></root>')
+
+ def testAddUrlSchema(self):
+ # Test if the expanded schema description of a policy is handled correctly.
+ policy = {'url_schema': 'https://example.com/details'}
+ self.writer._AddTextWithLinks(self.doc_root, policy['url_schema'])
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '<root><a href="https://example.com/details">https://example.com/details</a></root>'
+ )
+
+ def testAddFeatures(self):
+ # Test if the list of features of a policy is handled correctly.
+ policy = {
+ 'features': {
+ 'spaceship_docking': False,
+ 'dynamic_refresh': True,
+ 'can_be_recommended': True,
+ }
+ }
+ self.writer._FEATURE_MAP = {
+ 'can_be_recommended': 'Can Be Recommended',
+ 'dynamic_refresh': 'Dynamic Refresh',
+ 'spaceship_docking': 'Spaceship Docking',
+ }
+ self.writer._AddFeatures(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ 'Can Be Recommended: _test_supported, '
+ 'Dynamic Refresh: _test_supported, '
+ 'Spaceship Docking: _test_not_supported'
+ '</root>')
+
+ def testAddListExample(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'example_value': ['Foo', 'Bar'],
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'mac'
+ }, {
+ 'platform': 'linux'
+ }, {
+ 'platform': 'chrome_os'
+ }]
+ }
+ self.writer._AddListExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<dl style="style_dd dl;">'
+ '<dt>_test_example_value_win</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'MockKey\\PolicyName\\1 = &quot;Foo&quot;\n'
+ 'MockKey\\PolicyName\\2 = &quot;Bar&quot;'
+ '</dd>'
+ '<dt>_test_example_value_chrome_os</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'MockKeyCrOS\\PolicyName\\1 = &quot;Foo&quot;\n'
+ 'MockKeyCrOS\\PolicyName\\2 = &quot;Bar&quot;'
+ '</dd>'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '[\n'
+ ' &quot;Foo&quot;,\n'
+ ' &quot;Bar&quot;\n'
+ ']'
+ '</dd>'
+ '<dt>Mac:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '&lt;array&gt;\n'
+ ' &lt;string&gt;Foo&lt;/string&gt;\n'
+ ' &lt;string&gt;Bar&lt;/string&gt;\n'
+ '&lt;/array&gt;'
+ '</dd>'
+ '</dl>'
+ '</root>')
+
+ def testBoolExample(self):
+ # Test representation of boolean example values.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'type':
+ 'main',
+ 'example_value':
+ True,
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'mac'
+ }, {
+ 'platform': 'linux'
+ }, {
+ 'platform': 'android'
+ }]
+ }
+ e1 = self.writer.AddElement(self.doc_root, 'e1')
+ self.writer._AddExample(e1, policy)
+ self.assertEquals(
+ e1.toxml(), '<e1>0x00000001 (Windows),'
+ ' true (Linux), true (Android),'
+ ' &lt;true /&gt; (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd></dl>'
+ '</e1>')
+
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'type':
+ 'main',
+ 'example_value':
+ False,
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'mac'
+ }, {
+ 'platform': 'linux'
+ }, {
+ 'platform': 'android'
+ }]
+ }
+ e2 = self.writer.AddElement(self.doc_root, 'e2')
+ self.writer._AddExample(e2, policy)
+ self.assertEquals(
+ e2.toxml(), '<e2>0x00000000 (Windows),'
+ ' false (Linux), false (Android),'
+ ' &lt;false /&gt; (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;disabled/&gt;</dd></dl>'
+ '</e2>')
+
+ def testIntEnumExample(self):
+ # Test representation of 'int-enum' example values.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'type':
+ 'int-enum',
+ 'example_value':
+ 16,
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'mac'
+ }, {
+ 'platform': 'linux'
+ }, {
+ 'platform': 'android'
+ }]
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '<root>0x00000010 (Windows), 16 (Linux), 16 (Android), 16 (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;16&quot;/&gt;</dd></dl>'
+ '</root>')
+
+ def testStringEnumExample(self):
+ # Test representation of 'string-enum' example values.
+ policy = {
+ 'name': 'PolicyName',
+ 'type': 'string-enum',
+ 'example_value': "wacky",
+ 'supported_on': []
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(self.doc_root.toxml(), '<root>&quot;wacky&quot;</root>')
+
+ def testListExample(self):
+ # Test representation of 'list' example values.
+ policy = {
+ 'name': 'PolicyName',
+ 'type': 'list',
+ 'example_value': ['one', 'two'],
+ 'supported_on': [{
+ 'platform': 'linux'
+ }]
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl style="style_dd dl;">'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '[\n'
+ ' &quot;one&quot;,\n'
+ ' &quot;two&quot;\n'
+ ']'
+ '</dd></dl></root>')
+
+ def testStringEnumListExample(self):
+ # Test representation of 'string-enum-list' example values.
+ policy = {
+ 'name': 'PolicyName',
+ 'type': 'string-enum-list',
+ 'example_value': ['one', 'two'],
+ 'supported_on': [{
+ 'platform': 'linux'
+ }]
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl style="style_dd dl;">'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '[\n'
+ ' &quot;one&quot;,\n'
+ ' &quot;two&quot;\n'
+ ']'
+ '</dd></dl></root>')
+
+ def testStringExample(self):
+ # Test representation of 'string' example values.
+ policy = {
+ 'name': 'PolicyName',
+ 'type': 'string',
+ 'example_value': 'awesome-example',
+ 'supported_on': []
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(self.doc_root.toxml(),
+ '<root>&quot;awesome-example&quot;</root>')
+
+ def testIntExample(self):
+ # Test representation of 'int' example values.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'type':
+ 'int',
+ 'example_value':
+ 26,
+ 'supported_on': [{
+ 'platform': 'win'
+ }, {
+ 'platform': 'mac'
+ }, {
+ 'platform': 'linux'
+ }, {
+ 'platform': 'android'
+ }]
+ }
+ self.writer._AddExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '<root>0x0000001a (Windows), 26 (Linux), 26 (Android), 26 (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;26&quot;/&gt;</dd></dl>'
+ '</root>')
+
+ def testAddPolicyAttribute(self):
+ # Test creating a policy attribute term-definition pair.
+ self.writer._AddPolicyAttribute(self.doc_root, 'bla', 'hello, world',
+ ['key1'])
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<dt style="style_dt;">_test_bla</dt>'
+ '<dd style="style1;">hello, world</dd>'
+ '</root>')
+
+ def testAddPolicyDetails(self):
+ # Test if the definition list (<dl>) of policy details is created correctly.
+ policy = {
+ 'type':
+ 'main',
+ 'name':
+ 'TestPolicyName',
+ 'caption':
+ 'TestPolicyCaption',
+ 'desc':
+ 'TestPolicyDesc',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'android',
+ 'since_version': '30',
+ 'until_version': '',
+ },
+ {
+ 'product': 'webview',
+ 'platform': 'android',
+ 'since_version': '47',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'chrome_os',
+ 'since_version': '55',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ False,
+ 'arc_support':
+ 'TestArcSupportNote'
+ }
+ self.writer._AddPolicyDetails(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Boolean [Windows:REG_DWORD]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOS\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_android_restriction_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_android_webview_restriction_name</dt>'
+ '<dd style="style_.monospace;">mock.prefix:TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..8..</li>'
+ '<li>Chrome (Mac) ..8..</li>'
+ '<li>Chrome (Linux) ..8..</li>'
+ '<li>Chrome (Android) ..30..</li>'
+ '<li>WebView (Android) ..47..</li>'
+ '<li>Chrome (Chrome OS) ..55..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_arc_support</dt>'
+ '<dd><p>TestArcSupportNote</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>0x00000000 (Windows), false (Linux),'
+ ' false (Android), &lt;false /&gt; (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;disabled/&gt;</dd></dl>'
+ '</dd>'
+ '</dl></root>')
+
+ def testAddPolicyDetailsNoArcSupport(self):
+ # Test that the entire Android-on-Chrome-OS sub-section is left out when
+ # 'arc_support' is not specified.
+ policy = {
+ 'type':
+ 'main',
+ 'name':
+ 'TestPolicyName',
+ 'caption':
+ 'TestPolicyCaption',
+ 'desc':
+ 'TestPolicyDesc',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ False
+ }
+ self.writer._AddPolicyDetails(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Boolean</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Linux) ..8..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>TestPolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>false (Linux)</dd>'
+ '</dl></root>')
+
+ def testAddDictPolicyDetails(self):
+ # Test if the definition list (<dl>) of policy details is created correctly
+ # for 'dict' policies.
+ policy = {
+ 'type':
+ 'dict',
+ 'name':
+ 'TestPolicyName',
+ 'caption':
+ 'TestPolicyCaption',
+ 'desc':
+ 'TestPolicyDesc',
+ 'schema': {
+ 'properties': {
+ 'foo': {
+ 'type': 'integer'
+ }
+ },
+ 'type': 'object'
+ },
+ 'url_schema':
+ 'https://example.com/details',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value': {
+ 'foo': 123
+ }
+ }
+ self.writer._AddPolicyDetails(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Dictionary [Windows:REG_SZ] (_test_complex_policies_win)</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOS\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..8..</li>'
+ '<li>Chrome (Mac) ..8..</li>'
+ '<li>Chrome (Linux) ..8..</li>'
+ '<li>Chrome OS (Chrome OS) ..8..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_schema</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">{\n'
+ ' &quot;properties&quot;: {\n'
+ ' &quot;foo&quot;: {\n'
+ ' &quot;type&quot;: &quot;integer&quot;\n'
+ ' }\n'
+ ' }, \n'
+ ' &quot;type&quot;: &quot;object&quot;\n'
+ '}</dd>'
+ '<dt style="style_dt;">_test_url_schema</dt>'
+ '<dd><a href="https://example.com/details">https://example.com/details</a></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>'
+ '<dl style="style_dd dl;">'
+ '<dt>_test_example_value_win</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'MockKey\TestPolicyName = {\n'
+ ' &quot;foo&quot;: 123\n'
+ '}'
+ '</dd>'
+ '<dt>_test_example_value_chrome_os</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'MockKeyCrOS\TestPolicyName = {\n'
+ ' &quot;foo&quot;: 123\n'
+ '}'
+ '</dd>'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'TestPolicyName: {\n'
+ ' &quot;foo&quot;: 123\n'
+ '}'
+ '</dd>'
+ '<dt>Mac:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '&lt;key&gt;TestPolicyName&lt;/key&gt;\n'
+ '&lt;dict&gt;\n'
+ ' &lt;key&gt;foo&lt;/key&gt;\n'
+ ' &lt;integer&gt;123&lt;/integer&gt;\n'
+ '&lt;/dict&gt;'
+ '</dd>'
+ '</dl>'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;TestPolicyName&quot; value=&quot;&quot;foo&quot;: 123&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '</dl></root>')
+
+ def testAddExternalPolicyDetails(self):
+ # Test if the definition list (<dl>) of policy details is created correctly
+ # for 'external' policies.
+ policy = {
+ 'type':
+ 'external',
+ 'name':
+ 'TestPolicyName',
+ 'caption':
+ 'TestPolicyCaption',
+ 'desc':
+ 'TestPolicyDesc',
+ 'description_schema': {
+ 'properties': {
+ 'url': {
+ 'type': 'string'
+ },
+ 'hash': {
+ 'type': 'string'
+ },
+ },
+ 'type': 'object'
+ },
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value': {
+ "url": "https://example.com/avatar.jpg",
+ "hash": "deadbeef",
+ },
+ }
+ self.writer.messages['doc_since_version'] = {'text': '...$6...'}
+ self.writer._AddPolicyDetails(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>External data reference [Windows:REG_SZ] (_test_complex_policies_win)</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ...8...</li>'
+ '<li>Chrome (Mac) ...8...</li>'
+ '<li>Chrome (Linux) ...8...</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_schema</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">{\n'
+ ' &quot;properties&quot;: {\n'
+ ' &quot;hash&quot;: {\n'
+ ' &quot;type&quot;: &quot;string&quot;\n'
+ ' }, \n'
+ ' &quot;url&quot;: {\n'
+ ' &quot;type&quot;: &quot;string&quot;\n'
+ ' }\n'
+ ' }, \n'
+ ' &quot;type&quot;: &quot;object&quot;\n'
+ '}</dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>'
+ '<dl style="style_dd dl;">'
+ '<dt>_test_example_value_win</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'MockKey\TestPolicyName = {\n'
+ ' &quot;hash&quot;: &quot;deadbeef&quot;, \n'
+ ' &quot;url&quot;: &quot;https://example.com/avatar.jpg&quot;\n'
+ '}'
+ '</dd>'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ 'TestPolicyName: {\n'
+ ' &quot;hash&quot;: &quot;deadbeef&quot;, \n'
+ ' &quot;url&quot;: &quot;https://example.com/avatar.jpg&quot;\n'
+ '}'
+ '</dd>'
+ '<dt>Mac:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '&lt;key&gt;TestPolicyName&lt;/key&gt;\n'
+ '&lt;dict&gt;\n'
+ ' &lt;key&gt;hash&lt;/key&gt;\n'
+ ' &lt;string&gt;deadbeef&lt;/string&gt;\n'
+ ' &lt;key&gt;url&lt;/key&gt;\n'
+ ' &lt;string&gt;https://example.com/avatar.jpg&lt;/string&gt;\n&lt;'
+ '/dict&gt;'
+ '</dd>'
+ '</dl>'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;TestPolicyName&quot; value=&quot;&quot;url&quot;: &quot;https://example.com/avatar.jpg&quot;, &quot;hash&quot;: &quot;deadbeef&quot;&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '</dl></root>')
+
+ def testAddPolicyDetailsRecommendedOnly(self):
+ policy = {
+ 'type':
+ 'main',
+ 'name':
+ 'TestPolicyName',
+ 'caption':
+ 'TestPolicyCaption',
+ 'desc':
+ 'TestPolicyDesc',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '8',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'android',
+ 'since_version': '30',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'chrome_os',
+ 'since_version': '53',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False,
+ 'can_be_mandatory': False,
+ 'can_be_recommended': True
+ },
+ 'example_value':
+ False
+ }
+ self.writer._AddPolicyDetails(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Boolean [Windows:REG_DWORD]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyRec\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOSRec\TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_android_restriction_name</dt>'
+ '<dd style="style_.monospace;">TestPolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..8..</li>'
+ '<li>Chrome (Mac) ..8..</li>'
+ '<li>Chrome (Linux) ..8..</li>'
+ '<li>Chrome (Android) ..30..</li>'
+ '<li>Chrome (Chrome OS) ..53..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_mandatory: _test_not_supported,'
+ ' _test_feature_recommended: _test_supported,'
+ ' _test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt><dd><p>TestPolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>0x00000000 (Windows), false (Linux),'
+ ' false (Android), &lt;false /&gt; (Mac)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;disabled/&gt;</dd></dl>'
+ '</dd>'
+ '</dl></root>')
+
+ def testAddPolicyRow(self):
+ # Test if policies are correctly added to the summary table.
+ policy = {
+ 'name': 'PolicyName',
+ 'caption': 'PolicyCaption',
+ 'type': 'string',
+ }
+ self.writer._indent_level = 3
+ self.writer._AddPolicyRow(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><tr style="style_tr;">'
+ '<td style="style_td;style_td.left;padding-left: 49px;">'
+ '<a href="#PolicyName">PolicyName</a>'
+ '</td>'
+ '<td style="style_td;style_td.right;">PolicyCaption</td>'
+ '</tr></root>')
+ self.setUp()
+ policy = {
+ 'name': 'PolicyName',
+ 'caption': 'PolicyCaption',
+ 'type': 'group',
+ }
+ self.writer._indent_level = 2
+ self.writer._AddPolicyRow(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root><tr style="style_tr;">'
+ '<td colspan="2" style="style_td;style_td.left;padding-left: 35px;">'
+ '<a href="#PolicyName">PolicyCaption</a>'
+ '</td>'
+ '</tr></root>')
+
+ def testAddPolicySection(self):
+ # Test if policy details are correctly added to the document.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'string',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '7',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 'False'
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>String [Windows:REG_SZ]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..7..</li>'
+ '<li>Chrome (Mac) ..7..</li>'
+ '<li>Chrome OS (Chrome OS) ..7..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>&quot;False&quot;'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;False&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+ # Test for groups.
+ self.setUp()
+ policy['type'] = 'group'
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h2><a name="PolicyName"/>PolicyCaption</h2>'
+ '<div style="style_div.group_desc;">PolicyDesc</div>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionWithAtomicGroup(self):
+ # Test if policy details are correctly added to the document.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'string',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '7',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 'False',
+ 'atomic_group':
+ 'PolicyGroup'
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>String [Windows:REG_SZ]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..7..</li>'
+ '<li>Chrome (Mac) ..7..</li>'
+ '<li>Chrome OS (Chrome OS) ..7..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>&quot;False&quot;'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;False&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '<dt style="style_dt;">_test_policy_atomic_group</dt>'
+ '<dd>_test_policy_in_atomic_group <a href="./policy-list-3/atomic_groups#PolicyGroup">PolicyGroup</a></dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionForWindowsOnly(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '33',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 123
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer [Windows:REG_DWORD]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows) ..33..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>0x0000007b (Windows)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;123&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionForWindows7Only(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win7',
+ 'since_version': '33',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 123
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer [Windows:REG_DWORD]</dd>'
+ '<dt style="style_dt;">_test_win_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKey\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_oma_uri</dt>'
+ '<dd style="style_.monospace;">.\\Device\\Vendor\\MSFT\\Policy\\Config\\Chrome~Policy~chromium\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Windows 7) ..33..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>0x0000007b (Windows)'
+ '<dl><dt>Windows (Intune):</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;enabled/&gt;</dd>'
+ '<dd style="style_.monospace;style_.pre-wrap;">&lt;data id=&quot;PolicyName&quot; value=&quot;123&quot;/&gt;</dd></dl>'
+ '</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionForMacOnly(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '33',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 123
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Mac) ..33..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>123 (Mac)</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionForLinuxOnly(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '33',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 123
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer</dd>'
+ '<dt style="style_dt;">_test_mac_linux_pref_name</dt>'
+ '<dd style="style_.monospace;">PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Linux) ..33..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>123 (Linux)</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddPolicySectionForAndroidOnly(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'android',
+ 'since_version': '33',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value':
+ 123
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertTrue(
+ self.writer.IsPolicyOrItemSupportedOnPlatform(policy, 'android'))
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer</dd>'
+ '<dt style="style_dt;">_test_android_restriction_name</dt>'
+ '<dd style="style_.monospace;">PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome (Android) ..33..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd>_test_feature_dynamic_refresh: _test_not_supported</dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>123 (Android)</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testAddDictionaryExample(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'dict',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '7',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value': {
+ "ProxyMode": "direct",
+ "List": ["1", "2", "3"],
+ "True": True,
+ "False": False,
+ "Integer": 123,
+ "DictList": [
+ {
+ "A": 1,
+ "B": 2,
+ },
+ {
+ "C": 3,
+ "D": 4,
+ },
+ ],
+ },
+ }
+ self.writer._AddDictionaryExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<dl style="style_dd dl;">'
+ '<dt>_test_example_value_win</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">MockKey\PolicyName = {\n'
+ ' &quot;DictList&quot;: [\n'
+ ' {\n'
+ ' &quot;A&quot;: 1, \n'
+ ' &quot;B&quot;: 2\n'
+ ' }, \n'
+ ' {\n'
+ ' &quot;C&quot;: 3, \n'
+ ' &quot;D&quot;: 4\n'
+ ' }\n'
+ ' ], \n'
+ ' &quot;False&quot;: false, \n'
+ ' &quot;Integer&quot;: 123, \n'
+ ' &quot;List&quot;: [\n'
+ ' &quot;1&quot;, \n'
+ ' &quot;2&quot;, \n'
+ ' &quot;3&quot;\n'
+ ' ], \n'
+ ' &quot;ProxyMode&quot;: &quot;direct&quot;, \n'
+ ' &quot;True&quot;: true\n'
+ '}'
+ '</dd>'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">PolicyName: {\n'
+ ' &quot;DictList&quot;: [\n'
+ ' {\n'
+ ' &quot;A&quot;: 1, \n'
+ ' &quot;B&quot;: 2\n'
+ ' }, \n'
+ ' {\n'
+ ' &quot;C&quot;: 3, \n'
+ ' &quot;D&quot;: 4\n'
+ ' }\n'
+ ' ], \n'
+ ' &quot;False&quot;: false, \n'
+ ' &quot;Integer&quot;: 123, \n'
+ ' &quot;List&quot;: [\n'
+ ' &quot;1&quot;, \n'
+ ' &quot;2&quot;, \n'
+ ' &quot;3&quot;\n'
+ ' ], \n'
+ ' &quot;ProxyMode&quot;: &quot;direct&quot;, \n'
+ ' &quot;True&quot;: true\n'
+ '}'
+ '</dd>'
+ '<dt>Mac:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '&lt;key&gt;PolicyName&lt;/key&gt;\n'
+ '&lt;dict&gt;\n'
+ ' &lt;key&gt;DictList&lt;/key&gt;\n'
+ ' &lt;array&gt;\n'
+ ' &lt;dict&gt;\n'
+ ' &lt;key&gt;A&lt;/key&gt;\n'
+ ' &lt;integer&gt;1&lt;/integer&gt;\n'
+ ' &lt;key&gt;B&lt;/key&gt;\n'
+ ' &lt;integer&gt;2&lt;/integer&gt;\n'
+ ' &lt;/dict&gt;\n'
+ ' &lt;dict&gt;\n'
+ ' &lt;key&gt;C&lt;/key&gt;\n'
+ ' &lt;integer&gt;3&lt;/integer&gt;\n'
+ ' &lt;key&gt;D&lt;/key&gt;\n'
+ ' &lt;integer&gt;4&lt;/integer&gt;\n'
+ ' &lt;/dict&gt;\n'
+ ' &lt;/array&gt;\n'
+ ' &lt;key&gt;False&lt;/key&gt;\n'
+ ' &lt;false/&gt;\n'
+ ' &lt;key&gt;Integer&lt;/key&gt;\n'
+ ' &lt;integer&gt;123&lt;/integer&gt;\n'
+ ' &lt;key&gt;List&lt;/key&gt;\n'
+ ' &lt;array&gt;\n'
+ ' &lt;string&gt;1&lt;/string&gt;\n'
+ ' &lt;string&gt;2&lt;/string&gt;\n'
+ ' &lt;string&gt;3&lt;/string&gt;\n'
+ ' &lt;/array&gt;\n'
+ ' &lt;key&gt;ProxyMode&lt;/key&gt;\n'
+ ' &lt;string&gt;direct&lt;/string&gt;\n'
+ ' &lt;key&gt;True&lt;/key&gt;\n'
+ ' &lt;true/&gt;\n'
+ '&lt;/dict&gt;'
+ '</dd>'
+ '</dl>'
+ '</root>')
+
+ def testAddExternalExample(self):
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'external',
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'mac',
+ 'since_version': '7',
+ 'until_version': '',
+ },
+ {
+ 'product': 'chrome',
+ 'platform': 'linux',
+ 'since_version': '7',
+ 'until_version': '',
+ }],
+ 'features': {
+ 'dynamic_refresh': False
+ },
+ 'example_value': {
+ "url": "https://example.com/avatar.jpg",
+ "hash": "deadbeef",
+ },
+ }
+ self.writer._AddDictionaryExample(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<dl style="style_dd dl;">'
+ '<dt>_test_example_value_win</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">MockKey\PolicyName = {\n'
+ ' &quot;hash&quot;: &quot;deadbeef&quot;, \n'
+ ' &quot;url&quot;: &quot;https://example.com/avatar.jpg&quot;\n'
+ '}'
+ '</dd>'
+ '<dt>Android/Linux:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">PolicyName: {\n'
+ ' &quot;hash&quot;: &quot;deadbeef&quot;, \n'
+ ' &quot;url&quot;: &quot;https://example.com/avatar.jpg&quot;\n'
+ '}'
+ '</dd>'
+ '<dt>Mac:</dt>'
+ '<dd style="style_.monospace;style_.pre-wrap;">'
+ '&lt;key&gt;PolicyName&lt;/key&gt;\n'
+ '&lt;dict&gt;\n'
+ ' &lt;key&gt;hash&lt;/key&gt;\n'
+ ' &lt;string&gt;deadbeef&lt;/string&gt;\n'
+ ' &lt;key&gt;url&lt;/key&gt;\n'
+ ' &lt;string&gt;https://example.com/avatar.jpg&lt;/string&gt;\n'
+ '&lt;/dict&gt;'
+ '</dd>'
+ '</dl>'
+ '</root>')
+
+ def testParagraphs(self):
+ text = 'Paragraph 1\n\nParagraph 2\n\nParagraph 3'
+ self.writer._AddParagraphs(self.doc_root, text)
+ self.assertEquals(
+ self.doc_root.toxml(),
+ '<root><p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p></root>')
+
+ def testGoogleCloudChromeOsPolicies(self):
+ # Tests whether Chrome OS policies with management type 'google_cloud'
+ # don't print example values etc. since they are managed through Google's
+ # Admin console, not Active Directory GPO.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'features': {},
+ 'example_value':
+ 42,
+ 'supported_on': [{
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ 'supported_chrome_os_management': ['google_cloud']
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome OS (Chrome OS) ..8..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd></dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+ def testActiveDirectoryChromeOsPolicies(self):
+ # Tests whether Chrome OS policies with management type 'active_directory'
+ # print example values etc.
+ policy = {
+ 'name':
+ 'PolicyName',
+ 'caption':
+ 'PolicyCaption',
+ 'desc':
+ 'PolicyDesc',
+ 'type':
+ 'int',
+ 'features': {},
+ 'example_value':
+ 42,
+ 'supported_on': [{
+ 'product': 'chrome_os',
+ 'platform': 'chrome_os',
+ 'since_version': '8',
+ 'until_version': '',
+ }],
+ 'supported_chrome_os_management': ['active_directory']
+ }
+ self.writer._AddPolicySection(self.doc_root, policy)
+ self.assertEquals(
+ self.doc_root.toxml(), '<root>'
+ '<div style="margin-left: 0px">'
+ '<h3><a name="PolicyName"/>PolicyName</h3>'
+ '<span>PolicyCaption</span>'
+ '<dl>'
+ '<dt style="style_dt;">_test_data_type</dt>'
+ '<dd>Integer [Windows:REG_DWORD]</dd>'
+ '<dt style="style_dt;">_test_chrome_os_reg_loc</dt>'
+ '<dd style="style_.monospace;">MockKeyCrOS\\PolicyName</dd>'
+ '<dt style="style_dt;">_test_supported_on</dt>'
+ '<dd>'
+ '<ul style="style_ul;">'
+ '<li>Chrome OS (Chrome OS) ..8..</li>'
+ '</ul>'
+ '</dd>'
+ '<dt style="style_dt;">_test_supported_features</dt>'
+ '<dd></dd>'
+ '<dt style="style_dt;">_test_description</dt>'
+ '<dd><p>PolicyDesc</p></dd>'
+ '<dt style="style_dt;">_test_example_value</dt>'
+ '<dd>0x0000002a (Windows)</dd>'
+ '</dl>'
+ '<a href="#top">_test_back_to_top</a>'
+ '</div>'
+ '</root>')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py b/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py
new file mode 100755
index 00000000000..a126d34096b
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/google_adml_writer.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import template_writer
+
+
+def GetWriter(config):
+ '''Factory method for instanciating the GoogleADMLWriter. Every Writer needs a
+ GetWriter method because the TemplateFormatter uses this method to
+ instantiate a Writer.
+ '''
+ return GoogleADMLWriter(None, config) # platforms unused
+
+
+class GoogleADMLWriter(template_writer.TemplateWriter):
+ '''Simple writer that writes fixed google.adml files.
+ '''
+
+ def WriteTemplate(self, template):
+ '''Returns the contents of the google.adml file. It's independent of
+ policy_templates.json.
+ '''
+
+ return '''<?xml version="1.0" ?>
+<policyDefinitionResources revision="1.0" schemaVersion="1.0">
+ <displayName/>
+ <description/>
+ <resources>
+ <stringTable>
+ <string id="google">Google</string>
+ </stringTable>
+ </resources>
+</policyDefinitionResources>
+'''
diff --git a/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py
new file mode 100755
index 00000000000..3170f57f5f2
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/google_adml_writer_unittest.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.google_adml_writer."""
+
+import unittest
+from writers import google_adml_writer
+
+
+class GoogleAdmlWriterUnittest(unittest.TestCase):
+
+ def setUp(self):
+ self.writer = google_adml_writer.GetWriter(None) # Config unused
+
+ def testGoogleAdml(self):
+ output = self.writer.WriteTemplate(None) # Template unused
+
+ # No point to duplicate the full XML.
+ self.assertTrue('<string id="google">Google</string>' in output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py b/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py
new file mode 100755
index 00000000000..e1f885ff600
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/google_admx_writer.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import template_writer
+
+
+def GetWriter(config):
+ '''Factory method for instanciating the GoogleADMXWriter. Every Writer needs a
+ GetWriter method because the TemplateFormatter uses this method to
+ instantiate a Writer.
+ '''
+ return GoogleADMXWriter(None, config) # platforms unused
+
+
+class GoogleADMXWriter(template_writer.TemplateWriter):
+ '''Simple writer that writes fixed google.admx files.
+ '''
+
+ def WriteTemplate(self, template):
+ '''Returns the contents of the google.admx file. It's independent of
+ policy_templates.json.
+ '''
+
+ return '''<?xml version="1.0" ?>
+<policyDefinitions revision="1.0" schemaVersion="1.0">
+ <policyNamespaces>
+ <target namespace="Google.Policies" prefix="Google"/>
+ </policyNamespaces>
+ <resources minRequiredRevision="1.0" />
+ <categories>
+ <category displayName="$(string.google)" name="Cat_Google"/>
+ </categories>
+</policyDefinitions>
+'''
diff --git a/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py
new file mode 100755
index 00000000000..f4f1e6dcdc2
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/google_admx_writer_unittest.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.google_admx_writer."""
+
+import unittest
+from writers import google_admx_writer
+
+
+class GoogleAdmxWriterUnittest(unittest.TestCase):
+
+ def setUp(self):
+ self.writer = google_admx_writer.GetWriter(None) # Config unused
+
+ def testGoogleAdmx(self):
+ output = self.writer.WriteTemplate(None) # Template unused
+
+ # No point to duplicate the full XML.
+ self.assertTrue('namespace="Google.Policies"' in output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py b/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py
new file mode 100755
index 00000000000..ff36b98df5d
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/gpo_editor_writer.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import template_writer
+
+
+class GpoEditorWriter(template_writer.TemplateWriter):
+ '''Abstract class for ADM and ADMX writers.
+
+ It includes deprecated policies in its output, and places them in a dedicated
+ 'DeprecatedPolicies' group. Every deprecated policy has the same description.
+
+ It is a superclass for AdmWriter and AdmxWriter.
+ '''
+
+ def IsDeprecatedPolicySupported(self, policy):
+ # Include deprecated policies in the output.
+ return True
+
+ def IsVersionSupported(self, policy, supported_on):
+ # Include deprecated policies in the 'DeprecatedPolicies' group, even if
+ # they aren't supported anymore.
+ major_version = self._GetChromiumMajorVersion()
+ if not major_version:
+ return True
+
+ since_version = supported_on.get('since_version', None)
+
+ return not since_version or major_version >= int(since_version)
+
+ def IsPolicyOnWin7Only(self, policy):
+ ''' Returns true if the policy is supported on win7 only.'''
+ for suppported_on in policy.get('supported_on', []):
+ if 'win7' == suppported_on.get('platform', []):
+ return True
+ return False
+
+ def _IsRemovedPolicy(self, policy):
+ major_version = self._GetChromiumMajorVersion()
+ for supported_on in policy.get('supported_on', []):
+ if '*' in self.platforms or supported_on['platform'] in self.platforms:
+ until_version = supported_on.get('until_version', None)
+ if not until_version or major_version <= int(until_version):
+ # The policy is still supported, return False.
+ return False
+ # No platform+version combo supports this version, return True.
+ return True
+
+ def _FilterPolicies(self, predicate, policy_list):
+ filtered_policies = []
+ for policy in policy_list:
+ if policy['type'] == 'group':
+ for p in policy['policies']:
+ if predicate(p):
+ filtered_policies.append(p)
+ else:
+ if predicate(policy):
+ filtered_policies.append(policy)
+ return filtered_policies
+
+ def _RemovePoliciesFromList(self, policy_list, policies_to_remove):
+ '''Remove policies_to_remove from groups and the top-level list.'''
+ # We only compare the 'name' property.
+ policies_to_remove = set([p['name'] for p in policies_to_remove])
+
+ # Remove from top-level list.
+ policy_list[:] = [
+ p for p in policy_list if p['name'] not in policies_to_remove
+ ]
+
+ # Remove from groups.
+ for group in policy_list:
+ if group['type'] != 'group':
+ continue
+ group['policies'] = [
+ p for p in group['policies'] if p['name'] not in policies_to_remove
+ ]
+
+ # Remove empty groups.
+ policy_list[:] = [
+ p for p in policy_list if p['type'] != 'group' or p['policies']
+ ]
+
+ def _MovePolicyGroup(self, policy_list, predicate, policy_desc, group):
+ '''Remove policies from |policy_list| that satisfy |predicate| and add them
+ to |group|.'''
+ filtered_policies = self._FilterPolicies(predicate, policy_list)
+ self._RemovePoliciesFromList(policy_list, filtered_policies)
+
+ for p in filtered_policies:
+ p['desc'] = policy_desc
+
+ group['policies'] = filtered_policies
+
+ def PreprocessPolicies(self, policy_list):
+ '''Put policies under the DeprecatedPolicies/RemovedPolicies groups.'''
+ removed_policies_group = {
+ 'name': 'RemovedPolicies',
+ 'type': 'group',
+ 'caption': self.messages['removed_policy_group_caption']['text'],
+ 'desc': self.messages['removed_policy_group_desc']['text'],
+ 'policies': []
+ }
+ self._MovePolicyGroup(
+ policy_list,
+ lambda p: self._IsRemovedPolicy(p),
+ self.messages['removed_policy_desc']['text'],
+ removed_policies_group)
+
+ deprecated_policies_group = {
+ 'name': 'DeprecatedPolicies',
+ 'type': 'group',
+ 'caption': self.messages['deprecated_policy_group_caption']['text'],
+ 'desc': self.messages['deprecated_policy_group_desc']['text'],
+ 'policies': []
+ }
+ self._MovePolicyGroup(
+ policy_list,
+ lambda p: p.get('deprecated', False),
+ self.messages['deprecated_policy_desc']['text'],
+ deprecated_policies_group)
+
+ policy_list.append(deprecated_policies_group)
+ policy_list.append(removed_policies_group)
+
+ return super(GpoEditorWriter, self).SortPoliciesGroupsFirst(policy_list)
diff --git a/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py
new file mode 100755
index 00000000000..a5a61f9d404
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from xml.dom import minidom
+import json
+from writers import xml_formatted_writer
+
+_POLICY_TYPE_TO_XML_TAG = {
+ 'string': 'string',
+ 'int': 'integer',
+ 'int-enum': 'integer',
+ 'string-enum': 'string',
+ 'string-enum-list': 'stringArray',
+ 'main': 'boolean',
+ 'list': 'stringArray',
+ 'dict': 'string',
+}
+
+_POLICY_TYPE_TO_INPUT_TYPE = {
+ 'string': 'input',
+ 'int': 'input',
+ 'int-enum': 'select',
+ 'string-enum': 'select',
+ 'string-enum-list': 'multiselect',
+ 'main': 'checkbox',
+ 'list': 'list',
+ 'dict': 'input'
+}
+
+_JSON_SCHEMA_TYPES = [
+ "string", "number", "integer", "boolean", "null", "object", "array"
+]
+
+
+class Error(Exception):
+ pass
+
+
+def _ParseSchemaTypeValueToString(value, type):
+ '''Parses the value of a given JSON schema type to a string.
+ '''
+ if type not in _JSON_SCHEMA_TYPES:
+ raise Error('schema type "{}" not supported'.format(type))
+
+ if type == 'integer':
+ return '{0:d}'.format(value)
+
+ # Use the default string parser.
+ return str(value)
+
+
+def GetWriter(config):
+ '''Factory method for instanciating the IOSAppConfigWriter. Every Writer needs
+ a GetWriter method because the TemplateFormatter uses this method to
+ instantiate a Writer.
+ '''
+ return IOSAppConfigWriter(['ios'], config) # platforms unused
+
+
+class IOSAppConfigWriter(xml_formatted_writer.XMLFormattedWriter):
+ '''Simple writer that writes app_config.xml files.
+ '''
+
+ def _WritePolicyPresentation(self, policy, field_group):
+ element_type = _POLICY_TYPE_TO_INPUT_TYPE[policy['type']]
+ if element_type:
+ attributes = {'type': element_type, 'keyName': policy['name']}
+ field = self.AddElement(field_group, 'field', attributes)
+ self._AddLocalizedElement(field, 'label', policy['caption'])
+ self._AddLocalizedElement(field, 'description', policy['desc'])
+
+ if 'enum' in policy['type']:
+ options = self.AddElement(field, 'options', {})
+ for item in policy['items']:
+ self._AddLocalizedElement(
+ options, 'option', str(item['caption']), {
+ 'value':
+ _ParseSchemaTypeValueToString(item['value'],
+ policy['schema']['type'])
+ })
+
+ def _AddLocalizedElement(self,
+ parent,
+ element_type,
+ text,
+ attributes={},
+ localization={'value': 'en-US'}):
+ item = self.AddElement(parent, element_type, attributes)
+ localized = self.AddElement(item, 'language', localization)
+ self.AddText(localized, text)
+
+ def _WritePresentation(self, policy_list):
+ groups = [policy for policy in policy_list if policy['type'] == 'group']
+ policies_without_group = [
+ policy for policy in policy_list if policy['type'] != 'group'
+ ]
+ for policy in groups:
+ child_policies = self._GetPoliciesForWriter(policy)
+ if child_policies:
+ field_group = self.AddElement(self._presentation, 'fieldGroup', {})
+ self._AddLocalizedElement(field_group, 'name', policy['caption'])
+ for child_policy in child_policies:
+ self._WritePolicyPresentation(child_policy, field_group)
+ for policy in self._GetPoliciesForWriter(
+ {'policies': policies_without_group}):
+ self._WritePolicyPresentation(policy, self._presentation)
+
+ def _WritePolicyDefaultValue(self, parent, policy):
+ if 'default' in policy:
+ default_value = self.AddElement(parent, 'defaultValue', {})
+ value = self.AddElement(default_value, 'value', {})
+ if policy['type'] == 'main':
+ if policy['default'] == True:
+ self.AddText(value, 'true')
+ elif policy['default'] == False:
+ self.AddText(value, 'false')
+ elif policy['type'] in ['list', 'string-enum-list']:
+ for v in policy['default']:
+ if value == None:
+ value = self.AddElement(default_value, 'value', {})
+ self.AddText(value, v)
+ value = None
+ else:
+ self.AddText(value, policy['default'])
+
+ def _WritePolicyConstraint(self, parent, policy):
+ attrs = {'nullable': 'true'}
+ if 'schema' in policy:
+ if 'minimum' in policy['schema']:
+ attrs['min'] = _ParseSchemaTypeValueToString(
+ policy['schema']['minimum'], policy['schema']['type'])
+ if 'maximum' in policy['schema']:
+ attrs['max'] = _ParseSchemaTypeValueToString(
+ policy['schema']['maximum'], policy['schema']['type'])
+
+ constraint = self.AddElement(parent, 'constraint', attrs)
+ if 'enum' in policy['type']:
+ values_element = self.AddElement(constraint, 'values', {})
+ for v in policy['schema']['enum']:
+ value = self.AddElement(values_element, 'value', {})
+ self.AddText(value,
+ _ParseSchemaTypeValueToString(v, policy['schema']['type']))
+
+ def IsFuturePolicySupported(self, policy):
+ # For now, include all future policies in appconfig.xml.
+ return True
+
+ def CreateDocument(self):
+ dom_impl = minidom.getDOMImplementation('')
+ return dom_impl.createDocument('http://www.w3.org/2001/XMLSchema-instance',
+ 'managedAppConfiguration', None)
+
+ def WriteTemplate(self, template):
+ self.messages = template['messages']
+ self.Init()
+ template['policy_definitions'] = \
+ self.PreprocessPolicies(template['policy_definitions'])
+ self.BeginTemplate()
+ self.WritePolicies(template['policy_definitions'])
+ self._WritePresentation(template['policy_definitions'])
+ self.EndTemplate()
+
+ return self.GetTemplateText()
+
+ def BeginTemplate(self):
+ self._app_config.attributes[
+ 'xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
+ schema_location = 'https://storage.googleapis.com/appconfig-media/appconfigschema.xsd'
+ self._app_config.attributes[
+ 'xsi:noNamespaceSchemaLocation'] = schema_location
+
+ version = self.AddElement(self._app_config, 'version', {})
+ milestone = self.config['version'].split(".", 1)[0]
+ self.AddText(version, milestone)
+
+ bundle_id = self.AddElement(self._app_config, 'bundleId', {})
+ self.AddText(bundle_id, self.config['bundle_id'])
+ self._policies = self.AddElement(self._app_config, 'dict', {})
+ self._presentation = self.AddElement(self._app_config, 'presentation',
+ {'defaultLocale': 'en-US'})
+
+ def WritePolicy(self, policy):
+ element_type = _POLICY_TYPE_TO_XML_TAG[policy['type']]
+ if element_type:
+ attributes = {'keyName': policy['name']}
+ # Add a "<!--FUTURE POLICY-->" comment before future policies.
+ if 'future_on' in policy:
+ for config in policy['future_on']:
+ if config['platform'] == 'ios':
+ self.AddComment(self._policies, 'FUTURE POLICY')
+ policy_element = self.AddElement(self._policies, element_type, attributes)
+ self._WritePolicyDefaultValue(policy_element, policy)
+ self._WritePolicyConstraint(policy_element, policy)
+
+ def Init(self):
+ self._doc = self.CreateDocument()
+ self._app_config = self._doc.documentElement
+
+ def GetTemplateText(self):
+ return self.ToPrettyXml(self._doc)
diff --git a/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py
new file mode 100755
index 00000000000..8774f2e84a3
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py
@@ -0,0 +1,434 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import json
+import unittest
+
+from writers import writer_unittest_common
+
+
+class IOSAppConfigWriterUnitTests(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for IOSAppConfigWriter.'''
+
+ def _GetTestPolicyTemplate(self, policy_definitions):
+ return '''
+{
+ 'policy_definitions': %s,
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+}
+''' % (policy_definitions)
+
+ def _GetExpectedOutput(self, version, policy_definition, policy_presentation):
+ if policy_definition:
+ definition = '<dict>\n %s\n </dict>' % policy_definition
+ else:
+ definition = '<dict/>'
+ if policy_presentation:
+ presentation = '<presentation defaultLocale="en-US">\n %s\n </presentation>' % policy_presentation
+ else:
+ presentation = '<presentation defaultLocale="en-US"/>'
+
+ return '''<?xml version="1.0" ?>
+<managedAppConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://storage.googleapis.com/appconfig-media/appconfigschema.xsd">
+ <version>%s</version>
+ <bundleId>com.google.chrome.ios</bundleId>
+ %s
+ %s
+</managedAppConfiguration>''' % (version, definition, presentation)
+
+ def testStringPolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'string policy',
+ 'type': 'string',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'string caption',
+ 'desc': 'string description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<string keyName="string policy">
+ <constraint nullable="true"/>
+ </string>'''
+ expected_presentation = '''<field keyName="string policy" type="input">
+ <label>
+ <language value="en-US">string caption</language>
+ </label>
+ <description>
+ <language value="en-US">string description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testIntPolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'IntPolicy',
+ 'type': 'int',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'int caption',
+ 'desc': 'int description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<integer keyName="IntPolicy">
+ <constraint nullable="true"/>
+ </integer>'''
+ expected_presentation = '''<field keyName="IntPolicy" type="input">
+ <label>
+ <language value="en-US">int caption</language>
+ </label>
+ <description>
+ <language value="en-US">int description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testIntEnumPolicy(self):
+ policy_definition = json.dumps([{
+ 'name':
+ 'IntEnumPolicy',
+ 'type':
+ 'int-enum',
+ 'supported_on': ['ios:80-'],
+ 'caption':
+ 'int-enum caption',
+ 'desc':
+ 'int-enum description',
+ 'schema': {
+ 'type': 'integer',
+ 'enum': [0, 1],
+ },
+ 'items': [{
+ 'name': 'item0',
+ 'value': 0,
+ 'caption': 'item 0',
+ }, {
+ 'name': 'item1',
+ 'value': 1,
+ 'caption': 'item 1',
+ }]
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<integer keyName="IntEnumPolicy">
+ <constraint nullable="true">
+ <values>
+ <value>0</value>
+ <value>1</value>
+ </values>
+ </constraint>
+ </integer>'''
+ expected_presentation = '''<field keyName="IntEnumPolicy" type="select">
+ <label>
+ <language value="en-US">int-enum caption</language>
+ </label>
+ <description>
+ <language value="en-US">int-enum description</language>
+ </description>
+ <options>
+ <option value="0">
+ <language value="en-US">item 0</language>
+ </option>
+ <option value="1">
+ <language value="en-US">item 1</language>
+ </option>
+ </options>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testStringEnumPolicy(self):
+ policy_definition = json.dumps([{
+ 'name':
+ 'StringEnumPolicy',
+ 'type':
+ 'string-enum',
+ 'supported_on': ['ios:80-'],
+ 'caption':
+ 'string-enum caption',
+ 'desc':
+ 'string-enum description',
+ 'schema': {
+ 'type': 'string',
+ 'enum': ['0', '1'],
+ },
+ 'items': [{
+ 'name': 'item0',
+ 'value': '0',
+ 'caption': 'item 0',
+ }, {
+ 'name': 'item1',
+ 'value': '1',
+ 'caption': 'item 1',
+ }]
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<string keyName="StringEnumPolicy">
+ <constraint nullable="true">
+ <values>
+ <value>0</value>
+ <value>1</value>
+ </values>
+ </constraint>
+ </string>'''
+ expected_presentation = '''<field keyName="StringEnumPolicy" type="select">
+ <label>
+ <language value="en-US">string-enum caption</language>
+ </label>
+ <description>
+ <language value="en-US">string-enum description</language>
+ </description>
+ <options>
+ <option value="0">
+ <language value="en-US">item 0</language>
+ </option>
+ <option value="1">
+ <language value="en-US">item 1</language>
+ </option>
+ </options>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testStringEnumListPolicy(self):
+ policy_definition = json.dumps([{
+ 'name':
+ 'StringEnumListPolicy',
+ 'type':
+ 'string-enum-list',
+ 'supported_on': ['ios:80-'],
+ 'caption':
+ 'string-enum-list caption',
+ 'desc':
+ 'string-enum-list description',
+ 'schema': {
+ 'type': 'string',
+ 'enum': ['0', '1'],
+ },
+ 'items': [{
+ 'name': 'item0',
+ 'value': '0',
+ 'caption': 'item 0',
+ }, {
+ 'name': 'item1',
+ 'value': '1',
+ 'caption': 'item 1',
+ }]
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<stringArray keyName="StringEnumListPolicy">
+ <constraint nullable="true">
+ <values>
+ <value>0</value>
+ <value>1</value>
+ </values>
+ </constraint>
+ </stringArray>'''
+ expected_presentation = '''<field keyName="StringEnumListPolicy" type="multiselect">
+ <label>
+ <language value="en-US">string-enum-list caption</language>
+ </label>
+ <description>
+ <language value="en-US">string-enum-list description</language>
+ </description>
+ <options>
+ <option value="0">
+ <language value="en-US">item 0</language>
+ </option>
+ <option value="1">
+ <language value="en-US">item 1</language>
+ </option>
+ </options>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testBooleanPolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'BooleanPolicy',
+ 'type': 'main',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'boolean caption',
+ 'desc': 'boolean description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<boolean keyName="BooleanPolicy">
+ <constraint nullable="true"/>
+ </boolean>'''
+ expected_presentation = '''<field keyName="BooleanPolicy" type="checkbox">
+ <label>
+ <language value="en-US">boolean caption</language>
+ </label>
+ <description>
+ <language value="en-US">boolean description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testListPolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'ListPolicy',
+ 'type': 'list',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'list caption',
+ 'desc': 'list description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<stringArray keyName="ListPolicy">
+ <constraint nullable="true"/>
+ </stringArray>'''
+ expected_presentation = '''<field keyName="ListPolicy" type="list">
+ <label>
+ <language value="en-US">list caption</language>
+ </label>
+ <description>
+ <language value="en-US">list description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testDictPolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'DictPolicy',
+ 'type': 'dict',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'dict caption',
+ 'desc': 'dict description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ # Dict policies are not supported by the appconfig.xml format, therefore
+ # they are treated as JSON strings.
+ expected_configuration = '''<string keyName="DictPolicy">
+ <constraint nullable="true"/>
+ </string>'''
+ expected_presentation = '''<field keyName="DictPolicy" type="input">
+ <label>
+ <language value="en-US">dict caption</language>
+ </label>
+ <description>
+ <language value="en-US">dict description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testFuturePolicy(self):
+ policy_definition = json.dumps([{
+ 'name': 'FuturePolicy',
+ 'type': 'string',
+ 'future_on': ['ios'],
+ 'caption': 'string caption',
+ 'desc': 'string description'
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<!--FUTURE POLICY-->
+ <string keyName="FuturePolicy">
+ <constraint nullable="true"/>
+ </string>'''
+ expected_presentation = '''<field keyName="FuturePolicy" type="input">
+ <label>
+ <language value="en-US">string caption</language>
+ </label>
+ <description>
+ <language value="en-US">string description</language>
+ </description>
+ </field>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testPolicyWithGroup(self):
+ policy_definition = json.dumps([{
+ 'name': 'PolicyInGroup',
+ 'type': 'string',
+ 'supported_on': ['ios:80-'],
+ 'caption': 'string caption',
+ 'desc': 'string description'
+ }, {
+ 'name': 'DummyGroup',
+ 'type': 'group',
+ 'caption': 'Dummy Group',
+ 'desc': 'Dummy group for testing',
+ 'policies': ['PolicyInGroup']
+ }])
+ policy_json = self._GetTestPolicyTemplate(policy_definition)
+ expected_configuration = '''<string keyName="PolicyInGroup">
+ <constraint nullable="true"/>
+ </string>'''
+ expected_presentation = '''<fieldGroup>
+ <name>
+ <language value="en-US">Dummy Group</language>
+ </name>
+ <field keyName="PolicyInGroup" type="input">
+ <label>
+ <language value="en-US">string caption</language>
+ </label>
+ <description>
+ <language value="en-US">string description</language>
+ </description>
+ </field>
+ </fieldGroup>'''
+ expected = self._GetExpectedOutput('83', expected_configuration,
+ expected_presentation)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0'
+ }, 'ios_app_config')
+ self.assertEquals(output.strip(), expected.strip())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/jamf_writer.py b/chromium/components/policy/tools/template_writers/writers/jamf_writer.py
new file mode 100755
index 00000000000..f8379c0bca9
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/jamf_writer.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import copy
+import json
+
+from writers import template_writer
+
+
+def GetWriter(config):
+ '''Factory method for creating JamfWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return JamfWriter(['mac', 'ios'], config)
+
+
+class JamfWriter(template_writer.TemplateWriter):
+ '''Simple writer that writes a jamf.json file.
+ '''
+ MAX_RECURSIVE_FIELDS_DEPTH = 5
+ TYPE_TO_INPUT = {
+ 'string': 'string',
+ 'int': 'integer',
+ 'int-enum': 'integer',
+ 'string-enum': 'string',
+ 'string-enum-list': 'array',
+ 'main': 'boolean',
+ 'list': 'array',
+ 'dict': 'object',
+ 'external': 'object',
+ }
+
+ # Some policies are forced to a certain schema, so they bypass TYPE_TO_INPUT
+ POLICY_ID_TO_INPUT = {
+ 227: 'string', # ManagedBookmarks
+ 278: 'string', # ExtensionSettings
+ }
+
+
+ def WriteTemplate(self, template):
+ '''Writes the given template definition.
+
+ Args:
+ template: Template definition to write.
+
+ Returns:
+ Generated output for the passed template definition.
+ '''
+ self.messages = template['messages']
+ # Keep track of all items that can be referred to by an id.
+ # This is used for '$ref' fields in the policy templates.
+ ref_ids_schemas = {}
+ policies = []
+ for policy_def in template['policy_definitions']:
+ # Iterate over all policies, even the policies contained inside a policy
+ # group.
+ if policy_def['type'] == 'group':
+ policies += policy_def['policies']
+ else:
+ policies += [policy_def]
+
+ for policy in policies:
+ if policy['type'] == 'int-enum' or policy['type'] == 'string-enum':
+ self.RecordEnumIds(policy, ref_ids_schemas)
+ elif 'schema' in policy:
+ if 'id' in policy['schema']:
+ self.RecordKnownPropertyIds(policy['schema'], ref_ids_schemas)
+ if 'items' in policy['schema']:
+ self.RecordKnownPropertyIds(policy['schema']['items'],
+ ref_ids_schemas)
+ if 'properties' in policy['schema']:
+ self.RecordKnownPropertyIds(policy['schema']['properties'],
+ ref_ids_schemas)
+ if 'patternProperties' in policy['schema']:
+ self.RecordKnownPropertyIds(policy['schema']['patternProperties'],
+ ref_ids_schemas)
+
+ policies = [policy for policy in policies if self.IsPolicySupported(policy)]
+ output = {
+ 'title': self.config['bundle_id'],
+ 'version': self.config['version'].split(".", 1)[0],
+ 'description': self.config['app_name'],
+ 'options': {
+ 'remove_empty_properties': True
+ },
+ 'properties': {}
+ }
+
+ for policy in policies:
+ output['properties'][policy['name']] = {
+ 'title':
+ policy['name'],
+ 'description':
+ policy['caption'],
+ 'type':
+ self.TYPE_TO_INPUT[policy['type']],
+ 'links': [{
+ 'rel': self.messages['doc_policy_documentation']['text'],
+ 'href': self.config['doc_url'] + '#' + policy['name']
+ }]
+ }
+
+ policy_output = output['properties'][policy['name']]
+ if policy['id'] in self.POLICY_ID_TO_INPUT:
+ policy_output['type'] = self.POLICY_ID_TO_INPUT[policy['id']]
+
+ policy_type = policy_output['type']
+ if policy['type'] == 'int-enum' or policy['type'] == 'string-enum':
+ policy_output['options'] = {
+ 'enum_titles': [item['name'] for item in policy['items']]
+ }
+ policy_output['enum'] = [item['value'] for item in policy['items']]
+ elif policy['type'] == 'int' and 'schema' in policy:
+ if 'minimum' in policy['schema']:
+ policy_output['minimum'] = policy['schema']['minimum']
+ if 'maximum' in policy['schema']:
+ policy_output['maximum'] = policy['schema']['maximum']
+ elif policy['type'] == 'list':
+ policy_output['items'] = policy['schema']['items']
+ elif policy['type'] == 'string-enum-list' or policy[
+ 'type'] == 'int-enum-list':
+ policy_output['items'] = {
+ 'type': policy['schema']['items']['type'],
+ 'options': {
+ 'enum_titles': [item['name'] for item in policy['items']]
+ },
+ 'enum': [item['value'] for item in policy['items']]
+ }
+ elif policy_output['type'] == 'object' and policy['type'] != 'external':
+ policy_output['type'] = policy['schema']['type']
+ if policy_output['type'] == 'array':
+ policy_output['items'] = policy['schema']['items']
+ self.WriteRefItems(policy_output['items'], policy_output['items'], [],
+ ref_ids_schemas, set())
+ elif policy_output['type'] == 'object':
+ policy_output['properties'] = policy['schema']['properties']
+ self.WriteRefItems(policy_output['properties'],
+ policy_output['properties'], [], ref_ids_schemas,
+ set())
+
+ return json.dumps(output, indent=2, sort_keys=True, separators=(',', ': '))
+
+ def RecordEnumIds(self, policy, known_ids):
+ '''Writes the a dictionary mapping ids of enums that can be referred to by
+ '$ref' to their schema.
+
+ Args:
+ policy: The policy to scan for refs.
+ known_ids: The dictionary and output of all the known ids.
+ '''
+ if 'id' in policy['schema']:
+ known_ids[policy['schema']['id']] = {
+ 'type': policy['schema']['type'],
+ 'options': {
+ 'enum_titles': [item['name'] for item in policy['items']]
+ },
+ 'enum': [item['value'] for item in policy['items']]
+ }
+
+ def RecordKnownPropertyIds(self, obj, known_ids):
+ '''Writes the a dictionary mapping ids of schemas properties that can be
+ referred to by '$ref' to their schema.
+
+ Args:
+ obj: The object to scan for refs.
+ known_ids: The dictionary and output of all the known ids.
+ '''
+ if type(obj) is not dict:
+ return
+ if 'id' in obj:
+ known_ids[obj['id']] = obj
+ for value in obj.values():
+ self.RecordKnownPropertyIds(value, known_ids)
+
+ def WriteRefItems(self, root, obj, path_to_obj_parent, known_ids,
+ ids_in_ancestry):
+ '''Replaces all the '$ref' items by their actual value. Nested properties
+ are limited to a depth of MAX_RECURSIVE_FIELDS_DEPTH, after which the
+ recursive field is removed.
+
+ Args:
+ root: The root of the object tree to scan for refs.
+ obj: The current object being checked for ids.
+ path_to_obj_parent: A array of all the keys leading to the parent of |obj|
+ starting at |root|.
+ known_ids: The dictionary of all the known ids.
+ ids_in_ancestry: A list of ids found in the tree starting at root. Use to
+ keep nested fields in check.
+ '''
+ if type(obj) is not dict:
+ return
+ if 'id' in obj:
+ ids_in_ancestry.add(obj['id'])
+ # Make a copy of items since we are going to change |obj|.
+ for key, value in list(obj.items()):
+ if type(value) is not dict:
+ continue
+ if '$ref' in value:
+ # If the id is an ancestor, we have a nested field.
+ if value['$ref'] in ids_in_ancestry:
+ id = value['$ref']
+
+ last_obj = None
+ parent = root
+ grandparent = root
+
+ # Find the parent and grandparent of obj to create the |last_obj|
+ # which is the field where the nesting stops.
+ for i in range(0, len(path_to_obj_parent)):
+ if i + 1 < len(path_to_obj_parent):
+ grandparent = grandparent[path_to_obj_parent[i]]
+ else:
+ parent = grandparent[path_to_obj_parent[i]]
+ # Remove the link between grand parent and parent so we can have a
+ # copy of the object without nesting.
+ grandparent[path_to_obj_parent[i]] = None
+ del grandparent[path_to_obj_parent[i]]
+ # last_obj is a copy of the reference object without nesting.
+ last_obj = copy.deepcopy(known_ids[id])
+ # Re-establish the link between grand parent and parent.
+ grandparent[path_to_obj_parent[i]] = parent
+
+ del obj[key]
+ obj[key] = last_obj
+ # Create nested '$ref' objects with |last_obj| as the last object.
+ for count in range(1, self.MAX_RECURSIVE_FIELDS_DEPTH):
+ obj[key] = copy.deepcopy(known_ids[id])
+ obj_grandparent_ref = path_to_obj_parent[len(path_to_obj_parent) - 1]
+ else:
+ # If no nested field, simply assign the '$ref'.
+ obj[key] = dict(known_ids[value['$ref']])
+ self.WriteRefItems(root, obj[key], path_to_obj_parent + [key],
+ known_ids, ids_in_ancestry)
+ else:
+ self.WriteRefItems(root, value, path_to_obj_parent + [key], known_ids,
+ ids_in_ancestry)
diff --git a/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py
new file mode 100755
index 00000000000..1dc8fc5d114
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/jamf_writer_unittest.py
@@ -0,0 +1,383 @@
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+import copy
+import json
+from writers import writer_unittest_common
+
+
+def _JsonFormat(input):
+ return json.dumps(input, indent=2, sort_keys=True, separators=(',', ': '))
+
+
+class JamfWriterUnitTests(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for JamfWriter.'''
+
+ doc_url = 'https://chromeenterprise.google/policies/'
+
+ def _GetTestPolicyTemplate(self, policy_name, policy_type, schema_type,
+ policy_caption):
+ template = {
+ 'policy_definitions': [{
+ 'name':
+ policy_name,
+ 'id':
+ 1,
+ 'type':
+ policy_type,
+ 'supported_on': ['chrome.mac:*-'],
+ 'caption':
+ policy_caption,
+ 'desc':
+ '',
+ 'items': [{
+ 'name': 'title1',
+ 'value': 1,
+ 'caption': '',
+ 'type': 'integer'
+ }],
+ 'schema': {
+ 'type': schema_type,
+ 'id': 'enumid',
+ 'properties' if schema_type == 'object' else 'items': {
+ 'title':
+ 'title_obj' if schema_type == 'object' else 'title_array',
+ 'type': 'integer'
+ },
+ }
+ }],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'doc_policy_documentation': {
+ 'text': 'Documentation for policy'
+ }
+ },
+ }
+ return _JsonFormat(template)
+
+ def _GetExpectedOutput(self, policy_name, policy_type, policy_caption,
+ initial_type, version):
+ output = {
+ 'description': 'Google Chrome',
+ 'options': {
+ 'remove_empty_properties': True
+ },
+ 'properties': {
+ policy_name: {
+ 'description':
+ policy_caption,
+ 'title':
+ policy_name,
+ 'type':
+ policy_type,
+ 'links': [{
+ 'rel': 'Documentation for policy',
+ 'href': self.doc_url + '#' + policy_name
+ }]
+ }
+ },
+ 'title': 'com.google.chrome.ios',
+ 'version': version
+ }
+ if initial_type == 'int-enum' or initial_type == 'string-enum':
+ output['properties'][policy_name]['enum'] = [1]
+ output['properties'][policy_name]['options'] = {'enum_titles': ['title1']}
+ if initial_type == 'string-enum-list' or initial_type == 'int-enum-list':
+ output['properties'][policy_name]['items'] = {
+ 'type': 'integer',
+ 'enum': [1],
+ 'options': {
+ 'enum_titles': ['title1']
+ }
+ }
+ elif policy_type == 'array':
+ output['properties'][policy_name]['items'] = {
+ 'type': 'integer',
+ 'title': 'title_array'
+ }
+ elif initial_type == 'dict':
+ output['properties'][policy_name]['properties'] = {
+ 'type': 'integer',
+ 'title': 'title_obj'
+ }
+
+ return _JsonFormat(output)
+
+ def testStringPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string', '',
+ 'A string policy')
+ expected = self._GetExpectedOutput('stringPolicy', 'string',
+ 'A string policy', 'string', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testIntPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('intPolicy', 'int', '',
+ 'An int policy')
+ expected = self._GetExpectedOutput('intPolicy', 'integer', 'An int policy',
+ 'int', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testIntPolicyWithMinAndMax(self):
+ template = {
+ 'policy_definitions': [{
+ 'name': 'intPolicyWithMinAndMax',
+ 'id': 1,
+ 'type': 'int',
+ 'supported_on': ['chrome.mac:*-'],
+ 'caption': 'An int policy with min and max',
+ 'desc': '',
+ 'schema': {
+ 'type': 'int',
+ 'minimum': 0,
+ 'maximum': 10
+ }
+ }],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'doc_policy_documentation': {
+ 'text': 'Documentation for policy'
+ }
+ }
+ }
+ policy_json = _JsonFormat(template)
+
+ expected = {
+ 'description': 'Google Chrome',
+ 'options': {
+ 'remove_empty_properties': True
+ },
+ 'properties': {
+ 'intPolicyWithMinAndMax': {
+ 'description':
+ 'An int policy with min and max',
+ 'maximum':
+ 10,
+ 'minimum':
+ 0,
+ 'title':
+ 'intPolicyWithMinAndMax',
+ 'type':
+ 'integer',
+ 'links': [{
+ 'rel': 'Documentation for policy',
+ 'href': self.doc_url + '#' + 'intPolicyWithMinAndMax'
+ }]
+ }
+ },
+ 'title': 'com.google.chrome.ios',
+ 'version': '83'
+ }
+ expected_json = _JsonFormat(expected)
+
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected_json.strip())
+
+ def testIntEnumPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('intPolicy', 'int-enum', '',
+ 'An int-enum policy')
+ expected = self._GetExpectedOutput('intPolicy', 'integer',
+ 'An int-enum policy', 'int-enum', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testStringEnumPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('stringPolicy', 'string-enum', '',
+ 'A string-enum policy')
+ expected = self._GetExpectedOutput('stringPolicy', 'string',
+ 'A string-enum policy', 'string-enum',
+ '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testStringEnumListPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('stringPolicy',
+ 'string-enum-list', '',
+ 'A string-enum-list policy')
+ expected = self._GetExpectedOutput('stringPolicy', 'array',
+ 'A string-enum-list policy',
+ 'string-enum-list', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testBooleanPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('booleanPolicy', 'main', '',
+ 'A boolean policy')
+ expected = self._GetExpectedOutput('booleanPolicy', 'boolean',
+ 'A boolean policy', 'main', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testListPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('listPolicy', 'list', '',
+ 'A list policy')
+ expected = self._GetExpectedOutput('listPolicy', 'array', 'A list policy',
+ 'list', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testDictPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'object',
+ 'A dict policy')
+ expected = self._GetExpectedOutput('dictPolicy', 'object', 'A dict policy',
+ 'dict', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testArrayDictPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('dictPolicy', 'dict', 'array',
+ 'A dict policy')
+ expected = self._GetExpectedOutput('dictPolicy', 'array', 'A dict policy',
+ 'dict', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+ def testNestedArrayDict(self):
+ template = {
+ 'policy_definitions': [{
+ 'name': 'name',
+ 'id': 1,
+ 'type': 'dict',
+ 'supported_on': ['chrome.mac:*-'],
+ 'caption': 'caption',
+ 'desc': '',
+ 'schema': {
+ 'type': 'array',
+ 'items': {
+ 'title': 'title2',
+ 'id': 'id',
+ 'type': 'object',
+ 'properties': {
+ 'name': 'name',
+ 'children': {
+ 'type': 'array',
+ 'items': {
+ '$ref': 'id'
+ }
+ }
+ }
+ }
+ }
+ }],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'doc_policy_documentation': {
+ 'text': 'Documentation for policy'
+ }
+ }
+ }
+ policy_json = _JsonFormat(template)
+
+ expected = {
+ 'description': 'Google Chrome',
+ 'options': {
+ 'remove_empty_properties': True
+ },
+ 'properties': {
+ 'name': {
+ 'description':
+ 'caption',
+ 'title':
+ 'name',
+ 'type':
+ 'array',
+ 'items': {
+ 'title': 'title2',
+ 'id': 'id',
+ 'type': 'object',
+ 'properties': {
+ 'name': 'name'
+ }
+ },
+ 'links': [{
+ 'rel': 'Documentation for policy',
+ 'href': self.doc_url + '#' + 'name'
+ }]
+ }
+ },
+ 'title': 'com.google.chrome.ios',
+ 'version': '83'
+ }
+
+ for i in range(0, 5):
+ expected['properties']['name']['items']['properties']['children'] = {
+ 'type': 'array',
+ 'items': copy.deepcopy(expected['properties']['name']['items'])
+ }
+
+ output_expected = _JsonFormat(expected)
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), output_expected.strip())
+
+ def testExternalPolicy(self):
+ policy_json = self._GetTestPolicyTemplate('externalPolicy', 'external', '',
+ 'A external policy')
+ expected = self._GetExpectedOutput('externalPolicy', 'object',
+ 'A external policy', 'external', '83')
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'version': '83.0.4089.0',
+ 'doc_url': self.doc_url
+ }, 'jamf')
+ self.assertEquals(output.strip(), expected.strip())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/json_writer.py b/chromium/components/policy/tools/template_writers/writers/json_writer.py
new file mode 100755
index 00000000000..7d5d166c7c9
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/json_writer.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+
+from textwrap import TextWrapper
+from writers import template_writer
+
+TEMPLATE_HEADER = """\
+// Policy template for Linux.
+// Uncomment the policies you wish to activate and change their values to
+// something useful for your case. The provided values are for reference only
+// and do not provide meaningful defaults!
+{"""
+
+HEADER_DELIMETER = """\
+ //-------------------------------------------------------------------------"""
+
+
+def GetWriter(config):
+ '''Factory method for creating JsonWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return JsonWriter(['linux'], config)
+
+
+class JsonWriter(template_writer.TemplateWriter):
+ '''Class for generating policy files in JSON format (for Linux). The
+ generated files will define all the supported policies with example values
+ set for them. This class is used by PolicyTemplateGenerator to write .json
+ files.
+ '''
+
+ def PreprocessPolicies(self, policy_list):
+ return self.FlattenGroupsAndSortPolicies(policy_list)
+
+ def WriteComment(self, comment):
+ self._out.append('// ' + comment)
+
+ def WritePolicy(self, policy):
+ example_value_str = json.dumps(policy['example_value'], sort_keys=True)
+
+ # Add comma to the end of the previous line.
+ if not self._first_written:
+ self._out[-2] += ','
+
+ if not self.CanBeMandatory(policy) and self.CanBeRecommended(policy):
+ line = ' // Note: this policy is supported only in recommended mode.'
+ self._out.append(line)
+ line = ' // The JSON file should be placed in %srecommended.' % \
+ self.config['linux_policy_path']
+ self._out.append(line)
+
+ line = ' // %s' % policy['caption']
+ self._out.append(line)
+ self._out.append(HEADER_DELIMETER)
+ description = policy['desc']
+ if self.HasExpandedPolicyDescription(policy):
+ description += ' ' + self.GetExpandedPolicyDescription(policy) + '\n'
+ description = self._text_wrapper.wrap(description)
+ self._out += description
+ line = ' //"%s": %s' % (policy['name'], example_value_str)
+ self._out.append('')
+ self._out.append(line)
+ self._out.append('')
+
+ self._first_written = False
+
+ def BeginTemplate(self):
+ if self._GetChromiumVersionString() is not None:
+ self.WriteComment(self.config['build'] + ''' version: ''' + \
+ self._GetChromiumVersionString())
+ self._out.append(TEMPLATE_HEADER)
+
+ def EndTemplate(self):
+ self._out.append('}')
+
+ def Init(self):
+ self._out = []
+ # The following boolean member is true until the first policy is written.
+ self._first_written = True
+ # Create the TextWrapper object once.
+ self._text_wrapper = TextWrapper(
+ initial_indent=' // ',
+ subsequent_indent=' // ',
+ break_long_words=False,
+ width=80)
+
+ def GetTemplateText(self):
+ return '\n'.join(self._out)
diff --git a/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py
new file mode 100755
index 00000000000..5f7c3ad9660
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/json_writer_unittest.py
@@ -0,0 +1,460 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.json_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import writer_unittest_common
+
+TEMPLATE_HEADER = """\
+// Policy template for Linux.
+// Uncomment the policies you wish to activate and change their values to
+// something useful for your case. The provided values are for reference only
+// and do not provide meaningful defaults!
+{
+"""
+
+TEMPLATE_HEADER_WITH_VERSION = """\
+// chromium version: 39.0.0.0
+// Policy template for Linux.
+// Uncomment the policies you wish to activate and change their values to
+// something useful for your case. The provided values are for reference only
+// and do not provide meaningful defaults!
+{
+"""
+
+HEADER_DELIMETER = """\
+ //-------------------------------------------------------------------------
+"""
+
+MESSAGES = '''
+ {
+ 'doc_schema_description_link': {
+ 'text': 'See $6'
+ },
+ }'''
+
+
+class JsonWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for JsonWriter.'''
+
+ def CompareOutputs(self, output, expected_output):
+ '''Compares the output of the json_writer with its expected output.
+
+ Args:
+ output: The output of the json writer.
+ expected_output: The expected output.
+
+ Raises:
+ AssertionError: if the two strings are not equivalent.
+ '''
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testEmpty(self):
+ # Test the handling of an empty policy list.
+ policy_json = '''
+ {
+ "policy_definitions": [],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = TEMPLATE_HEADER + '}'
+ self.CompareOutputs(output, expected_output)
+
+ def testEmptyWithVersion(self):
+ # Test the handling of an empty policy list.
+ policy_json = '''
+ {
+ "policy_definitions": [],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'version': '39.0.0.0'
+ }, 'json')
+ expected_output = TEMPLATE_HEADER_WITH_VERSION + '}'
+ self.CompareOutputs(output, expected_output)
+
+ def testMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "MainPolicy",
+ "type": "main",
+ "caption": "Example Main Policy",
+ "desc": "Example Main Policy",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": True
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example Main Policy\n' + HEADER_DELIMETER +
+ ' // Example Main Policy\n\n'
+ ' //"MainPolicy": true\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testRecommendedOnlyPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "MainPolicy",
+ "type": "main",
+ "caption": "Example Main Policy",
+ "desc": "Example Main Policy",
+ "features": {
+ "can_be_recommended": True,
+ "can_be_mandatory": False
+ },
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": True
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER +
+ ' // Note: this policy is supported only in recommended mode.\n' +
+ ' // The JSON file should be placed in' +
+ ' /etc/opt/chrome/policies/recommended.\n' +
+ ' // Example Main Policy\n' + HEADER_DELIMETER +
+ ' // Example Main Policy\n\n'
+ ' //"MainPolicy": true\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringPolicy(self):
+ # Tests a policy group with a single policy of type 'string'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "StringPolicy",
+ "type": "string",
+ "caption": "Example String Policy",
+ "desc": "Example String Policy",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": "hello, world!"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example String Policy\n' + HEADER_DELIMETER +
+ ' // Example String Policy\n\n'
+ ' //"StringPolicy": "hello, world!"\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntPolicy(self):
+ # Tests a policy group with a single policy of type 'string'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "IntPolicy",
+ "type": "int",
+ "caption": "Example Int Policy",
+ "desc": "Example Int Policy",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": 15
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example Int Policy\n' + HEADER_DELIMETER +
+ ' // Example Int Policy\n\n'
+ ' //"IntPolicy": 15\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testIntEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "EnumPolicy",
+ "type": "int-enum",
+ "caption": "Example Int Enum",
+ "desc": "Example Int Enum",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": 0, "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": 1, "caption": ""},
+ ],
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": 1
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example Int Enum\n' + HEADER_DELIMETER +
+ ' // Example Int Enum\n\n'
+ ' //"EnumPolicy": 1\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "EnumPolicy",
+ "type": "string-enum",
+ "caption": "Example String Enum",
+ "desc": "Example String Enum",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": "one",
+ "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": "two",
+ "caption": ""},
+ ],
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": "one"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example String Enum\n' + HEADER_DELIMETER +
+ ' // Example String Enum\n\n'
+ ' //"EnumPolicy": "one"\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testListPolicy(self):
+ # Tests a policy group with a single policy of type 'list'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ListPolicy",
+ "type": "list",
+ "caption": "Example List",
+ "desc": "Example List",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ["foo", "bar"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example List\n' + HEADER_DELIMETER +
+ ' // Example List\n\n'
+ ' //"ListPolicy": ["foo", "bar"]\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testStringEnumListPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum-list'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ListPolicy",
+ "type": "string-enum-list",
+ "caption": "Example List",
+ "desc": "Example List",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": "one",
+ "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": "two",
+ "caption": ""},
+ ],
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ["one", "two"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example List\n' + HEADER_DELIMETER +
+ ' // Example List\n\n'
+ ' //"ListPolicy": ["one", "two"]\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testDictionaryPolicy(self):
+ # Tests a policy group with a single policy of type 'dict'.
+ example = {
+ 'bool': True,
+ 'dict': {
+ 'a': 1,
+ 'b': 2,
+ },
+ 'int': 10,
+ 'list': [1, 2, 3],
+ 'string': 'abc',
+ }
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "DictionaryPolicy",
+ "type": "dict",
+ "caption": "Example Dictionary Policy",
+ "desc": "Example Dictionary Policy",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ''' + str(example) + '''
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": %s,
+ }''' % MESSAGES
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example Dictionary Policy\n' + HEADER_DELIMETER
+ + ' // Example Dictionary Policy See '
+ 'https://cloud.google.com/docs/chrome-\n'
+ ' // enterprise/policies/?policy=DictionaryPolicy\n\n'
+ ' //"DictionaryPolicy": {"bool": true, "dict": {"a": 1, '
+ '"b": 2}, "int": 10, "list": [1, 2, 3], "string": "abc"}\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testExternalPolicy(self):
+ # Tests a policy group with a single policy of type 'external'.
+ example = {
+ "url": "https://example.com/avatar.jpg",
+ "hash": "deadbeef",
+ }
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ExternalPolicy",
+ "type": "external",
+ "caption": "Example External Policy",
+ "desc": "Example External Policy",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": %s
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": %s,
+ }''' % (str(example), MESSAGES)
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Example External Policy\n' + HEADER_DELIMETER +
+ ' // Example External Policy See '
+ 'https://cloud.google.com/docs/chrome-\n'
+ ' // enterprise/policies/?policy=ExternalPolicy\n\n'
+ ' //"ExternalPolicy": {"hash": "deadbeef", "url": "https://example.com/avatar.jpg"}\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+ def testNonSupportedPolicy(self):
+ # Tests a policy that is not supported on Linux, so it shouldn't
+ # be included in the JSON file.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "NonLinuxPolicy",
+ "type": "list",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.mac:8-"],
+ "example_value": ["a"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = TEMPLATE_HEADER + '}'
+ self.CompareOutputs(output, expected_output)
+
+ def testPolicyGroup(self):
+ # Tests a policy group that has more than one policies.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "Group1",
+ "type": "group",
+ "caption": "",
+ "desc": "",
+ "policies": ["Policy1", "Policy2"],
+ },
+ {
+ "name": "Policy1",
+ "type": "list",
+ "caption": "Policy One",
+ "desc": "Policy One",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ["a", "b"]
+ },
+ {
+ "name": "Policy2",
+ "type": "string",
+ "caption": "Policy Two",
+ "desc": "Policy Two",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": "c"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'json')
+ expected_output = (
+ TEMPLATE_HEADER + ' // Policy One\n' + HEADER_DELIMETER +
+ ' // Policy One\n\n'
+ ' //"Policy1": ["a", "b"],\n\n'
+ ' // Policy Two\n' + HEADER_DELIMETER + ' // Policy Two\n\n'
+ ' //"Policy2": "c"\n\n'
+ '}')
+ self.CompareOutputs(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/mock_writer.py b/chromium/components/policy/tools/template_writers/writers/mock_writer.py
new file mode 100755
index 00000000000..19e1d748908
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/mock_writer.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from .template_writer import TemplateWriter
+
+
+class MockWriter(TemplateWriter):
+ '''Helper class for unit tests in policy_template_generator_unittest.py
+ '''
+
+ def __init__(self, platforms=[], config={}):
+ super(MockWriter, self).__init__(platforms, config)
+
+ def WritePolicy(self, policy):
+ pass
+
+ def BeginTemplate(self):
+ pass
+
+ def GetTemplateText(self):
+ pass
+
+ def IsPolicySupported(self, policy):
+ return True
+
+ def Test(self):
+ pass
diff --git a/chromium/components/policy/tools/template_writers/writers/plist_helper.py b/chromium/components/policy/tools/template_writers/writers/plist_helper.py
new file mode 100755
index 00000000000..e69f00dcb68
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/plist_helper.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Common functions for plist_writer and plist_strings_writer.
+'''
+
+
+def GetPlistFriendlyName(name):
+ '''Transforms a string so that it will be suitable for use as
+ a pfm_name in the plist manifest file.
+ '''
+ return name.replace(' ', '_')
diff --git a/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py
new file mode 100755
index 00000000000..adfbc6d279e
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import plist_helper
+from writers import template_writer
+
+
+def GetWriter(config):
+ '''Factory method for creating PListStringsWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return PListStringsWriter(['mac'], config)
+
+
+class PListStringsWriter(template_writer.TemplateWriter):
+ '''Outputs localized string table files for the Mac policy file.
+ These files are named Localizable.strings and they are in the
+ [lang].lproj subdirectories of the manifest bundle.
+ '''
+
+ def WriteComment(self, comment):
+ self._out.append('/* ' + comment + ' */')
+
+ def _AddToStringTable(self, item_name, caption, desc):
+ '''Add a title and a description of an item to the string table.
+
+ Args:
+ item_name: The name of the item that will get the title and the
+ description.
+ title: The text of the title to add.
+ desc: The text of the description to add.
+ '''
+ caption = caption.replace('"', '\\"')
+ caption = caption.replace('\n', '\\n')
+ desc = desc.replace('"', '\\"')
+ desc = desc.replace('\n', '\\n')
+ self._out.append('%s.pfm_title = \"%s\";' % (item_name, caption))
+ self._out.append('%s.pfm_description = \"%s\";' % (item_name, desc))
+
+ def PreprocessPolicies(self, policy_list):
+ return self.FlattenGroupsAndSortPolicies(policy_list)
+
+ def WritePolicy(self, policy):
+ '''Add strings to the stringtable corresponding a given policy.
+
+ Args:
+ policy: The policy for which the strings will be added to the
+ string table.
+ '''
+ desc = policy['desc']
+ if policy['type'] in ('int-enum', 'string-enum', 'string-enum-list'):
+ # Append the captions of enum items to the description string.
+ item_descs = []
+ for item in policy['items']:
+ item_descs.append(str(item['value']) + ' - ' + item['caption'])
+ desc = '\n'.join(item_descs) + '\n' + desc
+ if self.HasExpandedPolicyDescription(policy):
+ desc += '\n' + self.GetExpandedPolicyDescription(policy)
+
+ self._AddToStringTable(policy['name'], policy['label'], desc)
+
+ def BeginTemplate(self):
+ app_name = plist_helper.GetPlistFriendlyName(self.config['app_name'])
+ if self._GetChromiumVersionString() is not None:
+ self.WriteComment(self.config['build'] + ''' version: ''' + \
+ self._GetChromiumVersionString())
+ self._AddToStringTable(app_name, self.config['app_name'],
+ self.messages['mac_chrome_preferences']['text'])
+
+ def Init(self):
+ # A buffer for the lines of the string table being generated.
+ self._out = []
+
+ def GetTemplateText(self):
+ return '\n'.join(self._out)
diff --git a/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py
new file mode 100755
index 00000000000..91c39f431e3
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/plist_strings_writer_unittest.py
@@ -0,0 +1,402 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.plist_strings_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import writer_unittest_common
+
+
+class PListStringsWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for PListStringsWriter.'''
+
+ def testEmpty(self):
+ # Test PListStringsWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Chromium preferen"ces',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist_strings')
+ expected_output = ('Chromium.pfm_title = "Chromium";\n'
+ 'Chromium.pfm_description = "Chromium preferen\\"ces";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testEmptyVersion(self):
+ # Test PListStringsWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Chromium preferen"ces',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(
+ policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test',
+ 'version': '39.0.0.0'
+ }, 'plist_strings')
+ expected_output = ('/* chromium version: 39.0.0.0 */\n'
+ 'Chromium.pfm_title = "Chromium";\n'
+ 'Chromium.pfm_description = "Chromium preferen\\"ces";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainGroup',
+ 'type': 'group',
+ 'caption': 'Caption of main.',
+ 'desc': 'Description of main.',
+ 'policies': ['MainPolicy'],
+ },
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'supported_on': ['chrome.mac:8-'],
+ 'caption': 'Caption of main policy.',
+ 'desc': 'Description of main policy.',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Preferences of Google Chrome',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist_strings')
+ expected_output = (
+ 'Google_Chrome.pfm_title = "Google Chrome";\n'
+ 'Google_Chrome.pfm_description = "Preferences of Google Chrome";\n'
+ 'MainPolicy.pfm_title = "Caption of main policy.";\n'
+ 'MainPolicy.pfm_description = "Description of main policy.";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringPolicy(self):
+ # Tests a policy group with a single policy of type 'string'. Also test
+ # inheriting group description to policy description.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'StringGroup',
+ 'type': 'group',
+ 'caption': 'Caption of group.',
+ 'desc': """Description of group.
+With a newline.""",
+ 'policies': ['StringPolicy'],
+ },
+ {
+ 'name': 'StringPolicy',
+ 'type': 'string',
+ 'caption': 'Caption of policy.',
+ 'desc': """Description of policy.
+With a newline.""",
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Preferences of Chromium',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist_strings')
+ expected_output = ('Chromium.pfm_title = "Chromium";\n'
+ 'Chromium.pfm_description = "Preferences of Chromium";\n'
+ 'StringPolicy.pfm_title = "Caption of policy.";\n'
+ 'StringPolicy.pfm_description = '
+ '"Description of policy.\\nWith a newline.";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringListPolicy(self):
+ # Tests a policy group with a single policy of type 'list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ListGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['ListPolicy'],
+ },
+ {
+ 'name': 'ListPolicy',
+ 'type': 'list',
+ 'caption': 'Caption of policy.',
+ 'desc': """Description of policy.
+With a newline.""",
+ 'schema': {
+ 'type': 'array',
+ 'items': { 'type': 'string' },
+ },
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Preferences of Chromium',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist_strings')
+ expected_output = ('Chromium.pfm_title = "Chromium";\n'
+ 'Chromium.pfm_description = "Preferences of Chromium";\n'
+ 'ListPolicy.pfm_title = "Caption of policy.";\n'
+ 'ListPolicy.pfm_description = '
+ '"Description of policy.\\nWith a newline.";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringEnumListPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum-list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['EnumPolicy'],
+ },
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'string-enum-list',
+ 'caption': 'Caption of policy.',
+ 'desc': """Description of policy.
+With a newline.""",
+ 'schema': {
+ 'type': 'array',
+ 'items': { 'type': 'string' },
+ },
+ 'items': [
+ {
+ 'name': 'ProxyServerDisabled',
+ 'value': 'one',
+ 'caption': 'Option1'
+ },
+ {
+ 'name': 'ProxyServerAutoDetect',
+ 'value': 'two',
+ 'caption': 'Option2'
+ },
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Preferences of Chromium',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist_strings')
+ expected_output = ('Chromium.pfm_title = "Chromium";\n'
+ 'Chromium.pfm_description = "Preferences of Chromium";\n'
+ 'EnumPolicy.pfm_title = "Caption of policy.";\n'
+ 'EnumPolicy.pfm_description = '
+ '"one - Option1\\ntwo - Option2\\n'
+ 'Description of policy.\\nWith a newline.";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testIntEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['EnumPolicy'],
+ },
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'int-enum',
+ 'desc': 'Description of policy.',
+ 'caption': 'Caption of policy.',
+ 'items': [
+ {
+ 'name': 'ProxyServerDisabled',
+ 'value': 0,
+ 'caption': 'Option1'
+ },
+ {
+ 'name': 'ProxyServerAutoDetect',
+ 'value': 1,
+ 'caption': 'Option2'
+ },
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Google Chrome preferences',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist_strings')
+ expected_output = (
+ 'Google_Chrome.pfm_title = "Google Chrome";\n'
+ 'Google_Chrome.pfm_description = "Google Chrome preferences";\n'
+ 'EnumPolicy.pfm_title = "Caption of policy.";\n'
+ 'EnumPolicy.pfm_description = '
+ '"0 - Option1\\n1 - Option2\\nDescription of policy.";\n')
+
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['EnumPolicy'],
+ },
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'string-enum',
+ 'desc': 'Description of policy.',
+ 'caption': 'Caption of policy.',
+ 'items': [
+ {
+ 'name': 'ProxyServerDisabled',
+ 'value': 'one',
+ 'caption': 'Option1'
+ },
+ {
+ 'name': 'ProxyServerAutoDetect',
+ 'value': 'two',
+ 'caption': 'Option2'
+ },
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Google Chrome preferences',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist_strings')
+ expected_output = (
+ 'Google_Chrome.pfm_title = "Google Chrome";\n'
+ 'Google_Chrome.pfm_description = "Google Chrome preferences";\n'
+ 'EnumPolicy.pfm_title = "Caption of policy.";\n'
+ 'EnumPolicy.pfm_description = '
+ '"one - Option1\\ntwo - Option2\\nDescription of policy.";\n')
+
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testNonSupportedPolicy(self):
+ # Tests a policy that is not supported on Mac, so its strings shouldn't
+ # be included in the plist string table.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'NonMacGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['NonMacPolicy'],
+ },
+ {
+ 'name': 'NonMacPolicy',
+ 'type': 'string',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome_os:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {
+ 'mac_chrome_preferences': {
+ 'text': 'Google Chrome preferences',
+ 'desc': 'blah'
+ }
+ }
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist_strings')
+ expected_output = (
+ 'Google_Chrome.pfm_title = "Google Chrome";\n'
+ 'Google_Chrome.pfm_description = "Google Chrome preferences";')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/plist_writer.py b/chromium/components/policy/tools/template_writers/writers/plist_writer.py
new file mode 100755
index 00000000000..e8666486457
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/plist_writer.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from xml.dom import minidom
+from writers import plist_helper
+from writers import xml_formatted_writer
+
+# This writer outputs a Preferences Manifest file as documented at
+# https://developer.apple.com/library/mac/documentation/MacOSXServer/Conceptual/Preference_Manifest_Files
+
+
+def GetWriter(config):
+ '''Factory method for creating PListWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return PListWriter(['mac'], config)
+
+
+class PListWriter(xml_formatted_writer.XMLFormattedWriter):
+ '''Class for generating policy templates in Mac plist format.
+ It is used by PolicyTemplateGenerator to write plist files.
+ '''
+
+ STRING_TABLE = 'Localizable.strings'
+ TYPE_TO_INPUT = {
+ 'string': 'string',
+ 'int': 'integer',
+ 'int-enum': 'integer',
+ 'string-enum': 'string',
+ 'string-enum-list': 'array',
+ 'main': 'boolean',
+ 'list': 'array',
+ 'dict': 'dictionary',
+ 'external': 'dictionary',
+ }
+
+ def _AddKeyValuePair(self, parent, key_string, value_tag):
+ '''Adds a plist key-value pair to a parent XML element.
+
+ A key-value pair in plist consists of two XML elements next two each other:
+ <key>key_string</key>
+ <value_tag>...</value_tag>
+
+ Args:
+ key_string: The content of the key tag.
+ value_tag: The name of the value element.
+
+ Returns:
+ The XML element of the value tag.
+ '''
+ self.AddElement(parent, 'key', {}, key_string)
+ return self.AddElement(parent, value_tag)
+
+ def _AddStringKeyValuePair(self, parent, key_string, value_string):
+ '''Adds a plist key-value pair to a parent XML element, where the
+ value element contains a string. The name of the value element will be
+ <string>.
+
+ Args:
+ key_string: The content of the key tag.
+ value_string: The content of the value tag.
+ '''
+ self.AddElement(parent, 'key', {}, key_string)
+ self.AddElement(parent, 'string', {}, value_string)
+
+ def _AddRealKeyValuePair(self, parent, key_string, value_string):
+ '''Adds a plist key-value pair to a parent XML element, where the
+ value element contains a real number. The name of the value element will be
+ <real>.
+
+ Args:
+ key_string: The content of the key tag.
+ value_string: The content of the value tag.
+ '''
+ self.AddElement(parent, 'key', {}, key_string)
+ self.AddElement(parent, 'real', {}, value_string)
+
+ def _AddTargets(self, parent, policy):
+ '''Adds the following XML snippet to an XML element:
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+
+ Args:
+ parent: The parent XML element where the snippet will be added.
+ '''
+ array = self._AddKeyValuePair(parent, 'pfm_targets', 'array')
+ if self.CanBeRecommended(policy):
+ self.AddElement(array, 'string', {}, 'user')
+ if self.CanBeMandatory(policy):
+ self.AddElement(array, 'string', {}, 'user-managed')
+
+ def PreprocessPolicies(self, policy_list):
+ return self.FlattenGroupsAndSortPolicies(policy_list)
+
+ def WritePolicy(self, policy):
+ policy_name = policy['name']
+ policy_type = policy['type']
+
+ dict = self.AddElement(self._array, 'dict')
+ self._AddStringKeyValuePair(dict, 'pfm_name', policy_name)
+ # Set empty strings for title and description. They will be taken by the
+ # OSX Workgroup Manager from the string table in a Localizable.strings file.
+ # Those files are generated by plist_strings_writer.
+ self._AddStringKeyValuePair(dict, 'pfm_description', '')
+ self._AddStringKeyValuePair(dict, 'pfm_title', '')
+ self._AddTargets(dict, policy)
+ self._AddStringKeyValuePair(dict, 'pfm_type',
+ self.TYPE_TO_INPUT[policy_type])
+ if policy_type in ('int-enum', 'string-enum'):
+ range_list = self._AddKeyValuePair(dict, 'pfm_range_list', 'array')
+ for item in policy['items']:
+ if policy_type == 'int-enum':
+ element_type = 'integer'
+ else:
+ element_type = 'string'
+ self.AddElement(range_list, element_type, {}, str(item['value']))
+ elif policy_type in ('list', 'string-enum-list'):
+ subkeys = self._AddKeyValuePair(dict, 'pfm_subkeys', 'array')
+ subkeys_dict = self.AddElement(subkeys, 'dict')
+ subkeys_type = self._AddKeyValuePair(subkeys_dict, 'pfm_type', 'string')
+ self.AddText(subkeys_type, 'string')
+
+ def BeginTemplate(self):
+ self._plist.attributes['version'] = '1'
+ dict = self.AddElement(self._plist, 'dict')
+ if self._GetChromiumVersionString() is not None:
+ self.AddComment(self._plist, self.config['build'] + ' version: ' + \
+ self._GetChromiumVersionString())
+ app_name = plist_helper.GetPlistFriendlyName(self.config['app_name'])
+ self._AddStringKeyValuePair(dict, 'pfm_name', app_name)
+ self._AddStringKeyValuePair(dict, 'pfm_description', '')
+ self._AddStringKeyValuePair(dict, 'pfm_title', '')
+ self._AddRealKeyValuePair(dict, 'pfm_version', '1')
+ self._AddStringKeyValuePair(dict, 'pfm_domain',
+ self.config['mac_bundle_id'])
+
+ self._array = self._AddKeyValuePair(dict, 'pfm_subkeys', 'array')
+
+ def CreatePlistDocument(self):
+ dom_impl = minidom.getDOMImplementation('')
+ doctype = dom_impl.createDocumentType(
+ 'plist', '-//Apple//DTD PLIST 1.0//EN',
+ 'http://www.apple.com/DTDs/PropertyList-1.0.dtd')
+ return dom_impl.createDocument(None, 'plist', doctype)
+
+ def Init(self):
+ self._doc = self.CreatePlistDocument()
+ self._plist = self._doc.documentElement
+
+ def GetTemplateText(self):
+ return self.ToPrettyXml(self._doc)
diff --git a/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py
new file mode 100755
index 00000000000..92d75ee7f8f
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/plist_writer_unittest.py
@@ -0,0 +1,732 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.plist_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import writer_unittest_common
+
+
+class PListWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for PListWriter.'''
+
+ def _GetExpectedOutputs(self, product_name, bundle_id, policies):
+ '''Substitutes the variable parts into a plist template. The result
+ of this function can be used as an expected result to test the output
+ of PListWriter.
+
+ Args:
+ product_name: The name of the product, normally Chromium or Google Chrome.
+ bundle_id: The mac bundle id of the product.
+ policies: The list of policies.
+
+ Returns:
+ The text of a plist template with the variable parts substituted.
+ '''
+ return '''
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
+<plist version="1">
+ <dict>
+ <key>pfm_name</key>
+ <string>%s</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_version</key>
+ <real>1</real>
+ <key>pfm_domain</key>
+ <string>%s</string>
+ <key>pfm_subkeys</key>
+ %s
+ </dict>
+</plist>''' % (product_name, bundle_id, policies)
+
+ def _GetExpectedOutputsWithVersion(self, product_name, bundle_id, policies,
+ version):
+ '''Substitutes the variable parts into a plist template. The result
+ of this function can be used as an expected result to test the output
+ of PListWriter.
+
+ Args:
+ product_name: The name of the product, normally Chromium or Google Chrome.
+ bundle_id: The mac bundle id of the product.
+ policies: The list of policies.
+
+ Returns:
+ The text of a plist template with the variable parts substituted.
+ '''
+ return '''
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
+<plist version="1">
+ <dict>
+ <key>pfm_name</key>
+ <string>%s</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_version</key>
+ <real>1</real>
+ <key>pfm_domain</key>
+ <string>%s</string>
+ <key>pfm_subkeys</key>
+ %s
+ </dict>
+ <!--%s-->
+</plist>''' % (product_name, bundle_id, policies, version)
+
+ def testEmpty(self):
+ # Test PListWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs('Chromium', 'com.example.Test',
+ '<array/>')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testEmptyVersion(self):
+ # Test PListWriter in case of empty polices.
+ policy_json = '''
+ {
+ 'policy_definitions': [],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+
+ output = self.GetOutput(
+ policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test',
+ 'version': '39.0.0.0'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputsWithVersion(
+ 'Chromium', 'com.example.Test', '<array/>',
+ 'chromium version: 39.0.0.0')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainGroup',
+ 'type': 'group',
+ 'policies': ['MainPolicy'],
+ 'desc': '',
+ 'caption': '',
+ },
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'desc': '',
+ 'caption': '',
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {}
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>MainPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>boolean</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testRecommendedPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainGroup',
+ 'type': 'group',
+ 'policies': ['MainPolicy'],
+ 'desc': '',
+ 'caption': '',
+ },
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'desc': '',
+ 'caption': '',
+ 'features': {
+ 'can_be_recommended' : True
+ },
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {}
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>MainPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user</string>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>boolean</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testRecommendedOnlyPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'MainGroup',
+ 'type': 'group',
+ 'policies': ['MainPolicy'],
+ 'desc': '',
+ 'caption': '',
+ },
+ {
+ 'name': 'MainPolicy',
+ 'type': 'main',
+ 'desc': '',
+ 'caption': '',
+ 'features': {
+ 'can_be_recommended' : True,
+ 'can_be_mandatory' : False
+ },
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {}
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>MainPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user</string>
+ </array>
+ <key>pfm_type</key>
+ <string>boolean</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringPolicy(self):
+ # Tests a policy group with a single policy of type 'string'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'StringGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['StringPolicy'],
+ },
+ {
+ 'name': 'StringPolicy',
+ 'type': 'string',
+ 'supported_on': ['chrome.mac:8-'],
+ 'desc': '',
+ 'caption': '',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>StringPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>string</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testListPolicy(self):
+ # Tests a policy group with a single policy of type 'list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ListGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['ListPolicy'],
+ },
+ {
+ 'name': 'ListPolicy',
+ 'type': 'list',
+ 'schema': {
+ 'type': 'array',
+ 'items': { 'type': 'string' },
+ },
+ 'supported_on': ['chrome.mac:8-'],
+ 'desc': '',
+ 'caption': '',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>ListPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>array</string>
+ <key>pfm_subkeys</key>
+ <array>
+ <dict>
+ <key>pfm_type</key>
+ <string>string</string>
+ </dict>
+ </array>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringEnumListPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum-list'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ListGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['ListPolicy'],
+ },
+ {
+ 'name': 'ListPolicy',
+ 'type': 'string-enum-list',
+ 'schema': {
+ 'type': 'array',
+ 'items': { 'type': 'string' },
+ },
+ 'items': [
+ {'name': 'ProxyServerDisabled', 'value': 'one', 'caption': ''},
+ {'name': 'ProxyServerAutoDetect', 'value': 'two', 'caption': ''},
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ 'supported_on': ['chrome.mac:8-'],
+ 'desc': '',
+ 'caption': '',
+ }
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>ListPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>array</string>
+ <key>pfm_subkeys</key>
+ <array>
+ <dict>
+ <key>pfm_type</key>
+ <string>string</string>
+ </dict>
+ </array>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testIntPolicy(self):
+ # Tests a policy group with a single policy of type 'int'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'IntGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['IntPolicy'],
+ },
+ {
+ 'name': 'IntPolicy',
+ 'type': 'int',
+ 'caption': '',
+ 'desc': '',
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>IntPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>integer</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testIntEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['EnumPolicy'],
+ },
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'int-enum',
+ 'desc': '',
+ 'caption': '',
+ 'items': [
+ {'name': 'ProxyServerDisabled', 'value': 0, 'caption': ''},
+ {'name': 'ProxyServerAutoDetect', 'value': 1, 'caption': ''},
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Google_Chrome', 'com.example.Test2', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>EnumPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>integer</string>
+ <key>pfm_range_list</key>
+ <array>
+ <integer>0</integer>
+ <integer>1</integer>
+ </array>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testStringEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'EnumGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['EnumPolicy'],
+ },
+ {
+ 'name': 'EnumPolicy',
+ 'type': 'string-enum',
+ 'desc': '',
+ 'caption': '',
+ 'items': [
+ {'name': 'ProxyServerDisabled', 'value': 'one', 'caption': ''},
+ {'name': 'ProxyServerAutoDetect', 'value': 'two', 'caption': ''},
+ ],
+ 'supported_on': ['chrome.mac:8-'],
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Google_Chrome', 'com.example.Test2', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>EnumPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>string</string>
+ <key>pfm_range_list</key>
+ <array>
+ <string>one</string>
+ <string>two</string>
+ </array>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testDictionaryPolicy(self):
+ # Tests a policy group with a single policy of type 'dict'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'DictionaryGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['DictionaryPolicy'],
+ },
+ {
+ 'name': 'DictionaryPolicy',
+ 'type': 'dict',
+ 'supported_on': ['chrome.mac:8-'],
+ 'desc': '',
+ 'caption': '',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>DictionaryPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>dictionary</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testExternalPolicy(self):
+ # Tests a policy group with a single policy of type 'dict'.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'ExternalGroup',
+ 'type': 'group',
+ 'desc': '',
+ 'caption': '',
+ 'policies': ['ExternalPolicy'],
+ },
+ {
+ 'name': 'ExternalPolicy',
+ 'type': 'external',
+ 'supported_on': ['chrome.mac:8-'],
+ 'desc': '',
+ 'caption': '',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'mac_bundle_id': 'com.example.Test'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Chromium', 'com.example.Test', '''<array>
+ <dict>
+ <key>pfm_name</key>
+ <string>ExternalPolicy</string>
+ <key>pfm_description</key>
+ <string/>
+ <key>pfm_title</key>
+ <string/>
+ <key>pfm_targets</key>
+ <array>
+ <string>user-managed</string>
+ </array>
+ <key>pfm_type</key>
+ <string>dictionary</string>
+ </dict>
+ </array>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testNonSupportedPolicy(self):
+ # Tests a policy that is not supported on Mac, so it shouldn't
+ # be included in the plist file.
+ policy_json = '''
+ {
+ 'policy_definitions': [
+ {
+ 'name': 'NonMacGroup',
+ 'type': 'group',
+ 'caption': '',
+ 'desc': '',
+ 'policies': ['NonMacPolicy'],
+ },
+ {
+ 'name': 'NonMacPolicy',
+ 'type': 'string',
+ 'supported_on': ['chrome.linux:8-', 'chrome.win:7-'],
+ 'caption': '',
+ 'desc': '',
+ },
+ ],
+ 'policy_atomic_group_definitions': [],
+ 'placeholders': [],
+ 'messages': {},
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_google_chrome': '1',
+ 'mac_bundle_id': 'com.example.Test2'
+ }, 'plist')
+ expected_output = self._GetExpectedOutputs(
+ 'Google_Chrome', 'com.example.Test2', '''<array/>''')
+ self.assertEquals(output.strip(), expected_output.strip())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/reg_writer.py b/chromium/components/policy/tools/template_writers/writers/reg_writer.py
new file mode 100755
index 00000000000..3fbd0a6b358
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/reg_writer.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+
+from writers import template_writer
+
+
+def GetWriter(config):
+ '''Factory method for creating RegWriter objects.
+ See the constructor of TemplateWriter for description of
+ arguments.
+ '''
+ return RegWriter(['win', 'win7'], config)
+
+
+class RegWriter(template_writer.TemplateWriter):
+ '''Class for generating policy example files in .reg format (for Windows).
+ The generated files will define all the supported policies with example
+ values set for them. This class is used by PolicyTemplateGenerator to
+ write .reg files.
+ '''
+
+ NEWLINE = '\r\n'
+
+ def _QuoteAndEscapeString(self, string):
+ assert isinstance(string, str)
+ return json.dumps(string)
+
+ def _StartBlock(self, key, suffix, list):
+ key = 'HKEY_LOCAL_MACHINE\\' + key
+ if suffix:
+ key = key + '\\' + suffix
+ if key != self._last_key.get(id(list), None):
+ list.append('')
+ list.append('[%s]' % key)
+ self._last_key[id(list)] = key
+
+ def PreprocessPolicies(self, policy_list):
+ return self.FlattenGroupsAndSortPolicies(policy_list,
+ self.GetPolicySortingKey)
+
+ def GetPolicySortingKey(self, policy):
+ '''Extracts a sorting key from a policy. These keys can be used for
+ list.sort() methods to sort policies.
+ See TemplateWriter.SortPoliciesGroupsFirst for usage.
+ '''
+ is_list = policy['type'] in ('list', 'string-enum-list')
+ # Lists come after regular policies.
+ return (is_list, policy['name'])
+
+ def _WritePolicy(self, policy, key, list):
+ example_value = policy['example_value']
+
+ if policy['type'] in ('list', 'string-enum-list'):
+ self._StartBlock(key, policy['name'], list)
+ i = 1
+ for item in example_value:
+ list.append('"%d"=%s' % (i, self._QuoteAndEscapeString(item)))
+ i = i + 1
+ else:
+ self._StartBlock(key, None, list)
+ if policy['type'] in ('string', 'string-enum'):
+ example_value_str = self._QuoteAndEscapeString(example_value)
+ elif policy['type'] in ('dict', 'external'):
+ example_value_str = self._QuoteAndEscapeString(
+ json.dumps(example_value, sort_keys=True))
+ elif policy['type'] in ('main', 'int', 'int-enum'):
+ example_value_str = 'dword:%08x' % int(example_value)
+ else:
+ raise Exception('unknown policy type %s:' % policy['type'])
+
+ list.append('"%s"=%s' % (policy['name'], example_value_str))
+
+ def WriteComment(self, comment):
+ self._prefix.append('; ' + comment)
+
+ def WritePolicy(self, policy):
+ if self.CanBeMandatory(policy):
+ self._WritePolicy(policy, self._winconfig['reg_mandatory_key_name'],
+ self._mandatory)
+
+ def WriteRecommendedPolicy(self, policy):
+ self._WritePolicy(policy, self._winconfig['reg_recommended_key_name'],
+ self._recommended)
+
+ def BeginTemplate(self):
+ pass
+
+ def EndTemplate(self):
+ pass
+
+ def Init(self):
+ self._mandatory = []
+ self._recommended = []
+ self._last_key = {}
+ self._prefix = []
+ self._winconfig = self.config['win_config']['win']
+
+ def GetTemplateText(self):
+ self._prefix.append('Windows Registry Editor Version 5.00')
+ if self._GetChromiumVersionString() is not None:
+ self.WriteComment(self.config['build'] + ' version: ' + \
+ self._GetChromiumVersionString())
+ all = self._prefix + self._mandatory + self._recommended
+ return self.NEWLINE.join(all)
diff --git a/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py
new file mode 100755
index 00000000000..7278f76b007
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/reg_writer_unittest.py
@@ -0,0 +1,428 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.reg_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import writer_unittest_common
+
+
+class RegWriterUnittest(writer_unittest_common.WriterUnittestCommon):
+ '''Unit tests for RegWriter.'''
+
+ NEWLINE = '\r\n'
+
+ def CompareOutputs(self, output, expected_output):
+ '''Compares the output of the reg_writer with its expected output.
+
+ Args:
+ output: The output of the reg writer.
+ expected_output: The expected output.
+
+ Raises:
+ AssertionError: if the two strings are not equivalent.
+ '''
+ self.assertEquals(output.strip(), expected_output.strip())
+
+ def testEmpty(self):
+ # Test the handling of an empty policy list.
+ policy_json = '''
+ {
+ "policy_definitions": [],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {}
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ }, 'reg')
+ expected_output = 'Windows Registry Editor Version 5.00'
+ self.CompareOutputs(output, expected_output)
+
+ def testEmptyVersion(self):
+ # Test the handling of an empty policy list.
+ policy_json = '''
+ {
+ "policy_definitions": [],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {}
+ }'''
+ output = self.GetOutput(policy_json, {
+ '_chromium': '1',
+ 'version': '39.0.0.0'
+ }, 'reg')
+ expected_output = ('Windows Registry Editor Version 5.00\r\n'
+ '; chromium version: 39.0.0.0\r\n')
+ self.CompareOutputs(output, expected_output)
+
+ def testMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "MainPolicy",
+ "type": "main",
+ "features": { "can_be_recommended": True },
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": True
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]',
+ '"MainPolicy"=dword:00000001', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\Recommended]',
+ '"MainPolicy"=dword:00000001'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testRecommendedMainPolicy(self):
+ # Tests a policy group with a single policy of type 'main'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "MainPolicy",
+ "type": "main",
+ "features": {
+ "can_be_recommended": True,
+ "can_be_mandatory": False
+ },
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": True
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\Recommended]',
+ '"MainPolicy"=dword:00000001'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testStringPolicy(self):
+ # Tests a policy group with a single policy of type 'string'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "StringPolicy",
+ "type": "string",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": "hello, world! \\\" \\\\"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]',
+ '"StringPolicy"="hello, world! \\\" \\\\"'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testIntPolicy(self):
+ # Tests a policy group with a single policy of type 'int'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "IntPolicy",
+ "type": "int",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": 26
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]',
+ '"IntPolicy"=dword:0000001a'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testIntEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'int-enum'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "EnumPolicy",
+ "type": "int-enum",
+ "caption": "",
+ "desc": "",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": 0, "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": 1, "caption": ""},
+ ],
+ "supported_on": ["chrome.win:8-"],
+ "example_value": 1
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]',
+ '"EnumPolicy"=dword:00000001'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testStringEnumPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "EnumPolicy",
+ "type": "string-enum",
+ "caption": "",
+ "desc": "",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": "one", "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": "two","caption": ""},
+ ],
+ "supported_on": ["chrome.win:8-"],
+ "example_value": "two"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_google_chrome': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome]',
+ '"EnumPolicy"="two"'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testListPolicy(self):
+ # Tests a policy group with a single policy of type 'list'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ListPolicy",
+ "type": "list",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ["foo", "bar"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\ListPolicy]',
+ '"1"="foo"', '"2"="bar"'
+ ])
+
+ def testStringEnumListPolicy(self):
+ # Tests a policy group with a single policy of type 'string-enum-list'.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ListPolicy",
+ "type": "string-enum-list",
+ "caption": "",
+ "desc": "",
+ "items": [
+ {"name": "ProxyServerDisabled", "value": "foo", "caption": ""},
+ {"name": "ProxyServerAutoDetect", "value": "bar","caption": ""},
+ ],
+ "supported_on": ["chrome.linux:8-"],
+ "example_value": ["foo", "bar"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\ListPolicy]',
+ '"1"="foo"', '"2"="bar"'
+ ])
+
+ def testDictionaryPolicy(self):
+ # Tests a policy group with a single policy of type 'dict'.
+ example = {
+ 'bool': True,
+ 'dict': {
+ 'a': 1,
+ 'b': 2,
+ },
+ 'int': 10,
+ 'list': [1, 2, 3],
+ 'string': 'abc',
+ }
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "DictionaryPolicy",
+ "type": "dict",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": ''' + str(example) + '''
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]',
+ '"DictionaryPolicy"="{\\"bool\\": true, '
+ '\\"dict\\": {\\"a\\": 1, \\"b\\": 2}, \\"int\\": 10, '
+ '\\"list\\": [1, 2, 3], \\"string\\": \\"abc\\"}"'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testExternalPolicy(self):
+ # Tests a policy group with a single policy of type 'external'.
+ example = {
+ 'url': "https://example.com/avatar.jpg",
+ 'hash': "deadbeef",
+ }
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "ExternalPolicy",
+ "type": "external",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": %s
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }''' % str(example)
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]',
+ '"ExternalPolicy"="{\\"hash\\": \\"deadbeef\\", '
+ '\\"url\\": \\"https://example.com/avatar.jpg\\"}"'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+ def testNonSupportedPolicy(self):
+ # Tests a policy that is not supported on Windows, so it shouldn't
+ # be included in the .REG file.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "NonWindowsPolicy",
+ "type": "list",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.mac:8-"],
+ "example_value": ["a"]
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join(
+ ['Windows Registry Editor Version 5.00'])
+ self.CompareOutputs(output, expected_output)
+
+ def testPolicyGroup(self):
+ # Tests a policy group that has more than one policies.
+ policy_json = '''
+ {
+ "policy_definitions": [
+ {
+ "name": "Group1",
+ "type": "group",
+ "caption": "",
+ "desc": "",
+ "policies": ["Policy1", "Policy2"],
+ },
+ {
+ "name": "Policy1",
+ "type": "list",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": ["a", "b"]
+ },
+ {
+ "name": "Policy2",
+ "type": "string",
+ "caption": "",
+ "desc": "",
+ "supported_on": ["chrome.win:8-"],
+ "example_value": "c"
+ },
+ ],
+ "policy_atomic_group_definitions": [],
+ "placeholders": [],
+ "messages": {},
+ }'''
+ output = self.GetOutput(policy_json, {'_chromium': '1'}, 'reg')
+ expected_output = self.NEWLINE.join([
+ 'Windows Registry Editor Version 5.00', '',
+ '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium]', '"Policy2"="c"',
+ '', '[HKEY_LOCAL_MACHINE\\Software\\Policies\\Chromium\\Policy1]',
+ '"1"="a"', '"2"="b"'
+ ])
+ self.CompareOutputs(output, expected_output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/template_writer.py b/chromium/components/policy/tools/template_writers/writers/template_writer.py
new file mode 100755
index 00000000000..075a381aa2d
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/template_writer.py
@@ -0,0 +1,468 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+class TemplateWriter(object):
+ '''Abstract base class for writing policy templates in various formats.
+ The methods of this class will be called by PolicyTemplateGenerator.
+ '''
+ def __init__(self, platforms, config):
+ '''Initializes a TemplateWriter object.
+
+ Args:
+ platforms: List of platforms for which this writer can write policies.
+ config: A dictionary of information required to generate the template.
+ It contains some key-value pairs, including the following examples:
+ 'build': 'chrome' or 'chromium'
+ 'branding': 'Google Chrome' or 'Chromium'
+ 'mac_bundle_id': The Mac bundle id of Chrome. (Only set when building
+ for Mac.)
+ '''
+ self.platforms = platforms
+ self.config = config
+
+ def IsDeprecatedPolicySupported(self, policy):
+ '''Checks if the given deprecated policy is supported by the writer.
+
+ Args:
+ policy: The dictionary of the policy.
+
+ Returns:
+ True if the writer chooses to include the deprecated 'policy' in its
+ output.
+ '''
+ return False
+
+ def IsFuturePolicySupported(self, policy):
+ '''Checks if the given future policy is supported by the writer.
+
+ Args:
+ policy: The dictionary of the policy.
+
+ Returns:
+ True if the writer chooses to include the unreleased 'policy' in its
+ output.
+ '''
+ return False
+
+ def IsCloudOnlyPolicySupported(self, policy):
+ '''Checks if the given cloud only policy is supported by the writer.
+
+ Args:
+ policy: The dictionary of the policy.
+
+ Returns:
+ True if the writer chooses to include the cloud only 'policy' in its
+ output.
+ '''
+ return False
+
+ def IsInternalOnlyPolicySupported(self, policy):
+ '''Checks if the given internal policy is supported by the writer.
+
+ Args:
+ policy: The dictionary of the policy.
+
+ Returns:
+ True if the writer chooses to include the internal only 'policy' in its
+ output.
+ '''
+ return False
+
+ def IsPolicySupported(self, policy):
+ '''Checks if the given policy is supported by the writer.
+ In other words, the set of platforms supported by the writer
+ has a common subset with the set of platforms that support
+ the policy.
+
+ Args:
+ policy: The dictionary of the policy.
+
+ Returns:
+ True if the writer chooses to include 'policy' in its output.
+ '''
+ if ('deprecated' in policy and policy['deprecated'] is True
+ and not self.IsDeprecatedPolicySupported(policy)):
+ return False
+
+ if (self.IsCloudOnlyPolicy(policy)
+ and not self.IsCloudOnlyPolicySupported(policy)):
+ return False
+
+ if (self.IsInternalOnlyPolicy(policy)
+ and not self.IsInternalOnlyPolicySupported(policy)):
+ return False
+
+ for supported_on in policy['supported_on']:
+ if not self.IsVersionSupported(policy, supported_on):
+ continue
+ if '*' in self.platforms or supported_on['platform'] in self.platforms:
+ return True
+
+ if self.IsFuturePolicySupported(policy):
+ if '*' in self.platforms and policy['future_on']:
+ return True
+ for future in policy['future_on']:
+ if future['platform'] in self.platforms:
+ return True
+ return False
+
+ def GetPolicyFeature(self, policy, feature_name, value=None):
+ '''Returns policy feature with |feature_name| if exsits. Otherwise, returns
+ |value|.'''
+ return policy.get('features', {}).get(feature_name, value)
+
+ def CanBeRecommended(self, policy):
+ '''Checks if the given policy can be recommended.'''
+ return self.GetPolicyFeature(policy, 'can_be_recommended', False)
+
+ def CanBeMandatory(self, policy):
+ '''Checks if the given policy can be mandatory.'''
+ return self.GetPolicyFeature(policy, 'can_be_mandatory', True)
+
+ def IsCloudOnlyPolicy(self, policy):
+ '''Checks if the given policy is cloud only'''
+ return self.GetPolicyFeature(policy, 'cloud_only', False)
+
+ def IsInternalOnlyPolicy(self, policy):
+ '''Checks if the given policy is internal only'''
+ return self.GetPolicyFeature(policy, 'internal_only', False)
+
+ def IsPolicyOrItemSupportedOnPlatform(self,
+ item,
+ platform,
+ product=None,
+ management=None):
+ '''Checks if |item| is supported on |product| for |platform|. If
+ |product| is not specified, only the platform support is checked.
+ If |management| is specified, also checks for support for Chrome OS
+ management type.
+
+ Args:
+ item: The dictionary of the policy or item.
+ platform: The platform to check; one of
+ 'win', 'mac', 'linux', 'chrome_os', 'android'.
+ product: Optional product to check; one of
+ 'chrome', 'chrome_frame', 'chrome_os', 'webview'.
+ management: Optional Chrome OS management type to check; one of
+ 'active_directory', 'google_cloud'.
+ '''
+ if management and not self.IsCrOSManagementSupported(item, management):
+ return False
+
+ for supported_on in item['supported_on']:
+ if (platform == supported_on['platform']
+ and (not product or product in supported_on['product'])
+ and self.IsVersionSupported(item, supported_on)):
+ return True
+ if self.IsFuturePolicySupported(item):
+ if (product and {
+ 'platform': platform,
+ 'product': product
+ } in item.get('future_on', [])):
+ return True
+ if (not product and filter(lambda f: f['platform'] == platform,
+ item.get('future_on', []))):
+ return True
+ return False
+
+ def IsPolicySupportedOnWindows(self, policy, product=None):
+ ''' Checks if |policy| is supported on any Windows platform.
+
+ Args:
+ policy: The dictionary of the policy.
+ product: Optional product to check; one of
+ 'chrome', 'chrome_frame', 'chrome_os', 'webview'
+ '''
+ return (self.IsPolicyOrItemSupportedOnPlatform(policy, 'win', product)
+ or self.IsPolicyOrItemSupportedOnPlatform(policy, 'win7', product))
+
+ def IsCrOSManagementSupported(self, policy, management):
+ '''Checks whether |policy| supports the Chrome OS |management| type.
+
+ Args:
+ policy: The dictionary of the policy.
+ management: Chrome OS management type to check; one of
+ 'active_directory', 'google_cloud'.
+ '''
+ # By default, i.e. if supported_chrome_os_management is not set, all
+ # management types are supported.
+ return management in policy.get('supported_chrome_os_management',
+ ['active_directory', 'google_cloud'])
+
+ def IsVersionSupported(self, policy, supported_on):
+ '''Checks whether the policy is supported on current version'''
+ major_version = self._GetChromiumMajorVersion()
+ if not major_version:
+ return True
+
+ since_version = supported_on.get('since_version', None)
+ until_version = supported_on.get('until_version', None)
+
+ return ((not since_version or int(since_version) <= major_version)
+ and (not until_version or int(until_version) >= major_version))
+
+ def _GetChromiumVersionString(self):
+ '''Returns the Chromium version string stored in the environment variable
+ version (if it is set).
+
+ Returns: The Chromium version string or None if it has not been set.'''
+
+ return self.config.get('version', None)
+
+ def _GetChromiumMajorVersion(self):
+ ''' Returns the major version of Chromium if it exists
+ in config.
+ '''
+ return self.config.get('major_version', None)
+
+ def _GetPoliciesForWriter(self, group):
+ '''Filters the list of policies in the passed group that are supported by
+ the writer.
+
+ Args:
+ group: The dictionary of the policy group.
+
+ Returns: The list of policies of the policy group that are compatible
+ with the writer.
+ '''
+ if not 'policies' in group:
+ return []
+ result = []
+ for policy in group['policies']:
+ if self.IsPolicySupported(policy):
+ result.append(policy)
+ return result
+
+ def Init(self):
+ '''Initializes the writer. If the WriteTemplate method is overridden, then
+ this method must be called as first step of each template generation
+ process.
+ '''
+ pass
+
+ def WriteTemplate(self, template):
+ '''Writes the given template definition.
+
+ Args:
+ template: Template definition to write.
+
+ Returns:
+ Generated output for the passed template definition.
+ '''
+ self.messages = template['messages']
+ self.Init()
+ template['policy_definitions'] = \
+ self.PreprocessPolicies(template['policy_definitions'])
+ self.BeginTemplate()
+ self.WritePolicies(template['policy_definitions'])
+ self.EndTemplate()
+
+ return self.GetTemplateText()
+
+ def PreprocessPolicies(self, policy_list):
+ '''Preprocesses a list of policies according to a given writer's needs.
+ Preprocessing steps include sorting policies and stripping unneeded
+ information such as groups (for writers that ignore them).
+ Subclasses are encouraged to override this method, overriding
+ implementations may call one of the provided specialized implementations.
+ The default behaviour is to use SortPoliciesGroupsFirst().
+
+ Args:
+ policy_list: A list containing the policies to sort.
+
+ Returns:
+ The sorted policy list.
+ '''
+ return self.SortPoliciesGroupsFirst(policy_list)
+
+ def WritePolicies(self, policy_list):
+ '''Appends the template text corresponding to all the policies into the
+ internal buffer.
+
+ Args:
+ policy_list: A list containing the policies to write.
+ '''
+ for policy in policy_list:
+ if policy['type'] == 'group':
+ child_policies = list(self._GetPoliciesForWriter(policy))
+ child_recommended_policies = list(
+ filter(self.CanBeRecommended, child_policies))
+ if child_policies:
+ # Only write nonempty groups.
+ self.BeginPolicyGroup(policy)
+ for child_policy in child_policies:
+ # Nesting of groups is currently not supported.
+ self.WritePolicy(child_policy)
+ self.EndPolicyGroup()
+ if child_recommended_policies:
+ self.BeginRecommendedPolicyGroup(policy)
+ for child_policy in child_recommended_policies:
+ self.WriteRecommendedPolicy(child_policy)
+ self.EndRecommendedPolicyGroup()
+ elif self.IsPolicySupported(policy):
+ self.WritePolicy(policy)
+ if self.CanBeRecommended(policy):
+ self.WriteRecommendedPolicy(policy)
+
+ def WritePolicy(self, policy):
+ '''Appends the template text corresponding to a policy into the
+ internal buffer.
+
+ Args:
+ policy: The policy as it is found in the JSON file.
+ '''
+ raise NotImplementedError()
+
+ def WriteComment(self, comment):
+ '''Appends the comment to the internal buffer.
+
+ comment: The comment to be added.
+ '''
+ raise NotImplementedError()
+
+ def WriteRecommendedPolicy(self, policy):
+ '''Appends the template text corresponding to a recommended policy into the
+ internal buffer.
+
+ Args:
+ policy: The recommended policy as it is found in the JSON file.
+ '''
+ # TODO
+ #raise NotImplementedError()
+ pass
+
+ def BeginPolicyGroup(self, group):
+ '''Appends the template text corresponding to the beginning of a
+ policy group into the internal buffer.
+
+ Args:
+ group: The policy group as it is found in the JSON file.
+ '''
+ pass
+
+ def EndPolicyGroup(self):
+ '''Appends the template text corresponding to the end of a
+ policy group into the internal buffer.
+ '''
+ pass
+
+ def BeginRecommendedPolicyGroup(self, group):
+ '''Appends the template text corresponding to the beginning of a recommended
+ policy group into the internal buffer.
+
+ Args:
+ group: The recommended policy group as it is found in the JSON file.
+ '''
+ pass
+
+ def EndRecommendedPolicyGroup(self):
+ '''Appends the template text corresponding to the end of a recommended
+ policy group into the internal buffer.
+ '''
+ pass
+
+ def BeginTemplate(self):
+ '''Appends the text corresponding to the beginning of the whole
+ template into the internal buffer.
+ '''
+ raise NotImplementedError()
+
+ def EndTemplate(self):
+ '''Appends the text corresponding to the end of the whole
+ template into the internal buffer.
+ '''
+ pass
+
+ def GetTemplateText(self):
+ '''Gets the content of the internal template buffer.
+
+ Returns:
+ The generated template from the the internal buffer as a string.
+ '''
+ raise NotImplementedError()
+
+ def SortPoliciesGroupsFirst(self, policy_list):
+ '''Sorts a list of policies alphabetically. The order is the
+ following: first groups alphabetically by caption, then other policies
+ alphabetically by name. The order of policies inside groups is unchanged.
+
+ Args:
+ policy_list: The list of policies to sort. Sub-lists in groups will not
+ be sorted.
+ '''
+ policy_list.sort(key=self.GetPolicySortingKeyGroupsFirst)
+ return policy_list
+
+ def FlattenGroupsAndSortPolicies(self, policy_list, sorting_key=None):
+ '''Sorts a list of policies according to |sorting_key|, defaulting
+ to alphabetical sorting if no key is given. If |policy_list| contains
+ policies with type="group", it is flattened first, i.e. any groups' contents
+ are inserted into the list as first-class elements and the groups are then
+ removed.
+ '''
+ new_list = []
+ for policy in policy_list:
+ if policy['type'] == 'group':
+ for grouped_policy in policy['policies']:
+ new_list.append(grouped_policy)
+ else:
+ new_list.append(policy)
+ if sorting_key == None:
+ sorting_key = self.GetPolicySortingKeyName
+ new_list.sort(key=sorting_key)
+ return new_list
+
+ def GetPolicySortingKeyName(self, policy):
+ return policy['name']
+
+ def GetPolicySortingKeyGroupsFirst(self, policy):
+ '''Extracts a sorting key from a policy. These keys can be used for
+ list.sort() methods to sort policies.
+ See TemplateWriter.SortPolicies for usage.
+ '''
+ is_group = policy['type'] == 'group'
+ if is_group:
+ # Groups are sorted by caption.
+ str_key = policy['caption']
+ else:
+ # Regular policies are sorted by name.
+ str_key = policy['name']
+ # Groups come before regular policies.
+ return (not is_group, str_key)
+
+ def GetLocalizedMessage(self, msg_id):
+ '''Returns a localized message for this writer.
+
+ Args:
+ msg_id: The identifier of the message.
+
+ Returns:
+ The localized message.
+ '''
+ return self.messages['doc_' + msg_id]['text']
+
+ def HasExpandedPolicyDescription(self, policy):
+ '''Returns whether the policy has expanded documentation containing the link
+ to the documentation with schema and formatting.
+ '''
+ return (policy['type'] in ('dict', 'external') or 'url_schema' in policy
+ or 'validation_schema' in policy or 'description_schema' in policy)
+
+ def GetExpandedPolicyDescription(self, policy):
+ '''Returns the expanded description of the policy containing the link to the
+ documentation with schema and formatting.
+ '''
+ schema_description_link_text = self.GetLocalizedMessage(
+ 'schema_description_link')
+ url = None
+ if 'url_schema' in policy:
+ url = policy['url_schema']
+ if (policy['type'] in ('dict', 'external') or 'validation_schema' in policy
+ or 'description_schema' in policy):
+ url = (
+ 'https://cloud.google.com/docs/chrome-enterprise/policies/?policy=' +
+ policy['name'])
+ return schema_description_link_text.replace('$6', url) if url else ''
diff --git a/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py b/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py
new file mode 100755
index 00000000000..62c2e5e3fbc
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/template_writer_unittest.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Unit tests for writers.template_writer'''
+
+import os
+import sys
+if __name__ == '__main__':
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../../../..'))
+
+import unittest
+
+from writers import template_writer
+
+POLICY_DEFS = [
+ {
+ 'name': 'zp',
+ 'type': 'string',
+ 'caption': 'a1',
+ 'supported_on': []
+ },
+ {
+ 'type':
+ 'group',
+ 'caption':
+ 'z_group1_caption',
+ 'name':
+ 'group1',
+ 'policies': [{
+ 'name': 'z0',
+ 'type': 'string',
+ 'supported_on': []
+ }, {
+ 'name': 'a0',
+ 'type': 'string',
+ 'supported_on': []
+ }]
+ },
+ {
+ 'type': 'group',
+ 'caption': 'b_group2_caption',
+ 'name': 'group2',
+ 'policies': [{
+ 'name': 'q',
+ 'type': 'string',
+ 'supported_on': []
+ }],
+ }, {
+ 'name': 'ap',
+ 'type': 'string',
+ 'caption': 'a2',
+ 'supported_on': []
+ }
+]
+
+GROUP_FIRST_SORTED_POLICY_DEFS = [
+ {
+ 'type': 'group',
+ 'caption': 'b_group2_caption',
+ 'name': 'group2',
+ 'policies': [{
+ 'name': 'q',
+ 'type': 'string',
+ 'supported_on': []
+ }],
+ },
+ {
+ 'type':
+ 'group',
+ 'caption':
+ 'z_group1_caption',
+ 'name':
+ 'group1',
+ 'policies': [{
+ 'name': 'z0',
+ 'type': 'string',
+ 'supported_on': []
+ }, {
+ 'name': 'a0',
+ 'type': 'string',
+ 'supported_on': []
+ }]
+ },
+ {
+ 'name': 'ap',
+ 'type': 'string',
+ 'caption': 'a2',
+ 'supported_on': []
+ },
+ {
+ 'name': 'zp',
+ 'type': 'string',
+ 'caption': 'a1',
+ 'supported_on': []
+ },
+]
+
+IGNORE_GROUPS_SORTED_POLICY_DEFS = [
+ {
+ 'name': 'a0',
+ 'type': 'string',
+ 'supported_on': []
+ },
+ {
+ 'name': 'ap',
+ 'type': 'string',
+ 'caption': 'a2',
+ 'supported_on': []
+ },
+ {
+ 'name': 'q',
+ 'type': 'string',
+ 'supported_on': []
+ },
+ {
+ 'name': 'z0',
+ 'type': 'string',
+ 'supported_on': []
+ },
+ {
+ 'name': 'zp',
+ 'type': 'string',
+ 'caption': 'a1',
+ 'supported_on': []
+ },
+]
+
+
+class TemplateWriterUnittests(unittest.TestCase):
+ '''Unit tests for templater_writer.py.'''
+
+ def _IsPolicySupported(self,
+ platform,
+ version,
+ policy,
+ writer=template_writer.TemplateWriter):
+ tw = writer([platform], {'major_version': version})
+ if platform != '*':
+ self.assertEqual(
+ tw.IsPolicySupported(policy),
+ tw.IsPolicyOrItemSupportedOnPlatform(policy, platform))
+ return tw.IsPolicySupported(policy)
+
+ def testSortingGroupsFirst(self):
+ tw = template_writer.TemplateWriter(None, None)
+ sorted_list = tw.SortPoliciesGroupsFirst(POLICY_DEFS)
+ self.assertEqual(sorted_list, GROUP_FIRST_SORTED_POLICY_DEFS)
+
+ def testSortingIgnoreGroups(self):
+ tw = template_writer.TemplateWriter(None, None)
+ sorted_list = tw.FlattenGroupsAndSortPolicies(POLICY_DEFS)
+ self.assertEqual(sorted_list, IGNORE_GROUPS_SORTED_POLICY_DEFS)
+
+ def testPoliciesIsNotSupported(self):
+ tw = template_writer.TemplateWriter(None, None)
+ self.assertFalse(tw.IsPolicySupported({'deprecated': True}))
+ self.assertFalse(tw.IsPolicySupported({'features': {'cloud_only': True}}))
+ self.assertFalse(tw.IsPolicySupported({'features': {
+ 'internal_only': True
+ }}))
+
+ def testFuturePoliciesSupport(self):
+ class FutureWriter(template_writer.TemplateWriter):
+ def IsFuturePolicySupported(self, policy):
+ return True
+
+ expected_request_for_all_platforms = [[False, True, True],
+ [True, True, True]]
+ expected_request_for_all_win = [[False, False, True], [True, True, True]]
+ for i, writer in enumerate([template_writer.TemplateWriter, FutureWriter]):
+ for j, policy in enumerate([{
+ 'supported_on': [],
+ 'future_on': [{
+ 'product': 'chrome',
+ 'platform': 'win'
+ }, {
+ 'product': 'chrome',
+ 'platform': 'mac'
+ }]
+ }, {
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'mac'
+ }],
+ 'future_on': [{
+ 'product': 'chrome',
+ 'platform': 'win'
+ }]
+ }, {
+ 'supported_on': [{
+ 'product': 'chrome',
+ 'platform': 'win'
+ }, {
+ 'product': 'chrome',
+ 'platform': 'mac'
+ }],
+ 'future_on': []
+ }]):
+ self.assertEqual(expected_request_for_all_platforms[i][j],
+ self._IsPolicySupported('*', None, policy, writer))
+ self.assertEqual(
+ expected_request_for_all_win[i][j],
+ self._IsPolicySupported('win', None, policy, writer),
+ )
+
+ def testPoliciesIsSupportedOnCertainVersion(self):
+ platform = 'win'
+ policy = {
+ 'supported_on': [{
+ 'platform': 'win',
+ 'since_version': '11',
+ 'until_version': '12'
+ }]
+ }
+ self.assertFalse(self._IsPolicySupported(platform, 10, policy))
+ self.assertTrue(self._IsPolicySupported(platform, 11, policy))
+ self.assertTrue(self._IsPolicySupported(platform, 12, policy))
+ self.assertFalse(self._IsPolicySupported(platform, 13, policy))
+
+ policy = {
+ 'supported_on': [{
+ 'platform': 'win',
+ 'since_version': '11',
+ 'until_version': ''
+ }]
+ }
+ self.assertFalse(self._IsPolicySupported(platform, 10, policy))
+ self.assertTrue(self._IsPolicySupported(platform, 11, policy))
+ self.assertTrue(self._IsPolicySupported(platform, 12, policy))
+ self.assertTrue(self._IsPolicySupported(platform, 13, policy))
+
+ def testPoliciesIsSupportedOnMulitplePlatform(self):
+ policy = {
+ 'supported_on': [{
+ 'platform': 'win',
+ 'since_version': '12',
+ 'until_version': ''
+ }, {
+ 'platform': 'mac',
+ 'since_version': '11',
+ 'until_version': ''
+ }]
+ }
+ self.assertFalse(self._IsPolicySupported('win', 11, policy))
+ self.assertTrue(self._IsPolicySupported('mac', 11, policy))
+ self.assertTrue(self._IsPolicySupported('*', 11, policy))
+ self.assertFalse(self._IsPolicySupported('*', 10, policy))
+
+ def testHasExpandedPolicyDescriptionForUrlSchema(self):
+ policy = {'url_schema': 'https://example.com/details', 'type': 'list'}
+ tw = template_writer.TemplateWriter(None, None)
+ self.assertTrue(tw.HasExpandedPolicyDescription(policy))
+
+ def testHasExpandedPolicyDescriptionForJSONPolicies(self):
+ policy = {'name': 'PolicyName', 'type': 'dict'}
+ tw = template_writer.TemplateWriter(None, None)
+ self.assertTrue(tw.HasExpandedPolicyDescription(policy))
+
+ def testGetExpandedPolicyDescriptionForUrlSchema(self):
+ policy = {'type': 'integer', 'url_schema': 'https://example.com/details'}
+ tw = template_writer.TemplateWriter(None, None)
+ tw.messages = {
+ 'doc_schema_description_link': {
+ 'text': '''See $6'''
+ },
+ }
+ expanded_description = tw.GetExpandedPolicyDescription(policy)
+ self.assertEqual(expanded_description, 'See https://example.com/details')
+
+ def testGetExpandedPolicyDescriptionForJSONPolicies(self):
+ policy = {'name': 'PolicyName', 'type': 'dict'}
+ tw = template_writer.TemplateWriter(None, None)
+ tw.messages = {
+ 'doc_schema_description_link': {
+ 'text': '''See $6'''
+ },
+ }
+ expanded_description = tw.GetExpandedPolicyDescription(policy)
+ self.assertEqual(
+ expanded_description,
+ 'See https://cloud.google.com/docs/chrome-enterprise/policies/?policy=PolicyName'
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py b/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py
new file mode 100755
index 00000000000..d96bb7986d2
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/writer_unittest_common.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+'''Common tools for unit-testing writers.'''
+
+import unittest
+import policy_template_generator
+import template_formatter
+import textwrap
+import writer_configuration
+
+
+class WriterUnittestCommon(unittest.TestCase):
+ '''Common class for unittesting writers.'''
+
+ def GetOutput(self, policy_json, definitions, writer_type):
+ '''Generates an output of a writer.
+
+ Args:
+ policy_json: Raw policy JSON string.
+ definitions: Definitions to create writer configurations.
+ writer_type: Writer type (e.g. 'admx'), see template_formatter.py.
+
+ Returns:
+ The string of the template created by the writer.
+ '''
+
+ # Evaluate policy_json. For convenience, fix indentation in statements like
+ # policy_json = '''
+ # {
+ # ...
+ # }''')
+ start_idx = 1 if policy_json[0] == '\n' else 0
+ policy_data = eval(textwrap.dedent(policy_json[start_idx:]))
+
+ config = writer_configuration.GetConfigurationForBuild(definitions)
+ policy_generator = \
+ policy_template_generator.PolicyTemplateGenerator(config, policy_data)
+ writer = template_formatter.GetWriter(writer_type, config)
+ return policy_generator.GetTemplateText(writer)
diff --git a/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py b/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py
new file mode 100755
index 00000000000..a8815370472
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/xml_formatted_writer.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from writers import template_writer
+
+
+class XMLFormattedWriter(template_writer.TemplateWriter):
+ '''Helper class for generating XML-based templates.
+ '''
+
+ def AddElement(self, parent, name, attrs=None, text=None):
+ '''
+ Adds a new XML Element as a child to an existing element or the Document.
+
+ Args:
+ parent: An XML element or the document, where the new element will be
+ added.
+ name: The name of the new element.
+ attrs: A dictionary of the attributes' names and values for the new
+ element.
+ text: Text content for the new element.
+
+ Returns:
+ The created new element.
+ '''
+ if attrs == None:
+ attrs = {}
+
+ doc = parent.ownerDocument
+ element = doc.createElement(name)
+ for key, value in sorted(attrs.items()):
+ element.setAttribute(key, value)
+ if text:
+ element.appendChild(doc.createTextNode(text))
+ parent.appendChild(element)
+ return element
+
+ def AddText(self, parent, text):
+ '''Adds text to a parent node.
+ '''
+ doc = parent.ownerDocument
+ parent.appendChild(doc.createTextNode(text))
+
+ def AddAttribute(self, parent, name, value):
+ '''Adds a new attribute to the parent Element. If an attribute with the
+ given name already exists then it will be replaced.
+ '''
+ doc = parent.ownerDocument
+ attribute = doc.createAttribute(name)
+ attribute.value = value
+ parent.setAttributeNode(attribute)
+
+ def AddComment(self, parent, comment):
+ '''Adds a comment node.'''
+ parent.appendChild(parent.ownerDocument.createComment(comment))
+
+ def ToPrettyXml(self, doc, **kwargs):
+ # return doc.toprettyxml(indent=' ')
+ # The above pretty-printer does not print the doctype and adds spaces
+ # around texts, e.g.:
+ # <string>
+ # value of the string
+ # </string>
+ # This is problematic both for the OSX Workgroup Manager (plist files) and
+ # the Windows Group Policy Editor (admx files). What they need instead:
+ # <string>value of string</string>
+ # So we use a hacky pretty printer here. It assumes that there are no
+ # mixed-content nodes.
+ # Get all the XML content in a one-line string.
+ xml = doc.toxml(**kwargs)
+ # Determine where the line breaks will be. (They will only be between tags.)
+ lines = xml[1:len(xml) - 1].split('><')
+ indent = ''
+ res = ''
+ # Determine indent for each line.
+ for i, line in enumerate(lines):
+ if line[0] == '/':
+ # If the current line starts with a closing tag, decrease indent before
+ # printing.
+ indent = indent[2:]
+ lines[i] = indent + '<' + line + '>'
+ if (line[0] not in ['/', '?', '!'] and '</' not in line and
+ line[len(line) - 1] != '/'):
+ # If the current line starts with an opening tag and does not conatin a
+ # closing tag, increase indent after the line is printed.
+ indent += ' '
+ # Reconstruct XML text from the lines.
+ return '\n'.join(lines)
diff --git a/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py b/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py
new file mode 100755
index 00000000000..40c2ff52bb5
--- /dev/null
+++ b/chromium/components/policy/tools/template_writers/writers/xml_writer_base_unittest.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Unittests for writers.admx_writer."""
+
+import re
+import unittest
+
+
+class XmlWriterBaseTest(unittest.TestCase):
+ '''Base class for XML writer unit-tests.
+ '''
+
+ def GetXMLOfChildren(self, parent):
+ '''Returns the XML of all child nodes of the given parent node.
+ Args:
+ parent: The XML of the children of this node will be returned.
+
+ Return: XML of the chrildren of the parent node.
+ '''
+ raw_pretty_xml = ''.join(
+ child.toprettyxml(indent=' ') for child in parent.childNodes)
+ # Python 2.6.5 which is present in Lucid has bug in its pretty print
+ # function which produces new lines around string literals. This has been
+ # fixed in Precise which has Python 2.7.3 but we have to keep compatibility
+ # with both for now.
+ text_re = re.compile('>\n\s+([^<>\s].*?)\n\s*</', re.DOTALL)
+ return text_re.sub('>\g<1></', raw_pretty_xml)
+
+ def AssertXMLEquals(self, output, expected_output):
+ '''Asserts if the passed XML arguements are equal.
+ Args:
+ output: Actual XML text.
+ expected_output: Expected XML text.
+ '''
+ self.assertEquals(output.strip(), expected_output.strip())
diff --git a/chromium/components/strings/components_strings_af.xtb b/chromium/components/strings/components_strings_af.xtb
index 8097e849afb..963bce1e930 100644
--- a/chromium/components/strings/components_strings_af.xtb
+++ b/chromium/components/strings/components_strings_af.xtb
@@ -947,7 +947,7 @@ Dit sal andersins deur jou privaatheidinstellings geblokkeer word. Dit sal die i
<translation id="3532844647053365774"><ph name="HOST" /> wil jou mikrofoon gebruik</translation>
<translation id="3533328374079021623">Posbus 5</translation>
<translation id="3550112004925580947">Gasvryheidsbedryf</translation>
-<translation id="3552297013052089404">Sans serif-lettertipe</translation>
+<translation id="3552297013052089404">Sans Serif-lettertipe</translation>
<translation id="3558573058928565255">Dag</translation>
<translation id="3566021033012934673">Jou verbinding is nie privaat nie</translation>
<translation id="3567778190852720481">Kan nie met werkrekening inskryf nie (werkrekening kwalifiseer nie).</translation>
diff --git a/chromium/components/strings/components_strings_ar.xtb b/chromium/components/strings/components_strings_ar.xtb
index 9f063901bfe..1fe4b2bf4b3 100644
--- a/chromium/components/strings/components_strings_ar.xtb
+++ b/chromium/components/strings/components_strings_ar.xtb
@@ -595,7 +595,8 @@
<translation id="2544546346215446551">المشر٠أعاد تشغيل الجهاز</translation>
<translation id="2544644783021658368">مستند واحد</translation>
<translation id="254524874071906077">â€ØªØ¹ÙŠÙŠÙ† Chrome كمتصÙØ­ تلقائي</translation>
-<translation id="2546283357679194313">ملÙات تعري٠الارتباط وبيانات المواقع</translation>
+<translation id="2546283357679194313">ملÙات تعري٠الارتباط وبيانات المواقع الإلكترونية
+</translation>
<translation id="2547466893236767989">تصميم وتطوير مواقع إلكترونية</translation>
<translation id="254947805923345898">قيمة السياسة غير صحيحة.</translation>
<translation id="255002559098805027">أرسل <ph name="HOST_NAME" /> استجابة غير صالحة.</translation>
diff --git a/chromium/components/strings/components_strings_as.xtb b/chromium/components/strings/components_strings_as.xtb
index 922e3e049fc..698ede8ac07 100644
--- a/chromium/components/strings/components_strings_as.xtb
+++ b/chromium/components/strings/components_strings_as.xtb
@@ -54,7 +54,7 @@
<translation id="1112828774174131240">মূলà§à¦¯à§±à¦¾à¦¨ কাঠৰ সামগà§à§°à§€</translation>
<translation id="1113869188872983271">পà§à¦¨à¦ƒà¦•à§à§°à¦® কৰাটো &amp;আনডৠকৰক</translation>
<translation id="1123753900084781868">à¦à¦‡ মà§à¦¹à§‚ৰà§à¦¤à¦¤ লাইভ কেপশà§à¦¬à¦¨ উপলবà§à¦§ নহয়</translation>
-<translation id="1125573121925420732">ৱেবছাইটসমূহে নিজৰ সà§à§°à¦•à§à¦·à¦¾ আপডে'ট কৰোà¦à¦¤à§‡ সরà§à¦¤à¦•à¦¤à¦¾ বারà§à¦¤à¦¾ পোৱাটো সাধাৰণ কথা। à¦à¦‡à§Ÿà¦¾ অতি সোনকালেই উনà§à¦¨à¦¤ হ'ব লাগে।</translation>
+<translation id="1125573121925420732">ৱেবছাইটসমূহে নিজৰ সà§à§°à¦•à§à¦·à¦¾ আপডে'ট কৰোà¦à¦¤à§‡ সতৰà§à¦•à¦¤à¦¾ বারà§à¦¤à¦¾ পোৱাটো সাধাৰণ কথা। à¦à¦‡à§Ÿà¦¾ অতি সোনকালেই উনà§à¦¨à¦¤ হ'ব লাগে।</translation>
<translation id="112840717907525620">নীতিটোৰ কেশà§à¦¬ ঠিকে আছে</translation>
<translation id="1130564665089811311">পৃষà§à¦ à¦¾ অনà§à¦¬à¦¾à¦¦ কৰক বà§à¦Ÿà¦¾à¦®, à¦à¦‡ পৃষà§à¦ à¦¾à¦–ন Google Translateৰ জৰিয়তে অনà§à¦¬à¦¾à¦¦ কৰিবলৈ à¦à¦£à§à¦Ÿà¦¾à§° টিপক</translation>
<translation id="1131264053432022307">আপà§à¦¨à¦¿ পà§à§°à¦¤à¦¿à¦²à¦¿à¦ªà¦¿ কৰা পà§à§°à¦¤à¦¿à¦šà§à¦›à¦¬à¦¿</translation>
diff --git a/chromium/components/strings/components_strings_da.xtb b/chromium/components/strings/components_strings_da.xtb
index ba52e56898d..9756551c598 100644
--- a/chromium/components/strings/components_strings_da.xtb
+++ b/chromium/components/strings/components_strings_da.xtb
@@ -2064,7 +2064,7 @@ Ellers vil det blive blokeret af dine privatlivsindstillinger. Det giver det ind
<translation id="6694681292321232194"><ph name="FIND_MY_PHONE_FOCUSED_FRIENDLY_MATCH_TEXT" /> – tryk på Tab-tasten og derefter Enter for at finde din enhed på Google-kontoen</translation>
<translation id="6696588630955820014">Knappen "Del denne fane" – tryk på Enter for at dele denne fane ved at dele linket, oprette en QR-kode, caste m.m.</translation>
<translation id="6698381487523150993">Oprettet:</translation>
-<translation id="6702919718839027939">Nutid</translation>
+<translation id="6702919718839027939">Præsenter</translation>
<translation id="6709133671862442373">Nyheder</translation>
<translation id="6709888928011386878">Musikinstrumenter</translation>
<translation id="6710213216561001401">Forrige</translation>
diff --git a/chromium/components/strings/components_strings_de.xtb b/chromium/components/strings/components_strings_de.xtb
index 5017c81f421..1ed66f535b9 100644
--- a/chromium/components/strings/components_strings_de.xtb
+++ b/chromium/components/strings/components_strings_de.xtb
@@ -2966,7 +2966,7 @@ Weitere Details:
<translation id="950736567201356821">Dreifache Lochung oben</translation>
<translation id="961663415146723894">Bindung unten</translation>
<translation id="962484866189421427">Dieser Inhalt könnte betrügerische Apps installieren, die scheinbar einem anderen Zweck dienen oder Daten erfassen, um dich auszuspionieren. <ph name="BEGIN_LINK" />Trotzdem zeigen<ph name="END_LINK" /></translation>
-<translation id="96680173638229310">Autos und Fahrzeuge</translation>
+<translation id="96680173638229310">Fahrzeuge</translation>
<translation id="969892804517981540">Offizieller Build</translation>
<translation id="973773823069644502">Lieferadresse hinzufügen</translation>
<translation id="975560348586398090">{COUNT,plural, =0{Keine}=1{1 Eintrag}other{# Einträge}}</translation>
diff --git a/chromium/components/strings/components_strings_en-GB.xtb b/chromium/components/strings/components_strings_en-GB.xtb
index 6586ebd3063..b2067efb301 100644
--- a/chromium/components/strings/components_strings_en-GB.xtb
+++ b/chromium/components/strings/components_strings_en-GB.xtb
@@ -45,7 +45,7 @@
<translation id="1081061862829655580">Tray 19</translation>
<translation id="1086953900555227778">Index-5x8</translation>
<translation id="1088860948719068836">Add Name on Card</translation>
-<translation id="1089439967362294234">Change password</translation>
+<translation id="1089439967362294234">Change Password</translation>
<translation id="1096545575934602868">This field should not have more than <ph name="MAX_ITEMS_LIMIT" /> entries. All further entries will be discarded.</translation>
<translation id="1100782917270858593">Resume your journey button, press Enter to resume your journey and see relevant activity in your Chrome history</translation>
<translation id="1101672080107056897">Error action</translation>
@@ -77,7 +77,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="1177802847690410663">Web browsers</translation>
<translation id="1178581264944972037">Pause</translation>
<translation id="1181037720776840403">Remove</translation>
-<translation id="1186201132766001848">Check passwords</translation>
+<translation id="1186201132766001848">Check Passwords</translation>
<translation id="1195558154361252544">Notifications are automatically blocked for all sites except ones that you allow</translation>
<translation id="1197088940767939838">Orange</translation>
<translation id="1201402288615127009">Next</translation>
@@ -138,7 +138,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="1323433172918577554">Show More</translation>
<translation id="132390688737681464">Save and fill addresses</translation>
<translation id="1330449323196174374">Left gate fold</translation>
-<translation id="1333745675627230582">Play Chrome Dino game</translation>
+<translation id="1333745675627230582">Play Chrome Dino Game</translation>
<translation id="1333989956347591814">Your activity <ph name="BEGIN_EMPHASIS" />might still be visible<ph name="END_EMPHASIS" /> to:
<ph name="BEGIN_LIST" />
<ph name="LIST_ITEM" />Websites that you visit
@@ -452,7 +452,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="2126374524350484896">PDF producer:</translation>
<translation id="2130448033692577677">The templates that you've specified may not be applied due to the DnsOverHttpsMode policy not being set.</translation>
<translation id="2135799067377889518">Men's clothing</translation>
-<translation id="213826338245044447">Mobile bookmarks</translation>
+<translation id="213826338245044447">Mobile Bookmarks</translation>
<translation id="214556005048008348">Cancel payment</translation>
<translation id="2148613324460538318">Add Card</translation>
<translation id="2149968176347646218">Connection is not secure</translation>
@@ -574,7 +574,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="2495083838625180221">JSON Parser</translation>
<translation id="2498091847651709837">Scan new card</translation>
<translation id="2501278716633472235">Go back</translation>
-<translation id="2505268675989099013">Protect account</translation>
+<translation id="2505268675989099013">Protect Account</translation>
<translation id="2509167091171468975">Food and grocery retailers</translation>
<translation id="2512101340618156538">Not allowed (default)</translation>
<translation id="2512413427717747692">Set Chrome as default browser button, press Enter to set Chrome as the system's default browser in iOS settings</translation>
@@ -744,7 +744,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="2972581237482394796">&amp;Redo</translation>
<translation id="2977665033722899841"><ph name="ROW_NAME" />, currently selected. <ph name="ROW_CONTENT" /></translation>
<translation id="2978824962390592855">Opera</translation>
-<translation id="2983666748527428214">Open incognito tab</translation>
+<translation id="2983666748527428214">Open Incognito Tab</translation>
<translation id="2985306909656435243">If enabled, Chromium will store a copy of your card on this device for faster form filling.</translation>
<translation id="2985398929374701810">Enter a valid address</translation>
<translation id="2986368408720340940">This pickup method isn’t available. Try a different method.</translation>
@@ -1388,7 +1388,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="4757993714154412917">You just entered your password on a deceptive site. To secure your accounts, Chromium recommends checking your saved passwords.</translation>
<translation id="4758311279753947758">Add contact info</translation>
<translation id="4761104368405085019">Use your microphone</translation>
-<translation id="4761869838909035636">Run Chrome safety check</translation>
+<translation id="4761869838909035636">Run Chrome Safety Check</translation>
<translation id="4764776831041365478">The web page at <ph name="URL" /> might be temporarily down or it may have moved permanently to a new web address.</translation>
<translation id="4766713847338118463">Dual staple bottom</translation>
<translation id="4771973620359291008">An unknown error has occurred.</translation>
@@ -1515,7 +1515,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="5097099694988056070">Device statistics such as CPU/RAM usage</translation>
<translation id="5097501891273180634">A2</translation>
<translation id="5108881358339761672">Site is not secure</translation>
-<translation id="5109892411553231226">Manage payment methods</translation>
+<translation id="5109892411553231226">Manage Payment Methods</translation>
<translation id="5112422516732747637">A5</translation>
<translation id="5114288597538800140">Tray 18</translation>
<translation id="5114987907971894280">virtual reality</translation>
@@ -1613,7 +1613,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="5332219387342487447">Delivery method</translation>
<translation id="5333022057423422993">Chrome found the password that you just used in a data breach. To secure your accounts, we recommend checking your saved passwords.</translation>
<translation id="5334013548165032829">Detailed system logs</translation>
-<translation id="5334145288572353250">Save address?</translation>
+<translation id="5334145288572353250">Save Address?</translation>
<translation id="5335920952954443287">Beating heart</translation>
<translation id="5340250774223869109">Application is blocked</translation>
<translation id="534295439873310000">NFC devices</translation>
@@ -1764,7 +1764,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="5789643057113097023">.</translation>
<translation id="5803412860119678065">Do you want to fill in your <ph name="CARD_DETAIL" />?</translation>
<translation id="5804241973901381774">Permissions</translation>
-<translation id="5808435672482059465">View your Chrome history</translation>
+<translation id="5808435672482059465">View Your Chrome History</translation>
<translation id="5808542072418270309">Simulation games</translation>
<translation id="5810442152076338065">Your connection to <ph name="DOMAIN" /> is encrypted using an obsolete cipher suite.</translation>
<translation id="5812947184178430888">When security events are flagged by Chrome, relevant data about the events is sent to your administrator. This can include URLs of pages that you visit in Chrome, file names or metadata, and the username that you use to sign in to web-based applications, your device and Chrome.</translation>
@@ -1904,7 +1904,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="6232271601416750929">Clean beauty</translation>
<translation id="6233160458685643793">Grinning</translation>
<translation id="6234122620015464377">Trim after each document</translation>
-<translation id="6236290670123303279">Manage settings</translation>
+<translation id="6236290670123303279">Manage Settings</translation>
<translation id="6240447795304464094">Google Pay logo</translation>
<translation id="6241121617266208201">Hide suggestions</translation>
<translation id="624499991300733384">Print compositor service</translation>
@@ -2081,7 +2081,7 @@ This will otherwise be blocked by your privacy settings. This will allow the con
<translation id="6753269504797312559">Policy Value</translation>
<translation id="6755241357817244406">chrome://flags</translation>
<translation id="6757797048963528358">Your device went to sleep.</translation>
-<translation id="6767985426384634228">Update address?</translation>
+<translation id="6767985426384634228">Update Address?</translation>
<translation id="6768213884286397650">Hagaki (Postcard)</translation>
<translation id="6775759552199460396">JIS B2</translation>
<translation id="6784045420901191374">Commercial lending</translation>
@@ -2308,7 +2308,7 @@ Additional details:
<translation id="7386364858855961704">Not applicable</translation>
<translation id="7388594495505979117">{0,plural, =1{Your device will restart in 1 minute}other{Your device will restart in # minutes}}</translation>
<translation id="7390545607259442187">Confirm Card</translation>
-<translation id="7392089738299859607">Update address</translation>
+<translation id="7392089738299859607">Update Address</translation>
<translation id="7399802613464275309">Safety check</translation>
<translation id="7400418766976504921">URL</translation>
<translation id="7403392780200267761">Share this tab by sharing the link, creating a QR code, casting and more</translation>
@@ -2342,7 +2342,7 @@ Additional details:
<translation id="7460618730930299168">The screening is different from what you selected. Continue?</translation>
<translation id="7463075493919226237">Australian football</translation>
<translation id="7469935732330206581">Form is not secure</translation>
-<translation id="7473891865547856676">No, thanks</translation>
+<translation id="7473891865547856676">No, Thanks</translation>
<translation id="7481312909269577407">Forward</translation>
<translation id="7485870689360869515">No data found.</translation>
<translation id="7485948220959728508">Who’s behind this information?</translation>
@@ -2499,7 +2499,7 @@ Additional details:
<translation id="7871445724586827387">Change your Google Account password</translation>
<translation id="7877007680666472091">protected content IDs</translation>
<translation id="7878562273885520351">Your password may be compromised</translation>
-<translation id="7880146494886811634">Save address</translation>
+<translation id="7880146494886811634">Save Address</translation>
<translation id="7882421473871500483">Brown</translation>
<translation id="7882995332186050355">Cargo trucks and trailers</translation>
<translation id="7887683347370398519">Check your CVC and try again</translation>
@@ -2634,7 +2634,7 @@ Additional details:
<translation id="8252991034201168845">Manage accessibility settings button, Enter to personalise your accessibility tools in Chrome OS settings</translation>
<translation id="8253091569723639551">Billing address required</translation>
<translation id="8257387598443225809">This app is designed for mobile</translation>
-<translation id="825929999321470778">Show all saved passwords</translation>
+<translation id="825929999321470778">Show All Saved Passwords</translation>
<translation id="8261506727792406068">Delete</translation>
<translation id="8262952874573525464">Edge stitch bottom</translation>
<translation id="8265992338205884890">Visible data</translation>
diff --git a/chromium/components/strings/components_strings_eu.xtb b/chromium/components/strings/components_strings_eu.xtb
index 71935ed2aef..69f099c37ce 100644
--- a/chromium/components/strings/components_strings_eu.xtb
+++ b/chromium/components/strings/components_strings_eu.xtb
@@ -468,7 +468,7 @@ Bestela, pribatutasun-ezarpenek blokeatu egingo dute baimen hori. Baimen honekin
<translation id="2185836064961771414">Futbol amerikarra</translation>
<translation id="2187317261103489799">Hauteman (lehenetsia)</translation>
<translation id="2188375229972301266">Hainbat zulo behean</translation>
-<translation id="2188852899391513400">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Kontuak babesteko, Google-ren Pasahitz-kudeatzailea eginbideak pasahitza orain aldatzea gomendatzen du, eta gero gordeta dauzkazun pasahitzak seguruak direla egiaztatzea.</translation>
+<translation id="2188852899391513400">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Kontuak babesteko, Google-ren Pasahitz-kudeatzailea zerbitzuak pasahitza orain aldatzea gomendatzen du, eta gero gordeta dauzkazun pasahitzak seguruak direla egiaztatzea.</translation>
<translation id="219906046732893612">Etxeko hobekuntzak</translation>
<translation id="2202020181578195191">Idatzi balio duen iraungitze-urte bat</translation>
<translation id="22081806969704220">3. erretilua</translation>
@@ -712,7 +712,7 @@ Bestela, pribatutasun-ezarpenek blokeatu egingo dute baimen hori. Baimen honekin
<translation id="2903493209154104877">Helbideak</translation>
<translation id="290376772003165898">Ez al da <ph name="LANGUAGE" /> orriko hizkuntza?</translation>
<translation id="2909946352844186028">Aldaketa bat hauteman da sarean.</translation>
-<translation id="2911973620368911614">Laneko kontabilitateaz arduratzen den erabiltzailearen IDa</translation>
+<translation id="2911973620368911614">Laneko kontabilitateaz arduratzen den erabiltzaile IDa</translation>
<translation id="2914160345369867329">Eskuarki, <ph name="SITE" /> webguneak enkriptatzea erabiltzen du informazioa babesteko. Chrome <ph name="SITE" /> webgunera konektatzen saiatu denean, webguneak kredentzial desegokiak eta ezohikoak bidali ditu. Erasotzaile bat <ph name="SITE" /> webgunearen plantak egiten ari delako gerta daiteke hori, edo wifi-sarean saioa hasteko pantailak konexioa eten duelako. Zure informazioa seguru dago, datuak trukatu aurretik eten baitu Chrome-k konexioa.</translation>
<translation id="2915068235268646559">Hutsegitearen ordua: <ph name="CRASH_TIME" /></translation>
<translation id="2915496182262110498">Margotzea</translation>
@@ -1266,7 +1266,7 @@ Bestela, pribatutasun-ezarpenek blokeatu egingo dute baimen hori. Baimen honekin
<translation id="4441832193888514600">Ez ikusi egin zaio hodeiko erabiltzaile-gidalerro gisa soilik ezar daitekeelako gidalerroa.</translation>
<translation id="4450893287417543264">Ez erakutsi berriro</translation>
<translation id="4451135742916150903">HID gailuetara konektatzea eska dezake</translation>
-<translation id="4452328064229197696">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Kontuak babesteko, Google-ren Pasahitz-kudeatzailea eginbideak gordeta dauzkazun pasahitzak seguruak direla egiaztatzea gomendatzen du.</translation>
+<translation id="4452328064229197696">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Kontuak babesteko, Google-ren Pasahitz-kudeatzailea zerbitzuak gordeta dauzkazun pasahitzak seguruak direla egiaztatzea gomendatzen du.</translation>
<translation id="4455222631300069614">Aldatu pasahitza</translation>
<translation id="4460315069258617173">Baimenduta webgune honetako fitxak ixten dituzun arte</translation>
<translation id="4464826014807964867">Zure erakundeari buruzko informazioa duten webguneak</translation>
@@ -1844,7 +1844,7 @@ Bestela, pribatutasun-ezarpenek blokeatu egingo dute baimen hori. Baimen honekin
<translation id="6051221802930200923">Une honetan ezin zara joan <ph name="SITE" /> webgunera, ziurtagiri-ainguratzea baitarabil. Sareko erroreak eta erasoak aldi baterakoak izan ohi dira; beraz, geroago funtzionatuko du orriak, segur aski.</translation>
<translation id="6051898664905071243">Orri kopurua:</translation>
<translation id="6052284303005792909">•</translation>
-<translation id="6052319569711353666">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Google-ren Pasahitz-kudeatzailea eginbideak pasahitza orain aldatzea gomendatzen du.</translation>
+<translation id="6052319569711353666">Erabili berri duzun pasahitza datuen isilpekotasunaren urratze batean aurkitu da. Google-ren Pasahitz-kudeatzailea zerbitzuak pasahitza orain aldatzea gomendatzen du.</translation>
<translation id="6055888660316801977">Ordainketak egiteko kredentzial segurua bat ez datorrela dioen kredentzial-orria</translation>
<translation id="6058977677006700226">Gailu guztietan erabili nahi dituzu txartelak?</translation>
<translation id="6059925163896151826">USB bidezko ailuak</translation>
diff --git a/chromium/components/strings/components_strings_fa.xtb b/chromium/components/strings/components_strings_fa.xtb
index 3277824004b..6e5c5880f30 100644
--- a/chromium/components/strings/components_strings_fa.xtb
+++ b/chromium/components/strings/components_strings_fa.xtb
@@ -147,7 +147,7 @@
<ph name="END_LIST" /></translation>
<translation id="1337692097987160377">هم‌رسانی این برگه</translation>
<translation id="1339601241726513588">دامنه ثبت‌نام:</translation>
-<translation id="1340482604681802745">نشانی تحویل گرÙتن کالا</translation>
+<translation id="1340482604681802745">نشانی تحویل گرÙتن</translation>
<translation id="1346748346194534595">راست</translation>
<translation id="1348198688976932919">سایت پیش‌رو حاوی برنامه‌های خطرناک است</translation>
<translation id="1348779747280417563">تأیید نام</translation>
@@ -207,7 +207,7 @@
<translation id="1476595624592550506">گذرواژه‌تان را تغییر دهید</translation>
<translation id="1482879811280872320">دوچرخه‌سواری</translation>
<translation id="1483493594462132177">ارسال</translation>
-<translation id="1484290072879560759">انتخاب نشانی تحویل کالا</translation>
+<translation id="1484290072879560759">انتخاب نشانی ارسال کالا</translation>
<translation id="1492194039220927094">اعمال خط‌مشی‌ها:</translation>
<translation id="149293076951187737">â€ÙˆÙ‚تی همه برگه‌های ناشناس Chrome را ببندید، Ùعالیتتان در این برگه‌ها از دستگاهتان پاک می‌شود:
<ph name="BEGIN_LIST" />
@@ -236,7 +236,7 @@
<translation id="153384715582417236">درحال‌حاضر مورد دیگری وجود ندارد</translation>
<translation id="1536390784834419204">ترجمه صÙحه</translation>
<translation id="1539840569003678498">گزارش ارسال شد:</translation>
-<translation id="154408704832528245">انتخاب نشانی ارسال</translation>
+<translation id="154408704832528245">انتخاب نشانی تحویل کالا</translation>
<translation id="1549470594296187301">برای استÙاده از این قابلیت، جاوا اسکریپت باید Ùعال باشد.</translation>
<translation id="155039086686388498">Engineering-D</translation>
<translation id="1551884710160394169">نرم‌اÙزار رایگان Ùˆ اشتراکی</translation>
@@ -674,7 +674,7 @@
<translation id="2728127805433021124">گواهی سرور با استÙاده از یک الگوریتم امضای ضعی٠امضا شده است.</translation>
<translation id="2730326759066348565"><ph name="BEGIN_LINK" />در حال اجرای عیب‌یابی اتصال<ph name="END_LINK" /></translation>
<translation id="2730600605555029057">موسیقی کلاسیک</translation>
-<translation id="2738330467931008676">انتخاب نشانی تحویل گرÙتن</translation>
+<translation id="2738330467931008676">انتخاب نشانی تحویل کالا</translation>
<translation id="2740531572673183784">تأیید</translation>
<translation id="2742511345840685325">تنیس روی میز</translation>
<translation id="2742870351467570537">حذ٠موارد انتخابی</translation>
@@ -894,10 +894,10 @@
<translation id="3381668585148405088">به تأیید رساندن خرید</translation>
<translation id="3383566085871012386">ترتیب اولویت کنونی</translation>
<translation id="3387261909427947069">روش‌های پرداخت</translation>
-<translation id="3391030046425686457">نشانی ارسال</translation>
+<translation id="3391030046425686457">نشانی تحویل کالا</translation>
<translation id="3391482648489541560">ویرایش Ùایل</translation>
<translation id="3395827396354264108">روش تحویل گرÙتن</translation>
-<translation id="3399952811970034796">نشانی ارسال</translation>
+<translation id="3399952811970034796">نشانی تحویل کالا</translation>
<translation id="3402261774528610252">اتصال استÙاده‌شده برای بار کردن این سایت از «امنیت لایه انتقال» نسخه Û±.Û° یا Û±.Û± استÙاده می‌کرد Ú©Ù‡ منسوخ شده است Ùˆ در آینده غیرÙعال خواهد شد. بعد از غیرÙعال شدن، کاربران نمی‌توانند این سایت را بار کنند. سرور باید «امنیت لایه انتقال» نسخه Û±.Û² یا بالاتر را Ùعال کند.</translation>
<translation id="3405664148539009465">سÙارشی کردن قلم‌ها</translation>
<translation id="3409896703495473338">مدیریت تنظیمات امنیتی</translation>
@@ -1566,7 +1566,7 @@
<translation id="5222812217790122047">ایمیل ضروری است</translation>
<translation id="5228404122310299359">لوازم مهمانی و تعطیلات</translation>
<translation id="5229588705416009823">بازی چندنÙره گسترده</translation>
-<translation id="5230733896359313003">نشانی تحویل کالا</translation>
+<translation id="5230733896359313003">نشانی ارسال کالا</translation>
<translation id="5230815978613972521">B8</translation>
<translation id="5233045608889518621">12x19</translation>
<translation id="5234764350956374838">رد کردن</translation>
@@ -1727,7 +1727,7 @@
<translation id="568292603005599551">â€Ù…وقعیت X تصویر</translation>
<translation id="5684277895745049190">Ùهرست</translation>
<translation id="5684874026226664614">متأسÙیم. این صÙحه ترجمه نشد.</translation>
-<translation id="5685654322157854305">اÙزودن نشانی تحویل کالا</translation>
+<translation id="5685654322157854305">اÙزودن نشانی ارسال کالا</translation>
<translation id="5689199277474810259">â€ØµØ§Ø¯Ø± کردن به JSON</translation>
<translation id="5689516760719285838">مکان</translation>
<translation id="569000877158168851">â€Ù…قدار DnsOverHttpsTemplates مرتبط نیست Ùˆ استÙاده نخواهد شد، مگراینکه خط‌مشی DnsOverHttpsMode روی <ph name="SECURE_DNS_MODE_AUTOMATIC" /> یا<ph name="SECURE_DNS_MODE_SECURE" /> تنظیم شده باشد.</translation>
@@ -2062,7 +2062,7 @@
<translation id="6689249931105087298">نسبی با Ùشرده‌سازی نقطه سیاه</translation>
<translation id="6689271823431384964">â€Ú†ÙˆÙ† به سیستم وارد شده‌اید، Chrome پیشنهاد می‌کند کارت‌ها را در حساب Google ذخیره کنید. در تنظیمات می‌توانید این رÙتار را تغییر دهید. نام دارنده کارت از حساب شما گرÙته شده است.</translation>
<translation id="6694681292321232194">â€<ph name="FIND_MY_PHONE_FOCUSED_FRIENDLY_MATCH_TEXT" />ØŒ برای پیدا کردن دستگاهتان در «حساب Google»، کلید «جهش» Ùˆ سپس «ورود» را Ùشار دهید</translation>
-<translation id="6696588630955820014">دکمه «هم‌رسانی این برگه»؛ برای هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، ارسال محتوا، Ùˆ موارد دیگر، کلید «ورود» را Ùشار دهید</translation>
+<translation id="6696588630955820014">دکمه «هم‌رسانی این برگه»؛ برای هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، پخش محتوا، Ùˆ موارد دیگر، کلید «ورود» را Ùشار دهید</translation>
<translation id="6698381487523150993">ایجاد شده:</translation>
<translation id="6702919718839027939">ارائه کردن</translation>
<translation id="6709133671862442373">اخبار</translation>
@@ -2312,7 +2312,7 @@
<translation id="7392089738299859607">به‌روزرسانی نشانی</translation>
<translation id="7399802613464275309">بررسی ایمنی</translation>
<translation id="7400418766976504921">نشانی وب</translation>
-<translation id="7403392780200267761">هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، ارسال محتوا، و موارد دیگر</translation>
+<translation id="7403392780200267761">هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، پخش محتوا، و موارد دیگر</translation>
<translation id="7403591733719184120">دستگاه <ph name="DEVICE_NAME" /> تحت مدیریت است</translation>
<translation id="7407424307057130981">â€&lt;p&gt;اگر در رایانه Windows نرم‌اÙزار Superfish داشته باشید، این خطا را می‌بینید.&lt;/p&gt;
&lt;p&gt;برای غیرÙعال کردن موقت این نرم‌اÙزار Ùˆ دسترسی به وب، این مراحل را دنبال کنید. لازم است امتیازهای سرپرست را داشته باشید.&lt;/p&gt;
@@ -2860,7 +2860,7 @@
<translation id="8963213021028234748"><ph name="MARKUP_1" />پیشنهادات:<ph name="MARKUP_2" />مطمئن شوید اتصال داده دارید<ph name="MARKUP_3" />بعداً این صÙحه وب را تازه‌سازی کنید<ph name="MARKUP_4" />آدرسی را Ú©Ù‡ وارد کرده‌اید، بررسی کنید<ph name="MARKUP_5" /></translation>
<translation id="8968766641738584599">ذخیره کارت</translation>
<translation id="8971063699422889582">گواهی سرور منقضی شده است.</translation>
-<translation id="8975012916872825179">شامل اطلاعاتی مانند شماره تلÙن، نشانی ایمیل Ùˆ نشانی تحویل کالا می‌شود</translation>
+<translation id="8975012916872825179">شامل اطلاعاتی مانند شماره تلÙن، نشانی ایمیل، Ùˆ نشانی تحویل کالا می‌شود</translation>
<translation id="8975263830901772334">نام Ùایل‌هایی Ú©Ù‡ چاپ می‌کنید</translation>
<translation id="8978053250194585037">â€Google Safe Browsing اخیراً در <ph name="SITE" />ØŒ <ph name="BEGIN_LINK" />رمزگیری شناسایی کرده است<ph name="END_LINK" />. سایت‌های رمزگیری وانمود می‌کنند وب‌سایت‌های دیگری هستند تا شما را Ùریب دهند.</translation>
<translation id="8983369100812962543">اکنون می‌توانید برنامه را تغییر اندازه دهید</translation>
@@ -2883,7 +2883,7 @@
<translation id="9020200922353704812">نشانی صورت‌حساب کارت لازم است</translation>
<translation id="9020542370529661692">این صÙحه به <ph name="TARGET_LANGUAGE" /> ترجمه شده است</translation>
<translation id="9020742383383852663">A8</translation>
-<translation id="9021429684248523859"><ph name="SHARE_THIS_PAGE_FOCUSED_FRIENDLY_MATCH_TEXT" />Ø› برای هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، ارسال محتوا، Ùˆ موارد دیگر، کلید «جهش» Ùˆ سپس «ورود» را Ùشار دهید</translation>
+<translation id="9021429684248523859"><ph name="SHARE_THIS_PAGE_FOCUSED_FRIENDLY_MATCH_TEXT" />Ø› برای هم‌رسانی این برگه ازطریق هم‌رسانی پیوند، ایجاد رمزینه پاسخ‌سریع، پخش محتوا، Ùˆ موارد دیگر، کلید «جهش» Ùˆ سپس «ورود» را Ùشار دهید</translation>
<translation id="9025348182339809926">(نامعتبر)</translation>
<translation id="9030265603405983977">تک‌رنگ</translation>
<translation id="9035022520814077154">خطای امنیتی</translation>
@@ -2977,7 +2977,7 @@
<translation id="962484866189421427">این محتوا ممکن است برنامه‌های Ùریب‌دهنده‌ای نصب کند Ú©Ù‡ وانمود می‌کنند برنامه دیگری هستند یا اینکه داده‌هایی برای ردیابی شما جمع‌آوری می‌کنند. <ph name="BEGIN_LINK" />درهرصورت نشان داده شود<ph name="END_LINK" /></translation>
<translation id="96680173638229310">خودرو و وسیله نقلیه</translation>
<translation id="969892804517981540">ساخت رسمی</translation>
-<translation id="973773823069644502">اÙزودن نشانی ارسال</translation>
+<translation id="973773823069644502">اÙزودن نشانی تحویل کالا</translation>
<translation id="975560348586398090">{COUNT,plural, =0{هیچ‌کدام}=1{۱ مورد}one{# مورد}other{# مورد}}</translation>
<translation id="977502174772294970">مراسم ازدواج</translation>
<translation id="981121421437150478">Ø¢Ùلاین</translation>
diff --git a/chromium/components/strings/components_strings_iw.xtb b/chromium/components/strings/components_strings_iw.xtb
index 66ec696b130..365e620f091 100644
--- a/chromium/components/strings/components_strings_iw.xtb
+++ b/chromium/components/strings/components_strings_iw.xtb
@@ -299,7 +299,7 @@
<translation id="1706954506755087368">{1,plural, =1{השרת ×”×–×” ×œ× ×”×¦×œ×™×— להוכיח ×©×”×•× <ph name="DOMAIN" />; ×ישור ×”×בטחה שלו ×מור להיכנס לתוקף רק מחר. ייתכן שהסיבה לכך ×”×™× ×”×’×“×¨×” שגויה ×ו שתוקף מיירט ×ת החיבור שלך.}two{השרת ×”×–×” ×œ× ×”×¦×œ×™×— להוכיח ×©×”×•× <ph name="DOMAIN" />; ×ישור ×”×בטחה שלו ×מור להיכנס לתוקף רק בעוד יומיי×. ייתכן שהסיבה לכך ×”×™× ×”×’×“×¨×” שגויה ×ו שתוקף מיירט ×ת החיבור שלך.}many{השרת ×”×–×” ×œ× ×”×¦×œ×™×— להוכיח ×©×”×•× <ph name="DOMAIN" />; ×ישור ×”×בטחה שלו ×מור להיכנס לתוקף רק בעוד # ימי×. ייתכן שהסיבה לכך ×”×™× ×”×’×“×¨×” שגויה ×ו שתוקף מיירט ×ת החיבור שלך.}other{השרת ×”×–×” ×œ× ×”×¦×œ×™×— להוכיח ×©×”×•× <ph name="DOMAIN" />; ×ישור ×”×בטחה שלו ×מור להיכנס לתוקף רק בעוד # ימי×. ייתכן שהסיבה לכך ×”×™× ×”×’×“×¨×” שגויה ×ו שתוקף מיירט ×ת החיבור שלך.}}</translation>
<translation id="1710259589646384581">מערכת הפעלה</translation>
<translation id="1711234383449478798">×ין התייחסות למדיניות ×›×™ ×œ× ×”×•×’×“×¨ הערך <ph name="VALUE" /> ב-<ph name="POLICY_NAME" />.</translation>
-<translation id="1711528724596764268">â€×œ×ž×™×“×” חישובית ובינה מל×כותית (AI)</translation>
+<translation id="1711528724596764268">â€×œ×ž×™×“ת מכונה ובינה מל×כותית (AI)</translation>
<translation id="1712552549805331520"><ph name="URL" /> רוצה ל×חסן × ×ª×•× ×™× ×‘×ž×—×©×‘ המקומי שלך ב×ופן קבוע</translation>
<translation id="1713628304598226412">מגש 2</translation>
<translation id="1715874602234207">ו'</translation>
diff --git a/chromium/components/strings/components_strings_kn.xtb b/chromium/components/strings/components_strings_kn.xtb
index f91561ef8ec..515816d0bdc 100644
--- a/chromium/components/strings/components_strings_kn.xtb
+++ b/chromium/components/strings/components_strings_kn.xtb
@@ -159,7 +159,7 @@
<translation id="1360955481084547712">ಖಾಸಗಿಯಾಗಿ ಬà³à²°à³Œà²¸à³ ಮಾಡಲೠಹೊಸ ಅದೃಶà³à²¯ ವಿಂಡೋವನà³à²¨à³ ತೆರೆಯಿರಿ</translation>
<translation id="1363819917331173092">ಪà³à²Ÿà²—ಳನà³à²¨à³ <ph name="SOURCE_LANGUAGE" /> ಭಾಷೆಯಲà³à²²à²¿ ಅನà³à²µà²¾à²¦à²¿à²¸à³à²µ ಪà³à²°à²¸à³à²¤à²¾à²ª ಮಾಡಬೇಡಿ</translation>
<translation id="1364822246244961190">ಈ ಕಾರà³à²¯à²¨à³€à²¤à²¿à²¯à²¨à³à²¨à³ ನಿರà³à²¬à²‚ಧಿಸಲಾಗಿದà³à²¦à³, ಅದರ ಮೌಲà³à²¯à²µà²¨à³à²¨à³ ನಿರà³à²²à²•à³à²·à²¿à²¸à²²à²¾à²—à³à²¤à³à²¤à²¦à³†.</translation>
-<translation id="1368318639262510626">Dino ಗೇಮà³. ಪಾಳà³à²­à³‚ಮಿಯಲà³à²²à²¿ ಓಡà³à²µà²¾à²— ಪಿಕà³à²¸à³†à²²à³‡à²Ÿà³†à²¡à³ ಡೈನೋಸಾರà³â€Œà²—ಳೠಪಾಪಾಸà³à²•à²³à³à²³à²¿ ಮತà³à²¤à³ ಸà³à²Ÿà³†à²°à³‹à²¡à²¾à²•à³à²Ÿà³ˆà²²à³â€Œà²—ಳನà³à²¨à³ ತಪà³à²ªà²¿à²¸à³à²¤à³à²¤à²µà³†. ನಿಮಗೆ ಆಡಿಯೊ ಬೀಪೠಕೇಳಿಸಿದಾಗ, ಅಡೆತಡೆಗಳನà³à²¨à³ ದಾಟಲೠಸà³à²ªà³‡à²¸à³ ಅನà³à²¨à³ ಒತà³à²¤à²¿.</translation>
+<translation id="1368318639262510626">Dino ಗೇಮà³. ಪಾಳà³à²­à³‚ಮಿಯಲà³à²²à²¿ ಓಡà³à²µà²¾à²— ಪಿಕà³à²¸à³†à²²à³‡à²Ÿà³†à²¡à³ ಡೈನೋಸಾರà³â€Œà²—ಳೠಪಾಪಾಸà³à²•à²³à³à²³à²¿ ಮತà³à²¤à³ ಸà³à²Ÿà³†à²°à³‹à²¡à²¾à²•à³à²Ÿà³ˆà²²à³â€Œà²—ಳನà³à²¨à³ ತಪà³à²ªà²¿à²¸à³à²¤à³à²¤à²µà³†. ನಿಮಗೆ ಆಡಿಯೋ ಬೀಪೠಕೇಳಿಸಿದಾಗ, ಅಡೆತಡೆಗಳನà³à²¨à³ ದಾಟಲೠಸà³à²ªà³‡à²¸à³ ಅನà³à²¨à³ ಒತà³à²¤à²¿.</translation>
<translation id="1374468813861204354">ಸಲಹೆಗಳà³</translation>
<translation id="1374692235857187091">Index-4x6 (Postcard)</translation>
<translation id="1375198122581997741">ಆವೃತà³à²¤à²¿à²¯ ಕà³à²°à²¿à²¤à³</translation>
@@ -368,7 +368,7 @@
<translation id="187918866476621466">ಆರಂಭಿಕ ಪà³à²Ÿà²—ಳನà³à²¨à³ ತೆರೆಯಿರಿ</translation>
<translation id="1883255238294161206">ಪಟà³à²Ÿà²¿à²¯à²¨à³à²¨à³ ಸಂಕà³à²šà²¿à²¸à²¿</translation>
<translation id="1884843295353628214">ಜಾà²à³</translation>
-<translation id="1890171020361705182">Dino ಗೇಮà³. ಪಾಳà³à²­à³‚ಮಿಯಲà³à²²à²¿ ಓಡà³à²µà²¾à²— ಪಿಕà³à²¸à³†à²²à³‡à²Ÿà³†à²¡à³ ಡೈನೋಸಾರà³â€Œà²—ಳೠಪಾಪಾಸà³à²•à²³à³à²³à²¿ ಮತà³à²¤à³ ಸà³à²Ÿà³†à²°à³‹à²¡à²¾à²•à³à²Ÿà³ˆà²²à³â€Œà²—ಳನà³à²¨à³ ತಪà³à²ªà²¿à²¸à³à²¤à³à²¤à²µà³†. ನಿಮಗೆ ಆಡಿಯೊ ಬೀಪೠಕೇಳಿಸಿದಾಗ, ಅಡೆತಡೆಗಳನà³à²¨à³ ದಾಟಲೠಅದನà³à²¨à³ ಟà³à²¯à²¾à²ªà³ ಮಾಡಿ.</translation>
+<translation id="1890171020361705182">Dino ಗೇಮà³. ಪಾಳà³à²­à³‚ಮಿಯಲà³à²²à²¿ ಓಡà³à²µà²¾à²— ಪಿಕà³à²¸à³†à²²à³‡à²Ÿà³†à²¡à³ ಡೈನೋಸಾರà³â€Œà²—ಳೠಪಾಪಾಸà³à²•à²³à³à²³à²¿ ಮತà³à²¤à³ ಸà³à²Ÿà³†à²°à³‹à²¡à²¾à²•à³à²Ÿà³ˆà²²à³â€Œà²—ಳನà³à²¨à³ ತಪà³à²ªà²¿à²¸à³à²¤à³à²¤à²µà³†. ನಿಮಗೆ ಆಡಿಯೋ ಬೀಪೠಕೇಳಿಸಿದಾಗ, ಅಡೆತಡೆಗಳನà³à²¨à³ ದಾಟಲೠಅದನà³à²¨à³ ಟà³à²¯à²¾à²ªà³ ಮಾಡಿ.</translation>
<translation id="1898423065542865115">ಫಿಲà³à²Ÿà²°à²¿à²‚ಗà³</translation>
<translation id="1901443836186977402">{1,plural, =1{ಈ ಸರà³à²µà²°à³ <ph name="DOMAIN" /> ಆಗಿದೆ ಎಂಬà³à²¦à²¨à³à²¨à³ ಸಾಬೀತà³à²ªà²¡à²¿à²¸à²²à³ ಅದಕà³à²•à³† ಸಾಧà³à²¯à²µà²¾à²—ಲಿಲà³à²²; ಹಿಂದಿನ ದಿನವೇ ಅದರ ಸà³à²°à²•à³à²·à²¤à²¾ ಪà³à²°à²®à²¾à²£à²ªà²¤à³à²°à²¦ ಅವಧಿ ಮà³à²—ಿದಿದೆ. ಇದೠತಪà³à²ªà³ ಕಾನà³à²«à²¿à²—ರೇಶನà³â€Œà²¨à²¿à²‚ದ ಅಥವಾ ದಾಳಿಕೋರರೠನಿಮà³à²® ಕನೆಕà³à²·à²¨à³â€Œà²—ೆ ಅಡà³à²¡à²¿à²ªà²¡à²¿à²¸à²¿à²°à³à²µà³à²¦à²°à²¿à²‚ದ ಉಂಟಾಗಿರಬಹà³à²¦à³. ನಿಮà³à²® ಕಂಪà³à²¯à³‚ಟರà³â€Œà²¨ ಗಡಿಯಾರವನà³à²¨à³ ಪà³à²°à²¸à³à²¤à³à²¤ <ph name="CURRENT_DATE" /> ಕà³à²•à³† ಹೊಂದಿಸಲಾಗಿದೆ. ಅದೠಸರಿಯಾಗಿದೆಯೇ? ಇಲà³à²²à²¦à²¿à²¦à³à²¦à²°à³†, ನಿಮà³à²® ಸಿಸà³à²Ÿà²‚ನ ಗಡಿಯಾರವನà³à²¨à³ ನೀವೠಸರಿಪಡಿಸಬೇಕೠಹಾಗೂ ನಂತರ ಈ ಪà³à²Ÿà²µà²¨à³à²¨à³ ರಿಫà³à²°à³†à²¶à³ ಮಾಡಬೇಕà³.}one{ಈ ಸರà³à²µà²°à³ <ph name="DOMAIN" /> ಆಗಿದೆ ಎಂಬà³à²¦à²¨à³à²¨à³ ಸಾಬೀತà³à²ªà²¡à²¿à²¸à²²à³ ಅದಕà³à²•à³† ಸಾಧà³à²¯à²µà²¾à²—ಲಿಲà³à²²; ಅದರ ಸà³à²°à²•à³à²·à²¤à²¾ ಪà³à²°à²®à²¾à²£à²ªà²¤à³à²°à²¦ ಅವಧಿ # ದಿನಗಳ ಹಿಂದೆಯೇ ಮà³à²—ಿದಿದೆ. ಇದೠತಪà³à²ªà³ ಕಾನà³à²«à²¿à²—ರೇಶನà³â€Œà²¨à²¿à²‚ದ ಅಥವಾ ದಾಳಿಕೋರರೠನಿಮà³à²® ಕನೆಕà³à²·à²¨à³â€Œà²—ೆ ಅಡà³à²¡à²¿à²ªà²¡à²¿à²¸à²¿à²°à³à²µà³à²¦à²°à²¿à²‚ದ ಉಂಟಾಗಿರಬಹà³à²¦à³. ನಿಮà³à²® ಕಂಪà³à²¯à³‚ಟರà³â€Œà²¨ ಗಡಿಯಾರವನà³à²¨à³ ಪà³à²°à²¸à³à²¤à³à²¤ <ph name="CURRENT_DATE" /> ಕà³à²•à³† ಹೊಂದಿಸಲಾಗಿದೆ. ಅದೠಸರಿಯಾಗಿದೆಯೇ? ಇಲà³à²²à²¦à²¿à²¦à³à²¦à²°à³†, ನಿಮà³à²® ಸಿಸà³à²Ÿà²®à³â€Œà²¨ ಗಡಿಯಾರವನà³à²¨à³ ನೀವೠಸರಿಪಡಿಸಬೇಕೠಹಾಗೂ ನಂತರ ಈ ಪà³à²Ÿà²µà²¨à³à²¨à³ ರಿಫà³à²°à³†à²¶à³ ಮಾಡಬೇಕà³.}other{ಈ ಸರà³à²µà²°à³ <ph name="DOMAIN" /> ಆಗಿದೆ ಎಂಬà³à²¦à²¨à³à²¨à³ ಸಾಬೀತà³à²ªà²¡à²¿à²¸à²²à³ ಅದಕà³à²•à³† ಸಾಧà³à²¯à²µà²¾à²—ಲಿಲà³à²²; ಅದರ ಸà³à²°à²•à³à²·à²¤à²¾ ಪà³à²°à²®à²¾à²£à²ªà²¤à³à²°à²¦ ಅವಧಿ # ದಿನಗಳ ಹಿಂದೆಯೇ ಮà³à²—ಿದಿದೆ. ಇದೠತಪà³à²ªà³ ಕಾನà³à²«à²¿à²—ರೇಶನà³â€Œà²¨à²¿à²‚ದ ಅಥವಾ ದಾಳಿಕೋರರೠನಿಮà³à²® ಕನೆಕà³à²·à²¨à³â€Œà²—ೆ ಅಡà³à²¡à²¿à²ªà²¡à²¿à²¸à²¿à²°à³à²µà³à²¦à²°à²¿à²‚ದ ಉಂಟಾಗಿರಬಹà³à²¦à³. ನಿಮà³à²® ಕಂಪà³à²¯à³‚ಟರà³â€Œà²¨ ಗಡಿಯಾರವನà³à²¨à³ ಪà³à²°à²¸à³à²¤à³à²¤ <ph name="CURRENT_DATE" /> ಕà³à²•à³† ಹೊಂದಿಸಲಾಗಿದೆ. ಅದೠಸರಿಯಾಗಿದೆಯೇ? ಇಲà³à²²à²¦à²¿à²¦à³à²¦à²°à³†, ನಿಮà³à²® ಸಿಸà³à²Ÿà²®à³â€Œà²¨ ಗಡಿಯಾರವನà³à²¨à³ ನೀವೠಸರಿಪಡಿಸಬೇಕೠಹಾಗೂ ನಂತರ ಈ ಪà³à²Ÿà²µà²¨à³à²¨à³ ರಿಫà³à²°à³†à²¶à³ ಮಾಡಬೇಕà³.}}</translation>
<translation id="1902576642799138955">ವಾಯಿದೆ ಅವಧಿ</translation>
@@ -1003,7 +1003,7 @@
<translation id="370665806235115550">ಲೋಡೠಆಗà³à²¤à³à²¤à²¿à²¦à³†...</translation>
<translation id="3709599264800900598">ನೀವೠನಕಲಿಸಿದ ಪಠà³à²¯</translation>
<translation id="370972442370243704">ಪà³à²°à²¯à²¾à²£à²—ಳನà³à²¨à³ ಆನೠಮಾಡಿ</translation>
-<translation id="3709866969787468031">ಆಡಿಯೊ ಮತà³à²¤à³ ಸಂಗೀತ ಸಾಫà³à²Ÿà³â€Œà²µà³‡à²°à³</translation>
+<translation id="3709866969787468031">ಆಡಿಯೋ ಮತà³à²¤à³ ಸಂಗೀತ ಸಾಫà³à²Ÿà³â€Œà²µà³‡à²°à³</translation>
<translation id="3711895659073496551">ಅಮಾನತà³</translation>
<translation id="3712624925041724820">ಪರವಾನಗಿಗಳೠಬರಿದಾಗಿವೆ</translation>
<translation id="3713662424819367124">ಪರà³à²«à³à²¯à³‚ಮà³â€Œà²—ಳೠಮತà³à²¤à³ ಸà³à²—ಂಧಗಳà³</translation>
@@ -2924,7 +2924,7 @@
<translation id="9150045010208374699">ನಿಮà³à²® ಕà³à²¯à²¾à²®à²°à²¾à²µà²¨à³à²¨à³ ಬಳಸಿ</translation>
<translation id="9150685862434908345">ನಿಮà³à²® ನಿರà³à²µà²¾à²¹à²•à²°à³ ದೂರದಿಂದಲೇ ನಿಮà³à²® ಬà³à²°à³Œà²¸à²°à³ ಸೆಟಪೠಅನà³à²¨à³ ಬದಲಾಯಿಸಬಹà³à²¦à³. ಈ ಸಾಧನದಲà³à²²à²¿à²¨ ಚಟà³à²µà²Ÿà²¿à²•à³†à²¯à²¨à³à²¨à³ Chrome ನಿಂದ ಹೊರಗೂ ನಿರà³à²µà²¹à²¿à²¸à²¬à²¹à³à²¦à³. <ph name="BEGIN_LINK" />ಇನà³à²¨à²·à³à²Ÿà³ ತಿಳಿಯಿರಿ<ph name="END_LINK" /></translation>
<translation id="9154194610265714752">ಅಪà³â€Œà²¡à³‡à²Ÿà³â€Œ ಮಾಡಲಾಗಿದೆ</translation>
-<translation id="9155211586651734179">ಆಡಿಯೊ ಬಾಹà³à²¯à³‹à²ªà²•à²°à²£à²—ಳನà³à²¨à³ ಲಗತà³à²¤à²¿à²¸à²²à²¾à²—ಿದೆ</translation>
+<translation id="9155211586651734179">ಆಡಿಯೋ ಬಾಹà³à²¯à³‹à²ªà²•à²°à²£à²—ಳನà³à²¨à³ ಲಗತà³à²¤à²¿à²¸à²²à²¾à²—ಿದೆ</translation>
<translation id="9157595877708044936">ಹೊಂದಿಸಲಾಗà³à²¤à³à²¤à²¿à²¦à³†...</translation>
<translation id="9158625974267017556">C6 (Envelope)</translation>
<translation id="9164742147345933553">os://flags</translation>
diff --git a/chromium/components/strings/components_strings_ky.xtb b/chromium/components/strings/components_strings_ky.xtb
index 22debf66a76..7f2feae5c2f 100644
--- a/chromium/components/strings/components_strings_ky.xtb
+++ b/chromium/components/strings/components_strings_ky.xtb
@@ -455,7 +455,7 @@
<translation id="2135799067377889518">Эркектердин кийими</translation>
<translation id="213826338245044447">Мобилдик кыÑтармалар</translation>
<translation id="214556005048008348">Төлөмдү жокко чыгаруу</translation>
-<translation id="2148613324460538318">Картаны кошуу</translation>
+<translation id="2148613324460538318">Карта кошуу</translation>
<translation id="2149968176347646218">Туташуу кооптуу</translation>
<translation id="2154054054215849342">Домениңизде шайкештирүү функциÑÑÑ‹ жок</translation>
<translation id="2154484045852737596">Карточканы түзөтүү</translation>
@@ -606,7 +606,7 @@
<translation id="2557417190997681027">БизнеÑ-ÑаÑкат</translation>
<translation id="2562087035394240049">Кантип жаÑоону үйрөтүүчү, ӨЖ жана ÑкÑперттик мазмундар</translation>
<translation id="2563042576090522782">Бал айлары жана романтикалык ÑÑ Ð°Ð»ÑƒÑƒÐ»Ð°Ñ€</translation>
-<translation id="2570734079541893434">Жөндөөлөрдү башкаруу</translation>
+<translation id="2570734079541893434">Тууралоо</translation>
<translation id="2573834589046842510">БаÑып чыгаруу жана жарыÑлоо</translation>
<translation id="257674075312929031">Топ</translation>
<translation id="2576880857912732701">КоопÑуздук жөндөөлөрүн башкаруу баÑкычы, Chrome жөндөөлөрүнөн КоопÑуз Ñерептөөнү жана башка нерÑелерди башкаруу үчүн Enter баÑкычын баÑыңыз</translation>
@@ -927,7 +927,7 @@
<translation id="3462200631372590220">Өркүндөтүлгөндөрдү жашыруу</translation>
<translation id="346601286295919445">ХимиÑ</translation>
<translation id="3467763166455606212">Карта ÑÑÑинин аты-жөнүн киргизиңиз</translation>
-<translation id="3468054117417088249"><ph name="TAB_SWITCH_SUFFIX" />, учурда ачылып турат. Ðчык өтмөккө Ó©Ñ‚Ò¯Ò¯ үчүн, өтмөктү, андан Ñоң "Enter" баÑкычын баÑыңыз</translation>
+<translation id="3468054117417088249"><ph name="TAB_SWITCH_SUFFIX" />, учурда ачылып турат. Ðчык өтмөккө Ó©Ñ‚Ò¯Ò¯ үчүн өтмөктү, андан Ñоң "Enter" баÑкычын баÑыңыз</translation>
<translation id="3470563864795286535"><ph name="CLOSE_INCOGNITO_WINDOWS_FOCUSED_FRIENDLY_MATCH_TEXT" />, Учурда ачылып турган бардык Жашыруун терезелерди жабуу үчүн Tab, андан кийин Enter баÑкычын баÑыңыз</translation>
<translation id="3477679029130949506">Кинолордун тизмеÑи жана театрдын көрÑÓ©Ñ‚Ò¯Ò¯ убактыÑÑ‹</translation>
<translation id="3479552764303398839">Ðзыр ÑмеÑ</translation>
@@ -1245,7 +1245,7 @@
<translation id="4349365535725594680">ÐšÑƒÐ¿ÑƒÑ Ð¼Ð°Ð·Ð¼ÑƒÐ½ бөлүшүлбөй жатат</translation>
<translation id="4350629523305688469">Көп функционалдуу түпкүч</translation>
<translation id="4351060348582610152"><ph name="ORIGIN" /> Ñайты жакын жердеги Bluetooth түзмөктөрүн Ñкандаганы жатат. Төмөнкү түзмөктөр табылды:</translation>
-<translation id="4351175281479794167">Текшерүү кодун киргизиңиз</translation>
+<translation id="4351175281479794167">ЫраÑтоо кодун киргизиңиз</translation>
<translation id="4356973930735388585">Бул Ñайттагы бүлдүргүчтөр түзмөгүңүздөгү маалыматыңызды (миÑалы, Ñүрөттөр, ÑÑ‹Ñ€Ñөздөр, билдирүүлөр жана наÑÑ‹Ñ ÐºÐ°Ñ€Ñ‚Ð°Ð»Ð°Ñ€Ñ‹) уурдап же жок кыла турган коркунучтуу программаларды орнотууга аракет кылышы мүмкүн.</translation>
<translation id="4358059973562876591">Сиз көрÑөткөн үлгүлөрдү колдонууга болбойт, анткени DnsOverHttpsMode ÑаÑÑатында ката кетти.</translation>
<translation id="4358461427845829800">Төлөм ыкмаларын башкаруу...</translation>
@@ -1453,7 +1453,7 @@
<translation id="4940163644868678279">Chrome'догу жашыруун өтмөк</translation>
<translation id="4943872375798546930">Ðатыйжалар жок</translation>
<translation id="4943933359574417591">SMS жана заматта кабарлашуу</translation>
-<translation id="4950898438188848926">Өтмөктү которуштуруу баÑкычы. Ðчык өтмөккө Ó©Ñ‚Ò¯Ò¯ үчүн, "Enter" баÑкычын баÑыңыз, <ph name="TAB_SWITCH_FOCUSED_FRIENDLY_MATCH_TEXT" /></translation>
+<translation id="4950898438188848926">Өтмөктү которуштуруу баÑкычы. Ðчык өтмөккө Ó©Ñ‚Ò¯Ò¯ үчүн "Enter" баÑкычын баÑыңыз, <ph name="TAB_SWITCH_FOCUSED_FRIENDLY_MATCH_TEXT" /></translation>
<translation id="495170559598752135">Ðракеттер</translation>
<translation id="4953689047182316270">Ðтайын мүмкүнчүлүктөрдү иштетүү</translation>
<translation id="4955242332710481440">A5-Extra</translation>
@@ -1904,7 +1904,7 @@
<translation id="6232271601416750929">Таза Ñулуулук</translation>
<translation id="6233160458685643793">ÐÑ€Ñаңдаган</translation>
<translation id="6234122620015464377">ÐÑ€ бир документтен кийин кыркуу</translation>
-<translation id="6236290670123303279">Жөндөөлөрдү башкаруу</translation>
+<translation id="6236290670123303279">Тууралоо</translation>
<translation id="6240447795304464094">Google Pay логотиби</translation>
<translation id="6241121617266208201">Сунуштарды жашыруу</translation>
<translation id="624499991300733384">БаÑып чыгарууну курамалоо кызматы</translation>
@@ -2778,7 +2778,7 @@
<translation id="8718314106902482036">Төлөм аÑгына чыккан жок</translation>
<translation id="8719263113926255150"><ph name="ENTITY" />, <ph name="DESCRIPTION" />, издөө Ñунушу</translation>
<translation id="8719528812645237045">Муштум менен өйдө жакка Ñки жолу уруу</translation>
-<translation id="8723535127346307411">Текшерүү кодун киргизиңиз</translation>
+<translation id="8723535127346307411">ЫраÑтоо кодун киргизиңиз</translation>
<translation id="8725066075913043281">Кайра аракет кылып көрүү</translation>
<translation id="8726549941689275341">Барактын өлчөмү:</translation>
<translation id="8730621377337864115">Бүттү</translation>
diff --git a/chromium/components/strings/components_strings_mr.xtb b/chromium/components/strings/components_strings_mr.xtb
index fe5fb3efd1f..4d28b6c9f60 100644
--- a/chromium/components/strings/components_strings_mr.xtb
+++ b/chromium/components/strings/components_strings_mr.xtb
@@ -1096,7 +1096,7 @@
<translation id="3964661563329879394">{COUNT,plural, =0{काहीही नाही}=1{1 साइटकडून }other{# साइटकडून }}</translation>
<translation id="3969052498612555048">तà¥à¤®à¤šà¤¾ कोड सापडत नाही का? <ph name="BEGIN_LINK" />नवीन कोड मिळवा<ph name="END_LINK" /></translation>
<translation id="397105322502079400">गणना करत आहे...</translation>
-<translation id="3973234410852337861"><ph name="HOST_NAME" /> अवरोधित केले आहे</translation>
+<translation id="3973234410852337861"><ph name="HOST_NAME" /> बà¥à¤²à¥‰à¤• केले आहे</translation>
<translation id="3978338123949022456">शोध मोड, कà¥à¤µà¥‡à¤°à¥€ टाइप करा आणि <ph name="KEYWORD_SUFFIX" /> सह शोधणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ à¤à¤‚टर दाबा</translation>
<translation id="398470910934384994">पकà¥à¤·à¥€</translation>
<translation id="3986705137476756801">आतापà¥à¤°à¤¤à¥‡ लाइवà¥à¤¹ कॅपà¥à¤¶à¤¨ बंद करा</translation>
@@ -1227,7 +1227,7 @@
<translation id="4306529830550717874">पतà¥à¤¤à¤¾ सेवà¥à¤¹ करायचा आहे का?</translation>
<translation id="4306812610847412719">कà¥à¤²à¤¿à¤ªà¤¬à¥‹à¤°à¥à¤¡</translation>
<translation id="4312613361423056926">B2</translation>
-<translation id="4312866146174492540">अवरोधित करा (डीफॉलà¥à¤Ÿ)</translation>
+<translation id="4312866146174492540">बà¥à¤²à¥‰à¤• करा (डीफॉलà¥à¤Ÿ)</translation>
<translation id="4314815835985389558">सिंक वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करा</translation>
<translation id="4318312030194671742">पेंट पूरà¥à¤µà¤¾à¤µà¤²à¥‹à¤•à¤¨ कंपोà¤à¤¿à¤Ÿà¤° सेवा</translation>
<translation id="4318566738941496689">तà¥à¤®à¤šà¥à¤¯à¤¾ डिवà¥à¤¹à¤¾à¤‡à¤¸à¤šà¥‡ नाव आणि नेटवरà¥à¤•à¤šà¤¾ पतà¥à¤¤à¤¾</translation>
@@ -1895,7 +1895,7 @@
<translation id="6180316780098470077">दोन पà¥à¤°à¤¯à¤¤à¥à¤¨à¤¾à¤‚मधील मधà¥à¤¯à¤¾à¤‚तर</translation>
<translation id="61877208875190028">महिलांचे कपडे</translation>
<translation id="6195371403461054755">भूशासà¥à¤¤à¥à¤°</translation>
-<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ अवरोधित करा</translation>
+<translation id="6196640612572343990">तृतीय-पकà¥à¤· कà¥à¤•à¥€à¤œ बà¥à¤²à¥‰à¤• करा</translation>
<translation id="6203231073485539293">तà¥à¤®à¤šà¥‡ इंटरनेट कनेकà¥à¤¶à¤¨ तपासा</translation>
<translation id="6218753634732582820">Chromium वरून पतà¥à¤¤à¤¾ काढून टाकायचा?</translation>
<translation id="622039917539443112">समांतर फोलà¥à¤¡</translation>
@@ -2181,7 +2181,7 @@
<translation id="7038063300915481831"><ph name="MANAGE_GOOGLE_PRIVACY_FOCUSED_FRIENDLY_MATCH_TEXT" />, तà¥à¤®à¤šà¥à¤¯à¤¾ Google खाते ची गोपनीयता सेटिंगà¥à¤œ वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ टॅब आणि तà¥à¤¯à¤¾à¤¨à¤‚तर à¤à¤‚टर दाबा</translation>
<translation id="7050187094878475250">तà¥à¤®à¥à¤¹à¥€ <ph name="DOMAIN" /> वर पोहोचणà¥à¤¯à¤¾à¤šà¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ केला, परंतॠसरà¥à¤µà¥à¤¹à¤°à¤¨à¥‡ à¤à¤• सरà¥à¤Ÿà¤¿à¤«à¤¿à¤•à¥‡à¤Ÿ सादर केले आहे जà¥à¤¯à¤¾à¤šà¤¾ वैधता कालावधी हा विशà¥à¤µà¤¾à¤¸à¤¾à¤°à¥à¤¹à¤¤à¥‡à¤¸à¤¾à¤ à¥€ खूप मोठा आहे.</translation>
<translation id="705310974202322020">{NUM_CARDS,plural, =1{हे कारà¥à¤¡ आतà¥à¤¤à¤¾ सेवà¥à¤¹ केले जाऊ शकत नाही}other{ही कारà¥à¤¡à¥‡Â à¤†à¤¤à¥à¤¤à¤¾ सेवà¥à¤¹ केली जाऊ शकत नाहीत}}</translation>
-<translation id="7053983685419859001">अवरोधित करा</translation>
+<translation id="7053983685419859001">बà¥à¤²à¥‰à¤• करा</translation>
<translation id="7058163556978339998"><ph name="ISSUER" /> ने या वेबसाइटचे सरà¥à¤Ÿà¤¿à¤«à¤¿à¤•à¥‡à¤Ÿ जारी केले आहे हे <ph name="BROWSER" /> ने पडताळले.</translation>
<translation id="7061777300866737982">घराशी संबंधित सà¥à¤°à¤•à¥à¤·à¤¿à¤¤à¤¤à¤¾ आणि सà¥à¤°à¤•à¥à¤·à¤¾</translation>
<translation id="7062635574500127092">हिरवट निळा</translation>
@@ -2339,7 +2339,7 @@
<translation id="7442725080345379071">फिकट नारिंगी</translation>
<translation id="7445762425076701745">तà¥à¤®à¥à¤¹à¥€ कनेकà¥à¤Ÿ केलेलà¥à¤¯à¤¾ सरà¥à¤µà¥à¤¹à¤°à¤šà¥€ ओळख पूरà¥à¤£à¤ªà¤£à¥‡ पडताळणे शकà¥à¤¯ नाही. तà¥à¤®à¥à¤¹à¥€ सरà¥à¤µà¥à¤¹à¤°à¤¶à¥€ फकà¥à¤¤ आपलà¥â€à¤¯à¤¾ डोमेनमधà¥à¤¯à¥‡ वैध असलेले नाव वापरून कनेकà¥à¤Ÿ केलेले आहे, जà¥à¤¯à¤¾à¤šà¥€ मालकी सतà¥à¤¯à¤¾à¤ªà¤¿à¤¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ बाहà¥à¤¯ सरà¥à¤Ÿà¤¿à¤«à¤¿à¤•à¥‡à¤Ÿ अधिकृततेला परवानगी नाही. काही सरà¥à¤Ÿà¤¿à¤«à¤¿à¤•à¥‡à¤Ÿ अधिकारी तरीही या नावांसाठी सरà¥à¤Ÿà¤¿à¤«à¤¿à¤•à¥‡à¤Ÿ जारी करतील, याची खातà¥à¤°à¥€ करणà¥à¤¯à¤¾à¤šà¤¾ काहीही मारà¥à¤— नाही की तà¥à¤®à¥à¤¹à¥€ इचà¥à¤›à¤¿à¤¤ वेबसाइटशी कनेकà¥à¤Ÿ केले आहे आणि हलà¥à¤²à¥‡à¤–ोराशी नाही.</translation>
<translation id="7451311239929941790">या समसà¥à¤¯à¥‡à¤µà¤¿à¤·à¤¯à¥€ <ph name="BEGIN_LINK" />अधिक जाणून घेणे<ph name="END_LINK" />.</translation>
-<translation id="7455133967321480974">सारà¥à¤µà¤¤à¥à¤°à¤¿à¤• डीफॉलà¥â€à¤Ÿ वापरा (अवरोधित करा)</translation>
+<translation id="7455133967321480974">गà¥à¤²à¥‹à¤¬à¤² डीफॉलà¥â€à¤Ÿ वापरा (बà¥à¤²à¥‰à¤• करा)</translation>
<translation id="7460618730930299168">तà¥à¤®à¥à¤¹à¥€ जे निवडले, तà¥à¤¯à¤¾à¤ªà¥‡à¤•à¥à¤·à¤¾ सà¥à¤•à¥à¤°à¥€à¤¨à¤¿à¤‚ग वेगळे आहे. सà¥à¤°à¥‚ ठेवायचे का?</translation>
<translation id="7463075493919226237">ऑसà¥à¤Ÿà¥à¤°à¥‡à¤²à¤¿à¤¯à¤¨ फà¥à¤Ÿà¤¬à¥‰à¤²</translation>
<translation id="7469935732330206581">हा फॉरà¥à¤® सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ नाही</translation>
@@ -2466,7 +2466,7 @@
<translation id="7752995774971033316">वà¥à¤¯à¤µà¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ न केलेले</translation>
<translation id="7755624218968747854">पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤• रोल</translation>
<translation id="7757555340166475417">Dai-Pa-Kai</translation>
-<translation id="7758069387465995638">फायरवॉल किंवा अà¤à¤Ÿà¥€à¤µà¥à¤¹à¤¾à¤¯à¤°à¤¸ सॉफà¥à¤Ÿà¤µà¥‡à¤…रने कदाचित कनेकà¥à¤¶à¤¨ अवरोधित केले असावे.</translation>
+<translation id="7758069387465995638">फायरवॉल किंवा अà¤à¤Ÿà¥€à¤µà¥à¤¹à¤¾à¤¯à¤°à¤¸ सॉफà¥à¤Ÿà¤µà¥‡à¤…रने कदाचित कनेकà¥à¤¶à¤¨ बà¥à¤²à¥‰à¤• केले असावे.</translation>
<translation id="7760497246331667482">रेगे आणि कॅरिबियन संगीत</translation>
<translation id="776110834126722255">कालबाहà¥à¤¯ à¤à¤¾à¤²à¥‡</translation>
<translation id="7761159795823346334">कॅमेराला अनà¥à¤®à¤¤à¥€ दà¥à¤¯à¤¾à¤¯à¤šà¥€ का?</translation>
@@ -2940,7 +2940,7 @@
<translation id="9158625974267017556">C6 (Envelope)C6 (Envelope)</translation>
<translation id="9164742147345933553">os://flags</translation>
<translation id="9168814207360376865">तà¥à¤®à¥à¤¹à¥€ पेमेंट पदà¥à¤§à¤¤à¥€ सेवà¥à¤¹ केलà¥à¤¯à¤¾ आहेत का हे तपासणà¥à¤¯à¤¾à¤šà¥€ साइटला परवानगी दà¥à¤¯à¤¾</translation>
-<translation id="9169664750068251925">या साइटवर नेहमी अवरोधित करा</translation>
+<translation id="9169664750068251925">या साइटवर नेहमी बà¥à¤²à¥‰à¤• करा</translation>
<translation id="9169931577761441333"><ph name="APP_NAME" /> ला होम सà¥à¤•à¥à¤°à¥€à¤¨à¤µà¤° जोडा</translation>
<translation id="9170848237812810038">&amp;पूरà¥à¤µà¤µà¤¤ करा</translation>
<translation id="9171296965991013597">ॲप सोडायचे?</translation>
diff --git a/chromium/components/strings/components_strings_ne.xtb b/chromium/components/strings/components_strings_ne.xtb
index 708e1ad80c8..74ec6d01838 100644
--- a/chromium/components/strings/components_strings_ne.xtb
+++ b/chromium/components/strings/components_strings_ne.xtb
@@ -416,7 +416,7 @@
<translation id="2034971124472263449">जे भठपनि सेभ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥</translation>
<translation id="2035400064145347639">यातà¥à¤°à¤¾ मारà¥à¤—दरà¥à¤¶à¤• तथा नियातà¥à¤°à¤¾à¤¹à¤°à¥‚</translation>
<translation id="2036983605131262583">वैकलà¥à¤ªà¤¿à¤• रोल</translation>
-<translation id="2040894699575719559">लोकेसनसमà¥à¤¬à¤¨à¥à¤§à¥€ जानकारी हेरà¥à¤¨ रोक लगाइà¤à¤•à¥‹ छ</translation>
+<translation id="2040894699575719559">लोकेसन हेरà¥à¤¨ रोक लगाइà¤à¤•à¥‹ छ</translation>
<translation id="2042213636306070719">टà¥à¤°à¥‡ ७</translation>
<translation id="204357726431741734">आफà¥à¤¨à¥‹ Google खातामा सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ गरिà¤à¤•à¤¾ पासवरà¥à¤¡à¤¹à¤°à¥‚ पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ साइन इन गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥</translation>
<translation id="205212645995975601">बारà¥à¤¬à¤¿à¤•à¥à¤¯à¥‚ तथा पोलेका परिकार</translation>
diff --git a/chromium/components/strings/components_strings_no.xtb b/chromium/components/strings/components_strings_no.xtb
index 8786400bdfc..c45f1e99d37 100644
--- a/chromium/components/strings/components_strings_no.xtb
+++ b/chromium/components/strings/components_strings_no.xtb
@@ -347,7 +347,7 @@ I motsatt fall blir dette blokkert av personverninnstillingene. Dette gjør at i
<translation id="1807246157184219062">Lys</translation>
<translation id="1807528111851433570">Startark</translation>
<translation id="1812527064848182527">liggende</translation>
-<translation id="1813414402673211292">Slett nettleserdata</translation>
+<translation id="1813414402673211292">Slett nettlesingsdata</translation>
<translation id="182139138257690338">automatiske nedlastinger</translation>
<translation id="1821930232296380041">Ugyldig forespørsel eller forespørselsparametere</translation>
<translation id="1822540298136254167">Nettsteder du besøker, og hvor mye tid du bruker på dem</translation>
@@ -973,7 +973,7 @@ I motsatt fall blir dette blokkert av personverninnstillingene. Dette gjør at i
<translation id="361438452008624280">Listeoppføringen «<ph name="LANGUAGE_ID" />»: Språket er ukjent eller støttes ikke.</translation>
<translation id="3614934205542186002"><ph name="RUN_CHROME_SAFETY_CHECK_FOCUSED_FRIENDLY_MATCH_TEXT" /> – trykk på Tab og deretter på Enter for å kjøre en sikkerhetssjekk i Chrome-innstillingene</translation>
<translation id="3615877443314183785">Angi en gyldig utløpsdato</translation>
-<translation id="36224234498066874">Slett nettleserdata</translation>
+<translation id="36224234498066874">Slett alle nettlesingsdata</translation>
<translation id="362276910939193118">Vis fullstendig logg</translation>
<translation id="3630155396527302611">Hvis programmet allerede har fått tillatelse til å bruke nettverket, kan du prøve
å fjerne det fra listen og så legge det til på nytt.</translation>
@@ -1637,7 +1637,7 @@ I motsatt fall blir dette blokkert av personverninnstillingene. Dette gjør at i
<translation id="540969355065856584">Denne tjeneren kunne ikke bevise at den er <ph name="DOMAIN" />. Sikkerhetssertifikatet til tjeneren er ikke gyldig for øyeblikket. Dette kan være forårsaket av en feilkonfigurasjon eller en angriper som lytter på tilkoblingen din.</translation>
<translation id="5412236728747081950">Dette nettstedet mottar interessene dine fra Chrome for å kunne vise deg mer relevante annonser</translation>
<translation id="541416427766103491">Hylle 4</translation>
-<translation id="5421136146218899937">Slett nettleserdata</translation>
+<translation id="5421136146218899937">Slett nettlesingsdata</translation>
<translation id="5426179911063097041"><ph name="SITE" /> vil sende deg varsler</translation>
<translation id="5428105026674456456">Spansk</translation>
<translation id="5430298929874300616">Fjern bokmerke</translation>
@@ -1674,7 +1674,7 @@ I motsatt fall blir dette blokkert av personverninnstillingene. Dette gjør at i
<translation id="5528532273234423708">Smarthus</translation>
<translation id="55293785478302737">Kantstifting</translation>
<translation id="553484882784876924">Prc6 (konvolutt)</translation>
-<translation id="5535133333442455806">Knappen «Slett nettleserdata» – trykk på Enter for å tømme nettleserloggen, slette informasjonskapsler, tømme bufferen med mer i Chrome-innstillingene</translation>
+<translation id="5535133333442455806">Knappen «Slett nettlesingsdata» – trykk på Enter for å tømme nettleserloggen, slette informasjonskapsler, tømme bufferen med mer i Chrome-innstillingene</translation>
<translation id="5536214594743852365">Vis «<ph name="SECTION" />»-delen</translation>
<translation id="5539243836947087108">Flåte</translation>
<translation id="5540224163453853">Den forespurte artikkelen ble ikke funnet.</translation>
@@ -2549,7 +2549,7 @@ I motsatt fall blir dette blokkert av personverninnstillingene. Dette gjør at i
<translation id="8009225694047762179">Administrer passord</translation>
<translation id="8012116502927253373">{NUM_CARDS,plural, =1{Dette kortet og den tilknyttede faktureringsadressen lagres. Du kan bruke det når du er logget på <ph name="USER_EMAIL" />.}other{Disse kortene og de tilknyttede faktureringsadressene lagres. Du kan bruke dem når du er logget på <ph name="USER_EMAIL" />.}}</translation>
<translation id="8025119109950072390">Angripere på dette nettstedet prøver kanskje å lure deg til å gjøre farlige ting som å installere programvare eller avsløre personopplysningene dine (for eksempel passord, telefonnumre eller kredittkortinformasjon).</translation>
-<translation id="8026334261755873520">Slett nettleserdata</translation>
+<translation id="8026334261755873520">Slett nettlesingsdata</translation>
<translation id="8027077570865220386">Skuff 15</translation>
<translation id="8028698320761417183"><ph name="CREATE_GOOGLE_FORM_FOCUSED_FRIENDLY_MATCH_TEXT" /> – trykk på Tab og deretter på Enter for å opprette et nytt skjema i Google Skjemaer raskt</translation>
<translation id="8028960012888758725">Beskjær etter jobben</translation>
diff --git a/chromium/components/strings/components_strings_pt-PT.xtb b/chromium/components/strings/components_strings_pt-PT.xtb
index 439b2f378c9..b5f1868d03d 100644
--- a/chromium/components/strings/components_strings_pt-PT.xtb
+++ b/chromium/components/strings/components_strings_pt-PT.xtb
@@ -69,7 +69,7 @@
<translation id="1165039591588034296">Erro</translation>
<translation id="1165174597379888365">A página é visitada</translation>
<translation id="1165852471352757509">Programas não ficcionais e documentários televisivos</translation>
-<translation id="1174723505405632867">Pretende permitir que <ph name="EMBEDDED_URL" /> utilize cookies e dados de sites em <ph name="TOP_LEVEL_URL" />?
+<translation id="1174723505405632867">Permitir que <ph name="EMBEDDED_URL" /> utilize cookies e dados de sites em <ph name="TOP_LEVEL_URL" />?
Se não permitir, isto será bloqueado pelas suas definições de privacidade. Isto vai permitir que o conteúdo com o qual interagiu funcione corretamente, mas pode permitir que <ph name="EMBEDDED_URL" /> monitorize a sua atividade.</translation>
<translation id="1175364870820465910">Im&amp;primir...</translation>
@@ -94,7 +94,7 @@ Se não permitir, isto será bloqueado pelas suas definições de privacidade. I
<translation id="1227224963052638717">Política desconhecida.</translation>
<translation id="1228893227497259893">Identificador de entidade errado</translation>
<translation id="1232569758102978740">Sem nome</translation>
-<translation id="1236081509407217141">Pretende permitir a RV?</translation>
+<translation id="1236081509407217141">Permitir a RV?</translation>
<translation id="1238915852705750309">Antivírus e software malicioso</translation>
<translation id="1240347957665416060">O nome do dispositivo</translation>
<translation id="124116460088058876">Mais idiomas</translation>
@@ -995,7 +995,7 @@ Se não permitir, isto será bloqueado pelas suas definições de privacidade. I
<translation id="3664782872746246217">Palavras-chave:</translation>
<translation id="3667704023705708645">Capital de risco</translation>
<translation id="367108335512738711">Marcadores do Chrome</translation>
-<translation id="3671540257457995106">Pretende permitir o redimensionamento?</translation>
+<translation id="3671540257457995106">Permitir o redimensionamento?</translation>
<translation id="3675563144891642599">Terceiro rolo</translation>
<translation id="3676592649209844519">ID do dispositivo:</translation>
<translation id="3677008721441257057">Será que quis dizer &lt;a href="#" id="dont-proceed-link"&gt;<ph name="DOMAIN" />&lt;/a&gt;?</translation>
@@ -1107,7 +1107,7 @@ Se não permitir, isto será bloqueado pelas suas definições de privacidade. I
a todos os pedidos, mas, de momento, não é possível aplicar essa política.</translation>
<translation id="4006465311664329701">Métodos de pagamento, ofertas e endereços com o Google Pay</translation>
<translation id="4009243425692662128">O conteúdo das páginas que imprime é enviado para o Google Cloud ou terceiros para análise. Por exemplo, pode ser analisado quanto a dados confidenciais.</translation>
-<translation id="4010758435855888356">Pretende permitir o acesso ao armazenamento?</translation>
+<translation id="4010758435855888356">Permitir o acesso ao armazenamento?</translation>
<translation id="4014128326099193693">{COUNT,plural, =1{Documento PDF com {COUNT} página}other{Documento PDF com {COUNT} páginas}}</translation>
<translation id="4023431997072828269">Uma vez que este formulário está a ser enviado através de uma ligação insegura, as suas informações vão ficar visíveis para outras pessoas.</translation>
<translation id="4025913568718019429">Botão Gerir definições de privacidade do Google, prima Enter para visitar as definições de privacidade da sua Conta Google</translation>
@@ -2101,7 +2101,7 @@ Se não permitir, isto será bloqueado pelas suas definições de privacidade. I
<translation id="6822437859461265552">Seguro de vida</translation>
<translation id="6823746213313229853">Debates na rádio</translation>
<translation id="6825578344716086703">Tentou aceder a <ph name="DOMAIN" />, mas o servidor apresentou um certificado assinado utilizando um algoritmo de assinatura fraco (como SHA-1). Isto significa que as credenciais de segurança apresentadas pelo servidor podem ter sido falsificadas e que o servidor pode não ser aquele que pretende (pode estar a comunicar com um utilizador mal intencionado).</translation>
-<translation id="6826993739343257035">Pretende permitir a realidade aumentada?</translation>
+<translation id="6826993739343257035">Permitir a realidade aumentada?</translation>
<translation id="6828866289116430505">Genética</translation>
<translation id="6831043979455480757">Traduzir</translation>
<translation id="6833752742582340615">Guarde o seu cartão e informações de faturação na sua Conta Google para pagamentos seguros e mais rápidos</translation>
@@ -2118,7 +2118,7 @@ Se não permitir, isto será bloqueado pelas suas definições de privacidade. I
<translation id="6884662655240309489">Tamanho 1</translation>
<translation id="6886577214605505410"><ph name="LOCATION_TITLE" /> <ph name="SHORT_URL" /></translation>
<translation id="6888584790432772780">O Chrome simplificou esta página para facilitar a leitura. O Chrome obteve a página original através de uma ligação insegura.</translation>
-<translation id="6890443033788248019">Pretende permitir o acesso à localização?</translation>
+<translation id="6890443033788248019">Permitir o acesso à localização?</translation>
<translation id="6891596781022320156">O nível da política não é suportado.</translation>
<translation id="6895143722905299846">Número virtual:</translation>
<translation id="6895330447102777224">O seu cartão foi confirmado</translation>
@@ -2468,7 +2468,7 @@ Detalhes adicionais:
<translation id="7758069387465995638">O software de firewall ou antivírus pode ter bloqueado a ligação.</translation>
<translation id="7760497246331667482">Música reggae e das Caraíbas</translation>
<translation id="776110834126722255">Descontinuada</translation>
-<translation id="7761159795823346334">Pretende permitir o acesso à câmara?</translation>
+<translation id="7761159795823346334">Permitir o acesso à câmara?</translation>
<translation id="7761701407923456692">O certificado do servidor não corresponde ao URL.</translation>
<translation id="7763386264682878361">Analisador de manifestos de pagamento</translation>
<translation id="7764225426217299476">Adicionar endereço</translation>
@@ -2726,7 +2726,7 @@ Detalhes adicionais:
<translation id="8530813470445476232">Limpe o seu histórico de navegação, cookies, cache e muito mais nas definições do Chrome.</translation>
<translation id="853332391023689529">Carrinhas de caixa aberta</translation>
<translation id="8533619373899488139">Visite &lt;strong&gt;chrome://policy&lt;/strong&gt; para ver a lista dos URLs bloqueados e outras políticas aplicadas pelo administrador do sistema.</translation>
-<translation id="8539500321752640291">Pretende permitir 2 autorizações?</translation>
+<translation id="8539500321752640291">Permitir 2 autorizações?</translation>
<translation id="8541158209346794904">Dispositivo Bluetooth</translation>
<translation id="8542014550340843547">Agrafo triplo na parte inferior</translation>
<translation id="8543181531796978784">Pode <ph name="BEGIN_ERROR_LINK" />comunicar um problema de deteção<ph name="END_ERROR_LINK" /> ou, se compreende os riscos para a sua segurança, <ph name="BEGIN_LINK" />aceda a este site não seguro<ph name="END_LINK" />.</translation>
@@ -2887,7 +2887,7 @@ Detalhes adicionais:
<translation id="9025348182339809926">(Inválido)</translation>
<translation id="9030265603405983977">Monocromático</translation>
<translation id="9035022520814077154">Erro de segurança</translation>
-<translation id="9036306139374661733">Pretende permitir o acesso ao microfone?</translation>
+<translation id="9036306139374661733">Permitir o acesso ao microfone?</translation>
<translation id="9038649477754266430">Utilizar um serviço de previsão para carregar páginas mais rapidamente</translation>
<translation id="9039213469156557790">Além disso, esta página inclui outros recursos que não são seguros. Estes recursos podem ser vistos por outros utilizadores em trânsito e modificados por um utilizador mal intencionado com o intuito de alterar o comportamento da página.</translation>
<translation id="9040464167025094690">Botão Localizar o meu dispositivo, prima Enter para visitar localizar o seu dispositivo na Conta Google</translation>
diff --git a/chromium/components/strings/components_strings_sr-Latn.xtb b/chromium/components/strings/components_strings_sr-Latn.xtb
index c8027dc34be..53a0ddedad3 100644
--- a/chromium/components/strings/components_strings_sr-Latn.xtb
+++ b/chromium/components/strings/components_strings_sr-Latn.xtb
@@ -533,7 +533,7 @@ To inaÄe blokiraju podeÅ¡avanja privatnosti. To omogucÌava da sadržaj sa koji
<translation id="2357481397660644965">Uređajem upravlja <ph name="DEVICE_MANAGER" />, a nalogom <ph name="ACCOUNT_MANAGER" />.</translation>
<translation id="2359347814217202136">{NUM_DAYS,plural, =0{Za manje od jednog dana}=1{Za jedan dan}one{Za {NUM_DAYS} dan}few{Za {NUM_DAYS} dana}other{Za {NUM_DAYS} dana}}</translation>
<translation id="2359629602545592467">Više</translation>
-<translation id="2359808026110333948">Nastavite</translation>
+<translation id="2359808026110333948">Nastavi</translation>
<translation id="236340516568226369">Meni za promenu veliÄine</translation>
<translation id="2367567093518048410">Nivo</translation>
<translation id="2380886658946992094">Legal</translation>
diff --git a/chromium/components/strings/components_strings_sr.xtb b/chromium/components/strings/components_strings_sr.xtb
index b008d04c3d3..a11d8ec6880 100644
--- a/chromium/components/strings/components_strings_sr.xtb
+++ b/chromium/components/strings/components_strings_sr.xtb
@@ -533,7 +533,7 @@
<translation id="2357481397660644965">Уређајем управља <ph name="DEVICE_MANAGER" />, а налогом <ph name="ACCOUNT_MANAGER" />.</translation>
<translation id="2359347814217202136">{NUM_DAYS,plural, =0{За мање од једног дана}=1{За један дан}one{За {NUM_DAYS} дан}few{За {NUM_DAYS} дана}other{За {NUM_DAYS} дана}}</translation>
<translation id="2359629602545592467">Више</translation>
-<translation id="2359808026110333948">ÐаÑтавите</translation>
+<translation id="2359808026110333948">ÐаÑтави</translation>
<translation id="236340516568226369">Мени за промену величине</translation>
<translation id="2367567093518048410">Ðиво</translation>
<translation id="2380886658946992094">Legal</translation>
diff --git a/chromium/components/strings/components_strings_te.xtb b/chromium/components/strings/components_strings_te.xtb
index c9abd68b6f9..4c6638f9b39 100644
--- a/chromium/components/strings/components_strings_te.xtb
+++ b/chromium/components/strings/components_strings_te.xtb
@@ -613,7 +613,7 @@
<translation id="257674075312929031">à°—à±à°°à±‚à°ªà±â€Œà°—à°¾ చేయి</translation>
<translation id="2576880857912732701">'సెకà±à°¯à±‚à°°à°¿à°Ÿà±€ సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°¨à± మేనేజౠచేయండి' బటనà±, Chrome సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°²à±‹ మీ à°¸à±à°°à°•à±à°·à°¿à°¤ à°¬à±à°°à±Œà°œà°¿à°‚à°—à±â€Œà°¨à±, అలాగే మరినà±à°¨à°¿à°‚టిని మేనేజౠచేయడానికి 'Enter'నౠనొకà±à°•à°‚à°¡à°¿</translation>
<translation id="2586657967955657006">à°•à±à°²à°¿à°ªà±â€Œà°¬à±‹à°°à±à°¡à±</translation>
-<translation id="2587730715158995865">à°ªà±à°°à°šà±à°°à°£à°•à°°à±à°¤ <ph name="ARTICLE_PUBLISHER" />. దీనà±à°¨à°¿ మరియౠమరో <ph name="OTHER_ARTICLE_COUNT" /> ఇతర కథనాలనౠచదవండి.</translation>
+<translation id="2587730715158995865">పబà±à°²à°¿à°·à°°à±â€Œ <ph name="ARTICLE_PUBLISHER" />. దీనà±à°¨à°¿ మరియౠమరో <ph name="OTHER_ARTICLE_COUNT" /> ఇతర కథనాలనౠచదవండి.</translation>
<translation id="2587841377698384444">డైరెకà±à°Ÿà°°à±€ API ID:</translation>
<translation id="2594318783181750337">వేగవంతమైన వెబౠవీకà±à°·à°£:</translation>
<translation id="2595719060046994702">à°ˆ పరికరం మరియౠఖాతా రెండూ కూడా కంపెనీ లేదా ఇతర సంసà±à°¥ నిరà±à°µà°¹à°£à°²à±‹ లేవà±.</translation>
@@ -939,7 +939,7 @@
<ph name="PAGE_TITLE" /></translation>
<translation id="350069200438440499">ఫైలౠపేరà±:</translation>
<translation id="3507936815618196901">మీ పరిసరాల 3D à°®à±à°¯à°¾à°ªà±â€Œà°¨à± రూపొందించడం, అలాగే కెమెరా పొజిషనà±â€Œà°¨à± à°Ÿà±à°°à°¾à°•à± చేయడం</translation>
-<translation id="3512163584740124171">à°ˆ విధానం విసà±à°®à°°à°¿à°‚చబడà±à°¤à±à°‚ది, à°Žà°‚à°¦à±à°•à°‚టే ఒకే విధాన సమూహం à°¨à±à°‚à°¡à°¿ మరొక విధానం అధిక à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°¨à± కలిగి ఉంది.</translation>
+<translation id="3512163584740124171">à°ˆ విధానం విసà±à°®à°°à°¿à°‚చబడà±à°¤à±à°‚ది, à°Žà°‚à°¦à±à°•à°‚టే ఒకే విధాన à°—à±à°°à±‚à°ªà±â€Œ à°¨à±à°‚à°¡à°¿ మరొక విధానం అధిక à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°¨à± కలిగి ఉంది.</translation>
<translation id="35172538073169599">'à°…à°¡à±à°°à°¸à±â€Œà°²à°¨à± మేనేజౠచేయి' బటనà±, Chrome సెటà±à°Ÿà°¿à°‚à°—à±â€Œà°²à°²à±‹ à°…à°¡à±à°°à°¸à±â€Œà°²à°¨à± జోడించడానికి, మేనేజౠచేయడానికి 'Enter'నౠనొకà±à°•à°‚à°¡à°¿</translation>
<translation id="3518941727116570328">అనేక వసà±à°¤à±à°µà±à°²à°¨à± à°¹à±à°¯à°¾à°‚డిలౠచేయడం</translation>
<translation id="3528171143076753409">సరà±à°µà°°à± à°ªà±à°°à°®à°¾à°£à°ªà°¤à±à°°à°‚ విశà±à°µà°¸à°¨à±€à°¯à°®à±ˆà°¨à°¦à°¿ కాదà±.</translation>
diff --git a/chromium/components/strings/components_strings_vi.xtb b/chromium/components/strings/components_strings_vi.xtb
index 737992fdba5..efc072d84b3 100644
--- a/chromium/components/strings/components_strings_vi.xtb
+++ b/chromium/components/strings/components_strings_vi.xtb
@@ -1658,7 +1658,7 @@ Nếu bạn từ chối, chế Ä‘á»™ cài đặt quyá»n riêng tÆ° của bạn
<translation id="54817484435770891">Thêm địa chỉ hợp lệ</translation>
<translation id="5485973315555778056">Thiết bị trên đám mây</translation>
<translation id="5487426985799386720">Thực phẩm và đồ uống</translation>
-<translation id="5490432419156082418">Äịa chỉ và các tùy chá»n khác</translation>
+<translation id="5490432419156082418">Äịa chỉ và các lá»±a chá»n khác</translation>
<translation id="5492298309214877701">Trang web trên mạng ná»™i bá»™ của công ty, tổ chức hoặc trÆ°á»ng há»c này có URL tÆ°Æ¡ng tá»± nhÆ° trang web bên ngoài.
<ph name="LINE_BREAK" />
Hãy thử liên hệ với quản trị viên hệ thống của bạn.</translation>
@@ -2708,7 +2708,7 @@ Thông tin chi tiết bổ sung:
<translation id="8457125768502047971">Vô thá»i hạn</translation>
<translation id="8461694314515752532">Mã hóa dữ liệu đã đồng bộ hóa bằng cụm mật khẩu đồng bộ hóa của riêng bạn</translation>
<translation id="8466379296835108687">{COUNT,plural, =1{1 thẻ tín dụng}other{# thẻ tín dụng}}</translation>
-<translation id="8473863474539038330">Äịa chỉ và các tùy chá»n khác</translation>
+<translation id="8473863474539038330">Äịa chỉ và các lá»±a chá»n khác</translation>
<translation id="8474910779563686872">Hiển thị thông tin chi tiết của nhà phát triển</translation>
<translation id="8479754468255770962">Dập ghim dưới cùng bên trái</translation>
<translation id="8483780878231876732">Äể sá»­ dụng thẻ từ Tài khoản Google, hãy đăng nhập vào Chrome</translation>
diff --git a/chromium/components/variations/service/variations_field_trial_creator.cc b/chromium/components/variations/service/variations_field_trial_creator.cc
index aef885cdb58..ac93e0cf388 100644
--- a/chromium/components/variations/service/variations_field_trial_creator.cc
+++ b/chromium/components/variations/service/variations_field_trial_creator.cc
@@ -301,7 +301,8 @@ bool VariationsFieldTrialCreator::SetUpFieldTrials(
// We log a recognizable token for the crash condition, to allow tests to
// recognize the crash location in the test output. See:
// TEST_P(FieldTrialTest, ExtendedSafeModeEndToEnd)
- LOG(FATAL) << "crash_for_testing";
+ LOG(ERROR) << "crash_for_testing";
+ abort();
}
// This must be called after |local_state_| is initialized.
diff --git a/chromium/content/browser/indexed_db/indexed_db_context_impl.cc b/chromium/content/browser/indexed_db/indexed_db_context_impl.cc
index b9e8f89301f..e7093652d4d 100644
--- a/chromium/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/chromium/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -166,38 +166,32 @@ IndexedDBContextImpl::IndexedDBContextImpl(
std::move(quota_client_remote),
storage::QuotaClientType::kIndexedDatabase,
{blink::mojom::StorageType::kTemporary});
+ IDBTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&IndexedDBContextImpl::BindPipesOnIDBSequence,
+ weak_factory_.GetWeakPtr(),
+ std::move(quota_client_receiver),
+ std::move(blob_storage_context),
+ std::move(file_system_access_context)));
+}
- // This is safe because the IndexedDBContextImpl must be destructed on the
- // IDBTaskRunner, and this task will always happen before that.
- idb_task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(
- [](mojo::Remote<storage::mojom::BlobStorageContext>*
- blob_storage_context,
- mojo::Remote<storage::mojom::FileSystemAccessContext>*
- file_system_access_context,
- mojo::Receiver<storage::mojom::QuotaClient>* quota_client_receiver,
- mojo::PendingRemote<storage::mojom::BlobStorageContext>
- pending_blob_storage_context,
- mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
- pending_file_system_access_context,
- mojo::PendingReceiver<storage::mojom::QuotaClient>
- quota_client_pending_receiver) {
- quota_client_receiver->Bind(
- std::move(quota_client_pending_receiver));
- if (pending_blob_storage_context) {
- blob_storage_context->Bind(
- std::move(pending_blob_storage_context));
- }
- if (pending_file_system_access_context) {
- file_system_access_context->Bind(
- std::move(pending_file_system_access_context));
- }
- },
- &blob_storage_context_, &file_system_access_context_,
- &quota_client_receiver_, std::move(blob_storage_context),
- std::move(file_system_access_context),
- std::move(quota_client_receiver)));
+void IndexedDBContextImpl::BindPipesOnIDBSequence(
+ mojo::PendingReceiver<storage::mojom::QuotaClient>
+ pending_quota_client_receiver,
+ mojo::PendingRemote<storage::mojom::BlobStorageContext>
+ pending_blob_storage_context,
+ mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
+ pending_file_system_access_context) {
+ DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
+ if (pending_quota_client_receiver) {
+ quota_client_receiver_.Bind(std::move(pending_quota_client_receiver));
+ }
+ if (pending_blob_storage_context) {
+ blob_storage_context_.Bind(std::move(pending_blob_storage_context));
+ }
+ if (pending_file_system_access_context) {
+ file_system_access_context_.Bind(
+ std::move(pending_file_system_access_context));
+ }
}
void IndexedDBContextImpl::Bind(
diff --git a/chromium/content/browser/indexed_db/indexed_db_context_impl.h b/chromium/content/browser/indexed_db/indexed_db_context_impl.h
index f9910a6336a..241956c6258 100644
--- a/chromium/content/browser/indexed_db/indexed_db_context_impl.h
+++ b/chromium/content/browser/indexed_db/indexed_db_context_impl.h
@@ -224,6 +224,14 @@ class CONTENT_EXPORT IndexedDBContextImpl
~IndexedDBContextImpl() override;
+ void BindPipesOnIDBSequence(
+ mojo::PendingReceiver<storage::mojom::QuotaClient>
+ pending_quota_client_receiver,
+ mojo::PendingRemote<storage::mojom::BlobStorageContext>
+ pending_blob_storage_context,
+ mojo::PendingRemote<storage::mojom::FileSystemAccessContext>
+ pending_file_system_access_context);
+
// Binds receiver on bucket retrieval to ensure that a bucket always exists
// for a storage key.
void BindIndexedDBWithBucket(
@@ -282,6 +290,8 @@ class CONTENT_EXPORT IndexedDBContextImpl
mojo::Receiver<storage::mojom::QuotaClient> quota_client_receiver_;
const std::unique_ptr<storage::FilesystemProxy> filesystem_proxy_;
+ // weak_factory_->GetWeakPtr() may be used on any thread, but the resulting
+ // pointer must only be checked/used on idb_task_runner_.
base::WeakPtrFactory<IndexedDBContextImpl> weak_factory_{this};
};
diff --git a/chromium/content/browser/loader/navigation_url_loader_impl.cc b/chromium/content/browser/loader/navigation_url_loader_impl.cc
index f24b5fa85e2..6cf47a1ca1e 100644
--- a/chromium/content/browser/loader/navigation_url_loader_impl.cc
+++ b/chromium/content/browser/loader/navigation_url_loader_impl.cc
@@ -983,6 +983,11 @@ void NavigationURLLoaderImpl::OnAcceptCHFrameReceived(
const std::vector<network::mojom::WebClientHintsType>& accept_ch_frame,
OnAcceptCHFrameReceivedCallback callback) {
received_accept_ch_frame_ = true;
+ if (!base::FeatureList::IsEnabled(network::features::kAcceptCHFrame)) {
+ std::move(callback).Run(net::OK);
+ return;
+ }
+
LogAcceptCHFrameStatus(AcceptCHFrameRestart::kFramePresent);
// Given that this is happening in the middle of navigation, there should
diff --git a/chromium/content/browser/network_service_instance_impl.cc b/chromium/content/browser/network_service_instance_impl.cc
index 7788ca39e1d..21da59942d9 100644
--- a/chromium/content/browser/network_service_instance_impl.cc
+++ b/chromium/content/browser/network_service_instance_impl.cc
@@ -817,10 +817,37 @@ void CreateNetworkContextInNetworkService(
MaybeCleanCacheDirectory(params.get());
#if BUILDFLAG(IS_ANDROID)
+ // On Android, if a cookie_manager pending receiver was passed then migration
+ // should not be attempted as the cookie file is already being accessed by the
+ // browser instance.
+ if (params->cookie_manager) {
+ if (params->file_paths) {
+ // No migration should ever be attempted under this configuration.
+ DCHECK(!params->file_paths->unsandboxed_data_path);
+ }
+ CreateNetworkContextInternal(
+ std::move(context), std::move(params),
+ SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess);
+ return;
+ }
+
+ // Note: This logic is duplicated from MaybeGrantAccessToDataPath to this fast
+ // path. This should be kept in sync if there are any changes to the logic.
+ SandboxGrantResult grant_result = SandboxGrantResult::kNoMigrationRequested;
+ if (!params->file_paths) {
+ // No file paths (e.g. in-memory context) so nothing to do.
+ grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess;
+ } else {
+ // If no `unsandboxed_data_path` is supplied, it means this is network
+ // context has been created by Android Webview, which does not understand
+ // the concept of `unsandboxed_data_path`. In this case, `data_directory`
+ // should always be used, if present.
+ if (!params->file_paths->unsandboxed_data_path)
+ grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess;
+ }
// Create network context immediately without thread hops.
- CreateNetworkContextInternal(
- std::move(context), std::move(params),
- SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess);
+ CreateNetworkContextInternal(std::move(context), std::move(params),
+ grant_result);
#else
// Restrict disk access to a certain path (on another thread) and continue
// with network context creation.
diff --git a/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
index 497e97f0616..2513aee380c 100644
--- a/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
+++ b/chromium/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
@@ -17,6 +17,7 @@
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
@@ -68,26 +69,40 @@ StartObservingWebContents(int render_process_id,
}
#if !BUILDFLAG(IS_ANDROID)
+// Helper for getting the top-level WebContents associated with a given ID.
+// Returns nullptr if one does not exist (e.g. has gone away).
+WebContents* GetMainFrameWebContents(const GlobalRoutingID& global_routing_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (global_routing_id == GlobalRoutingID()) {
+ return nullptr;
+ }
+
+ RenderFrameHost* const rfh = RenderFrameHost::FromID(
+ global_routing_id.child_id, global_routing_id.route_id);
+ return rfh ? WebContents::FromRenderFrameHost(rfh->GetMainFrame()) : nullptr;
+}
+
// Checks whether a track living in the WebContents indicated by
// (render_process_id, render_frame_id) may be cropped to the crop-target
// indicated by |crop_id|.
-bool IsCropTargetValid(int render_process_id,
- int render_frame_id,
- const base::Token& crop_id) {
- RenderFrameHost* const rfh =
- RenderFrameHost::FromID(render_process_id, render_frame_id);
- if (!rfh) {
+bool MayCrop(const GlobalRoutingID& capturing_id,
+ const GlobalRoutingID& captured_id,
+ const base::Token& crop_id) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ WebContents* const capturing_wc = GetMainFrameWebContents(capturing_id);
+ if (!capturing_wc) {
return false;
}
- WebContents* const web_contents =
- WebContents::FromRenderFrameHost(rfh->GetMainFrame());
- if (!web_contents) {
+ WebContents* const captured_wc = GetMainFrameWebContents(captured_id);
+ if (capturing_wc != captured_wc) { // Null or not-same-tab.
return false;
}
CropIdWebContentsHelper* const helper =
- CropIdWebContentsHelper::FromWebContents(web_contents);
+ CropIdWebContentsHelper::FromWebContents(captured_wc);
if (!helper) {
// No crop-IDs were ever produced on this WebContents.
// Any non-zero crop-ID should be rejected on account of being
@@ -529,6 +544,10 @@ void MediaStreamDispatcherHost::Crop(const base::UnguessableToken& device_id,
CropCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ const GlobalRoutingID captured_id =
+ media_stream_manager_->video_capture_manager()->GetGlobalRoutingID(
+ device_id);
+
// Hop to the UI thread to verify that cropping to |crop_id| is permitted
// from this particular context. Namely, cropping is currently only allowed
// for self-capture, so the crop_id has to be associated with the top-level
@@ -537,8 +556,9 @@ void MediaStreamDispatcherHost::Crop(const base::UnguessableToken& device_id,
// when SelfOwnedReceiver properly supports this.
GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
FROM_HERE,
- base::BindOnce(&IsCropTargetValid, render_process_id_, render_frame_id_,
- crop_id),
+ base::BindOnce(&MayCrop,
+ GlobalRoutingID(render_process_id_, render_frame_id_),
+ captured_id, crop_id),
base::BindOnce(&MediaStreamDispatcherHost::OnCropValidationComplete,
weak_factory_.GetWeakPtr(), device_id, crop_id,
crop_version,
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.cc b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
index 1047d87f9da..ce668fb5919 100644
--- a/chromium/content/browser/renderer_host/media/video_capture_manager.cc
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.cc
@@ -596,6 +596,29 @@ VideoCaptureManager::GetDeviceFormatInUse(
return device_in_use ? device_in_use->GetVideoCaptureFormat() : absl::nullopt;
}
+GlobalRoutingID VideoCaptureManager::GetGlobalRoutingID(
+ const base::UnguessableToken& session_id) const {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+ VideoCaptureController* const controller =
+ LookupControllerBySessionId(session_id);
+ if (!controller || !controller->IsDeviceAlive() ||
+ !blink::IsVideoDesktopCaptureMediaType(controller->stream_type())) {
+ return GlobalRoutingID();
+ }
+
+ const DesktopMediaID desktop_media_id =
+ DesktopMediaID::Parse(controller->device_id());
+
+ if (desktop_media_id.type != DesktopMediaID::Type::TYPE_WEB_CONTENTS ||
+ desktop_media_id.web_contents_id.is_null()) {
+ return GlobalRoutingID();
+ }
+
+ return GlobalRoutingID(desktop_media_id.web_contents_id.render_process_id,
+ desktop_media_id.web_contents_id.main_render_frame_id);
+}
+
void VideoCaptureManager::SetDesktopCaptureWindowId(
const media::VideoCaptureSessionId& session_id,
gfx::NativeViewId window_id) {
@@ -798,7 +821,7 @@ void VideoCaptureManager::DestroyControllerIfNoClients(
}
VideoCaptureController* VideoCaptureManager::LookupControllerBySessionId(
- const base::UnguessableToken& session_id) {
+ const base::UnguessableToken& session_id) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SessionMap::const_iterator session_it = sessions_.find(session_id);
if (session_it == sessions_.end())
diff --git a/chromium/content/browser/renderer_host/media/video_capture_manager.h b/chromium/content/browser/renderer_host/media/video_capture_manager.h
index cc30ec2a964..34a0e709b55 100644
--- a/chromium/content/browser/renderer_host/media/video_capture_manager.h
+++ b/chromium/content/browser/renderer_host/media/video_capture_manager.h
@@ -26,6 +26,7 @@
#include "content/browser/renderer_host/media/video_capture_device_launch_observer.h"
#include "content/browser/renderer_host/media/video_capture_provider.h"
#include "content/common/content_export.h"
+#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/screenlock_observer.h"
#include "media/base/video_facing.h"
#include "media/capture/video/video_capture_device.h"
@@ -178,6 +179,12 @@ class CONTENT_EXPORT VideoCaptureManager
blink::mojom::MediaStreamType stream_type,
const std::string& device_id);
+ // If there is a capture session associated with |session_id|, and the
+ // captured entity a tab, return the GlobalRoutingID of the captured tab.
+ // Otherwise, returns an empty GlobalRoutingID.
+ GlobalRoutingID GetGlobalRoutingID(
+ const base::UnguessableToken& session_id) const;
+
// Sets the platform-dependent window ID for the desktop capture notification
// UI for the given session.
void SetDesktopCaptureWindowId(const media::VideoCaptureSessionId& session_id,
@@ -251,7 +258,7 @@ class CONTENT_EXPORT VideoCaptureManager
// |device_id| and |type| (if it is already opened), by its |controller| or by
// its |serial_id|. In all cases, if not found, nullptr is returned.
VideoCaptureController* LookupControllerBySessionId(
- const base::UnguessableToken& session_id);
+ const base::UnguessableToken& session_id) const;
VideoCaptureController* LookupControllerByMediaTypeAndDeviceId(
blink::mojom::MediaStreamType type,
const std::string& device_id) const;
diff --git a/chromium/content/public/common/content_features.cc b/chromium/content/public/common/content_features.cc
index 534405f29ac..aa4a054fad5 100644
--- a/chromium/content/public/common/content_features.cc
+++ b/chromium/content/public/common/content_features.cc
@@ -715,7 +715,7 @@ const base::Feature kPrivateNetworkAccessRespectPreflightResults = {
// Enables sending CORS preflight requests ahead of private network requests.
// See: https://wicg.github.io/private-network-access/#cors-preflight
const base::Feature kPrivateNetworkAccessSendPreflights = {
- "PrivateNetworkAccessSendPreflights", base::FEATURE_ENABLED_BY_DEFAULT};
+ "PrivateNetworkAccessSendPreflights", base::FEATURE_DISABLED_BY_DEFAULT};
// Enable the ProactivelySwapBrowsingInstance experiment. A browsing instance
// represents a set of frames that can script each other. Currently, Chrome does
diff --git a/chromium/content/renderer/media/media_factory.cc b/chromium/content/renderer/media/media_factory.cc
index 0b7208e9c49..36310a3d74e 100644
--- a/chromium/content/renderer/media/media_factory.cc
+++ b/chromium/content/renderer/media/media_factory.cc
@@ -342,11 +342,7 @@ MediaFactory::MediaFactory(
RenderFrameImpl* render_frame,
media::RequestRoutingTokenCallback request_routing_token_cb)
: render_frame_(render_frame),
- request_routing_token_cb_(std::move(request_routing_token_cb)) {
- // Requesting a support check will ensure that the supplemental profiles
- // cache is populated prior to anything that needs it on the media thread.
- media::IsSupportedVideoType({});
-}
+ request_routing_token_cb_(std::move(request_routing_token_cb)) {}
MediaFactory::~MediaFactory() {
// Release the DecoderFactory to the media thread since it may still be in use
diff --git a/chromium/content/renderer/media/render_media_client.cc b/chromium/content/renderer/media/render_media_client.cc
index 87adab20f56..a0b92623bda 100644
--- a/chromium/content/renderer/media/render_media_client.cc
+++ b/chromium/content/renderer/media/render_media_client.cc
@@ -14,6 +14,34 @@
#include "media/base/video_color_space.h"
#include "ui/display/display_switches.h"
+namespace {
+
+// At present, HEVC is the only codec which has optional platform support.
+// Some clients need this knowledge synchronously, so we try to populate
+// it asynchronously ahead of time, but can fallback to a blocking call
+// when it's needed synchronously.
+#if BUILDFLAG(ENABLE_PLATFORM_HEVC) && \
+ (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX))
+#define NEEDS_PROFILE_UPDATER 1
+#else
+#define NEEDS_PROFILE_UPDATER 0
+#endif
+
+#if NEEDS_PROFILE_UPDATER
+void UpdateVideoProfilesInternal(const gpu::GPUInfo& info) {
+ const auto gpu_profiles = info.video_decode_accelerator_supported_profiles;
+ base::flat_set<media::VideoCodecProfile> media_profiles;
+ media_profiles.reserve(gpu_profiles.size());
+ for (const auto& profile : gpu_profiles) {
+ media_profiles.insert(
+ static_cast<media::VideoCodecProfile>(profile.profile));
+ }
+ media::UpdateDefaultSupportedVideoProfiles(media_profiles);
+}
+#endif
+
+} // namespace
+
namespace content {
void RenderMediaClient::Initialize() {
@@ -21,11 +49,17 @@ void RenderMediaClient::Initialize() {
media::SetMediaClient(client);
}
-RenderMediaClient::RenderMediaClient() {}
+RenderMediaClient::RenderMediaClient() {
+#if NEEDS_PROFILE_UPDATER
+ // Unretained is safe here since the MediaClient is never destructed.
+ RenderThreadImpl::current()->EstablishGpuChannel(base::BindOnce(
+ &RenderMediaClient::OnEstablishedGpuChannel, base::Unretained(this)));
-RenderMediaClient::~RenderMediaClient() {
+#endif
}
+RenderMediaClient::~RenderMediaClient() = default;
+
void RenderMediaClient::GetSupportedKeySystems(
media::GetSupportedKeySystemsCB cb) {
GetContentClient()->renderer()->GetSupportedKeySystems(std::move(cb));
@@ -36,27 +70,21 @@ bool RenderMediaClient::IsSupportedAudioType(const media::AudioType& type) {
}
bool RenderMediaClient::IsSupportedVideoType(const media::VideoType& type) {
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
- // If we're not on the render thread (ie, from the media thread), then don't
- // even bother trying to populate the cache.
- if (auto* render_thread = RenderThreadImpl::current()) {
- static const bool kHasGpuProfiles = [render_thread]() {
- if (auto gpu_host = render_thread->EstablishGpuChannelSync()) {
- const auto gpu_profiles =
- gpu_host->gpu_info().video_decode_accelerator_supported_profiles;
- base::flat_set<media::VideoCodecProfile> media_profiles;
- for (const auto& profile : gpu_profiles) {
- media_profiles.insert(
- static_cast<media::VideoCodecProfile>(profile.profile));
- }
- media::UpdateDefaultSupportedVideoProfiles(media_profiles);
- return true;
- }
- return false;
- }();
- std::ignore = kHasGpuProfiles;
+#if NEEDS_PROFILE_UPDATER
+ if (!did_update_.IsSignaled()) {
+ // The asynchronous request didn't complete in time, so we must now block
+ // until until the information from the GPU channel is available.
+ if (auto* render_thread = content::RenderThreadImpl::current()) {
+ if (auto gpu_host = render_thread->EstablishGpuChannelSync())
+ UpdateVideoProfilesInternal(gpu_host->gpu_info());
+ did_update_.Signal();
+ } else {
+ // There's already an asynchronous request on the main thread, so wait...
+ did_update_.Wait();
+ }
}
#endif
+
return GetContentClient()->renderer()->IsSupportedVideoType(type);
}
@@ -72,4 +100,15 @@ RenderMediaClient::GetAudioRendererAlgorithmParameters(
audio_parameters);
}
+void RenderMediaClient::OnEstablishedGpuChannel(
+ scoped_refptr<gpu::GpuChannelHost> host) {
+#if NEEDS_PROFILE_UPDATER
+ if (host && !did_update_.IsSignaled())
+ UpdateVideoProfilesInternal(host->gpu_info());
+
+ // Signal even if host is nullptr, since that's the same has having no GPU.
+ did_update_.Signal();
+#endif
+}
+
} // namespace content
diff --git a/chromium/content/renderer/media/render_media_client.h b/chromium/content/renderer/media/render_media_client.h
index 189f7344136..93e8d338212 100644
--- a/chromium/content/renderer/media/render_media_client.h
+++ b/chromium/content/renderer/media/render_media_client.h
@@ -5,9 +5,14 @@
#ifndef CONTENT_RENDERER_MEDIA_RENDER_MEDIA_CLIENT_H_
#define CONTENT_RENDERER_MEDIA_RENDER_MEDIA_CLIENT_H_
+#include "base/synchronization/waitable_event.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_client.h"
+namespace gpu {
+class GpuChannelHost;
+}
+
namespace content {
// RenderMediaClient is purely plumbing to make content embedder customizations
@@ -33,6 +38,13 @@ class RenderMediaClient : public media::MediaClient {
private:
RenderMediaClient();
~RenderMediaClient() override;
+
+ void OnEstablishedGpuChannel(scoped_refptr<gpu::GpuChannelHost> host);
+
+ // Used to indicate if optional video profile support information has been
+ // retrieved from the GPU channel. May be waited upon by any thread but the
+ // RenderThread since it's always signaled from the RenderThread.
+ base::WaitableEvent did_update_;
};
} // namespace content
diff --git a/chromium/content/shell/DEPS b/chromium/content/shell/DEPS
new file mode 100644
index 00000000000..5d0b5bd73ea
--- /dev/null
+++ b/chromium/content/shell/DEPS
@@ -0,0 +1,57 @@
+include_rules = [
+ "+gin/public",
+ "+gin/v8_initializer.h",
+ "+v8/include",
+
+ # For chromeos build config
+ "+chromeos/dbus",
+ "+device/bluetooth", # BluetoothAdapterFactory::Shutdown.
+
+ # The content_shell is the canonical sample embedder, so it only uses
+ # content's public API.
+ "+content/public",
+
+ # Network service public library.
+ "+services/network/public/cpp",
+
+ # The content_shell is an embedder so it must work with resource bundles.
+ "+ui/base/l10n",
+ "+ui/base/resource",
+
+ # Shell resources
+ "+grit/shell_resources.h",
+
+ # The content_shell for aura must work with the views and aura
+ "+ui/aura",
+ "+ui/color",
+ "+ui/platform_window",
+ "+ui/views",
+
+ # Content Shell can depend on more components than content/, since:
+ # 1) it's an example browser
+ # 2) it's not linked into the content library
+ "+components/embedder_support",
+ "+components/custom_handlers",
+ "+components/crash",
+ "+components/download",
+ "+components/keyed_service/core",
+ "+components/performance_manager",
+ "+components/permissions",
+ "+components/url_formatter",
+ "+components/network_session_configurator/browser",
+ "+components/viz/common/resources",
+ "+components/viz/common/switches.h",
+ "+services/test/echo",
+
+ # Separating content shell and web test code. Only narrow parts of content
+ # shell may access and inject web test code at runtime.
+ "-content/web_test/browser",
+ "-content/web_test/common",
+ "-content/web_test/renderer",
+]
+
+specific_include_rules = {
+ "shell_views\.cc": [
+ "+ui/wm/test"
+ ],
+}
diff --git a/chromium/content/shell/DIR_METADATA b/chromium/content/shell/DIR_METADATA
new file mode 100644
index 00000000000..77792367054
--- /dev/null
+++ b/chromium/content/shell/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail {
+ component: "Test"
+}
diff --git a/chromium/content/shell/OWNERS b/chromium/content/shell/OWNERS
new file mode 100644
index 00000000000..d2f8ec502af
--- /dev/null
+++ b/chromium/content/shell/OWNERS
@@ -0,0 +1,4 @@
+mkwst@chromium.org
+peter@chromium.org
+danakj@chromium.org
+
diff --git a/chromium/content/shell/app/DEPS b/chromium/content/shell/app/DEPS
new file mode 100644
index 00000000000..f64b894c822
--- /dev/null
+++ b/chromium/content/shell/app/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+]
+
+specific_include_rules = {
+ "shell_main_delegate\.cc": [
+ # Separating Content Shell and web test code. ShellMainDelegate injects web test
+ # code into Content Shell at runtime.
+ "+content/web_test/browser",
+ "+content/web_test/renderer",
+ ],
+}
diff --git a/chromium/content/shell/app/app-Info.plist b/chromium/content/shell/app/app-Info.plist
new file mode 100644
index 00000000000..65b019e3e2b
--- /dev/null
+++ b/chromium/content/shell/app/app-Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string>app.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.ContentShell</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSFileQuarantineEnabled</key>
+ <true/>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>NSBluetoothAlwaysUsageDescription</key>
+ <string>Content Shell may use Bluetooth to implement Web Platform features.</string>
+</dict>
+</plist>
diff --git a/chromium/content/shell/app/app.icns b/chromium/content/shell/app/app.icns
new file mode 100644
index 00000000000..f36742de257
--- /dev/null
+++ b/chromium/content/shell/app/app.icns
Binary files differ
diff --git a/chromium/content/shell/app/framework-Info.plist b/chromium/content/shell/app/framework-Info.plist
new file mode 100644
index 00000000000..ee19476052d
--- /dev/null
+++ b/chromium/content/shell/app/framework-Info.plist
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.ContentShell.framework</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+</dict>
+</plist>
diff --git a/chromium/content/shell/app/helper-Info.plist b/chromium/content/shell/app/helper-Info.plist
new file mode 100644
index 00000000000..2503b29efca
--- /dev/null
+++ b/chromium/content/shell/app/helper-Info.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.ContentShell.helper${CONTENT_SHELL_HELPER_BUNDLE_ID_SUFFIX}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>LSFileQuarantineEnabled</key>
+ <true/>
+ <key>LSUIElement</key>
+ <string>1</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/chromium/content/shell/app/paths_mac.h b/chromium/content/shell/app/paths_mac.h
new file mode 100644
index 00000000000..e704128cfee
--- /dev/null
+++ b/chromium/content/shell/app/paths_mac.h
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_APP_PATHS_MAC_H_
+#define CONTENT_SHELL_APP_PATHS_MAC_H_
+
+namespace base {
+class FilePath;
+}
+
+// Sets up base::mac::FrameworkBundle.
+void OverrideFrameworkBundlePath();
+
+// Set up base::mac::OuterBundle.
+void OverrideOuterBundlePath();
+
+// Sets up the CHILD_PROCESS_EXE path to properly point to the helper app.
+void OverrideChildProcessPath();
+
+// Sets up base::DIR_SOURCE_ROOT to properly point to the source directory.
+void OverrideSourceRootPath();
+
+// Gets the path to the content shell's pak file.
+base::FilePath GetResourcesPakFilePath();
+
+// Gets the path to content shell's Info.plist file.
+base::FilePath GetInfoPlistPath();
+
+#endif // CONTENT_SHELL_APP_PATHS_MAC_H_
diff --git a/chromium/content/shell/app/paths_mac.mm b/chromium/content/shell/app/paths_mac.mm
new file mode 100644
index 00000000000..c43cb6cadda
--- /dev/null
+++ b/chromium/content/shell/app/paths_mac.mm
@@ -0,0 +1,96 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/app/paths_mac.h"
+
+#include "base/base_paths.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/path_service.h"
+#include "content/public/common/content_paths.h"
+
+namespace {
+
+base::FilePath GetContentsPath() {
+ // Start out with the path to the running executable.
+ base::FilePath path;
+ base::PathService::Get(base::FILE_EXE, &path);
+
+ // Up to Contents.
+ if (base::mac::IsBackgroundOnlyProcess()) {
+ // The running executable is the helper, located at:
+ // Content Shell.app/Contents/Frameworks/
+ // Content Shell Framework.framework/Versions/C/Helpers/Content Shell
+ // Helper.app/ Contents/MacOS/Content Shell Helper. Go up nine steps to get
+ // to the main bundle's Contents directory.
+ path = path.DirName()
+ .DirName()
+ .DirName()
+ .DirName()
+ .DirName()
+ .DirName()
+ .DirName()
+ .DirName()
+ .DirName();
+ } else {
+ // One step up to MacOS, another to Contents.
+ path = path.DirName().DirName();
+ }
+ DCHECK_EQ("Contents", path.BaseName().value());
+
+ return path;
+}
+
+base::FilePath GetFrameworksPath() {
+ return GetContentsPath().Append("Frameworks");
+}
+
+} // namespace
+
+void OverrideFrameworkBundlePath() {
+ base::FilePath helper_path =
+ GetFrameworksPath().Append("Content Shell Framework.framework");
+
+ base::mac::SetOverrideFrameworkBundlePath(helper_path);
+}
+
+void OverrideOuterBundlePath() {
+ base::FilePath path = GetContentsPath().DirName();
+
+ base::mac::SetOverrideOuterBundlePath(path);
+}
+
+void OverrideChildProcessPath() {
+ base::FilePath helper_path = base::mac::FrameworkBundlePath()
+ .Append("Helpers")
+ .Append("Content Shell Helper.app")
+ .Append("Contents")
+ .Append("MacOS")
+ .Append("Content Shell Helper");
+
+ base::PathService::Override(content::CHILD_PROCESS_EXE, helper_path);
+}
+
+void OverrideSourceRootPath() {
+ // The base implementation to get base::DIR_SOURCE_ROOT assumes the current
+ // process path is the top level app path, not a nested one.
+ //
+ // Going up 5 levels is needed, since frameworks path looks something like
+ // src/out/foo/Content Shell.app/Contents/Framework/
+ base::PathService::Override(
+ base::DIR_SOURCE_ROOT,
+ GetFrameworksPath().DirName().DirName().DirName().DirName().DirName());
+}
+
+base::FilePath GetResourcesPakFilePath() {
+ NSString* pak_path =
+ [base::mac::FrameworkBundle() pathForResource:@"content_shell"
+ ofType:@"pak"];
+
+ return base::FilePath([pak_path fileSystemRepresentation]);
+}
+
+base::FilePath GetInfoPlistPath() {
+ return GetContentsPath().Append("Info.plist");
+}
diff --git a/chromium/content/shell/app/resource.h b/chromium/content/shell/app/resource.h
new file mode 100644
index 00000000000..29bb74dfa78
--- /dev/null
+++ b/chromium/content/shell/app/resource.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by test_shell.rc
+//
+
+
+#define IDR_MAINFRAME 128
+#define IDM_EXIT 105
+#define IDM_CLOSE_WINDOW 106
+#define IDM_NEW_WINDOW 107
+#define IDM_SHOW_DEVELOPER_TOOLS 108
+#define IDC_CONTENTSHELL 109
+#define IDD_ALERT 130
+#define IDD_CONFIRM 131
+#define IDD_PROMPT 132
+#define IDC_NAV_BACK 1001
+#define IDC_NAV_FORWARD 1002
+#define IDC_NAV_RELOAD 1003
+#define IDC_NAV_STOP 1004
+#define IDC_PROMPTEDIT 1005
+#define IDC_DIALOGTEXT 1006
+
+#ifndef IDC_STATIC
+#define IDC_STATIC -1
+#endif
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+
+#define _APS_NO_MFC 130
+#define _APS_NEXT_RESOURCE_VALUE 129
+#define _APS_NEXT_COMMAND_VALUE 32771
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 117
+#endif
+#endif
diff --git a/chromium/content/shell/app/shell.rc b/chromium/content/shell/app/shell.rc
new file mode 100644
index 00000000000..2987c067570
--- /dev/null
+++ b/chromium/content/shell/app/shell.rc
@@ -0,0 +1,146 @@
+//Microsoft Visual C++ generated resource script.
+//
+#include "content/shell/app/resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#define APSTUDIO_HIDDEN_SYMBOLS
+#include "windows.h"
+#undef APSTUDIO_HIDDEN_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE 9, 1
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Menu
+//
+
+IDC_CONTENTSHELL MENU
+BEGIN
+ POPUP "&File"
+ BEGIN
+ MENUITEM "&New Window", IDM_NEW_WINDOW
+ MENUITEM "&Close Window", IDM_CLOSE_WINDOW
+ MENUITEM "E&xit", IDM_EXIT
+ END
+ POPUP "&Debug"
+ BEGIN
+ MENUITEM "&Show Developer Tools...", IDM_SHOW_DEVELOPER_TOOLS
+ END
+END
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "#include ""windows.h""\r\n"
+ "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_ALERT DIALOGEX 0, 0, 241, 76
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Alert"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,184,55,50,14
+ LTEXT "",IDC_DIALOGTEXT,16,17,210,30
+END
+
+IDD_CONFIRM DIALOGEX 0, 0, 241, 76
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Confirm"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ PUSHBUTTON "Cancel",IDCANCEL,184,55,50,14
+ DEFPUSHBUTTON "OK",IDOK,131,55,50,14
+ LTEXT "",IDC_DIALOGTEXT,16,17,210,30
+END
+
+IDD_PROMPT DIALOGEX 0, 0, 241, 76
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "Prompt"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,131,55,50,14
+ LTEXT "",IDC_DIALOGTEXT,16,17,210,18
+ PUSHBUTTON "Cancel",IDCANCEL,184,55,50,14
+ EDITTEXT IDC_PROMPTEDIT,15,33,210,14,ES_AUTOHSCROLL
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_ALERT, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 234
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 69
+ END
+
+ IDD_CONFIRM, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 234
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 69
+ END
+
+ IDD_PROMPT, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 234
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 69
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/chromium/content/shell/app/shell_content_main.cc b/chromium/content/shell/app/shell_content_main.cc
new file mode 100644
index 00000000000..fc0caafbea8
--- /dev/null
+++ b/chromium/content/shell/app/shell_content_main.cc
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/app/shell_content_main.h"
+
+#include "build/build_config.h"
+#include "content/public/app/content_main.h"
+#include "content/public/common/content_switches.h"
+#include "content/shell/app/shell_main_delegate.h"
+
+#if BUILDFLAG(IS_MAC)
+int ContentMain(int argc,
+ const char** argv) {
+ bool is_browsertest = false;
+ std::string browser_test_flag(std::string("--") + switches::kBrowserTest);
+ for (int i = 0; i < argc; ++i) {
+ if (browser_test_flag == argv[i]) {
+ is_browsertest = true;
+ break;
+ }
+ }
+ content::ShellMainDelegate delegate(is_browsertest);
+ content::ContentMainParams params(&delegate);
+ params.argc = argc;
+ params.argv = argv;
+ return content::ContentMain(std::move(params));
+}
+#endif // BUILDFLAG(IS_MAC)
diff --git a/chromium/content/shell/app/shell_content_main.h b/chromium/content/shell/app/shell_content_main.h
new file mode 100644
index 00000000000..58bcbab9a1a
--- /dev/null
+++ b/chromium/content/shell/app/shell_content_main.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_APP_SHELL_CONTENT_MAIN_H_
+#define CONTENT_SHELL_APP_SHELL_CONTENT_MAIN_H_
+
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_MAC)
+extern "C" {
+__attribute__((visibility("default")))
+int ContentMain(int argc,
+ const char** argv);
+} // extern "C"
+#endif // BUILDFLAG(IS_MAC)
+
+#endif // CONTENT_SHELL_APP_SHELL_CONTENT_MAIN_H_
diff --git a/chromium/content/shell/app/shell_crash_reporter_client.cc b/chromium/content/shell/app/shell_crash_reporter_client.cc
new file mode 100644
index 00000000000..4c76c176753
--- /dev/null
+++ b/chromium/content/shell/app/shell_crash_reporter_client.cc
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/app/shell_crash_reporter_client.h"
+
+#include <string>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "content/public/common/content_switches.h"
+#include "content/shell/common/shell_switches.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include "content/shell/android/shell_descriptors.h"
+#endif
+
+namespace content {
+
+ShellCrashReporterClient::ShellCrashReporterClient() {}
+ShellCrashReporterClient::~ShellCrashReporterClient() {}
+
+#if BUILDFLAG(IS_WIN)
+void ShellCrashReporterClient::GetProductNameAndVersion(
+ const std::wstring& exe_path,
+ std::wstring* product_name,
+ std::wstring* version,
+ std::wstring* special_build,
+ std::wstring* channel_name) {
+ *product_name = L"content_shell";
+ *version = base::ASCIIToWide(CONTENT_SHELL_VERSION);
+ *special_build = std::wstring();
+ *channel_name = std::wstring();
+}
+#endif
+
+#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
+void ShellCrashReporterClient::GetProductNameAndVersion(
+ const char** product_name,
+ const char** version) {
+ *product_name = "content_shell";
+ *version = CONTENT_SHELL_VERSION;
+}
+
+void ShellCrashReporterClient::GetProductNameAndVersion(
+ std::string* product_name,
+ std::string* version,
+ std::string* channel) {
+ *product_name = "content_shell";
+ *version = CONTENT_SHELL_VERSION;
+ *channel = "";
+}
+
+base::FilePath ShellCrashReporterClient::GetReporterLogFilename() {
+ return base::FilePath(FILE_PATH_LITERAL("uploads.log"));
+}
+#endif
+
+#if BUILDFLAG(IS_WIN)
+bool ShellCrashReporterClient::GetCrashDumpLocation(std::wstring* crash_dir) {
+#else
+bool ShellCrashReporterClient::GetCrashDumpLocation(base::FilePath* crash_dir) {
+#endif
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kCrashDumpsDir))
+ return false;
+ base::FilePath crash_directory =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kCrashDumpsDir);
+#if BUILDFLAG(IS_WIN)
+ *crash_dir = crash_directory.value();
+#else
+ *crash_dir = std::move(crash_directory);
+#endif
+ return true;
+}
+
+#if BUILDFLAG(IS_ANDROID)
+int ShellCrashReporterClient::GetAndroidMinidumpDescriptor() {
+ return kAndroidMinidumpDescriptor;
+}
+#endif
+
+bool ShellCrashReporterClient::EnableBreakpadForProcess(
+ const std::string& process_type) {
+ return process_type == switches::kRendererProcess ||
+ process_type == switches::kPpapiPluginProcess ||
+ process_type == switches::kZygoteProcess ||
+ process_type == switches::kGpuProcess;
+}
+
+} // namespace content
diff --git a/chromium/content/shell/app/shell_crash_reporter_client.h b/chromium/content/shell/app/shell_crash_reporter_client.h
new file mode 100644
index 00000000000..3eb37a61932
--- /dev/null
+++ b/chromium/content/shell/app/shell_crash_reporter_client.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_APP_SHELL_CRASH_REPORTER_CLIENT_H_
+#define CONTENT_SHELL_APP_SHELL_CRASH_REPORTER_CLIENT_H_
+
+#include "build/build_config.h"
+#include "components/crash/core/app/crash_reporter_client.h"
+
+namespace content {
+
+class ShellCrashReporterClient : public crash_reporter::CrashReporterClient {
+ public:
+ ShellCrashReporterClient();
+
+ ShellCrashReporterClient(const ShellCrashReporterClient&) = delete;
+ ShellCrashReporterClient& operator=(const ShellCrashReporterClient&) = delete;
+
+ ~ShellCrashReporterClient() override;
+
+#if BUILDFLAG(IS_WIN)
+ // Returns a textual description of the product type and version to include
+ // in the crash report.
+ void GetProductNameAndVersion(const std::wstring& exe_path,
+ std::wstring* product_name,
+ std::wstring* version,
+ std::wstring* special_build,
+ std::wstring* channel_name) override;
+#endif
+
+#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
+ // Returns a textual description of the product type and version to include
+ // in the crash report.
+ void GetProductNameAndVersion(const char** product_name,
+ const char** version) override;
+ void GetProductNameAndVersion(std::string* product_name,
+ std::string* version,
+ std::string* channel) override;
+ base::FilePath GetReporterLogFilename() override;
+#endif
+
+ // The location where minidump files should be written. Returns true if
+ // |crash_dir| was set.
+#if BUILDFLAG(IS_WIN)
+ bool GetCrashDumpLocation(std::wstring* crash_dir) override;
+#else
+ bool GetCrashDumpLocation(base::FilePath* crash_dir) override;
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+ // Returns the descriptor key of the android minidump global descriptor.
+ int GetAndroidMinidumpDescriptor() override;
+#endif
+
+ bool EnableBreakpadForProcess(const std::string& process_type) override;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_APP_SHELL_CRASH_REPORTER_CLIENT_H_
diff --git a/chromium/content/shell/app/shell_main.cc b/chromium/content/shell/app/shell_main.cc
new file mode 100644
index 00000000000..421a4857d2d
--- /dev/null
+++ b/chromium/content/shell/app/shell_main.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+#include "content/public/app/content_main.h"
+#include "content/shell/app/shell_main_delegate.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/win/win_util.h"
+#include "content/public/app/sandbox_helper_win.h"
+#include "sandbox/win/src/sandbox_types.h"
+#endif
+
+#if BUILDFLAG(IS_WIN)
+
+#if !defined(WIN_CONSOLE_APP)
+int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t*, int) {
+#else
+int main() {
+ HINSTANCE instance = GetModuleHandle(NULL);
+#endif
+ // Load and pin user32.dll to avoid having to load it once tests start while
+ // on the main thread loop where blocking calls are disallowed.
+ base::win::PinUser32();
+ sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};
+ content::InitializeSandboxInfo(&sandbox_info);
+ content::ShellMainDelegate delegate;
+
+ content::ContentMainParams params(&delegate);
+ params.instance = instance;
+ params.sandbox_info = &sandbox_info;
+ return content::ContentMain(std::move(params));
+}
+
+#else
+
+int main(int argc, const char** argv) {
+ content::ShellMainDelegate delegate;
+ content::ContentMainParams params(&delegate);
+ params.argc = argc;
+ params.argv = argv;
+ return content::ContentMain(std::move(params));
+}
+
+#endif // BUILDFLAG(IS_WIN)
diff --git a/chromium/content/shell/app/shell_main_delegate.cc b/chromium/content/shell/app/shell_main_delegate.cc
new file mode 100644
index 00000000000..86e85bdc4ea
--- /dev/null
+++ b/chromium/content/shell/app/shell_main_delegate.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/app/shell_main_delegate.h"
+
+#include <iostream>
+#include <tuple>
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/cpu.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/trace_event/trace_log.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
+#include "content/common/content_constants_internal.h"
+#include "content/public/browser/browser_main_runner.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/main_function_params.h"
+#include "content/public/common/url_constants.h"
+#include "content/shell/app/shell_crash_reporter_client.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+#include "content/shell/browser/shell_paths.h"
+#include "content/shell/common/shell_content_client.h"
+#include "content/shell/common/shell_switches.h"
+#include "content/shell/gpu/shell_content_gpu_client.h"
+#include "content/shell/renderer/shell_content_renderer_client.h"
+#include "content/shell/utility/shell_content_utility_client.h"
+#include "ipc/ipc_buildflags.h"
+#include "net/cookies/cookie_monster.h"
+#include "ui/base/resource/resource_bundle.h"
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+#define IPC_MESSAGE_MACROS_LOG_ENABLED
+#include "content/public/common/content_ipc_logging.h"
+#define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) \
+ content::RegisterIPCLogger(msg_id, logger)
+#endif
+
+#if !BUILDFLAG(IS_ANDROID)
+#include "content/web_test/browser/web_test_browser_main_runner.h" // nogncheck
+#include "content/web_test/browser/web_test_content_browser_client.h" // nogncheck
+#include "content/web_test/renderer/web_test_content_renderer_client.h" // nogncheck
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/apk_assets.h"
+#include "base/posix/global_descriptors.h"
+#include "content/public/browser/android/compositor.h"
+#include "content/shell/android/shell_descriptors.h"
+#endif
+
+#if !BUILDFLAG(IS_FUCHSIA)
+#include "components/crash/core/app/crashpad.h" // nogncheck
+#endif
+
+#if BUILDFLAG(IS_MAC)
+#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/shell_main_delegate_mac.h"
+#endif // BUILDFLAG(IS_MAC)
+
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+
+#include <initguid.h>
+#include "base/logging_win.h"
+#include "content/shell/common/v8_crashpad_support_win.h"
+#endif
+
+#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
+#include "v8/include/v8-wasm-trap-handler-posix.h"
+#endif
+
+namespace {
+
+#if !BUILDFLAG(IS_FUCHSIA)
+base::LazyInstance<content::ShellCrashReporterClient>::Leaky
+ g_shell_crash_client = LAZY_INSTANCE_INITIALIZER;
+#endif
+
+#if BUILDFLAG(IS_WIN)
+// If "Content Shell" doesn't show up in your list of trace providers in
+// Sawbuck, add these registry entries to your machine (NOTE the optional
+// Wow6432Node key for x64 machines):
+// 1. Find: HKLM\SOFTWARE\[Wow6432Node\]Google\Sawbuck\Providers
+// 2. Add a subkey with the name "{6A3E50A4-7E15-4099-8413-EC94D8C2A4B6}"
+// 3. Add these values:
+// "default_flags"=dword:00000001
+// "default_level"=dword:00000004
+// @="Content Shell"
+
+// {6A3E50A4-7E15-4099-8413-EC94D8C2A4B6}
+const GUID kContentShellProviderName = {
+ 0x6a3e50a4, 0x7e15, 0x4099,
+ { 0x84, 0x13, 0xec, 0x94, 0xd8, 0xc2, 0xa4, 0xb6 } };
+#endif
+
+void InitLogging(const base::CommandLine& command_line) {
+ base::FilePath log_filename =
+ command_line.GetSwitchValuePath(switches::kLogFile);
+ if (log_filename.empty()) {
+#if BUILDFLAG(IS_FUCHSIA)
+ base::PathService::Get(base::DIR_TEMP, &log_filename);
+#else
+ base::PathService::Get(base::DIR_EXE, &log_filename);
+#endif
+ log_filename = log_filename.AppendASCII("content_shell.log");
+ }
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file_path = log_filename.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+ logging::SetLogItems(true /* Process ID */, true /* Thread ID */,
+ true /* Timestamp */, false /* Tick count */);
+}
+
+} // namespace
+
+namespace content {
+
+ShellMainDelegate::ShellMainDelegate(bool is_content_browsertests)
+ : is_content_browsertests_(is_content_browsertests) {}
+
+ShellMainDelegate::~ShellMainDelegate() {
+}
+
+bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
+ int dummy;
+ if (!exit_code)
+ exit_code = &dummy;
+
+ base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch("run-layout-test")) {
+ std::cerr << std::string(79, '*') << "\n"
+ << "* The flag --run-layout-test is obsolete. Please use --"
+ << switches::kRunWebTests << " instead. *\n"
+ << std::string(79, '*') << "\n";
+ command_line.AppendSwitch(switches::kRunWebTests);
+ }
+
+#if BUILDFLAG(IS_ANDROID)
+ Compositor::Initialize();
+#endif
+
+#if BUILDFLAG(IS_WIN)
+ // Enable trace control and transport through event tracing for Windows.
+ logging::LogEventProvider::Initialize(kContentShellProviderName);
+
+ v8_crashpad_support::SetUp();
+#endif
+
+#if BUILDFLAG(IS_MAC)
+ // Needs to happen before InitializeResourceBundle().
+ OverrideFrameworkBundlePath();
+ OverrideOuterBundlePath();
+ OverrideChildProcessPath();
+ OverrideSourceRootPath();
+ EnsureCorrectResolutionSettings();
+ OverrideBundleID();
+#endif // BUILDFLAG(IS_MAC)
+
+ InitLogging(command_line);
+
+#if !BUILDFLAG(IS_ANDROID)
+ if (switches::IsRunWebTestsSwitchPresent()) {
+ const bool browser_process =
+ command_line.GetSwitchValueASCII(switches::kProcessType).empty();
+ if (browser_process) {
+ web_test_runner_ = std::make_unique<WebTestBrowserMainRunner>();
+ web_test_runner_->Initialize();
+ }
+ }
+#endif
+
+ RegisterShellPathProvider();
+
+ return false;
+}
+
+bool ShellMainDelegate::ShouldCreateFeatureList() {
+ return false;
+}
+
+void ShellMainDelegate::PreSandboxStartup() {
+#if defined(ARCH_CPU_ARM_FAMILY) && \
+ (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS))
+ // Create an instance of the CPU class to parse /proc/cpuinfo and cache
+ // cpu_brand info.
+ base::CPU cpu_info;
+#endif
+
+// Disable platform crash handling and initialize the crash reporter, if
+// requested.
+// TODO(crbug.com/1226159): Implement crash reporter integration for Fuchsia.
+#if !BUILDFLAG(IS_FUCHSIA)
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableCrashReporter)) {
+ std::string process_type =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kProcessType);
+ crash_reporter::SetCrashReporterClient(g_shell_crash_client.Pointer());
+ // Reporting for sub-processes will be initialized in ZygoteForked.
+ if (process_type != switches::kZygoteProcess) {
+ crash_reporter::InitializeCrashpad(process_type.empty(), process_type);
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ crash_reporter::SetFirstChanceExceptionHandler(
+ v8::TryHandleWebAssemblyTrapPosix);
+#endif
+ }
+ }
+#endif // !BUILDFLAG(IS_FUCHSIA)
+
+ crash_reporter::InitializeCrashKeys();
+
+ InitializeResourceBundle();
+}
+
+absl::variant<int, MainFunctionParams> ShellMainDelegate::RunProcess(
+ const std::string& process_type,
+ MainFunctionParams main_function_params) {
+ // For non-browser process, return and have the caller run the main loop.
+ if (!process_type.empty())
+ return std::move(main_function_params);
+
+ base::trace_event::TraceLog::GetInstance()->set_process_name("Browser");
+ base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex(
+ kTraceEventBrowserProcessSortIndex);
+
+#if !BUILDFLAG(IS_ANDROID)
+ if (switches::IsRunWebTestsSwitchPresent()) {
+ // Web tests implement their own BrowserMain() replacement.
+ web_test_runner_->RunBrowserMain(std::move(main_function_params));
+ web_test_runner_.reset();
+ // Returning 0 to indicate that we have replaced BrowserMain() and the
+ // caller should not call BrowserMain() itself. Web tests do not ever
+ // return an error.
+ return 0;
+ }
+
+ // On non-Android, we can return the |main_function_params| back and have the
+ // caller run BrowserMain() normally.
+ return std::move(main_function_params);
+#else
+ // On Android, we defer to the system message loop when the stack unwinds.
+ // So here we only create (and leak) a BrowserMainRunner. The shutdown
+ // of BrowserMainRunner doesn't happen in Chrome Android and doesn't work
+ // properly on Android at all.
+ std::unique_ptr<BrowserMainRunner> main_runner = BrowserMainRunner::Create();
+ // In browser tests, the |main_function_params| contains a |ui_task| which
+ // will execute the testing. The task will be executed synchronously inside
+ // Initialize() so we don't depend on the BrowserMainRunner being Run().
+ int initialize_exit_code =
+ main_runner->Initialize(std::move(main_function_params));
+ DCHECK_LT(initialize_exit_code, 0)
+ << "BrowserMainRunner::Initialize failed in ShellMainDelegate";
+ std::ignore = main_runner.release();
+ // Return 0 as BrowserMain() should not be called after this, bounce up to
+ // the system message loop for ContentShell, and we're already done thanks
+ // to the |ui_task| for browser tests.
+ return 0;
+#endif
+}
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+void ShellMainDelegate::ZygoteForked() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableCrashReporter)) {
+ std::string process_type =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kProcessType);
+ crash_reporter::InitializeCrashpad(false, process_type);
+ crash_reporter::SetFirstChanceExceptionHandler(
+ v8::TryHandleWebAssemblyTrapPosix);
+ }
+}
+#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+void ShellMainDelegate::InitializeResourceBundle() {
+#if BUILDFLAG(IS_ANDROID)
+ // On Android, the renderer runs with a different UID and can never access
+ // the file system. Use the file descriptor passed in at launch time.
+ auto* global_descriptors = base::GlobalDescriptors::GetInstance();
+ int pak_fd = global_descriptors->MaybeGet(kShellPakDescriptor);
+ base::MemoryMappedFile::Region pak_region;
+ if (pak_fd >= 0) {
+ pak_region = global_descriptors->GetRegion(kShellPakDescriptor);
+ } else {
+ pak_fd =
+ base::android::OpenApkAsset("assets/content_shell.pak", &pak_region);
+ // Loaded from disk for browsertests.
+ if (pak_fd < 0) {
+ base::FilePath pak_file;
+ bool r = base::PathService::Get(base::DIR_ANDROID_APP_DATA, &pak_file);
+ DCHECK(r);
+ pak_file = pak_file.Append(FILE_PATH_LITERAL("paks"));
+ pak_file = pak_file.Append(FILE_PATH_LITERAL("content_shell.pak"));
+ int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ pak_fd = base::File(pak_file, flags).TakePlatformFile();
+ pak_region = base::MemoryMappedFile::Region::kWholeFile;
+ }
+ global_descriptors->Set(kShellPakDescriptor, pak_fd, pak_region);
+ }
+ DCHECK_GE(pak_fd, 0);
+ // TODO(crbug.com/330930): A better way to prevent fdsan error from a double
+ // close is to refactor GlobalDescriptors.{Get,MaybeGet} to return
+ // "const base::File&" rather than fd itself.
+ base::File android_pak_file(pak_fd);
+ ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
+ android_pak_file.Duplicate(), pak_region);
+ ui::ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion(
+ std::move(android_pak_file), pak_region, ui::k100Percent);
+#elif BUILDFLAG(IS_MAC)
+ ui::ResourceBundle::InitSharedInstanceWithPakPath(GetResourcesPakFilePath());
+#else
+ base::FilePath pak_file;
+ bool r = base::PathService::Get(base::DIR_ASSETS, &pak_file);
+ DCHECK(r);
+ pak_file = pak_file.Append(FILE_PATH_LITERAL("content_shell.pak"));
+ ui::ResourceBundle::InitSharedInstanceWithPakPath(pak_file);
+#endif
+}
+
+void ShellMainDelegate::PreBrowserMain() {
+#if BUILDFLAG(IS_MAC)
+ RegisterShellCrApp();
+#endif
+}
+
+void ShellMainDelegate::PostEarlyInitialization(bool is_running_tests) {
+ // Apply field trial testing configuration.
+ browser_client_->CreateFeatureListAndFieldTrials();
+}
+
+ContentClient* ShellMainDelegate::CreateContentClient() {
+ content_client_ = std::make_unique<ShellContentClient>();
+ return content_client_.get();
+}
+
+ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
+#if !BUILDFLAG(IS_ANDROID)
+ if (switches::IsRunWebTestsSwitchPresent()) {
+ browser_client_ = std::make_unique<WebTestContentBrowserClient>();
+ return browser_client_.get();
+ }
+#endif
+ browser_client_ = std::make_unique<ShellContentBrowserClient>();
+ return browser_client_.get();
+}
+
+ContentGpuClient* ShellMainDelegate::CreateContentGpuClient() {
+ gpu_client_ = std::make_unique<ShellContentGpuClient>();
+ return gpu_client_.get();
+}
+
+ContentRendererClient* ShellMainDelegate::CreateContentRendererClient() {
+#if !BUILDFLAG(IS_ANDROID)
+ if (switches::IsRunWebTestsSwitchPresent()) {
+ renderer_client_ = std::make_unique<WebTestContentRendererClient>();
+ return renderer_client_.get();
+ }
+#endif
+ renderer_client_ = std::make_unique<ShellContentRendererClient>();
+ return renderer_client_.get();
+}
+
+ContentUtilityClient* ShellMainDelegate::CreateContentUtilityClient() {
+ utility_client_ =
+ std::make_unique<ShellContentUtilityClient>(is_content_browsertests_);
+ return utility_client_.get();
+}
+
+} // namespace content
diff --git a/chromium/content/shell/app/shell_main_delegate.h b/chromium/content/shell/app/shell_main_delegate.h
new file mode 100644
index 00000000000..f19ee28d6c8
--- /dev/null
+++ b/chromium/content/shell/app/shell_main_delegate.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_H_
+#define CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_H_
+
+#include <memory>
+
+#include "build/build_config.h"
+#include "content/public/app/content_main_delegate.h"
+
+namespace content {
+class ShellContentClient;
+class ShellContentBrowserClient;
+class ShellContentGpuClient;
+class ShellContentRendererClient;
+class ShellContentUtilityClient;
+
+#if !BUILDFLAG(IS_ANDROID)
+class WebTestBrowserMainRunner;
+#endif
+
+class ShellMainDelegate : public ContentMainDelegate {
+ public:
+ explicit ShellMainDelegate(bool is_content_browsertests = false);
+
+ ShellMainDelegate(const ShellMainDelegate&) = delete;
+ ShellMainDelegate& operator=(const ShellMainDelegate&) = delete;
+
+ ~ShellMainDelegate() override;
+
+ // ContentMainDelegate implementation:
+ bool BasicStartupComplete(int* exit_code) override;
+ bool ShouldCreateFeatureList() override;
+ void PreSandboxStartup() override;
+ absl::variant<int, MainFunctionParams> RunProcess(
+ const std::string& process_type,
+ MainFunctionParams main_function_params) override;
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ void ZygoteForked() override;
+#endif
+ void PreBrowserMain() override;
+ void PostEarlyInitialization(bool is_running_tests) override;
+ ContentClient* CreateContentClient() override;
+ ContentBrowserClient* CreateContentBrowserClient() override;
+ ContentGpuClient* CreateContentGpuClient() override;
+ ContentRendererClient* CreateContentRendererClient() override;
+ ContentUtilityClient* CreateContentUtilityClient() override;
+
+ static void InitializeResourceBundle();
+
+ protected:
+ // Only present when running content_browsertests, which run inside Content
+ // Shell.
+ //
+ // content_browsertests should not set the kRunWebTests command line flag, so
+ // |is_content_browsertests_| and |web_test_runner_| are mututally exclusive.
+ bool is_content_browsertests_;
+#if !BUILDFLAG(IS_ANDROID)
+ // Only present when running web tests, which run inside Content Shell.
+ //
+ // Web tests are not browser tests, so |is_content_browsertests_| and
+ // |web_test_runner_| are mututally exclusive.
+ std::unique_ptr<WebTestBrowserMainRunner> web_test_runner_;
+#endif
+
+ std::unique_ptr<ShellContentBrowserClient> browser_client_;
+ std::unique_ptr<ShellContentGpuClient> gpu_client_;
+ std::unique_ptr<ShellContentRendererClient> renderer_client_;
+ std::unique_ptr<ShellContentUtilityClient> utility_client_;
+ std::unique_ptr<ShellContentClient> content_client_;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_H_
diff --git a/chromium/content/shell/app/shell_main_delegate_mac.h b/chromium/content/shell/app/shell_main_delegate_mac.h
new file mode 100644
index 00000000000..fe9696855c1
--- /dev/null
+++ b/chromium/content/shell/app/shell_main_delegate_mac.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_MAC_H_
+#define CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_MAC_H_
+
+namespace content {
+
+// Set NSHighResolutionCapable to false when running layout tests, so we match
+// the expected pixel results on retina capable displays.
+void EnsureCorrectResolutionSettings();
+
+// Sets up base::mac::BaseBundleID.
+void OverrideBundleID();
+
+// Initializes NSApplication.
+void RegisterShellCrApp();
+
+} // namespace content
+
+#endif // CONTENT_SHELL_APP_SHELL_MAIN_DELEGATE_MAC_H_
diff --git a/chromium/content/shell/app/shell_main_delegate_mac.mm b/chromium/content/shell/app/shell_main_delegate_mac.mm
new file mode 100644
index 00000000000..c1c3917db0b
--- /dev/null
+++ b/chromium/content/shell/app/shell_main_delegate_mac.mm
@@ -0,0 +1,72 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/app/shell_main_delegate_mac.h"
+
+#include <unistd.h>
+
+#include "base/check.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/public/common/content_switches.h"
+#include "content/shell/app/paths_mac.h"
+#include "content/shell/browser/shell_application_mac.h"
+#include "content/shell/common/shell_switches.h"
+
+namespace content {
+
+void EnsureCorrectResolutionSettings() {
+ // Exit early if this isn't a browser process.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessType))
+ return;
+
+ NSString* const kHighResolutionCapable = @"NSHighResolutionCapable";
+ base::FilePath info_plist = GetInfoPlistPath();
+ base::scoped_nsobject<NSMutableDictionary> info_dict(
+ [[NSMutableDictionary alloc]
+ initWithContentsOfFile:base::mac::FilePathToNSString(info_plist)]);
+
+ bool running_web_tests = switches::IsRunWebTestsSwitchPresent();
+ bool not_high_resolution_capable =
+ [info_dict objectForKey:kHighResolutionCapable] &&
+ [[info_dict objectForKey:kHighResolutionCapable] isEqualToNumber:@(NO)];
+ if (running_web_tests == not_high_resolution_capable)
+ return;
+
+ // We need to update our Info.plist before we can continue.
+ [info_dict setObject:@(!running_web_tests) forKey:kHighResolutionCapable];
+ CHECK([info_dict writeToFile:base::mac::FilePathToNSString(info_plist)
+ atomically:YES]);
+
+ const base::CommandLine::StringVector& original_argv =
+ base::CommandLine::ForCurrentProcess()->argv();
+ char** argv = new char*[original_argv.size() + 1];
+ for (unsigned i = 0; i < original_argv.size(); ++i)
+ argv[i] = const_cast<char*>(original_argv.at(i).c_str());
+ argv[original_argv.size()] = NULL;
+
+ CHECK(execvp(argv[0], argv));
+}
+
+void OverrideBundleID() {
+ NSBundle* bundle = base::mac::OuterBundle();
+ base::mac::SetBaseBundleID(
+ base::SysNSStringToUTF8([bundle bundleIdentifier]).c_str());
+}
+
+void RegisterShellCrApp() {
+ // Force the NSApplication subclass to be used.
+ [ShellCrApplication sharedApplication];
+
+ // If there was an invocation to NSApp prior to this method, then the NSApp
+ // will not be a ShellCrApplication, but will instead be an NSApplication.
+ // This is undesirable and we must enforce that this doesn't happen.
+ CHECK([NSApp isKindOfClass:[ShellCrApplication class]]);
+}
+
+} // namespace content
diff --git a/chromium/content/shell/app/shell_main_mac.cc b/chromium/content/shell/app/shell_main_mac.cc
new file mode 100644
index 00000000000..0097ce687a5
--- /dev/null
+++ b/chromium/content/shell/app/shell_main_mac.cc
@@ -0,0 +1,102 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <libgen.h>
+#include <mach-o/dyld.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/allocator/early_zone_registration_mac.h"
+
+#if defined(HELPER_EXECUTABLE)
+#include "sandbox/mac/seatbelt_exec.h" // nogncheck
+#endif // defined(HELPER_EXECUTABLE)
+
+namespace {
+
+using ContentMainPtr = int (*)(int, char**);
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ partition_alloc::EarlyMallocZoneRegistration();
+
+ uint32_t exec_path_size = 0;
+ int rv = _NSGetExecutablePath(NULL, &exec_path_size);
+ if (rv != -1) {
+ fprintf(stderr, "_NSGetExecutablePath: get length failed\n");
+ abort();
+ }
+
+ std::unique_ptr<char[]> exec_path(new char[exec_path_size]);
+ rv = _NSGetExecutablePath(exec_path.get(), &exec_path_size);
+ if (rv != 0) {
+ fprintf(stderr, "_NSGetExecutablePath: get path failed\n");
+ abort();
+ }
+
+#if defined(HELPER_EXECUTABLE)
+ sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
+ sandbox::SeatbeltExecServer::CreateFromArguments(exec_path.get(), argc,
+ argv);
+ if (seatbelt.sandbox_required) {
+ if (!seatbelt.server) {
+ fprintf(stderr, "Failed to create seatbelt sandbox server.\n");
+ abort();
+ }
+ if (!seatbelt.server->InitializeSandbox()) {
+ fprintf(stderr, "Failed to initialize sandbox.\n");
+ abort();
+ }
+ }
+
+ // The Helper app is in the versioned framework directory, so just go up to
+ // the version folder to locate the dylib.
+ const char rel_path[] = "../../../../" SHELL_PRODUCT_NAME " Framework";
+#else
+ const char rel_path[] =
+ "../Frameworks/" SHELL_PRODUCT_NAME
+ " Framework.framework/" SHELL_PRODUCT_NAME " Framework";
+#endif // defined(HELPER_EXECUTABLE)
+
+ // Slice off the last part of the main executable path, and append the
+ // version framework information.
+ const char* parent_dir = dirname(exec_path.get());
+ if (!parent_dir) {
+ fprintf(stderr, "dirname %s: %s\n", exec_path.get(), strerror(errno));
+ abort();
+ }
+
+ const size_t parent_dir_len = strlen(parent_dir);
+ const size_t rel_path_len = strlen(rel_path);
+ // 2 accounts for a trailing NUL byte and the '/' in the middle of the paths.
+ const size_t framework_path_size = parent_dir_len + rel_path_len + 2;
+ std::unique_ptr<char[]> framework_path(new char[framework_path_size]);
+ snprintf(framework_path.get(), framework_path_size, "%s/%s", parent_dir,
+ rel_path);
+
+ void* library =
+ dlopen(framework_path.get(), RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
+ if (!library) {
+ fprintf(stderr, "dlopen %s: %s\n", framework_path.get(), dlerror());
+ abort();
+ }
+
+ const ContentMainPtr content_main =
+ reinterpret_cast<ContentMainPtr>(dlsym(library, "ContentMain"));
+ if (!content_main) {
+ fprintf(stderr, "dlsym ContentMain: %s\n", dlerror());
+ abort();
+ }
+ rv = content_main(argc, argv);
+
+ // exit, don't return from main, to avoid the apparent removal of main from
+ // stack backtraces under tail call optimization.
+ exit(rv);
+}
diff --git a/chromium/content/shell/fuchsia/DIR_METADATA b/chromium/content/shell/fuchsia/DIR_METADATA
new file mode 100644
index 00000000000..9fa1ad6c884
--- /dev/null
+++ b/chromium/content/shell/fuchsia/DIR_METADATA
@@ -0,0 +1,2 @@
+mixins: "//build/fuchsia/COMMON_METADATA"
+os: FUCHSIA
diff --git a/chromium/content/shell/fuchsia/OWNERS b/chromium/content/shell/fuchsia/OWNERS
new file mode 100644
index 00000000000..3a1056b296f
--- /dev/null
+++ b/chromium/content/shell/fuchsia/OWNERS
@@ -0,0 +1,4 @@
+file://build/fuchsia/OWNERS
+
+per-file *.cmx=set noparent
+per-file *.cmx=file://fuchsia/SECURITY_OWNERS
diff --git a/chromium/content/shell/fuchsia/content_shell.cmx b/chromium/content/shell/fuchsia/content_shell.cmx
new file mode 100644
index 00000000000..b1d2a6fd0c1
--- /dev/null
+++ b/chromium/content/shell/fuchsia/content_shell.cmx
@@ -0,0 +1,42 @@
+{
+ "program": {
+ "binary": "content_shell"
+ },
+ "sandbox": {
+ "features": [
+ "deprecated-ambient-replace-as-executable",
+ "isolated-persistent-storage",
+ "isolated-temp",
+ "root-ssl-certificates",
+ "vulkan"
+ ],
+ "services": [
+ "fuchsia.accessibility.semantics.SemanticsManager",
+ "fuchsia.buildinfo.Provider",
+ "fuchsia.device.NameProvider",
+ "fuchsia.fonts.Provider",
+ "fuchsia.input.virtualkeyboard.ControllerCreator",
+ "fuchsia.intl.PropertyProvider",
+ "fuchsia.logger.LogSink",
+ "fuchsia.media.Audio",
+ "fuchsia.media.AudioDeviceEnumerator",
+ "fuchsia.media.ProfileProvider",
+ "fuchsia.media.SessionAudioConsumerFactory",
+ "fuchsia.media.drm.Widevine",
+ "fuchsia.mediacodec.CodecFactory",
+ "fuchsia.memorypressure.Provider",
+ "fuchsia.net.interfaces.State",
+ "fuchsia.net.name.Lookup",
+ "fuchsia.posix.socket.Provider",
+ "fuchsia.process.Launcher",
+ "fuchsia.sys.Launcher",
+ "fuchsia.sysmem.Allocator",
+ "fuchsia.ui.composition.Allocator",
+ "fuchsia.ui.composition.Flatland",
+ "fuchsia.ui.input3.Keyboard",
+ "fuchsia.ui.policy.Presenter",
+ "fuchsia.ui.scenic.Scenic",
+ "fuchsia.vulkan.loader.Loader"
+ ]
+ }
+}
diff --git a/chromium/content/shell/gpu/DEPS b/chromium/content/shell/gpu/DEPS
new file mode 100644
index 00000000000..5543434847d
--- /dev/null
+++ b/chromium/content/shell/gpu/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+services/network/public/mojom",
+ "+services/service_manager",
+]
diff --git a/chromium/content/shell/gpu/shell_content_gpu_client.cc b/chromium/content/shell/gpu/shell_content_gpu_client.cc
new file mode 100644
index 00000000000..22faaccb1f8
--- /dev/null
+++ b/chromium/content/shell/gpu/shell_content_gpu_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/gpu/shell_content_gpu_client.h"
+
+#include "base/bind.h"
+#include "content/shell/common/power_monitor_test_impl.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+
+namespace content {
+
+ShellContentGpuClient::ShellContentGpuClient() = default;
+
+ShellContentGpuClient::~ShellContentGpuClient() = default;
+
+void ShellContentGpuClient::ExposeInterfacesToBrowser(
+ const gpu::GpuPreferences& gpu_preferences,
+ const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
+ mojo::BinderMap* binders) {
+ binders->Add<mojom::PowerMonitorTest>(
+ base::BindRepeating(&PowerMonitorTestImpl::MakeSelfOwnedReceiver),
+ base::ThreadTaskRunnerHandle::Get());
+}
+
+} // namespace content
diff --git a/chromium/content/shell/gpu/shell_content_gpu_client.h b/chromium/content/shell/gpu/shell_content_gpu_client.h
new file mode 100644
index 00000000000..691412670ff
--- /dev/null
+++ b/chromium/content/shell/gpu/shell_content_gpu_client.h
@@ -0,0 +1,31 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_GPU_SHELL_CONTENT_GPU_CLIENT_H_
+#define CONTENT_SHELL_GPU_SHELL_CONTENT_GPU_CLIENT_H_
+
+#include "content/public/gpu/content_gpu_client.h"
+#include "services/network/public/mojom/network_service_test.mojom-forward.h"
+
+namespace content {
+
+class ShellContentGpuClient : public ContentGpuClient {
+ public:
+ ShellContentGpuClient();
+
+ ShellContentGpuClient(const ShellContentGpuClient&) = delete;
+ ShellContentGpuClient& operator=(const ShellContentGpuClient&) = delete;
+
+ ~ShellContentGpuClient() override;
+
+ // ContentGpuClient:
+ void ExposeInterfacesToBrowser(
+ const gpu::GpuPreferences& gpu_preferences,
+ const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
+ mojo::BinderMap* binders) override;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_GPU_SHELL_CONTENT_GPU_CLIENT_H_
diff --git a/chromium/content/shell/renderer/DEPS b/chromium/content/shell/renderer/DEPS
new file mode 100644
index 00000000000..be12c35170d
--- /dev/null
+++ b/chromium/content/shell/renderer/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+components/cdm",
+ "+components/web_cache/renderer",
+]
diff --git a/chromium/content/shell/renderer/shell_content_renderer_client.cc b/chromium/content/shell/renderer/shell_content_renderer_client.cc
new file mode 100644
index 00000000000..0194e1cad69
--- /dev/null
+++ b/chromium/content/shell/renderer/shell_content_renderer_client.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/renderer/shell_content_renderer_client.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/command_line.h"
+#include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/cdm/renderer/external_clear_key_key_system_properties.h"
+#include "components/web_cache/renderer/web_cache_impl.h"
+#include "content/public/test/test_service.mojom.h"
+#include "content/shell/common/power_monitor_test_impl.h"
+#include "content/shell/common/shell_switches.h"
+#include "content/shell/renderer/shell_render_frame_observer.h"
+#include "mojo/public/cpp/bindings/binder_map.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "net/base/net_errors.h"
+#include "ppapi/buildflags/buildflags.h"
+#include "sandbox/policy/sandbox.h"
+#include "third_party/blink/public/platform/web_url_error.h"
+#include "third_party/blink/public/web/web_testing_support.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "v8/include/v8.h"
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "ppapi/shared_impl/ppapi_switches.h" // nogncheck
+#endif
+
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+#include "base/feature_list.h"
+#include "media/base/media_switches.h"
+#endif
+
+namespace content {
+
+namespace {
+
+// A test service which can be driven by browser tests for various reasons.
+class TestRendererServiceImpl : public mojom::TestService {
+ public:
+ explicit TestRendererServiceImpl(
+ mojo::PendingReceiver<mojom::TestService> receiver)
+ : receiver_(this, std::move(receiver)) {
+ receiver_.set_disconnect_handler(base::BindOnce(
+ &TestRendererServiceImpl::OnConnectionError, base::Unretained(this)));
+ }
+
+ TestRendererServiceImpl(const TestRendererServiceImpl&) = delete;
+ TestRendererServiceImpl& operator=(const TestRendererServiceImpl&) = delete;
+
+ ~TestRendererServiceImpl() override {}
+
+ private:
+ void OnConnectionError() { delete this; }
+
+ // mojom::TestService:
+ void DoSomething(DoSomethingCallback callback) override {
+ // Instead of responding normally, unbind the pipe, write some garbage,
+ // and go away.
+ const std::string kBadMessage = "This is definitely not a valid response!";
+ mojo::ScopedMessagePipeHandle pipe = receiver_.Unbind().PassPipe();
+ MojoResult rv = mojo::WriteMessageRaw(
+ pipe.get(), kBadMessage.data(), kBadMessage.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ DCHECK_EQ(rv, MOJO_RESULT_OK);
+
+ // Deletes this.
+ OnConnectionError();
+ }
+
+ void DoTerminateProcess(DoTerminateProcessCallback callback) override {
+ NOTREACHED();
+ }
+
+ void DoCrashImmediately(DoCrashImmediatelyCallback callback) override {
+ NOTREACHED();
+ }
+
+ void CreateFolder(CreateFolderCallback callback) override { NOTREACHED(); }
+
+ void GetRequestorName(GetRequestorNameCallback callback) override {
+ std::move(callback).Run("Not implemented.");
+ }
+
+ void CreateReadOnlySharedMemoryRegion(
+ const std::string& message,
+ CreateReadOnlySharedMemoryRegionCallback callback) override {
+ NOTREACHED();
+ }
+
+ void CreateWritableSharedMemoryRegion(
+ const std::string& message,
+ CreateWritableSharedMemoryRegionCallback callback) override {
+ NOTREACHED();
+ }
+
+ void CreateUnsafeSharedMemoryRegion(
+ const std::string& message,
+ CreateUnsafeSharedMemoryRegionCallback callback) override {
+ NOTREACHED();
+ }
+
+ void IsProcessSandboxed(IsProcessSandboxedCallback callback) override {
+ std::move(callback).Run(sandbox::policy::Sandbox::IsProcessSandboxed());
+ }
+
+ mojo::Receiver<mojom::TestService> receiver_;
+};
+
+void CreateRendererTestService(
+ mojo::PendingReceiver<mojom::TestService> receiver) {
+ // Owns itself.
+ new TestRendererServiceImpl(std::move(receiver));
+}
+
+} // namespace
+
+ShellContentRendererClient::ShellContentRendererClient() {}
+
+ShellContentRendererClient::~ShellContentRendererClient() {
+}
+
+void ShellContentRendererClient::RenderThreadStarted() {
+ web_cache_impl_ = std::make_unique<web_cache::WebCacheImpl>();
+}
+
+void ShellContentRendererClient::ExposeInterfacesToBrowser(
+ mojo::BinderMap* binders) {
+ binders->Add(base::BindRepeating(&CreateRendererTestService),
+ base::ThreadTaskRunnerHandle::Get());
+ binders->Add(
+ base::BindRepeating(&PowerMonitorTestImpl::MakeSelfOwnedReceiver),
+ base::ThreadTaskRunnerHandle::Get());
+ binders->Add(base::BindRepeating(&web_cache::WebCacheImpl::BindReceiver,
+ base::Unretained(web_cache_impl_.get())),
+ base::ThreadTaskRunnerHandle::Get());
+}
+
+void ShellContentRendererClient::RenderFrameCreated(RenderFrame* render_frame) {
+ // TODO(danakj): The ShellRenderFrameObserver is doing stuff only for
+ // browser tests. If we only create that for browser tests then the override
+ // of this method in WebTestContentRendererClient would not be needed.
+ new ShellRenderFrameObserver(render_frame);
+}
+
+void ShellContentRendererClient::PrepareErrorPage(
+ RenderFrame* render_frame,
+ const blink::WebURLError& error,
+ const std::string& http_method,
+ content::mojom::AlternativeErrorPageOverrideInfoPtr
+ alternative_error_page_info,
+ std::string* error_html) {
+ if (error_html && error_html->empty()) {
+ *error_html =
+ "<head><title>Error</title></head><body>Could not load the requested "
+ "resource.<br/>Error code: " +
+ base::NumberToString(error.reason()) +
+ (error.reason() < 0 ? " (" + net::ErrorToString(error.reason()) + ")"
+ : "") +
+ "</body>";
+ }
+}
+
+void ShellContentRendererClient::PrepareErrorPageForHttpStatusError(
+ content::RenderFrame* render_frame,
+ const blink::WebURLError& error,
+ const std::string& http_method,
+ int http_status,
+ content::mojom::AlternativeErrorPageOverrideInfoPtr
+ alternative_error_page_info,
+ std::string* error_html) {
+ if (error_html) {
+ *error_html =
+ "<head><title>Error</title></head><body>Server returned HTTP status " +
+ base::NumberToString(http_status) + "</body>";
+ }
+}
+
+void ShellContentRendererClient::DidInitializeWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> context) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kExposeInternalsForTesting)) {
+ blink::WebTestingSupport::InjectInternalsObject(context);
+ }
+}
+
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+void ShellContentRendererClient::GetSupportedKeySystems(
+ media::GetSupportedKeySystemsCB cb) {
+ media::KeySystemPropertiesVector key_systems;
+ if (base::FeatureList::IsEnabled(media::kExternalClearKeyForTesting))
+ key_systems.push_back(std::make_unique<cdm::ExternalClearKeyProperties>());
+ std::move(cb).Run(std::move(key_systems));
+}
+#endif
+
+} // namespace content
diff --git a/chromium/content/shell/renderer/shell_content_renderer_client.h b/chromium/content/shell/renderer/shell_content_renderer_client.h
new file mode 100644
index 00000000000..617af079d6c
--- /dev/null
+++ b/chromium/content/shell/renderer/shell_content_renderer_client.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
+#define CONTENT_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include "build/build_config.h"
+#include "content/public/common/alternative_error_page_override_info.mojom-forward.h"
+#include "content/public/renderer/content_renderer_client.h"
+#include "media/mojo/buildflags.h"
+
+namespace web_cache {
+class WebCacheImpl;
+}
+
+namespace content {
+
+class ShellContentRendererClient : public ContentRendererClient {
+ public:
+ ShellContentRendererClient();
+ ~ShellContentRendererClient() override;
+
+ // ContentRendererClient implementation.
+ void RenderThreadStarted() override;
+ void ExposeInterfacesToBrowser(mojo::BinderMap* binders) override;
+ void RenderFrameCreated(RenderFrame* render_frame) override;
+ void PrepareErrorPage(RenderFrame* render_frame,
+ const blink::WebURLError& error,
+ const std::string& http_method,
+ content::mojom::AlternativeErrorPageOverrideInfoPtr
+ alternative_error_page_info,
+ std::string* error_html) override;
+ void PrepareErrorPageForHttpStatusError(
+ content::RenderFrame* render_frame,
+ const blink::WebURLError& error,
+ const std::string& http_method,
+ int http_status,
+ content::mojom::AlternativeErrorPageOverrideInfoPtr
+ alternative_error_page_info,
+ std::string* error_html) override;
+
+ void DidInitializeWorkerContextOnWorkerThread(
+ v8::Local<v8::Context> context) override;
+
+#if BUILDFLAG(ENABLE_MOJO_CDM)
+ void GetSupportedKeySystems(media::GetSupportedKeySystemsCB cb) override;
+#endif
+
+ private:
+ std::unique_ptr<web_cache::WebCacheImpl> web_cache_impl_;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_RENDERER_SHELL_CONTENT_RENDERER_CLIENT_H_
diff --git a/chromium/content/shell/renderer/shell_render_frame_observer.cc b/chromium/content/shell/renderer/shell_render_frame_observer.cc
new file mode 100644
index 00000000000..ed6134a67e3
--- /dev/null
+++ b/chromium/content/shell/renderer/shell_render_frame_observer.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/renderer/shell_render_frame_observer.h"
+
+#include "base/command_line.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/shell/common/shell_switches.h"
+#include "third_party/blink/public/web/web_testing_support.h"
+
+namespace content {
+
+ShellRenderFrameObserver::ShellRenderFrameObserver(RenderFrame* render_frame)
+ : RenderFrameObserver(render_frame) {}
+
+ShellRenderFrameObserver::~ShellRenderFrameObserver() = default;
+
+void ShellRenderFrameObserver::DidClearWindowObject() {
+ auto& cmd = *base::CommandLine::ForCurrentProcess();
+ if (cmd.HasSwitch(switches::kExposeInternalsForTesting)) {
+ blink::WebTestingSupport::InjectInternalsObject(
+ render_frame()->GetWebFrame());
+ }
+}
+
+void ShellRenderFrameObserver::OnDestruct() {
+ delete this;
+}
+
+} // namespace content
diff --git a/chromium/content/shell/renderer/shell_render_frame_observer.h b/chromium/content/shell/renderer/shell_render_frame_observer.h
new file mode 100644
index 00000000000..2f40722cf4b
--- /dev/null
+++ b/chromium/content/shell/renderer/shell_render_frame_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_RENDERER_SHELL_RENDER_FRAME_OBSERVER_H_
+#define CONTENT_SHELL_RENDERER_SHELL_RENDER_FRAME_OBSERVER_H_
+
+#include "content/public/renderer/render_frame_observer.h"
+
+namespace content {
+
+class ShellRenderFrameObserver : public RenderFrameObserver {
+ public:
+ explicit ShellRenderFrameObserver(RenderFrame* frame);
+ ~ShellRenderFrameObserver() override;
+
+ ShellRenderFrameObserver(const ShellRenderFrameObserver&) = delete;
+ ShellRenderFrameObserver& operator=(const ShellRenderFrameObserver&) = delete;
+
+ private:
+ // RenderFrameObserver implementation.
+ void DidClearWindowObject() override;
+ void OnDestruct() override;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_RENDERER_SHELL_RENDER_FRAME_OBSERVER_H_
diff --git a/chromium/content/shell/resources/README.txt b/chromium/content/shell/resources/README.txt
new file mode 100644
index 00000000000..4a4e10bce33
--- /dev/null
+++ b/chromium/content/shell/resources/README.txt
@@ -0,0 +1,26 @@
+missingImage.gif was created from Webkit data: WebCore/Resources/missingImage.tiff
+
+Licence text for missingImage.tiff from which missingImage.gif was generated:
+
+Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+missingImage.png is the same as WebCore/Resources/missingImage.png and carries exactly the same license.
diff --git a/chromium/content/shell/resources/brokenCanvas.png b/chromium/content/shell/resources/brokenCanvas.png
new file mode 100644
index 00000000000..e9185bf0966
--- /dev/null
+++ b/chromium/content/shell/resources/brokenCanvas.png
Binary files differ
diff --git a/chromium/content/shell/resources/shell_devtools_discovery_page.html b/chromium/content/shell/resources/shell_devtools_discovery_page.html
new file mode 100644
index 00000000000..9aa4c300941
--- /dev/null
+++ b/chromium/content/shell/resources/shell_devtools_discovery_page.html
@@ -0,0 +1,54 @@
+<html>
+<head>
+<title>Content shell remote debugging</title>
+<style>
+</style>
+
+<script>
+function onLoad() {
+ var tabs_list_request = new XMLHttpRequest();
+ tabs_list_request.open("GET", "/json/list?t=" + new Date().getTime(), true);
+ tabs_list_request.onreadystatechange = onReady;
+ tabs_list_request.send();
+}
+
+function onReady() {
+ if(this.readyState == 4 && this.status == 200) {
+ if(this.response != null)
+ var responseJSON = JSON.parse(this.response);
+ for (var i = 0; i < responseJSON.length; ++i)
+ appendItem(responseJSON[i]);
+ }
+}
+
+function appendItem(item_object) {
+ var frontend_ref;
+ if (item_object.devtoolsFrontendUrl) {
+ frontend_ref = document.createElement("a");
+ frontend_ref.href = item_object.devtoolsFrontendUrl;
+ frontend_ref.title = item_object.title;
+ } else {
+ frontend_ref = document.createElement("div");
+ frontend_ref.title = "The tab already has active debugging session";
+ }
+
+ var text = document.createElement("div");
+ if (item_object.title)
+ text.innerText = item_object.title;
+ else
+ text.innerText = "(untitled tab)";
+ text.style.cssText = "background-image:url(" + item_object.faviconUrl + ")";
+ frontend_ref.appendChild(text);
+
+ var item = document.createElement("p");
+ item.appendChild(frontend_ref);
+
+ document.getElementById("items").appendChild(item);
+}
+</script>
+</head>
+<body onload='onLoad()'>
+ <div id='caption'>Inspectable WebContents</div>
+ <div id='items'></div>
+</body>
+</html>
diff --git a/chromium/content/shell/tools/DEPS b/chromium/content/shell/tools/DEPS
new file mode 100644
index 00000000000..387561ade8e
--- /dev/null
+++ b/chromium/content/shell/tools/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content/public",
+ "+components/crash",
+]
diff --git a/chromium/content/shell/tools/breakpad_integration_test.py b/chromium/content/shell/tools/breakpad_integration_test.py
new file mode 100755
index 00000000000..45fd246a723
--- /dev/null
+++ b/chromium/content/shell/tools/breakpad_integration_test.py
@@ -0,0 +1,331 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Integration test for breakpad in content shell.
+
+This test checks that content shell and breakpad are correctly hooked up, as
+well as that the tools can symbolize a stack trace."""
+
+
+from __future__ import print_function
+from __future__ import absolute_import
+import glob
+import json
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+from six.moves import range
+
+TOP_SRC_DIR = os.path.join(os.path.dirname(__file__), '..', '..', '..')
+
+try:
+ sys.path.append(os.path.join(TOP_SRC_DIR, 'build', 'android'))
+ import devil_chromium
+ devil_chromium.Initialize()
+
+ from pylib.constants import host_paths
+ if host_paths.DEVIL_PATH not in sys.path:
+ sys.path.append(host_paths.DEVIL_PATH)
+
+ from devil.android import apk_helper
+ from devil.android import device_utils
+ from devil.android import flag_changer
+ from devil.android.sdk import intent
+except:
+ pass
+
+
+CONCURRENT_TASKS=4
+BREAKPAD_TOOLS_DIR = os.path.join(
+ TOP_SRC_DIR, 'components', 'crash', 'content', 'tools')
+ANDROID_CRASH_DIR = '/data/data/org.chromium.content_shell_apk/cache'
+
+
+def GetDevice():
+ if hasattr(GetDevice, 'device'):
+ return GetDevice.device
+
+ devices = device_utils.DeviceUtils.HealthyDevices()
+ assert len(devices) == 1
+ GetDevice.device = devices[0]
+ return GetDevice.device
+
+
+def clear_android_dumps(options, device):
+ try:
+ print('# Deleting stale crash dumps')
+ pending = os.path.join(ANDROID_CRASH_DIR, 'pending')
+ files = device.RunShellCommand(['ls', pending], as_root=True)
+ for f in files:
+ if f.endswith('.dmp'):
+ dump = os.path.join(pending, f)
+ try:
+ if options.verbose:
+ print(' deleting %s' % dump)
+ device.RunShellCommand(['rm', dump], check_return=True, as_root=True)
+ except:
+ print('Failed to delete %s' % dump)
+
+ except:
+ print('Failed to list dumps in android crash dir %s' % pending)
+
+
+def get_android_dump(options, crash_dir, symbols_dir):
+ global failure
+
+ pending = os.path.join(ANDROID_CRASH_DIR, 'pending')
+ device = GetDevice()
+
+ for attempts in range(5):
+ files = device.RunShellCommand(['ls', pending], as_root=True)
+
+ dumps = [f for f in files if f.endswith('.dmp')]
+ if len(dumps) > 0:
+ break
+ # Crashpad may still be writing the dump. Sleep and try again.
+ time.sleep(5)
+
+ if len(dumps) != 1:
+ # TODO(crbug.com/861730): Temporary code to debug unexpected crash dumps.
+ minidump_stackwalk = os.path.join(options.build_dir, 'minidump_stackwalk')
+ failure = 'Failed to run minidump_stackwalk.'
+ for dump in dumps:
+ device.PullFile(os.path.join(pending, dump), crash_dir, as_root=True)
+ minidump = os.path.join(crash_dir, os.path.basename(dump))
+ cmd = [minidump_stackwalk, minidump, symbols_dir]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stack = proc.communicate()[0].decode('utf-8')
+ print(stack)
+
+ device.RunShellCommand(['rm', os.path.join(pending, dump)],
+ check_return=True, as_root=True)
+
+ failure = 'Expected 1 crash dump, found %d.' % len(dumps)
+ print(dumps)
+ raise Exception(failure)
+
+ device.PullFile(os.path.join(pending, dumps[0]), crash_dir, as_root=True)
+ device.RunShellCommand(['rm', os.path.join(pending, dumps[0])],
+ check_return=True, as_root=True)
+
+ return os.path.join(crash_dir, os.path.basename(dumps[0]))
+
+def run_test(options, crash_dir, symbols_dir, platform,
+ additional_arguments = []):
+ global failure
+
+ print('# Run content_shell and make it crash.')
+ if platform == 'android':
+ device = GetDevice()
+
+ failure = None
+ clear_android_dumps(options, device)
+
+ apk_path = os.path.join(options.build_dir, 'apks', 'ContentShell.apk')
+ apk = apk_helper.ApkHelper(apk_path)
+ view_activity = apk.GetViewActivityName()
+ package_name = apk.GetPackageName()
+
+ device.RunShellCommand(['am', 'set-debug-app', '--persistent',
+ package_name])
+
+ changer = flag_changer.FlagChanger(device, 'content-shell-command-line')
+ changer.ReplaceFlags(['--enable-crash-reporter',
+ '--crash-dumps-dir=%s' % ANDROID_CRASH_DIR])
+
+ launch_intent = intent.Intent(action='android.intent.action.VIEW',
+ activity=view_activity, data='chrome://crash',
+ package=package_name)
+ device.StartActivity(launch_intent)
+ else:
+ cmd = [options.binary,
+ '--run-web-tests',
+ 'chrome://crash',
+ '--enable-crash-reporter',
+ '--crash-dumps-dir=%s' % crash_dir]
+ cmd += additional_arguments
+
+ if options.verbose:
+ print(' '.join(cmd))
+ failure = 'Failed to run content_shell.'
+ if options.verbose:
+ subprocess.check_call(cmd)
+ else:
+ # On Windows, using os.devnull can cause check_call to never return,
+ # so use a temporary file for the output.
+ with tempfile.TemporaryFile() as tmpfile:
+ subprocess.check_call(cmd, stdout=tmpfile, stderr=tmpfile)
+
+ print('# Retrieve crash dump.')
+ if platform == 'android':
+ minidump = get_android_dump(options, crash_dir, symbols_dir)
+ else:
+ dmp_dir = crash_dir
+ # TODO(crbug.com/782923): This test should not reach directly into the
+ # Crashpad database, but instead should use crashpad_database_util.
+ if platform == 'darwin' or platform.startswith('linux'):
+ dmp_dir = os.path.join(dmp_dir, 'pending')
+ elif platform == 'win32':
+ dmp_dir = os.path.join(dmp_dir, 'reports')
+
+ dmp_files = glob.glob(os.path.join(dmp_dir, '*.dmp'))
+ failure = 'Expected 1 crash dump, found %d.' % len(dmp_files)
+ if len(dmp_files) != 1:
+ raise Exception(failure)
+ minidump = dmp_files[0]
+
+ print('# Symbolize crash dump.')
+ if platform == 'win32':
+ cdb_exe = os.path.join(options.build_dir, 'cdb', 'cdb.exe')
+ cmd = [cdb_exe, '-y', options.build_dir, '-c', '.lines;.excr;k30;q',
+ '-z', minidump]
+ if options.verbose:
+ print(' '.join(cmd))
+ failure = 'Failed to run cdb.exe.'
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stack = proc.communicate()[0].decode('utf-8')
+ else:
+ minidump_stackwalk = os.path.join(options.build_dir, 'minidump_stackwalk')
+ cmd = [minidump_stackwalk, minidump, symbols_dir]
+ if options.verbose:
+ print(' '.join(cmd))
+ failure = 'Failed to run minidump_stackwalk.'
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stack = proc.communicate()[0].decode('utf-8')
+
+ # Check whether the stack contains a CrashIntentionally symbol.
+ found_symbol = 'CrashIntentionally' in stack
+
+ os.remove(minidump)
+
+ if options.no_symbols:
+ if found_symbol:
+ if options.verbose:
+ print(stack)
+ failure = 'Found unexpected reference to CrashIntentionally in stack'
+ raise Exception(failure)
+ else:
+ if not found_symbol:
+ if options.verbose:
+ print(stack)
+ failure = 'Could not find reference to CrashIntentionally in stack.'
+ raise Exception(failure)
+
+def main():
+ global failure
+
+ parser = optparse.OptionParser()
+ parser.add_option('', '--build-dir', default='',
+ help='The build output directory.')
+ parser.add_option('', '--binary', default='',
+ help='The path of the binary to generate symbols for and '
+ 'then run for the test.')
+ parser.add_option('', '--additional-binary', default='',
+ help='An additional binary for which to generate symbols. '
+ 'On Mac this is used for specifying the '
+ '"Content Shell Framework" library, which is not '
+ 'linked into --binary.')
+ parser.add_option('', '--no-symbols', default=False, action='store_true',
+ help='Symbols are not expected to work.')
+ parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
+ type='int', help='Number of parallel tasks to run.')
+ parser.add_option('-v', '--verbose', action='store_true',
+ help='Print verbose status output.')
+ parser.add_option('', '--json', default='',
+ help='Path to JSON output.')
+ parser.add_option('', '--platform', default=sys.platform,
+ help='Platform to run the test on.')
+
+ (options, _) = parser.parse_args()
+
+ if not options.build_dir:
+ print('Required option --build-dir missing.')
+ return 1
+
+ if not options.binary:
+ print('Required option --binary missing.')
+ return 1
+
+ if not os.access(options.binary, os.X_OK):
+ print('Cannot find %s.' % options.binary)
+ return 1
+
+ failure = ''
+
+ # Create a temporary directory to store the crash dumps and symbols in.
+ crash_dir = tempfile.mkdtemp()
+ symbols_dir = os.path.join(crash_dir, 'symbols')
+
+ crash_service = None
+
+ try:
+ if options.platform == 'android':
+ device = GetDevice()
+
+ print('# Install ContentShell.apk')
+ apk_path = os.path.join(options.build_dir, 'apks', 'ContentShell.apk')
+ device.Install(apk_path, reinstall=False, allow_downgrade=True)
+
+ if options.platform != 'win32':
+ print('# Generate symbols.')
+ bins = [options.binary]
+ if options.additional_binary:
+ bins.append(options.additional_binary)
+ generate_symbols = os.path.join(
+ BREAKPAD_TOOLS_DIR, 'generate_breakpad_symbols.py')
+ for binary in bins:
+ cmd = [generate_symbols,
+ '--build-dir=%s' % options.build_dir,
+ '--binary=%s' % binary,
+ '--symbols-dir=%s' % symbols_dir,
+ '--jobs=%d' % options.jobs,
+ '--platform=%s' % options.platform]
+ if options.verbose:
+ cmd.append('--verbose')
+ print(' '.join(cmd))
+ failure = 'Failed to run generate_breakpad_symbols.py.'
+ subprocess.check_call(cmd)
+
+ run_test(options, crash_dir, symbols_dir, options.platform)
+
+ except:
+ if failure == '':
+ failure = '%s: %s' % sys.exc_info()[:2]
+ print('FAIL: %s' % failure)
+ if options.json:
+ with open(options.json, 'w') as json_file:
+ json.dump([failure], json_file)
+
+ return 1
+
+ else:
+ print('PASS: Breakpad integration test ran successfully.')
+ if options.json:
+ with open(options.json, 'w') as json_file:
+ json.dump([], json_file)
+ return 0
+
+ finally:
+ if crash_service:
+ crash_service.terminate()
+ crash_service.wait()
+ try:
+ shutil.rmtree(crash_dir)
+ except:
+ print('Failed to delete temp directory "%s".' % crash_dir)
+ if options.platform == 'android':
+ clear_android_dumps(options, GetDevice())
+
+
+if '__main__' == __name__:
+ sys.exit(main())
diff --git a/chromium/content/shell/utility/DEPS b/chromium/content/shell/utility/DEPS
new file mode 100644
index 00000000000..ad55dbd28a3
--- /dev/null
+++ b/chromium/content/shell/utility/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/services/storage/test_api",
+ "+services/service_manager",
+ "+services/test/echo",
+]
diff --git a/chromium/content/shell/utility/shell_content_utility_client.cc b/chromium/content/shell/utility/shell_content_utility_client.cc
new file mode 100644
index 00000000000..9773ac66021
--- /dev/null
+++ b/chromium/content/shell/utility/shell_content_utility_client.cc
@@ -0,0 +1,166 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/shell/utility/shell_content_utility_client.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/span.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/process/process.h"
+#include "build/build_config.h"
+#include "components/services/storage/test_api/test_api.h"
+#include "content/public/child/child_thread.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_service.mojom.h"
+#include "content/public/utility/utility_thread.h"
+#include "content/shell/common/power_monitor_test_impl.h"
+#include "mojo/public/cpp/bindings/binder_map.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "mojo/public/cpp/bindings/service_factory.h"
+#include "mojo/public/cpp/system/buffer.h"
+#include "sandbox/policy/sandbox.h"
+#include "services/test/echo/echo_service.h"
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "content/test/sandbox_status_service.h"
+#endif
+
+namespace content {
+
+namespace {
+
+class TestUtilityServiceImpl : public mojom::TestService {
+ public:
+ static void Create(mojo::PendingReceiver<mojom::TestService> receiver) {
+ mojo::MakeSelfOwnedReceiver(base::WrapUnique(new TestUtilityServiceImpl),
+ std::move(receiver));
+ }
+
+ TestUtilityServiceImpl(const TestUtilityServiceImpl&) = delete;
+ TestUtilityServiceImpl& operator=(const TestUtilityServiceImpl&) = delete;
+
+ // mojom::TestService implementation:
+ void DoSomething(DoSomethingCallback callback) override {
+ std::move(callback).Run();
+ }
+
+ void DoTerminateProcess(DoTerminateProcessCallback callback) override {
+ base::Process::TerminateCurrentProcessImmediately(0);
+ }
+
+ void DoCrashImmediately(DoCrashImmediatelyCallback callback) override {
+ IMMEDIATE_CRASH();
+ }
+
+ void CreateFolder(CreateFolderCallback callback) override {
+ // Note: This is used to check if the sandbox is disabled or not since
+ // creating a folder is forbidden when it is enabled.
+ std::move(callback).Run(base::ScopedTempDir().CreateUniqueTempDir());
+ }
+
+ void GetRequestorName(GetRequestorNameCallback callback) override {
+ NOTREACHED();
+ }
+
+ void CreateReadOnlySharedMemoryRegion(
+ const std::string& message,
+ CreateReadOnlySharedMemoryRegionCallback callback) override {
+ base::MappedReadOnlyRegion map_and_region =
+ base::ReadOnlySharedMemoryRegion::Create(message.size());
+ CHECK(map_and_region.IsValid());
+ std::copy(message.begin(), message.end(),
+ map_and_region.mapping.GetMemoryAsSpan<char>().begin());
+ std::move(callback).Run(std::move(map_and_region.region));
+ }
+
+ void CreateWritableSharedMemoryRegion(
+ const std::string& message,
+ CreateWritableSharedMemoryRegionCallback callback) override {
+ auto region = base::WritableSharedMemoryRegion::Create(message.size());
+ CHECK(region.IsValid());
+ base::WritableSharedMemoryMapping mapping = region.Map();
+ CHECK(mapping.IsValid());
+ std::copy(message.begin(), message.end(),
+ mapping.GetMemoryAsSpan<char>().begin());
+ std::move(callback).Run(std::move(region));
+ }
+
+ void CreateUnsafeSharedMemoryRegion(
+ const std::string& message,
+ CreateUnsafeSharedMemoryRegionCallback callback) override {
+ auto region = base::UnsafeSharedMemoryRegion::Create(message.size());
+ CHECK(region.IsValid());
+ base::WritableSharedMemoryMapping mapping = region.Map();
+ CHECK(mapping.IsValid());
+ std::copy(message.begin(), message.end(),
+ mapping.GetMemoryAsSpan<char>().begin());
+ std::move(callback).Run(std::move(region));
+ }
+
+ void IsProcessSandboxed(IsProcessSandboxedCallback callback) override {
+ std::move(callback).Run(sandbox::policy::Sandbox::IsProcessSandboxed());
+ }
+
+ private:
+ TestUtilityServiceImpl() = default;
+};
+
+auto RunEchoService(mojo::PendingReceiver<echo::mojom::EchoService> receiver) {
+ return std::make_unique<echo::EchoService>(std::move(receiver));
+}
+
+} // namespace
+
+ShellContentUtilityClient::ShellContentUtilityClient(bool is_browsertest) {
+ if (is_browsertest &&
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kProcessType) == switches::kUtilityProcess) {
+ network_service_test_helper_ = std::make_unique<NetworkServiceTestHelper>();
+ audio_service_test_helper_ = std::make_unique<AudioServiceTestHelper>();
+ storage::InjectTestApiImplementation();
+ register_sandbox_status_helper_ = true;
+ }
+}
+
+ShellContentUtilityClient::~ShellContentUtilityClient() = default;
+
+void ShellContentUtilityClient::ExposeInterfacesToBrowser(
+ mojo::BinderMap* binders) {
+ binders->Add(base::BindRepeating(&TestUtilityServiceImpl::Create),
+ base::ThreadTaskRunnerHandle::Get());
+ binders->Add<mojom::PowerMonitorTest>(
+ base::BindRepeating(&PowerMonitorTestImpl::MakeSelfOwnedReceiver),
+ base::ThreadTaskRunnerHandle::Get());
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ if (register_sandbox_status_helper_) {
+ binders->Add<content::mojom::SandboxStatusService>(
+ base::BindRepeating(
+ &content::SandboxStatusService::MakeSelfOwnedReceiver),
+ base::ThreadTaskRunnerHandle::Get());
+ }
+#endif
+}
+
+void ShellContentUtilityClient::RegisterIOThreadServices(
+ mojo::ServiceFactory& services) {
+ services.Add(RunEchoService);
+}
+
+void ShellContentUtilityClient::RegisterNetworkBinders(
+ service_manager::BinderRegistry* registry) {
+ if (network_service_test_helper_)
+ network_service_test_helper_->RegisterNetworkBinders(registry);
+}
+
+} // namespace content
diff --git a/chromium/content/shell/utility/shell_content_utility_client.h b/chromium/content/shell/utility/shell_content_utility_client.h
new file mode 100644
index 00000000000..fc51dfd16ff
--- /dev/null
+++ b/chromium/content/shell/utility/shell_content_utility_client.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
+#define CONTENT_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
+
+#include "content/public/test/audio_service_test_helper.h"
+#include "content/public/test/network_service_test_helper.h"
+#include "content/public/utility/content_utility_client.h"
+
+namespace content {
+
+class ShellContentUtilityClient : public ContentUtilityClient {
+ public:
+ explicit ShellContentUtilityClient(bool is_browsertest = false);
+
+ ShellContentUtilityClient(const ShellContentUtilityClient&) = delete;
+ ShellContentUtilityClient& operator=(const ShellContentUtilityClient&) =
+ delete;
+
+ ~ShellContentUtilityClient() override;
+
+ // ContentUtilityClient:
+ void ExposeInterfacesToBrowser(mojo::BinderMap* binders) override;
+ void RegisterIOThreadServices(mojo::ServiceFactory& services) override;
+ void RegisterNetworkBinders(
+ service_manager::BinderRegistry* registry) override;
+
+ private:
+ std::unique_ptr<NetworkServiceTestHelper> network_service_test_helper_;
+ std::unique_ptr<AudioServiceTestHelper> audio_service_test_helper_;
+ bool register_sandbox_status_helper_ = false;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_UTILITY_SHELL_CONTENT_UTILITY_CLIENT_H_
diff --git a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc
index cfd96a78408..8719f0f732b 100644
--- a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc
+++ b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc
@@ -708,7 +708,8 @@ TEST_F(BluetoothAdapterTest, StartDiscoverySessionError_Destroy) {
}
// TODO(scheib): Enable BluetoothTest fixture tests on all platforms.
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
+// Flaky on Mac. See crbug.com/1334462
+#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ConstructDefaultAdapter ConstructDefaultAdapter
#else
#define MAYBE_ConstructDefaultAdapter DISABLED_ConstructDefaultAdapter
diff --git a/chromium/device/bluetooth/strings/bluetooth_strings_kn.xtb b/chromium/device/bluetooth/strings/bluetooth_strings_kn.xtb
index 40ea97a97e5..64e3c07fef8 100644
--- a/chromium/device/bluetooth/strings/bluetooth_strings_kn.xtb
+++ b/chromium/device/bluetooth/strings/bluetooth_strings_kn.xtb
@@ -12,7 +12,7 @@
<translation id="3689300613247337921"><ph name="DEVICE_NAME" />, ಕಂಪà³à²¯à³‚ಟರà³</translation>
<translation id="4287283380557401002">ಕಾರೠಆಡಿಯೊ (<ph name="ADDRESS" />)</translation>
<translation id="430326050669417502">ಜಾಯà³â€Œà²¸à³à²Ÿà²¿à²•à³ (<ph name="ADDRESS" />)</translation>
-<translation id="4698630591226285015"><ph name="DEVICE_NAME" />, ಆಡಿಯೊ ಸಾಧನ</translation>
+<translation id="4698630591226285015"><ph name="DEVICE_NAME" />, ಆಡಿಯೋ ಸಾಧನ</translation>
<translation id="4986357476502426173"><ph name="DEVICE_NAME" />, ವೀಡಿಯೊ ಸಾಧನ</translation>
<translation id="5271696982761495740">ಟà³à²¯à²¾à²¬à³à²²à³†à²Ÿà³ (<ph name="ADDRESS" />)</translation>
<translation id="5376363957846771741">ಅಪರಿಚಿತ ಅಥವಾ ಬೆಂಬಲಿತವಲà³à²²à²¦ ಸಾಧನ (<ph name="ADDRESS" />)</translation>
@@ -20,7 +20,7 @@
<translation id="5716052956047449618"><ph name="DEVICE_NAME" />, ಮೋಡೆಮà³</translation>
<translation id="6459740836740815150"><ph name="DEVICE_NAME" />, ಕಾರೠಆಡಿಯೋ ಸಾಧನ</translation>
<translation id="6542922424766292144"><ph name="DEVICE_NAME" />, ಕೀಬೋರà³à²¡à³</translation>
-<translation id="654594702871184195">ಆಡಿಯೊ (<ph name="ADDRESS" />)</translation>
+<translation id="654594702871184195">ಆಡಿಯೋ (<ph name="ADDRESS" />)</translation>
<translation id="6744468237221042970">ಕಂಪà³à²¯à³‚ಟರೠ(<ph name="ADDRESS" />)</translation>
<translation id="7220023250700248346"><ph name="DEVICE_NAME" />, ಜಾಯà³â€Œà²¸à³à²Ÿà²¿à²•à³</translation>
<translation id="7501330106833014351">ಗೇಮà³â€Œà²ªà³à²¯à²¾à²¡à³ (<ph name="ADDRESS" />)</translation>
diff --git a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.cc b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.cc
index 16ee1138c2c..3a004bb8438 100644
--- a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.cc
+++ b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.cc
@@ -219,7 +219,8 @@ void WebEngineAudioRenderer::OnBuffersAcquired(
SendInputPacket(std::move(packet));
}
- if (is_at_end_of_stream_) {
+ if (has_delayed_end_of_stream_) {
+ has_delayed_end_of_stream_ = false;
OnSysmemBufferStreamEndOfStream();
}
}
@@ -781,9 +782,11 @@ void WebEngineAudioRenderer::OnSysmemBufferStreamEndOfStream() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(is_at_end_of_stream_);
- // Stream sink is not bound yet, don't send EOS.
- if (!stream_sink_)
+ // Stream sink is not bound yet, queue EOS request until then.
+ if (!stream_sink_) {
+ has_delayed_end_of_stream_ = true;
return;
+ }
stream_sink_->EndOfStream();
diff --git a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.h b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.h
index a0adbc3b6c0..6da7d87bded 100644
--- a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.h
+++ b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer.h
@@ -199,10 +199,11 @@ class WEB_ENGINE_EXPORT WebEngineAudioRenderer final
// VmoBuffers for the buffers |input_buffer_collection_|.
std::vector<media::VmoBuffer> input_buffers_;
- // Packets produced before the |stream_sink_| is connected. They are sent as
- // soon as input buffers are acquired and |stream_sink_| is connected in
- // OnBuffersAcquired().
+ // Packets and EndOfStream produced before the |stream_sink_| is connected.
+ // They are sent as soon as input buffers are acquired and |stream_sink_| is
+ // connected in OnBuffersAcquired().
std::list<media::StreamProcessorHelper::IoPacket> delayed_packets_;
+ bool has_delayed_end_of_stream_ = false;
// Lead time range requested by the |audio_consumer_|. Initialized to the
// [100ms, 500ms] until the initial AudioConsumerStatus is received.
diff --git a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer_test.cc b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer_test.cc
index 5998865ae84..95577542d12 100644
--- a/chromium/fuchsia/engine/renderer/web_engine_audio_renderer_test.cc
+++ b/chromium/fuchsia/engine/renderer/web_engine_audio_renderer_test.cc
@@ -138,13 +138,17 @@ class TestStreamSink : public fuchsia::media::testing::StreamSink_TestBase {
// fuchsia::media::StreamSink overrides.
void SendPacket(fuchsia::media::StreamPacket packet,
SendPacketCallback callback) override {
- EXPECT_FALSE(received_end_of_stream_);
+ EXPECT_EQ(received_end_of_stream_, 0);
received_packets_.push_back(std::move(packet));
callback();
}
- void EndOfStream() override { received_end_of_stream_ = true; }
+ void EndOfStream() override {
+ EXPECT_FALSE(received_end_of_stream_);
+ received_end_of_stream_ = true;
+ }
void DiscardAllPackets(DiscardAllPacketsCallback callback) override {
DiscardAllPacketsNoReply();
+ received_end_of_stream_ = false;
callback();
}
void DiscardAllPacketsNoReply() override {
@@ -1143,4 +1147,5 @@ TEST_P(WebEngineAudioRendererTest, PlaybackBeforeSinkCreation) {
audio_consumer_->WaitStarted();
stream_sink_ = audio_consumer_->TakeStreamSink();
EXPECT_GT(stream_sink_->received_packets()->size(), 0U);
+ EXPECT_FALSE(stream_sink_->received_end_of_stream());
}
diff --git a/chromium/gpu/config/gpu_lists_version.h b/chromium/gpu/config/gpu_lists_version.h
index e58a7f6fa95..e99019069a3 100644
--- a/chromium/gpu/config/gpu_lists_version.h
+++ b/chromium/gpu/config/gpu_lists_version.h
@@ -3,6 +3,6 @@
#ifndef GPU_CONFIG_GPU_LISTS_VERSION_H_
#define GPU_CONFIG_GPU_LISTS_VERSION_H_
-#define GPU_LISTS_VERSION "76de02bc398043843909a8da1c2bc69ad3d2e5bf"
+#define GPU_LISTS_VERSION "d40c1c345c6c905254498a9622b8cd89297dd0f2"
#endif // GPU_CONFIG_GPU_LISTS_VERSION_H_
diff --git a/chromium/infra/config/generated/builders/ci/Android Release (Nexus 5X)/properties.json b/chromium/infra/config/generated/builders/ci/Android Release (Nexus 5X)/properties.json
index 6e13a4595da..eee79ff9995 100644
--- a/chromium/infra/config/generated/builders/ci/Android Release (Nexus 5X)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Android Release (Nexus 5X)/properties.json
@@ -1,4 +1,57 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-gpu-archive",
+ "builder_group": "chromium.gpu",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android-marshmallow-arm64-rel",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 250,
diff --git a/chromium/infra/config/generated/builders/ci/Android x86 Builder (dbg)/properties.json b/chromium/infra/config/generated/builders/ci/Android x86 Builder (dbg)/properties.json
index d68921bae92..d854fe10f91 100644
--- a/chromium/infra/config/generated/builders/ci/Android x86 Builder (dbg)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Android x86 Builder (dbg)/properties.json
@@ -1,4 +1,53 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Android x86 Builder (dbg)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "x86_builder_mb"
+ },
+ "legacy_chromium_config": {
+ "build_config": "Debug",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Android x86 Builder (dbg)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android_compile_x86_dbg",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/Cast Android (dbg)/properties.json b/chromium/infra/config/generated/builders/ci/Cast Android (dbg)/properties.json
index d68921bae92..4e97d585f35 100644
--- a/chromium/infra/config/generated/builders/ci/Cast Android (dbg)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Cast Android (dbg)/properties.json
@@ -1,4 +1,56 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Android (dbg)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "cast_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Android (dbg)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "cast_shell_android",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/Cast Linux Debug/properties.json b/chromium/infra/config/generated/builders/ci/Cast Linux Debug/properties.json
index 07adf83b2de..9702adcdec5 100644
--- a/chromium/infra/config/generated/builders/ci/Cast Linux Debug/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Cast Linux Debug/properties.json
@@ -1,4 +1,51 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Linux Debug",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "chromium_clang",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Linux Debug",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "cast_shell_linux_dbg",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/Cast Linux/properties.json b/chromium/infra/config/generated/builders/ci/Cast Linux/properties.json
index 07adf83b2de..5b7a1071a66 100644
--- a/chromium/infra/config/generated/builders/ci/Cast Linux/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Cast Linux/properties.json
@@ -1,4 +1,51 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Linux",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_clang",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Linux",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "cast_shell_linux",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Builder/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Builder/properties.json
index c38210fcc73..50e5862f9cd 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Builder/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Builder/properties.json
@@ -1,4 +1,125 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-linux-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 250,
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (Intel HD 630)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (Intel HD 630)/properties.json
index 3cfd0e407b7..83a0d1ca3e1 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (Intel HD 630)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (Intel HD 630)/properties.json
@@ -1,4 +1,83 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-linux-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (NVIDIA)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (NVIDIA)/properties.json
index 3cfd0e407b7..3381dfa3759 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (NVIDIA)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Linux x64 DEPS Release (NVIDIA)/properties.json
@@ -1,4 +1,83 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-linux-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Builder/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Builder/properties.json
index f932667ef90..2228bc4e2c0 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Builder/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Builder/properties.json
@@ -1,4 +1,122 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-mac-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$build/goma": {
"rpc_extra_params": "?prod",
"server_host": "goma.chromium.org",
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (AMD)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (AMD)/properties.json
index 3cfd0e407b7..c7b418aedd4 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (AMD)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (AMD)/properties.json
@@ -1,4 +1,80 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-mac-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (Intel)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (Intel)/properties.json
index 3cfd0e407b7..cc94454526d 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (Intel)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Mac x64 DEPS Release (Intel)/properties.json
@@ -1,4 +1,80 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-mac-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Builder/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Builder/properties.json
index e7fdad73634..e597b9e2478 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Builder/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Builder/properties.json
@@ -1,4 +1,122 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-win10-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 80,
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (Intel HD 630)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (Intel HD 630)/properties.json
index 3cfd0e407b7..8b28510781f 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (Intel HD 630)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (Intel HD 630)/properties.json
@@ -1,4 +1,80 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-win10-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (NVIDIA)/properties.json b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (NVIDIA)/properties.json
index 3cfd0e407b7..019adcbba5f 100644
--- a/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (NVIDIA)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Dawn Win10 x64 DEPS Release (NVIDIA)/properties.json
@@ -1,4 +1,80 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "dawn-win10-x64-deps-rel",
+ "group": "tryserver.chromium.dawn"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/Linux Builder (Wayland)/properties.json b/chromium/infra/config/generated/builders/ci/Linux Builder (Wayland)/properties.json
index 267c9a5897d..c121343ab1f 100644
--- a/chromium/infra/config/generated/builders/ci/Linux Builder (Wayland)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Linux Builder (Wayland)/properties.json
@@ -1,4 +1,91 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "linux-wayland-rel",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 250,
diff --git a/chromium/infra/config/generated/builders/ci/Linux TSan Builder/properties.json b/chromium/infra/config/generated/builders/ci/Linux TSan Builder/properties.json
index 85b348223fd..5575333dc62 100644
--- a/chromium/infra/config/generated/builders/ci/Linux TSan Builder/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Linux TSan Builder/properties.json
@@ -1,4 +1,89 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "linux_chromium_tsan_rel_ng",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/Linux TSan Tests/properties.json b/chromium/infra/config/generated/builders/ci/Linux TSan Tests/properties.json
index ac72d7f4ea2..8e6d9928fe7 100644
--- a/chromium/infra/config/generated/builders/ci/Linux TSan Tests/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Linux TSan Tests/properties.json
@@ -1,4 +1,82 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "linux_chromium_tsan_rel_ng",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 80,
diff --git a/chromium/infra/config/generated/builders/ci/Linux Tests (Wayland)/properties.json b/chromium/infra/config/generated/builders/ci/Linux Tests (Wayland)/properties.json
index 34d9bef603d..ab41bde364d 100644
--- a/chromium/infra/config/generated/builders/ci/Linux Tests (Wayland)/properties.json
+++ b/chromium/infra/config/generated/builders/ci/Linux Tests (Wayland)/properties.json
@@ -1,4 +1,84 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "linux-wayland-rel",
+ "group": "tryserver.chromium.linux"
+ }
+ ]
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json b/chromium/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
index d68921bae92..4c1bee9eeb1 100644
--- a/chromium/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
+++ b/chromium/infra/config/generated/builders/ci/android-cronet-arm-rel/properties.json
@@ -1,4 +1,57 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-cronet-arm-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "cronet_builder",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-cronet-arm-rel",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android_cronet",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/android-marshmallow-arm64-rel/properties.json b/chromium/infra/config/generated/builders/ci/android-marshmallow-arm64-rel/properties.json
index d68921bae92..8c26c3dec3b 100644
--- a/chromium/infra/config/generated/builders/ci/android-marshmallow-arm64-rel/properties.json
+++ b/chromium/infra/config/generated/builders/ci/android-marshmallow-arm64-rel/properties.json
@@ -1,4 +1,57 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android-marshmallow-arm64-rel",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/android-marshmallow-x86-rel/properties.json b/chromium/infra/config/generated/builders/ci/android-marshmallow-x86-rel/properties.json
index d68921bae92..fba62ed9840 100644
--- a/chromium/infra/config/generated/builders/ci/android-marshmallow-x86-rel/properties.json
+++ b/chromium/infra/config/generated/builders/ci/android-marshmallow-x86-rel/properties.json
@@ -1,4 +1,57 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "x86_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_wpr_tests",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android-marshmallow-x86-rel",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/android-pie-arm64-rel/properties.json b/chromium/infra/config/generated/builders/ci/android-pie-arm64-rel/properties.json
index d68921bae92..19f2dfef8df 100644
--- a/chromium/infra/config/generated/builders/ci/android-pie-arm64-rel/properties.json
+++ b/chromium/infra/config/generated/builders/ci/android-pie-arm64-rel/properties.json
@@ -1,4 +1,56 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "android-pie-arm64-rel",
+ "group": "tryserver.chromium.android"
+ }
+ ]
+ }
+ },
"$build/reclient": {
"instance": "rbe-chromium-trusted",
"jobs": 500,
diff --git a/chromium/infra/config/generated/builders/ci/lacros-amd64-generic-rel/properties.json b/chromium/infra/config/generated/builders/ci/lacros-amd64-generic-rel/properties.json
index 977efc1fb9a..1a2665c001e 100644
--- a/chromium/infra/config/generated/builders/ci/lacros-amd64-generic-rel/properties.json
+++ b/chromium/infra/config/generated/builders/ci/lacros-amd64-generic-rel/properties.json
@@ -1,4 +1,60 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "lacros-amd64-generic-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-chromiumos-archive",
+ "builder_group": "chromium.chromiumos",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "cros_boards_with_qemu_images": [
+ "amd64-generic"
+ ],
+ "target_arch": "intel",
+ "target_bits": 64,
+ "target_cros_boards": [
+ "eve"
+ ],
+ "target_platform": "chromeos"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "checkout_lacros_sdk",
+ "chromeos"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "lacros-amd64-generic-rel",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "lacros-amd64-generic-rel",
+ "group": "tryserver.chromium.chromiumos"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/ci/linux-chromeos-dbg/properties.json b/chromium/infra/config/generated/builders/ci/linux-chromeos-dbg/properties.json
index 977efc1fb9a..c9b1f3f8166 100644
--- a/chromium/infra/config/generated/builders/ci/linux-chromeos-dbg/properties.json
+++ b/chromium/infra/config/generated/builders/ci/linux-chromeos-dbg/properties.json
@@ -1,4 +1,56 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-chromiumos-archive",
+ "builder_group": "chromium.chromiumos",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "chromium",
+ "target_arch": "intel",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "chromeos"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ }
+ ],
+ "mirroring_builder_group_and_names": [
+ {
+ "builder": "linux-chromeos-compile-dbg",
+ "group": "tryserver.chromium.chromiumos"
+ },
+ {
+ "builder": "linux-chromeos-dbg",
+ "group": "tryserver.chromium.chromiumos"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel-compilator/properties.json b/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel-compilator/properties.json
index 08e81d1973d..ac7d659ff48 100644
--- a/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel-compilator/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel-compilator/properties.json
@@ -1,4 +1,88 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-gpu-archive",
+ "builder_group": "chromium.gpu",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/code_coverage": {
"coverage_test_types": [
"unit",
diff --git a/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel/properties.json b/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel/properties.json
index 1bce44f7432..46e62be432b 100644
--- a/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-marshmallow-arm64-rel/properties.json
@@ -3,6 +3,90 @@
"compilator": "android-marshmallow-arm64-rel-compilator",
"compilator_watcher_git_revision": "7809a690bbd935bcb3b4d922e24cabe168aaabc8"
},
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-gpu-archive",
+ "builder_group": "chromium.gpu",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "download_vr_test_apks",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Android Release (Nexus 5X)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-arm64-rel",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/code_coverage": {
"coverage_test_types": [
"unit",
diff --git a/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel-compilator/properties.json b/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel-compilator/properties.json
index 02aa1bf7c74..f13c7fc40e6 100644
--- a/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel-compilator/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel-compilator/properties.json
@@ -1,4 +1,54 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "x86_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_wpr_tests",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/code_coverage": {
"coverage_test_types": [
"unit",
diff --git a/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel/properties.json b/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel/properties.json
index fbb5b4efd14..16ad27f329d 100644
--- a/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-marshmallow-x86-rel/properties.json
@@ -3,6 +3,56 @@
"compilator": "android-marshmallow-x86-rel-compilator",
"compilator_watcher_git_revision": "7809a690bbd935bcb3b4d922e24cabe168aaabc8"
},
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "x86_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_wpr_tests",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-marshmallow-x86-rel",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/code_coverage": {
"coverage_test_types": [
"unit",
diff --git a/chromium/infra/config/generated/builders/try/android-pie-arm64-rel-compilator/properties.json b/chromium/infra/config/generated/builders/try/android-pie-arm64-rel-compilator/properties.json
index 4dd4b378fd8..357fabada24 100644
--- a/chromium/infra/config/generated/builders/try/android-pie-arm64-rel-compilator/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-pie-arm64-rel-compilator/properties.json
@@ -1,4 +1,53 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/flakiness": {
"check_for_flakiness": true
},
diff --git a/chromium/infra/config/generated/builders/try/android-pie-arm64-rel/properties.json b/chromium/infra/config/generated/builders/try/android-pie-arm64-rel/properties.json
index d21918da8f0..3ae606b81f1 100644
--- a/chromium/infra/config/generated/builders/try/android-pie-arm64-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/android-pie-arm64-rel/properties.json
@@ -3,6 +3,55 @@
"compilator": "android-pie-arm64-rel-compilator",
"compilator_watcher_git_revision": "7809a690bbd935bcb3b4d922e24cabe168aaabc8"
},
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 64,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-pie-arm64-rel",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/flakiness": {
"check_for_flakiness": true
},
diff --git a/chromium/infra/config/generated/builders/try/android_compile_x86_dbg/properties.json b/chromium/infra/config/generated/builders/try/android_compile_x86_dbg/properties.json
index d90599ce737..65aebb38abe 100644
--- a/chromium/infra/config/generated/builders/try/android_compile_x86_dbg/properties.json
+++ b/chromium/infra/config/generated/builders/try/android_compile_x86_dbg/properties.json
@@ -1,4 +1,48 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Android x86 Builder (dbg)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "x86_builder_mb"
+ },
+ "legacy_chromium_config": {
+ "build_config": "Debug",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Android x86 Builder (dbg)",
+ "project": "chromium"
+ }
+ ],
+ "is_compile_only": true
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/android_cronet/properties.json b/chromium/infra/config/generated/builders/try/android_cronet/properties.json
index d90599ce737..160fc425c26 100644
--- a/chromium/infra/config/generated/builders/try/android_cronet/properties.json
+++ b/chromium/infra/config/generated/builders/try/android_cronet/properties.json
@@ -1,4 +1,52 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "android-cronet-arm-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "main_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "cronet_builder",
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "android-cronet-arm-rel",
+ "project": "chromium"
+ }
+ ],
+ "is_compile_only": true
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/cast_shell_android/properties.json b/chromium/infra/config/generated/builders/try/cast_shell_android/properties.json
index d90599ce737..89a424c07d9 100644
--- a/chromium/infra/config/generated/builders/try/cast_shell_android/properties.json
+++ b/chromium/infra/config/generated/builders/try/cast_shell_android/properties.json
@@ -1,4 +1,50 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Android (dbg)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-android-archive",
+ "builder_group": "chromium.android",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_android_config": {
+ "config": "cast_builder"
+ },
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "android",
+ "target_bits": 32,
+ "target_platform": "android"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "android",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Android (dbg)",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/cast_shell_linux/properties.json b/chromium/infra/config/generated/builders/try/cast_shell_linux/properties.json
index 19e0479dcaf..c720bc02e23 100644
--- a/chromium/infra/config/generated/builders/try/cast_shell_linux/properties.json
+++ b/chromium/infra/config/generated/builders/try/cast_shell_linux/properties.json
@@ -1,4 +1,45 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Linux",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_clang",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Linux",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/cast_shell_linux_dbg/properties.json b/chromium/infra/config/generated/builders/try/cast_shell_linux_dbg/properties.json
index 19e0479dcaf..37d151089b9 100644
--- a/chromium/infra/config/generated/builders/try/cast_shell_linux_dbg/properties.json
+++ b/chromium/infra/config/generated/builders/try/cast_shell_linux_dbg/properties.json
@@ -1,4 +1,45 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Cast Linux Debug",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "chromium_clang",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Cast Linux Debug",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/dawn-linux-x64-deps-rel/properties.json b/chromium/infra/config/generated/builders/try/dawn-linux-x64-deps-rel/properties.json
index 3161e0a096c..0c2800ef9ed 100644
--- a/chromium/infra/config/generated/builders/try/dawn-linux-x64-deps-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/dawn-linux-x64-deps-rel/properties.json
@@ -1,4 +1,119 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "linux"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Linux x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/dawn-mac-x64-deps-rel/properties.json b/chromium/infra/config/generated/builders/try/dawn-mac-x64-deps-rel/properties.json
index ed3bf497c87..5e15584e84e 100644
--- a/chromium/infra/config/generated/builders/try/dawn-mac-x64-deps-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/dawn-mac-x64-deps-rel/properties.json
@@ -1,4 +1,116 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "mac"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (AMD)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Mac x64 DEPS Release (Intel)",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"rpc_extra_params": "?prod",
"server_host": "goma.chromium.org",
diff --git a/chromium/infra/config/generated/builders/try/dawn-win10-x64-deps-rel/properties.json b/chromium/infra/config/generated/builders/try/dawn-win10-x64-deps-rel/properties.json
index 72d586d6bd5..1f7df00c958 100644
--- a/chromium/infra/config/generated/builders/try/dawn-win10-x64-deps-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/dawn-win10-x64-deps-rel/properties.json
@@ -1,4 +1,116 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-dawn-archive",
+ "builder_group": "chromium.dawn",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64,
+ "target_platform": "win"
+ },
+ "legacy_gclient_config": {
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ },
+ "run_tests_serially": true
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "project": "chromium"
+ },
+ {
+ "bucket": "ci",
+ "builder": "Dawn Win10 x64 DEPS Release (NVIDIA)",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": false,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json b/chromium/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
index 4aada5e2c21..587c0d51c37 100644
--- a/chromium/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
@@ -1,4 +1,54 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "lacros-amd64-generic-rel",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-chromiumos-archive",
+ "builder_group": "chromium.chromiumos",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "cros_boards_with_qemu_images": [
+ "amd64-generic"
+ ],
+ "target_arch": "intel",
+ "target_bits": 64,
+ "target_cros_boards": [
+ "eve"
+ ],
+ "target_platform": "chromeos"
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "checkout_lacros_sdk",
+ "chromeos"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "lacros-amd64-generic-rel",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/linux-chromeos-compile-dbg/properties.json b/chromium/infra/config/generated/builders/try/linux-chromeos-compile-dbg/properties.json
index 4aada5e2c21..d3d3044be31 100644
--- a/chromium/infra/config/generated/builders/try/linux-chromeos-compile-dbg/properties.json
+++ b/chromium/infra/config/generated/builders/try/linux-chromeos-compile-dbg/properties.json
@@ -1,4 +1,47 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-chromiumos-archive",
+ "builder_group": "chromium.chromiumos",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "chromium",
+ "target_arch": "intel",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "chromeos"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ }
+ ],
+ "is_compile_only": true
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/linux-chromeos-dbg/properties.json b/chromium/infra/config/generated/builders/try/linux-chromeos-dbg/properties.json
index 4aada5e2c21..e36c9578e3b 100644
--- a/chromium/infra/config/generated/builders/try/linux-chromeos-dbg/properties.json
+++ b/chromium/infra/config/generated/builders/try/linux-chromeos-dbg/properties.json
@@ -1,4 +1,46 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-chromiumos-archive",
+ "builder_group": "chromium.chromiumos",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Debug",
+ "config": "chromium",
+ "target_arch": "intel",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "chromeos"
+ ],
+ "config": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "linux-chromeos-dbg",
+ "project": "chromium"
+ }
+ ]
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/linux-wayland-rel/properties.json b/chromium/infra/config/generated/builders/try/linux-wayland-rel/properties.json
index 19e0479dcaf..ac3b8a1406c 100644
--- a/chromium/infra/config/generated/builders/try/linux-wayland-rel/properties.json
+++ b/chromium/infra/config/generated/builders/try/linux-wayland-rel/properties.json
@@ -1,4 +1,88 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-linux-archive",
+ "builder_group": "chromium.linux",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "use_clang_coverage",
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux Builder (Wayland)",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Linux Tests (Wayland)",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/goma": {
"enable_ats": true,
"rpc_extra_params": "?prod",
diff --git a/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng-compilator/properties.json b/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng-compilator/properties.json
index 39082a331df..442bfd53e50 100644
--- a/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng-compilator/properties.json
+++ b/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng-compilator/properties.json
@@ -1,4 +1,86 @@
{
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$build/goma": {
"enable_ats": true,
"jobs": 150,
diff --git a/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/properties.json b/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/properties.json
index ba82e73fbbb..8ca01a178a7 100644
--- a/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/properties.json
+++ b/chromium/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/properties.json
@@ -3,6 +3,88 @@
"compilator": "linux_chromium_tsan_rel_ng-compilator",
"compilator_watcher_git_revision": "7809a690bbd935bcb3b4d922e24cabe168aaabc8"
},
+ "$build/chromium_tests_builder_config": {
+ "builder_config": {
+ "builder_db": {
+ "entries": [
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "COMPILE_AND_TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ }
+ }
+ },
+ {
+ "builder_id": {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ },
+ "builder_spec": {
+ "build_gs_bucket": "chromium-memory-archive",
+ "builder_group": "chromium.memory",
+ "execution_mode": "TEST",
+ "legacy_chromium_config": {
+ "apply_configs": [
+ "mb"
+ ],
+ "build_config": "Release",
+ "config": "chromium_tsan2",
+ "target_bits": 64
+ },
+ "legacy_gclient_config": {
+ "apply_configs": [
+ "enable_reclient"
+ ],
+ "config": "chromium"
+ },
+ "parent": {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ }
+ }
+ ]
+ },
+ "builder_ids": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Builder",
+ "project": "chromium"
+ }
+ ],
+ "builder_ids_in_scope_for_testing": [
+ {
+ "bucket": "ci",
+ "builder": "Linux TSan Tests",
+ "project": "chromium"
+ }
+ ],
+ "rts_config": {
+ "condition": "QUICK_RUN_ONLY"
+ }
+ }
+ },
"$recipe_engine/resultdb/test_presentation": {
"column_keys": [],
"grouping_keys": [
diff --git a/chromium/infra/config/generated/luci/cr-buildbucket.cfg b/chromium/infra/config/generated/luci/cr-buildbucket.cfg
index 5979a5be4ae..3e6522e93ca 100644
--- a/chromium/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/chromium/infra/config/generated/luci/cr-buildbucket.cfg
@@ -28,6 +28,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -110,6 +111,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -192,6 +194,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -274,6 +277,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -356,6 +360,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -438,6 +443,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -520,6 +526,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -602,6 +609,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -684,6 +692,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -766,6 +775,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -848,6 +858,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -930,6 +941,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -1012,6 +1024,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1094,6 +1107,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1176,6 +1190,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1257,6 +1272,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builderless:1"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Mac"
dimensions: "pool:luci.chromium.gpu.ci"
exe {
@@ -1338,6 +1354,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1420,6 +1437,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1502,6 +1520,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Windows"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1584,6 +1603,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1666,6 +1686,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1748,6 +1769,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Windows"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1830,6 +1852,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1912,6 +1935,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -1994,6 +2018,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2076,6 +2101,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2158,6 +2184,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -2239,6 +2266,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builderless:1"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Mac"
dimensions: "pool:luci.chromium.gpu.ci"
exe {
@@ -2320,6 +2348,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Windows"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -2402,6 +2431,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:1"
@@ -2484,6 +2514,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2566,6 +2597,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2648,6 +2680,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2730,6 +2763,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2812,6 +2846,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -2894,6 +2929,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -2976,6 +3012,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3058,6 +3095,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3140,6 +3178,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3232,6 +3271,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3314,6 +3354,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3395,7 +3436,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:Mac Builder"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.ci"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -3556,6 +3597,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -3638,6 +3680,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -3720,6 +3763,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3802,6 +3846,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3884,6 +3929,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -3966,6 +4012,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4048,6 +4095,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4130,6 +4178,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4212,6 +4261,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4294,6 +4344,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4376,6 +4427,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4458,6 +4510,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4540,6 +4593,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -4622,6 +4676,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Windows-10"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5028,6 +5083,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:2"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.gpu.ci"
dimensions: "ssd:0"
@@ -5110,6 +5166,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Windows-10"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5192,6 +5249,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5274,6 +5332,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5356,6 +5415,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5438,6 +5498,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5520,6 +5581,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5602,6 +5664,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5765,6 +5828,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5847,6 +5911,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -5929,6 +5994,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6011,6 +6077,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6093,6 +6160,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6175,6 +6243,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6257,6 +6326,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6339,6 +6409,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6421,6 +6492,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6584,6 +6656,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -6665,7 +6738,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:ios-simulator"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.ci"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -6833,7 +6906,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:ios-simulator-full-configs"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.ci"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -6918,6 +6991,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7000,6 +7074,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7082,6 +7157,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7164,6 +7240,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7246,6 +7323,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7328,6 +7406,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7410,6 +7489,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7492,6 +7572,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -7654,7 +7735,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:mac-arm64-rel"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.ci"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -7815,6 +7896,7 @@ buckets {
dimensions: "builderless:1"
dimensions: "cores:8"
dimensions: "cpu:x86-64"
+ dimensions: "free_space:standard"
dimensions: "os:Ubuntu-18.04"
dimensions: "pool:luci.chromium.ci"
dimensions: "ssd:0"
@@ -10141,6 +10223,10 @@ buckets {
key: "luci.recipes.use_python3"
value: 100
}
+ experiments {
+ key: "remove_src_checkout_experiment"
+ value: 100
+ }
resultdb {
enable: true
bq_exports {
@@ -11516,7 +11602,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:ios-simulator"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.try"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -11608,7 +11694,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:ios-simulator-cronet"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.try"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -11700,7 +11786,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builder:ios-simulator-full-configs"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.try"
exe {
cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -12293,6 +12379,10 @@ buckets {
key: "luci.recipes.use_python3"
value: 100
}
+ experiments {
+ key: "remove_src_checkout_experiment"
+ value: 100
+ }
resultdb {
enable: true
bq_exports {
@@ -13012,6 +13102,10 @@ buckets {
key: "luci.recipes.use_python3"
value: 100
}
+ experiments {
+ key: "remove_src_checkout_experiment"
+ value: 100
+ }
resultdb {
enable: true
bq_exports {
@@ -13381,6 +13475,10 @@ buckets {
key: "luci.recipes.use_python3"
value: 100
}
+ experiments {
+ key: "remove_src_checkout_experiment"
+ value: 100
+ }
resultdb {
enable: true
bq_exports {
@@ -13962,7 +14060,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builderless:1"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.try"
dimensions: "ssd:1"
exe {
@@ -14052,7 +14150,7 @@ buckets {
swarming_host: "chromium-swarm.appspot.com"
dimensions: "builderless:1"
dimensions: "cpu:x86-64"
- dimensions: "os:Mac-11"
+ dimensions: "os:Mac-11|Mac-12"
dimensions: "pool:luci.chromium.try"
dimensions: "ssd:1"
exe {
diff --git a/chromium/infra/config/lib/args.star b/chromium/infra/config/lib/args.star
index 2f3bbb42a57..a5947a9485c 100644
--- a/chromium/infra/config/lib/args.star
+++ b/chromium/infra/config/lib/args.star
@@ -96,6 +96,8 @@ def defaults(extends = None, **vars):
return listify(value)
return listify(default, value)
+ fail("unknown merge value: {}".format(merge))
+
def get_value_from_kwargs(name, kwargs, merge = None):
return get_value(name, kwargs.get(name, DEFAULT), merge = merge)
diff --git a/chromium/infra/config/lib/builders.star b/chromium/infra/config/lib/builders.star
index 24a04513f57..226ed51648b 100644
--- a/chromium/infra/config/lib/builders.star
+++ b/chromium/infra/config/lib/builders.star
@@ -72,7 +72,8 @@ os = struct(
MAC_10_15 = os_enum("Mac-10.15", os_category.MAC),
MAC_11 = os_enum("Mac-11", os_category.MAC),
MAC_12 = os_enum("Mac-12", os_category.MAC),
- MAC_DEFAULT = os_enum("Mac-11", os_category.MAC),
+ # TODO(crbug.com/1323966) Remove Mac11 once builders have been migrated to Mac12
+ MAC_DEFAULT = os_enum("Mac-11|Mac-12", os_category.MAC),
MAC_ANY = os_enum("Mac", os_category.MAC),
MAC_BETA = os_enum("Mac-12", os_category.MAC),
WINDOWS_7 = os_enum("Windows-7", os_category.WINDOWS),
@@ -170,6 +171,15 @@ xcode = struct(
x13wk = xcode_enum("13a1030dwk"),
)
+# Free disk space in a machine reserved for build tasks.
+# The values in this enum will be used to populate bot dimension "free_space",
+# and each bot will allocate a corresponding amount of free disk space based on
+# the value of the dimension through "bot_config.py".
+free_space = struct(
+ standard = "standard",
+ high = "high",
+)
+
################################################################################
# Implementation details #
################################################################################
@@ -296,6 +306,7 @@ defaults = args.defaults(
auto_builder_dimension = args.COMPUTE,
builder_group = None,
builderless = args.COMPUTE,
+ free_space = None,
cores = None,
cpu = None,
fully_qualified_builder_dimension = False,
@@ -344,6 +355,7 @@ def builder(
triggered_by = args.DEFAULT,
os = args.DEFAULT,
builderless = args.DEFAULT,
+ free_space = args.DEFAULT,
builder_cache_name = None,
override_builder_dimension = None,
auto_builder_dimension = args.DEFAULT,
@@ -424,6 +436,10 @@ def builder(
builderless: a boolean indicating whether the builder runs on
builderless machines. If True, emits a 'builderless:1' dimension. By
default, considered True iff `os` refers to a linux OS.
+ free_space: an enum that indicates the amount of free disk space reserved
+ in a machine for incoming build tasks. This value is used to create
+ a "free_space" dimension, and this dimension is appended to only
+ builderless builders.
override_builder_dimension: a string to assign to the "builder"
dimension. Ignores any other "builder" and "builderless" dimensions
that would have been assigned.
@@ -599,6 +615,12 @@ def builder(
if builderless:
dimensions["builderless"] = "1"
+ free_space = defaults.get_value("free_space", free_space)
+ if free_space:
+ dimensions["free_space"] = free_space
+ elif free_space and free_space != args.DEFAULT:
+ fail("\'free_space\' dimension can only be specified for builderless builders")
+
auto_builder_dimension = defaults.get_value(
"auto_builder_dimension",
auto_builder_dimension,
@@ -807,4 +829,5 @@ builders = struct(
os = os,
sheriff_rotations = sheriff_rotations,
xcode = xcode,
+ free_space = free_space,
)
diff --git a/chromium/infra/config/lib/ci.star b/chromium/infra/config/lib/ci.star
index 557b741b234..edc064ddc5b 100644
--- a/chromium/infra/config/lib/ci.star
+++ b/chromium/infra/config/lib/ci.star
@@ -15,6 +15,7 @@ to set the default value. Can also be accessed through `ci.defaults`.
load("./args.star", "args")
load("./branches.star", "branches")
+load("./builder_config.star", "builder_config")
load("./builders.star", "builders", "os", "os_category")
load("//project.star", "settings")
@@ -261,6 +262,9 @@ def thin_tester(
Returns:
The `luci.builder` keyset.
"""
+ builder_spec = kwargs.get("builder_spec")
+ if builder_spec and builder_spec.execution_mode != builder_config.execution_mode.TEST:
+ fail("thin testers with builder specs must have TEST execution mode")
cores = defaults.get_value("thin_tester_cores", cores)
kwargs.setdefault("goma_backend", None)
kwargs.setdefault("reclient_instance", None)
diff --git a/chromium/infra/config/lib/consoles.star b/chromium/infra/config/lib/consoles.star
index 3eed963fa62..d07a73baf77 100644
--- a/chromium/infra/config/lib/consoles.star
+++ b/chromium/infra/config/lib/consoles.star
@@ -40,7 +40,7 @@ defaults = args.defaults(
_CONSOLE_VIEW_ORDERING = nodes.create_unscoped_node_type("console_view_ordering")
_OVERVIEW_CONSOLE_ORDERING = nodes.create_unscoped_node_type("overview_console_ordering")
-def _console_view_ordering_impl(ctx, *, console_name, ordering):
+def _console_view_ordering_impl(_ctx, *, console_name, ordering):
key = _CONSOLE_VIEW_ORDERING.add(console_name, props = {
"ordering": ordering,
})
@@ -49,7 +49,7 @@ def _console_view_ordering_impl(ctx, *, console_name, ordering):
_console_view_ordering = lucicfg.rule(impl = _console_view_ordering_impl)
-def _overview_console_view_ordering_impl(ctx, *, console_name, top_level_ordering):
+def _overview_console_view_ordering_impl(_ctx, *, console_name, top_level_ordering):
key = _OVERVIEW_CONSOLE_ORDERING.add(console_name, props = {
"top_level_ordering": top_level_ordering,
})
@@ -266,7 +266,7 @@ def console_view(*, name, branch_selector = branches.MAIN, ordering = None, **kw
ordering = ordering or {},
)
-def overview_console_view(*, name, top_level_ordering, branch_selector = branches.MAIN, **kwargs):
+def overview_console_view(*, name, top_level_ordering, **kwargs):
"""Create an overview console view.
An overview console view is a console view that contains a subset of
@@ -285,9 +285,6 @@ def overview_console_view(*, name, top_level_ordering, branch_selector = branche
name does not appear in the list will be sorted lexicographically
by the console name and appear after entries whose console does
appear in the list.
- branch_selector - A branch selector value controlling whether the
- console view definition is executed. See branches.star for
- more information.
kwargs - Additional keyword arguments to forward on to
`luci.console_view`. The header and repo arguments support
module-level defaults.
@@ -351,7 +348,7 @@ def _get_list_view_key_fn(console_name):
return None
return lambda b: b.name
-def _sorted_list_view_impl(ctx, *, console_name):
+def _sorted_list_view_impl(_ctx, *, console_name):
key = _sorted_list_view_graph_key(console_name)
graph.add_node(key)
graph.add_edge(keys.project(), key)
diff --git a/chromium/infra/config/outages/outages.star b/chromium/infra/config/outages/outages.star
index 16736121c21..6ada1be7264 100644
--- a/chromium/infra/config/outages/outages.star
+++ b/chromium/infra/config/outages/outages.star
@@ -33,7 +33,7 @@ def _disable_cq_experiments(ctx):
for b in c.verifiers.tryjob.builders:
if not b.experiment_percentage:
continue
- project, bucket, builder = b.name.split("/", 2)
+ project, bucket, _ = b.name.split("/", 2)
if project == "chromium" and bucket == "try":
b.includable_only = True
b.experiment_percentage = 0
diff --git a/chromium/infra/config/subprojects/chromium/ci.star b/chromium/infra/config/subprojects/chromium/ci.star
index e11b770d24e..d19117eff5f 100644
--- a/chromium/infra/config/subprojects/chromium/ci.star
+++ b/chromium/infra/config/subprojects/chromium/ci.star
@@ -3,7 +3,7 @@
# found in the LICENSE file.
load("//lib/branches.star", "branches")
-load("//lib/builders.star", "cpu")
+load("//lib/builders.star", "builders", "cpu")
load("//lib/ci.star", "ci")
load("//lib/consoles.star", "consoles")
load("//project.star", "settings")
@@ -14,6 +14,7 @@ ci.defaults.set(
build_numbers = True,
cpu = cpu.X86_64,
triggered_by = ["chromium-gitiles-trigger"],
+ free_space = builders.free_space.standard,
)
luci.bucket(
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.android.star b/chromium/infra/config/subprojects/chromium/ci/chromium.android.star
index 1e0cd2fa93b..8905963349c 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -309,6 +309,25 @@ ci.builder(
ci.builder(
name = "Android x86 Builder (dbg)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ build_config = builder_config.build_config.DEBUG,
+ target_bits = 32,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "x86_builder_mb",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "builder|x86",
short_name = "32",
@@ -323,6 +342,28 @@ ci.builder(
ci.builder(
name = "Cast Android (dbg)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.DEBUG,
+ target_bits = 32,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "cast_builder",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "on_cq",
short_name = "cst",
@@ -591,6 +632,29 @@ ci.builder(
ci.builder(
name = "android-cronet-arm-rel",
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "cronet_builder",
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 32,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "main_builder",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "cronet|arm",
@@ -920,6 +984,29 @@ ci.builder(
ci.builder(
name = "android-marshmallow-arm64-rel",
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "download_vr_test_apks",
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "main_builder",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "on_cq",
@@ -936,6 +1023,29 @@ ci.builder(
ci.builder(
name = "android-marshmallow-x86-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_wpr_tests",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 32,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "x86_builder",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "on_cq|x86",
short_name = "M",
@@ -1011,6 +1121,28 @@ ci.builder(
ci.builder(
name = "android-pie-arm64-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "main_builder",
+ ),
+ build_gs_bucket = "chromium-android-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "on_cq",
short_name = "P",
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.chromiumos.star b/chromium/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
index 65816ab67c3..0168f101d54 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.chromiumos.star
@@ -517,6 +517,32 @@ ci.builder(
ci.builder(
name = "lacros-amd64-generic-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "checkout_lacros_sdk",
+ "chromeos",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_arch = builder_config.target_arch.INTEL,
+ target_bits = 64,
+ target_cros_boards = [
+ "eve",
+ ],
+ target_platform = builder_config.target_platform.CHROMEOS,
+ cros_boards_with_qemu_images = [
+ "amd64-generic",
+ ],
+ ),
+ build_gs_bucket = "chromium-chromiumos-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "lacros|x64",
short_name = "rel",
@@ -563,6 +589,24 @@ ci.builder(
ci.builder(
name = "linux-chromeos-dbg",
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "chromeos",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.DEBUG,
+ target_arch = builder_config.target_arch.INTEL,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-chromiumos-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "default",
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.dawn.star b/chromium/infra/config/subprojects/chromium/ci/chromium.dawn.star
index 3ceee6244fd..431a5e9e91b 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.dawn.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.dawn.star
@@ -51,6 +51,25 @@ ci.gpu.linux_builder(
ci.gpu.linux_builder(
name = "Dawn Linux x64 DEPS Builder",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.LINUX,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Linux|Builder",
short_name = "x64",
@@ -64,6 +83,23 @@ ci.gpu.linux_builder(
ci.thin_tester(
name = "Dawn Linux x64 DEPS Release (Intel HD 630)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.LINUX,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Linux|Intel",
short_name = "x64",
@@ -75,6 +111,23 @@ ci.thin_tester(
ci.thin_tester(
name = "Dawn Linux x64 DEPS Release (NVIDIA)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.LINUX,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Linux|Nvidia",
short_name = "x64",
@@ -112,6 +165,22 @@ ci.gpu.mac_builder(
ci.gpu.mac_builder(
name = "Dawn Mac x64 DEPS Builder",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.MAC,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Mac|Builder",
short_name = "x64",
@@ -124,6 +193,23 @@ ci.gpu.mac_builder(
ci.thin_tester(
name = "Dawn Mac x64 DEPS Release (AMD)",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.MAC,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Mac|AMD",
short_name = "x64",
@@ -135,6 +221,23 @@ ci.thin_tester(
ci.thin_tester(
name = "Dawn Mac x64 DEPS Release (Intel)",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.MAC,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Mac|Intel",
short_name = "x64",
@@ -207,6 +310,22 @@ ci.gpu.windows_builder(
ci.gpu.windows_builder(
name = "Dawn Win10 x64 DEPS Builder",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.WIN,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Windows|Builder",
short_name = "x64",
@@ -222,6 +341,23 @@ ci.gpu.windows_builder(
ci.thin_tester(
name = "Dawn Win10 x64 DEPS Release (Intel HD 630)",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.WIN,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Windows|Intel",
short_name = "x64",
@@ -233,6 +369,23 @@ ci.thin_tester(
ci.thin_tester(
name = "Dawn Win10 x64 DEPS Release (NVIDIA)",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.WIN,
+ ),
+ build_gs_bucket = "chromium-dawn-archive",
+ run_tests_serially = True,
+ ),
console_view_entry = consoles.console_view_entry(
category = "DEPS|Windows|Nvidia",
short_name = "x64",
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.gpu.star b/chromium/infra/config/subprojects/chromium/ci/chromium.gpu.star
index bb59b34ac1b..027cf7cde53 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.gpu.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.gpu.star
@@ -33,6 +33,29 @@ consoles.console_view(
ci.gpu.linux_builder(
name = "Android Release (Nexus 5X)",
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "android",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "android",
+ apply_configs = [
+ "download_vr_test_apks",
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ target_platform = builder_config.target_platform.ANDROID,
+ ),
+ android_config = builder_config.android_config(
+ config = "main_builder",
+ ),
+ build_gs_bucket = "chromium-gpu-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "Android",
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.linux.star b/chromium/infra/config/subprojects/chromium/ci/chromium.linux.star
index 24599fe82b5..7886856c3f1 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.linux.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.linux.star
@@ -51,6 +51,23 @@ ci.builder(
ci.builder(
name = "Cast Linux",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium_clang",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-linux-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "cast",
short_name = "vid",
@@ -64,6 +81,23 @@ ci.builder(
ci.builder(
name = "Cast Linux Debug",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium_clang",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.DEBUG,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-linux-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "cast",
short_name = "dbg",
@@ -294,6 +328,24 @@ ci.builder(
ci.builder(
name = "Linux Builder (Wayland)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "use_clang_coverage",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-linux-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "release",
short_name = "bld-wl",
@@ -376,6 +428,25 @@ ci.builder(
ci.builder(
name = "Linux Tests (Wayland)",
branch_selector = branches.STANDARD_MILESTONE,
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "use_clang_coverage",
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-linux-archive",
+ ),
console_view_entry = consoles.console_view_entry(
category = "release",
short_name = "tst-wl",
diff --git a/chromium/infra/config/subprojects/chromium/ci/chromium.memory.star b/chromium/infra/config/subprojects/chromium/ci/chromium.memory.star
index a742398daeb..4a520517e76 100644
--- a/chromium/infra/config/subprojects/chromium/ci/chromium.memory.star
+++ b/chromium/infra/config/subprojects/chromium/ci/chromium.memory.star
@@ -127,6 +127,23 @@ linux_memory_builder(
linux_memory_builder(
name = "Linux TSan Builder",
+ builder_spec = builder_config.builder_spec(
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium_tsan2",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-memory-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "linux|TSan v2",
@@ -236,6 +253,24 @@ ci.builder(
linux_memory_builder(
name = "Linux TSan Tests",
+ builder_spec = builder_config.builder_spec(
+ execution_mode = builder_config.execution_mode.TEST,
+ gclient_config = builder_config.gclient_config(
+ config = "chromium",
+ apply_configs = [
+ "enable_reclient",
+ ],
+ ),
+ chromium_config = builder_config.chromium_config(
+ config = "chromium_tsan2",
+ apply_configs = [
+ "mb",
+ ],
+ build_config = builder_config.build_config.RELEASE,
+ target_bits = 64,
+ ),
+ build_gs_bucket = "chromium-memory-archive",
+ ),
branch_selector = branches.STANDARD_MILESTONE,
console_view_entry = consoles.console_view_entry(
category = "linux|TSan v2",
diff --git a/chromium/infra/config/subprojects/chromium/gpu.try.star b/chromium/infra/config/subprojects/chromium/gpu.try.star
index 8fb0a537752..d239179dbfb 100644
--- a/chromium/infra/config/subprojects/chromium/gpu.try.star
+++ b/chromium/infra/config/subprojects/chromium/gpu.try.star
@@ -84,6 +84,9 @@ gpu_android_builder(
gpu_android_builder(
name = "gpu-try-android-m-nexus-5x-64",
pool = "luci.chromium.gpu.android.nexus5x.try",
+ mirrors = [
+ "ci/Android Release (Nexus 5X)",
+ ],
)
def gpu_chromeos_builder(*, name, **kwargs):
diff --git a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index 1d0b82f4f6c..994a280cb96 100644
--- a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -225,6 +225,10 @@ try_.orchestrator_builder(
# TODO(crbug.com/1313712): Re-enable check_for_flakiness when ResultDB RPCs
# no longer timeout.
#check_for_flakiness = True,
+ mirrors = [
+ "ci/android-marshmallow-arm64-rel",
+ "ci/Android Release (Nexus 5X)",
+ ],
compilator = "android-marshmallow-arm64-rel-compilator",
branch_selector = branches.STANDARD_MILESTONE,
main_list_view = "try",
@@ -245,6 +249,14 @@ try_.compilator_builder(
try_.orchestrator_builder(
name = "android-marshmallow-x86-rel",
+ mirrors = [
+ "ci/android-marshmallow-x86-rel",
+ ],
+ try_settings = builder_config.try_settings(
+ rts_config = builder_config.rts_config(
+ condition = builder_config.rts_condition.QUICK_RUN_ONLY,
+ ),
+ ),
check_for_flakiness = True,
compilator = "android-marshmallow-x86-rel-compilator",
branch_selector = branches.STANDARD_MILESTONE,
@@ -331,6 +343,14 @@ try_.builder(
try_.orchestrator_builder(
name = "android-pie-arm64-rel",
+ mirrors = [
+ "ci/android-pie-arm64-rel",
+ ],
+ try_settings = builder_config.try_settings(
+ rts_config = builder_config.rts_config(
+ condition = builder_config.rts_condition.QUICK_RUN_ONLY,
+ ),
+ ),
compilator = "android-pie-arm64-rel-compilator",
check_for_flakiness = True,
branch_selector = branches.STANDARD_MILESTONE,
@@ -509,6 +529,13 @@ try_.builder(
try_.builder(
name = "android_compile_x86_dbg",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Android x86 Builder (dbg)",
+ ],
+ try_settings = builder_config.try_settings(
+ include_all_triggered_testers = True,
+ is_compile_only = True,
+ ),
cores = 16,
ssd = True,
main_list_view = "try",
@@ -528,6 +555,12 @@ try_.builder(
try_.builder(
name = "android_cronet",
+ mirrors = [
+ "ci/android-cronet-arm-rel",
+ ],
+ try_settings = builder_config.try_settings(
+ is_compile_only = True,
+ ),
branch_selector = branches.STANDARD_MILESTONE,
builderless = not settings.is_main,
main_list_view = "try",
@@ -553,6 +586,9 @@ try_.builder(
try_.builder(
name = "cast_shell_android",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Cast Android (dbg)",
+ ],
builderless = not settings.is_main,
main_list_view = "try",
tryjob = try_.job(),
diff --git a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
index 37b3e67a893..fce255f9e11 100644
--- a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
+++ b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
@@ -54,6 +54,9 @@ try_.orchestrator_builder(
mirrors = ["ci/chromeos-amd64-generic-rel"],
main_list_view = "try",
tryjob = try_.job(),
+ experiments = {
+ "remove_src_checkout_experiment": 100,
+ },
)
try_.compilator_builder(
@@ -85,6 +88,9 @@ try_.builder(
try_.builder(
name = "lacros-amd64-generic-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/lacros-amd64-generic-rel",
+ ],
builderless = not settings.is_main,
main_list_view = "try",
tryjob = try_.job(),
@@ -108,6 +114,13 @@ try_.builder(
try_.builder(
name = "linux-chromeos-compile-dbg",
+ mirrors = [
+ "ci/linux-chromeos-dbg",
+ ],
+ try_settings = builder_config.try_settings(
+ include_all_triggered_testers = True,
+ is_compile_only = True,
+ ),
branch_selector = branches.STANDARD_MILESTONE,
builderless = not settings.is_main,
main_list_view = "try",
@@ -157,6 +170,9 @@ try_.orchestrator_builder(
use_clang_coverage = True,
coverage_test_types = ["unit", "overall"],
tryjob = try_.job(),
+ experiments = {
+ "remove_src_checkout_experiment": 100,
+ },
)
try_.compilator_builder(
@@ -214,6 +230,9 @@ try_.builder(
try_.builder(
name = "linux-chromeos-dbg",
+ mirrors = [
+ "ci/linux-chromeos-dbg",
+ ],
# The CI builder that this mirrors is enabled on branches, so this will
# allow testing changes that would break it before submitting
branch_selector = branches.STANDARD_MILESTONE,
diff --git a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
index 7f12fd25d8c..d2b88091e6e 100644
--- a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
+++ b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.dawn.star
@@ -27,6 +27,11 @@ consoles.list_view(
try_.builder(
name = "dawn-linux-x64-deps-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Dawn Linux x64 DEPS Builder",
+ "ci/Dawn Linux x64 DEPS Release (Intel HD 630)",
+ "ci/Dawn Linux x64 DEPS Release (NVIDIA)",
+ ],
main_list_view = "try",
tryjob = try_.job(
location_regexp = [
@@ -48,6 +53,11 @@ try_.builder(
try_.builder(
name = "dawn-mac-x64-deps-rel",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ mirrors = [
+ "ci/Dawn Mac x64 DEPS Builder",
+ "ci/Dawn Mac x64 DEPS Release (AMD)",
+ "ci/Dawn Mac x64 DEPS Release (Intel)",
+ ],
main_list_view = "try",
os = os.MAC_ANY,
tryjob = try_.job(
@@ -70,6 +80,11 @@ try_.builder(
try_.builder(
name = "dawn-win10-x64-deps-rel",
branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
+ mirrors = [
+ "ci/Dawn Win10 x64 DEPS Builder",
+ "ci/Dawn Win10 x64 DEPS Release (Intel HD 630)",
+ "ci/Dawn Win10 x64 DEPS Release (NVIDIA)",
+ ],
main_list_view = "try",
os = os.WINDOWS_ANY,
tryjob = try_.job(
diff --git a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index afca85de45c..fb217467357 100644
--- a/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/chromium/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -39,6 +39,9 @@ try_.builder(
try_.builder(
name = "cast_shell_linux",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Cast Linux",
+ ],
builderless = not settings.is_main,
main_list_view = "try",
tryjob = try_.job(),
@@ -47,6 +50,9 @@ try_.builder(
try_.builder(
name = "cast_shell_linux_dbg",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Cast Linux Debug",
+ ],
main_list_view = "try",
tryjob = try_.job(
location_regexp = [
@@ -380,6 +386,15 @@ try_.compilator_builder(
try_.builder(
name = "linux-wayland-rel",
branch_selector = branches.STANDARD_MILESTONE,
+ mirrors = [
+ "ci/Linux Builder (Wayland)",
+ "ci/Linux Tests (Wayland)",
+ ],
+ try_settings = builder_config.try_settings(
+ rts_config = builder_config.rts_config(
+ condition = builder_config.rts_condition.QUICK_RUN_ONLY,
+ ),
+ ),
builderless = not settings.is_main,
main_list_view = "try",
tryjob = try_.job(),
@@ -428,6 +443,9 @@ try_.orchestrator_builder(
condition = builder_config.rts_condition.QUICK_RUN_ONLY,
),
),
+ experiments = {
+ "remove_src_checkout_experiment": 100,
+ },
)
try_.compilator_builder(
@@ -526,10 +544,22 @@ try_.builder(
try_.orchestrator_builder(
name = "linux_chromium_tsan_rel_ng",
+ mirrors = [
+ "ci/Linux TSan Builder",
+ "ci/Linux TSan Tests",
+ ],
+ try_settings = builder_config.try_settings(
+ rts_config = builder_config.rts_config(
+ condition = builder_config.rts_condition.QUICK_RUN_ONLY,
+ ),
+ ),
compilator = "linux_chromium_tsan_rel_ng-compilator",
branch_selector = branches.STANDARD_MILESTONE,
main_list_view = "try",
tryjob = try_.job(),
+ experiments = {
+ "remove_src_checkout_experiment": 100,
+ },
)
try_.compilator_builder(
diff --git a/chromium/infra/config/subprojects/flakiness/flakiness.star b/chromium/infra/config/subprojects/flakiness/flakiness.star
index ee158bc947d..c2b4fe86cb0 100644
--- a/chromium/infra/config/subprojects/flakiness/flakiness.star
+++ b/chromium/infra/config/subprojects/flakiness/flakiness.star
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-load("//lib/builders.star", "os")
+load("//lib/builders.star", "builders", "os")
load("//lib/ci.star", "ci")
load("//lib/consoles.star", "consoles")
@@ -15,6 +15,7 @@ ci.defaults.set(
# TODO(jeffyoon): replace with smaller scoped service account, and update
# below for bucket ACL
service_account = "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com",
+ free_space = builders.free_space.standard,
)
luci.bucket(
diff --git a/chromium/infra/config/subprojects/goma/goma.star b/chromium/infra/config/subprojects/goma/goma.star
index be425023964..54a585a691a 100644
--- a/chromium/infra/config/subprojects/goma/goma.star
+++ b/chromium/infra/config/subprojects/goma/goma.star
@@ -3,7 +3,7 @@
# found in the LICENSE file.
load("//lib/builder_config.star", "builder_config")
-load("//lib/builders.star", "builder", "cpu", "defaults", "goma", "os", "xcode")
+load("//lib/builders.star", "builder", "builders", "cpu", "defaults", "goma", "os", "xcode")
load("//lib/structs.star", "structs")
luci.bucket(
@@ -32,6 +32,7 @@ defaults.executable.set("recipe:chromium")
defaults.execution_timeout.set(3 * time.hour)
defaults.os.set(os.LINUX_DEFAULT)
defaults.pool.set("luci.chromium.ci")
+defaults.free_space.set(builders.free_space.standard)
defaults.service_account.set(
"goma-release-testing@chops-service-accounts.iam.gserviceaccount.com",
)
diff --git a/chromium/infra/config/subprojects/reclient/reclient.star b/chromium/infra/config/subprojects/reclient/reclient.star
index ad898d6cb6c..fb789ff4b50 100644
--- a/chromium/infra/config/subprojects/reclient/reclient.star
+++ b/chromium/infra/config/subprojects/reclient/reclient.star
@@ -3,7 +3,7 @@
# found in the LICENSE file.
load("//lib/builder_config.star", "builder_config")
-load("//lib/builders.star", "cpu", "os")
+load("//lib/builders.star", "builders", "cpu", "os")
load("//lib/ci.star", "ci")
load("//lib/consoles.star", "consoles")
load("//lib/structs.star", "structs")
@@ -41,6 +41,7 @@ ci.defaults.set(
"chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
),
triggered_by = ["chromium-gitiles-trigger"],
+ free_space = builders.free_space.standard,
)
consoles.console_view(
diff --git a/chromium/ios/chrome/browser/ui/settings/password/BUILD.gn b/chromium/ios/chrome/browser/ui/settings/password/BUILD.gn
index de8185638e2..f6c637dbd8e 100644
--- a/chromium/ios/chrome/browser/ui/settings/password/BUILD.gn
+++ b/chromium/ios/chrome/browser/ui/settings/password/BUILD.gn
@@ -176,6 +176,7 @@ source_set("unit_tests") {
"//ios/chrome/common/ui/colors",
"//ios/chrome/common/ui/reauthentication",
"//ios/chrome/common/ui/table_view:cells_constants",
+ "//ios/chrome/test:test_support",
"//ios/chrome/test/app:test_support",
"//ios/web/public/test",
"//testing/gmock",
diff --git a/chromium/media/base/supported_types.cc b/chromium/media/base/supported_types.cc
index 41a4609a2a7..5540061ad77 100644
--- a/chromium/media/base/supported_types.cc
+++ b/chromium/media/base/supported_types.cc
@@ -41,7 +41,6 @@ class SupplementalProfileCache {
public:
void UpdateCache(const base::flat_set<media::VideoCodecProfile>& profiles) {
base::AutoLock lock(profiles_lock_);
- DCHECK_EQ(profiles_.size(), 0u);
profiles_ = profiles;
}
bool IsProfileSupported(media::VideoCodecProfile profile) {
diff --git a/chromium/media/fuchsia/common/vmo_buffer_writer_queue.cc b/chromium/media/fuchsia/common/vmo_buffer_writer_queue.cc
index b983e0033ec..902d19cb5ab 100644
--- a/chromium/media/fuchsia/common/vmo_buffer_writer_queue.cc
+++ b/chromium/media/fuchsia/common/vmo_buffer_writer_queue.cc
@@ -86,7 +86,15 @@ void VmoBufferWriterQueue::PumpPackets() {
PendingBuffer* current_buffer = &pending_buffers_[input_queue_position_];
if (current_buffer->buffer->end_of_stream()) {
- pending_buffers_.pop_front();
+ // Pop the EndOfStream buffer if it's the only buffer. Otherwise, mark it
+ // as complete so that ReleaseBuffer will pop it.
+ if (input_queue_position_ == 0) {
+ pending_buffers_.pop_front();
+ DCHECK(pending_buffers_.empty());
+ } else {
+ current_buffer->is_complete = true;
+ input_queue_position_ += 1;
+ }
end_of_stream_cb_.Run();
if (!weak_this)
return;
diff --git a/chromium/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc b/chromium/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
index 98be02b1ec2..b714eb8fd44 100644
--- a/chromium/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
+++ b/chromium/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
@@ -18,6 +18,7 @@
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
+#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "media/base/video_frame.h"
@@ -165,10 +166,12 @@ bool FrameResources::Initialize() {
gfx::BufferUsage::SCANOUT_CPU_READ_WRITE
#endif
;
+ constexpr gfx::BufferFormat kBufferFormat =
+ gfx::BufferFormat::YUV_420_BIPLANAR;
// Create the GpuMemoryBuffer.
- gpu_memory_buffer_ = context->CreateGpuMemoryBuffer(
- coded_size_, gfx::BufferFormat::YUV_420_BIPLANAR, kBufferUsage);
+ gpu_memory_buffer_ =
+ context->CreateGpuMemoryBuffer(coded_size_, kBufferFormat, kBufferUsage);
if (!gpu_memory_buffer_) {
DLOG(ERROR) << "Failed to allocate GpuMemoryBuffer for frame: coded_size="
<< coded_size_.ToString()
@@ -187,17 +190,17 @@ bool FrameResources::Initialize() {
gpu::SHARED_IMAGE_USAGE_GLES2 | gpu::SHARED_IMAGE_USAGE_RASTER |
gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
+ uint32_t texture_target = GL_TEXTURE_2D;
+#if BUILDFLAG(IS_MAC)
+ // TODO(https://crbug.com/1311844): Use gpu::GetBufferTextureTarget() instead.
+ texture_target = gpu::GetPlatformSpecificTextureTarget();
+#endif
for (size_t plane = 0; plane < kNumPlanes; ++plane) {
context->CreateSharedImage(
gpu_memory_buffer_.get(), kPlanes[plane], color_space_,
kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, kSharedImageUsage,
mailbox_holders_[plane].mailbox, mailbox_holders_[plane].sync_token);
- // TODO(https://crbug.com/1191956): This should be parameterized.
-#if BUILDFLAG(IS_MAC)
- mailbox_holders_[plane].texture_target = GL_TEXTURE_RECTANGLE_ARB;
-#else
- mailbox_holders_[plane].texture_target = GL_TEXTURE_2D;
-#endif
+ mailbox_holders_[plane].texture_target = texture_target;
}
return true;
}
diff --git a/chromium/net/http/transport_security_state_static.json.gz b/chromium/net/http/transport_security_state_static.json.gz
index 37ad27c8277..df8112dc1ce 100644
--- a/chromium/net/http/transport_security_state_static.json.gz
+++ b/chromium/net/http/transport_security_state_static.json.gz
Binary files differ
diff --git a/chromium/services/network/public/cpp/features.cc b/chromium/services/network/public/cpp/features.cc
index 74a970e853e..b61eeca9ab5 100644
--- a/chromium/services/network/public/cpp/features.cc
+++ b/chromium/services/network/public/cpp/features.cc
@@ -177,6 +177,12 @@ const base::FeatureParam<bool> kPlatformProvidedTrustTokenIssuance{
const base::Feature kWebSocketReassembleShortMessages{
"WebSocketReassembleShortMessages", base::FEATURE_ENABLED_BY_DEFAULT};
+// Enable support for ACCEPT_CH H2/3 frame as part of Client Hint Reliability.
+// See:
+// https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02#section-4.3
+const base::Feature kAcceptCHFrame{"AcceptCHFrame",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
const base::Feature kSCTAuditingRetryReports{"SCTAuditingRetryReports",
base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chromium/services/network/public/cpp/features.h b/chromium/services/network/public/cpp/features.h
index 6f26652e3ab..aefe8a7387b 100644
--- a/chromium/services/network/public/cpp/features.h
+++ b/chromium/services/network/public/cpp/features.h
@@ -65,6 +65,9 @@ COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kWebSocketReassembleShortMessages;
COMPONENT_EXPORT(NETWORK_CPP)
+extern const base::Feature kAcceptCHFrame;
+
+COMPONENT_EXPORT(NETWORK_CPP)
extern const base::Feature kSCTAuditingRetryReports;
COMPONENT_EXPORT(NETWORK_CPP)
diff --git a/chromium/services/network/url_loader.cc b/chromium/services/network/url_loader.cc
index c79ad06ecb8..85e984259a1 100644
--- a/chromium/services/network/url_loader.cc
+++ b/chromium/services/network/url_loader.cc
@@ -1149,7 +1149,8 @@ int URLLoader::OnConnected(net::URLRequest* url_request,
return net::ERR_FAILED;
}
- if (!accept_ch_frame_observer_ || info.accept_ch_frame.empty()) {
+ if (!accept_ch_frame_observer_ || info.accept_ch_frame.empty() ||
+ !base::FeatureList::IsEnabled(features::kAcceptCHFrame)) {
return net::OK;
}
diff --git a/chromium/services/network/url_loader_unittest.cc b/chromium/services/network/url_loader_unittest.cc
index 40cda17b1d7..f2f2d226167 100644
--- a/chromium/services/network/url_loader_unittest.cc
+++ b/chromium/services/network/url_loader_unittest.cc
@@ -686,7 +686,8 @@ class URLLoaderTest : public testing::Test {
net::URLRequestFailedJob::AddUrlHandler();
scoped_feature_list_.InitWithFeatures(
- /*enabled_features=*/{net::features::kRecordRadioWakeupTrigger},
+ /*enabled_features=*/{features::kAcceptCHFrame,
+ net::features::kRecordRadioWakeupTrigger},
/*disabled_features=*/{});
}
~URLLoaderTest() override {
diff --git a/chromium/storage/browser/quota/quota_manager_proxy.cc b/chromium/storage/browser/quota/quota_manager_proxy.cc
index c4c37ec1bd0..0c30182c7a8 100644
--- a/chromium/storage/browser/quota/quota_manager_proxy.cc
+++ b/chromium/storage/browser/quota/quota_manager_proxy.cc
@@ -17,6 +17,7 @@
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
@@ -34,39 +35,6 @@ using ::blink::StorageKey;
namespace storage {
-namespace {
-
-void DidGetBucket(scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
- base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
- QuotaErrorOr<BucketInfo> result) {
- DCHECK(callback_task_runner);
- DCHECK(callback);
-
- if (callback_task_runner->RunsTasksInCurrentSequence()) {
- std::move(callback).Run(std::move(result));
- return;
- }
- callback_task_runner->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
-}
-
-void DidGetStatus(
- scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
- base::OnceCallback<void(blink::mojom::QuotaStatusCode)> callback,
- blink::mojom::QuotaStatusCode status) {
- DCHECK(callback_task_runner);
- DCHECK(callback);
-
- if (callback_task_runner->RunsTasksInCurrentSequence()) {
- std::move(callback).Run(std::move(status));
- return;
- }
- callback_task_runner->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), std::move(status)));
-}
-
-} // namespace
-
QuotaManagerProxy::QuotaManagerProxy(
QuotaManagerImpl* quota_manager_impl,
scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner,
@@ -158,16 +126,16 @@ void QuotaManagerProxy::GetOrCreateBucket(
}
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_);
+
+ auto respond =
+ base::BindPostTask(std::move(callback_task_runner), std::move(callback));
if (!quota_manager_impl_) {
- DidGetBucket(std::move(callback_task_runner), std::move(callback),
- QuotaErrorOr<BucketInfo>(QuotaError::kUnknownError));
+ std::move(respond).Run(QuotaError::kUnknownError);
return;
}
- quota_manager_impl_->GetOrCreateBucket(
- storage_key, bucket_name,
- base::BindOnce(&DidGetBucket, std::move(callback_task_runner),
- std::move(callback)));
+ quota_manager_impl_->GetOrCreateBucket(storage_key, bucket_name,
+ std::move(respond));
}
QuotaErrorOr<BucketInfo> QuotaManagerProxy::GetOrCreateBucketSync(
@@ -211,16 +179,16 @@ void QuotaManagerProxy::GetOrCreateBucketDeprecated(
}
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_);
+
+ auto respond =
+ base::BindPostTask(std::move(callback_task_runner), std::move(callback));
if (!quota_manager_impl_) {
- DidGetBucket(std::move(callback_task_runner), std::move(callback),
- QuotaErrorOr<BucketInfo>(QuotaError::kUnknownError));
+ std::move(respond).Run(QuotaError::kUnknownError);
return;
}
quota_manager_impl_->GetOrCreateBucketDeprecated(
- storage_key, bucket_name, storage_type,
- base::BindOnce(&DidGetBucket, std::move(callback_task_runner),
- std::move(callback)));
+ storage_key, bucket_name, storage_type, std::move(respond));
}
void QuotaManagerProxy::CreateBucketForTesting(
@@ -242,16 +210,16 @@ void QuotaManagerProxy::CreateBucketForTesting(
}
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_);
+
+ auto respond =
+ base::BindPostTask(std::move(callback_task_runner), std::move(callback));
if (!quota_manager_impl_) {
- DidGetBucket(std::move(callback_task_runner), std::move(callback),
- QuotaErrorOr<BucketInfo>(QuotaError::kUnknownError));
+ std::move(respond).Run(QuotaError::kUnknownError);
return;
}
quota_manager_impl_->CreateBucketForTesting( // IN-TEST
- storage_key, bucket_name, storage_type,
- base::BindOnce(&DidGetBucket, std::move(callback_task_runner),
- std::move(callback)));
+ storage_key, bucket_name, storage_type, std::move(respond));
}
void QuotaManagerProxy::GetBucket(
@@ -273,16 +241,16 @@ void QuotaManagerProxy::GetBucket(
}
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_);
+
+ auto respond =
+ base::BindPostTask(std::move(callback_task_runner), std::move(callback));
if (!quota_manager_impl_) {
- DidGetBucket(std::move(callback_task_runner), std::move(callback),
- QuotaErrorOr<BucketInfo>(QuotaError::kUnknownError));
+ std::move(respond).Run(QuotaError::kUnknownError);
return;
}
- quota_manager_impl_->GetBucket(
- storage_key, bucket_name, type,
- base::BindOnce(&DidGetBucket, std::move(callback_task_runner),
- std::move(callback)));
+ quota_manager_impl_->GetBucket(storage_key, bucket_name, type,
+ std::move(respond));
}
void QuotaManagerProxy::DeleteBucket(
@@ -303,16 +271,16 @@ void QuotaManagerProxy::DeleteBucket(
}
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_manager_impl_sequence_checker_);
+
+ auto respond =
+ base::BindPostTask(std::move(callback_task_runner), std::move(callback));
if (!quota_manager_impl_) {
- DidGetStatus(std::move(callback_task_runner), std::move(callback),
- blink::mojom::QuotaStatusCode::kUnknown);
+ std::move(respond).Run(blink::mojom::QuotaStatusCode::kUnknown);
return;
}
- quota_manager_impl_->FindAndDeleteBucketData(
- storage_key, bucket_name,
- base::BindOnce(&DidGetStatus, std::move(callback_task_runner),
- std::move(callback)));
+ quota_manager_impl_->FindAndDeleteBucketData(storage_key, bucket_name,
+ std::move(respond));
}
void QuotaManagerProxy::NotifyStorageAccessed(const StorageKey& storage_key,
diff --git a/chromium/third_party/angle/src/libANGLE/Context.cpp b/chromium/third_party/angle/src/libANGLE/Context.cpp
index 75385dd7a1b..baf881d86ab 100644
--- a/chromium/third_party/angle/src/libANGLE/Context.cpp
+++ b/chromium/third_party/angle/src/libANGLE/Context.cpp
@@ -1364,6 +1364,7 @@ void Context::bindTransformFeedback(GLenum target, TransformFeedbackID transform
TransformFeedback *transformFeedback =
checkTransformFeedbackAllocation(transformFeedbackHandle);
mState.setTransformFeedbackBinding(this, transformFeedback);
+ mStateCache.onActiveTransformFeedbackChange(this);
}
void Context::bindProgramPipeline(ProgramPipelineID pipelineHandle)
diff --git a/chromium/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/chromium/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index eb57441d9f9..166403efc9e 100644
--- a/chromium/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/chromium/third_party/angle/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1684,12 +1684,6 @@ class ProgramD3D::GetVertexExecutableTask : public ProgramD3D::GetExecutableTask
angle::Result run() override
{
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramD3D::GetVertexExecutableTask::run");
- if (!mProgram->mState.getAttachedShader(gl::ShaderType::Vertex))
- {
- return angle::Result::Continue;
- }
-
- mProgram->updateCachedInputLayoutFromShader();
ANGLE_TRY(mProgram->getVertexExecutableForCachedInputLayout(this, &mExecutable, &mInfoLog));
@@ -2166,6 +2160,11 @@ std::unique_ptr<LinkEvent> ProgramD3D::link(const gl::Context *context,
linkResources(resources);
+ if (mState.getAttachedShader(gl::ShaderType::Vertex))
+ {
+ updateCachedInputLayoutFromShader();
+ }
+
return compileProgramExecutables(context, infoLog);
}
}
diff --git a/chromium/third_party/angle/src/libANGLE/validationEGL.cpp b/chromium/third_party/angle/src/libANGLE/validationEGL.cpp
index 472405d5b2b..5b775b495a0 100644
--- a/chromium/third_party/angle/src/libANGLE/validationEGL.cpp
+++ b/chromium/third_party/angle/src/libANGLE/validationEGL.cpp
@@ -4874,7 +4874,7 @@ bool ValidateBindTexImage(const ValidationContext *val,
}
gl::Context *context = val->eglThread->getContext();
- if (context)
+ if (context && !context->isContextLost())
{
gl::TextureType type = egl_gl::EGLTextureTargetToTextureType(surface->getTextureTarget());
gl::Texture *textureObject = context->getTextureByType(type);
diff --git a/chromium/third_party/angle/src/libGLESv2/egl_stubs.cpp b/chromium/third_party/angle/src/libGLESv2/egl_stubs.cpp
index 0554b7f40c6..645f53ba038 100644
--- a/chromium/third_party/angle/src/libGLESv2/egl_stubs.cpp
+++ b/chromium/third_party/angle/src/libGLESv2/egl_stubs.cpp
@@ -61,7 +61,7 @@ EGLBoolean BindTexImage(Thread *thread, Display *display, Surface *eglSurface, E
GetDisplayIfValid(display), EGL_FALSE);
gl::Context *context = thread->getContext();
- if (context)
+ if (context && !context->isContextLost())
{
gl::TextureType type =
egl_gl::EGLTextureTargetToTextureType(eglSurface->getTextureTarget());
@@ -573,15 +573,18 @@ EGLBoolean ReleaseTexImage(Thread *thread, Display *display, Surface *eglSurface
{
ANGLE_EGL_TRY_RETURN(thread, display->prepareForCall(), "eglReleaseTexImage",
GetDisplayIfValid(display), EGL_FALSE);
- gl::Texture *texture = eglSurface->getBoundTexture();
-
- if (texture)
+ gl::Context *context = thread->getContext();
+ if (context && !context->isContextLost())
{
- ANGLE_EGL_TRY_RETURN(thread, eglSurface->releaseTexImage(thread->getContext(), buffer),
- "eglReleaseTexImage", GetSurfaceIfValid(display, eglSurface),
- EGL_FALSE);
- }
+ gl::Texture *texture = eglSurface->getBoundTexture();
+ if (texture)
+ {
+ ANGLE_EGL_TRY_RETURN(thread, eglSurface->releaseTexImage(thread->getContext(), buffer),
+ "eglReleaseTexImage", GetSurfaceIfValid(display, eglSurface),
+ EGL_FALSE);
+ }
+ }
thread->setSuccess();
return EGL_TRUE;
}
diff --git a/chromium/third_party/blink/common/features.cc b/chromium/third_party/blink/common/features.cc
index f8077d8c242..dfb2b8044e5 100644
--- a/chromium/third_party/blink/common/features.cc
+++ b/chromium/third_party/blink/common/features.cc
@@ -973,7 +973,7 @@ const base::Feature kManagedConfiguration{"ManagedConfiguration",
// have their rendering throttled on display:none or zero-area.
const base::Feature kThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes{
"ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes",
- base::FEATURE_ENABLED_BY_DEFAULT};
+ base::FEATURE_DISABLED_BY_DEFAULT};
// Kill switch for the Interest Group API, i.e. if disabled, the
// API exposure will be disabled regardless of the OT config.
diff --git a/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_fa.xtb b/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_fa.xtb
index 1a0b12ff66e..298e9c74a67 100644
--- a/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_fa.xtb
+++ b/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_fa.xtb
@@ -33,7 +33,7 @@
<translation id="3904695548697879411">â€Ù†Ù…اد «Twitter»</translation>
<translation id="405782047075994056">نماد «جهت‌نمای چپ»، می‌تواند به‌معنای «برگشتن» باشد</translation>
<translation id="4302299849305494927">نماد «بلندگو»، می‌تواند به‌معنای «میزان صدا» باشد</translation>
-<translation id="4363712632243441817">نماد «ارسال محتوا»، می‌تواند به‌معنای ارسال محتوای ویدیویی به صÙحه‌نمایش ازراه‌دور باشد</translation>
+<translation id="4363712632243441817">نماد «پخش محتوا»، می‌تواند به‌معنای ارسال محتوای ویدیویی به صÙحه‌نمایش ازراه‌دور باشد</translation>
<translation id="4384249794467006333">این تصویر برچسب ندارد. برای دریاÙت شرح تصویر، منوی «گزینه‌های بیشتر» را در بالا سمت Ú†Ù¾ باز کنید.</translation>
<translation id="4436211924730548766">نماد «پیکان سمت چپ»</translation>
<translation id="4444765639179266822">ظاهراً <ph name="OCR_TEXT" /> را نشان می‌دهد</translation>
diff --git a/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_pt-PT.xtb b/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_pt-PT.xtb
index 28d850f0bc2..5e08600073a 100644
--- a/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_pt-PT.xtb
+++ b/chromium/third_party/blink/public/strings/translations/blink_accessibility_strings_pt-PT.xtb
@@ -71,7 +71,7 @@
<translation id="7742609585924342092">Ãcone Localização</translation>
<translation id="7745230546936012372">Para obter descrições de imagens em falta, abra o menu de contexto.</translation>
<translation id="7796968532285333302">Ãcone Divisa inferior, pode significar Reduzir</translation>
-<translation id="7819026464394689674">Ãcone Colocar em pausa</translation>
+<translation id="7819026464394689674">Ãcone Pausar</translation>
<translation id="7855610409192055689">Ãcone Clipe, pode significar anexo</translation>
<translation id="7994555495914042081">Ãcone Marca de verificação</translation>
<translation id="8097409774376213335">Ãcone Seta para baixo</translation>
diff --git a/chromium/third_party/blink/public/strings/translations/blink_strings_fa.xtb b/chromium/third_party/blink/public/strings/translations/blink_strings_fa.xtb
index 2c7dd213e6b..66850109927 100644
--- a/chromium/third_party/blink/public/strings/translations/blink_strings_fa.xtb
+++ b/chromium/third_party/blink/public/strings/translations/blink_strings_fa.xtb
@@ -102,7 +102,7 @@
<translation id="5787939484346677755">گسترده شد، گزینه‌های تکمیل خودکار دردسترس است.</translation>
<translation id="5860033963881614850">خاموش</translation>
<translation id="588258955323874662">تمام صÙحه</translation>
-<translation id="5888666972993069672">درحال ارسال محتوا به <ph name="DEVICE_FRIENDLY_NAME" /></translation>
+<translation id="5888666972993069672">درحال پخش محتوا به <ph name="DEVICE_FRIENDLY_NAME" /></translation>
<translation id="5916664084637901428">روشن</translation>
<translation id="5939518447894949180">بازنشانی</translation>
<translation id="5966707198760109579">Ù‡Ùته</translation>
@@ -168,7 +168,7 @@
<translation id="8637593834423658414">Û±Ù«Û·Ûµ</translation>
<translation id="8668988909814782445">شکست خط</translation>
<translation id="8750798805984357768">لطÙاً یکی از این گزینه‌ها را انتخاب کنید.</translation>
-<translation id="8845239796550121995">درحال ارسال محتوا به تلویزیون</translation>
+<translation id="8845239796550121995">درحال پخش محتوا به تلویزیون</translation>
<translation id="8875657656876809964">خطا در بازپخش ویدئو</translation>
<translation id="8889402386540077796">رنگ‌مایه</translation>
<translation id="8901569739625249689"><ph name="QUANTITY" /> کیلوبایت</translation>
diff --git a/chromium/third_party/blink/public/strings/translations/blink_strings_id.xtb b/chromium/third_party/blink/public/strings/translations/blink_strings_id.xtb
index 99f0c9c2565..56a4af71850 100644
--- a/chromium/third_party/blink/public/strings/translations/blink_strings_id.xtb
+++ b/chromium/third_party/blink/public/strings/translations/blink_strings_id.xtb
@@ -93,7 +93,7 @@
<translation id="5466621249238537318">Pilih salah satu atau beberapa file.</translation>
<translation id="5468998798572797635">keluar dari tampilan layar penuh</translation>
<translation id="5516235301412634559">0,75</translation>
-<translation id="5537725057119320332">Cast</translation>
+<translation id="5537725057119320332">Transmisikan</translation>
<translation id="5546461542133609677">suarakan</translation>
<translation id="5630795885300617244">Ketuk dua kali ke kiri atau kanan untuk melewati 10 detik</translation>
<translation id="5677946354068040947">opsi lainnya</translation>
diff --git a/chromium/third_party/blink/public/strings/translations/blink_strings_kn.xtb b/chromium/third_party/blink/public/strings/translations/blink_strings_kn.xtb
index e61028a9d85..30d7062a471 100644
--- a/chromium/third_party/blink/public/strings/translations/blink_strings_kn.xtb
+++ b/chromium/third_party/blink/public/strings/translations/blink_strings_kn.xtb
@@ -114,7 +114,7 @@
<translation id="6281588256137006900">ಲೈಟà³â€Œà²¨à³†à²¸à³</translation>
<translation id="6310801910862476708">ಚಿತà³à²°à²¦à²²à³à²²à²¿à²¨ ಚಿತà³à²°à²¦à²¿à²‚ದ ನಿರà³à²—ಮಿಸಿ</translation>
<translation id="6398862346408813489">ತಿಂಗಳ ಆಯà³à²•à³† ಪà³à²¯à²¾à²¨à²²à³ ತೋರಿಸಿ</translation>
-<translation id="6404546809543547843">ಆಡಿಯೊ ಸಮಯ ಸà³à²•à³à²°à²¬à³à²¬à²°à³</translation>
+<translation id="6404546809543547843">ಆಡಿಯೋ ಸಮಯ ಸà³à²•à³à²°à²¬à³à²¬à²°à³</translation>
<translation id="6443871981718447451">ಮà³à²šà³à²šà²¿à²¦ ಶೀರà³à²·à²¿à²•à³†à²—ಳ ಮೆನà³à²µà²¨à³à²¨à³ ತೋರಿಸಿ</translation>
<translation id="6550675742724504774">ಆಯà³à²•à³†à²—ಳà³</translation>
<translation id="6572309429103589720">ಅಮಾನà³à²¯ ವà³à²¯à²¾à²•à²°à²£</translation>
diff --git a/chromium/third_party/blink/renderer/core/css/css_property_value_set.cc b/chromium/third_party/blink/renderer/core/css/css_property_value_set.cc
index e697f272b64..6387e8ea5ff 100644
--- a/chromium/third_party/blink/renderer/core/css/css_property_value_set.cc
+++ b/chromium/third_party/blink/renderer/core/css/css_property_value_set.cc
@@ -322,8 +322,8 @@ bool CSSPropertyValueSet::PropertyIsImportant(const T& property) const {
return PropertyAt(found_property_index).IsImportant();
return ShorthandIsImportant(property);
}
-template bool CSSPropertyValueSet::PropertyIsImportant<CSSPropertyID>(
- const CSSPropertyID&) const;
+template CORE_EXPORT bool CSSPropertyValueSet::PropertyIsImportant<
+ CSSPropertyID>(const CSSPropertyID&) const;
template bool CSSPropertyValueSet::PropertyIsImportant<AtomicString>(
const AtomicString&) const;
diff --git a/chromium/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/chromium/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index afbdf399376..c2b6979ad63 100644
--- a/chromium/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/chromium/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -1600,6 +1600,54 @@ TEST_F(StyleResolverTest, CascadeLayersAfterModifyingAnotherSheet) {
EXPECT_EQ(properties[1].types_.origin, CascadeOrigin::kAuthor);
}
+// https://crbug.com/1326791
+TEST_F(StyleResolverTest, CascadeLayersAddLayersWithImportantDeclarations) {
+ GetDocument().documentElement()->setInnerHTML(R"HTML(
+ <style id="addrule"></style>
+ <target></target>
+ )HTML");
+
+ UpdateAllLifecyclePhasesForTest();
+
+ GetDocument().getElementById("addrule")->appendChild(
+ GetDocument().createTextNode(
+ "@layer { target { font-size: 20px !important; } }"
+ "@layer { target { font-size: 10px !important; } }"));
+
+ UpdateAllLifecyclePhasesForTest();
+
+ ASSERT_TRUE(GetDocument().GetScopedStyleResolver()->GetCascadeLayerMap());
+
+ StyleResolverState state(GetDocument(),
+ *GetDocument().QuerySelector("target"));
+ SelectorFilter filter;
+ MatchResult match_result;
+ ElementRuleCollector collector(state.ElementContext(), StyleRecalcContext(),
+ filter, match_result, state.Style(),
+ EInsideLink::kNotInsideLink);
+ MatchAllRules(state, collector);
+ const auto& properties = match_result.GetMatchedProperties();
+ ASSERT_EQ(properties.size(), 2u);
+
+ // @layer { target { font-size: 20px !important } }
+ EXPECT_TRUE(properties[0].properties->HasProperty(CSSPropertyID::kFontSize));
+ EXPECT_TRUE(
+ properties[0].properties->PropertyIsImportant(CSSPropertyID::kFontSize));
+ EXPECT_EQ("20px", properties[0].properties->GetPropertyValue(
+ CSSPropertyID::kFontSize));
+ EXPECT_EQ(0u, properties[0].types_.layer_order);
+ EXPECT_EQ(properties[0].types_.origin, CascadeOrigin::kAuthor);
+
+ // @layer { target { font-size: 10px !important } }
+ EXPECT_TRUE(properties[1].properties->HasProperty(CSSPropertyID::kFontSize));
+ EXPECT_TRUE(
+ properties[1].properties->PropertyIsImportant(CSSPropertyID::kFontSize));
+ EXPECT_EQ("10px", properties[1].properties->GetPropertyValue(
+ CSSPropertyID::kFontSize));
+ EXPECT_EQ(1u, properties[1].types_.layer_order);
+ EXPECT_EQ(properties[1].types_.origin, CascadeOrigin::kAuthor);
+}
+
// TODO(crbug.com/1095765): We should have a WPT for this test case, but
// currently Blink web test runner can't test @page rules in WPT.
TEST_F(StyleResolverTest, CascadeLayersAndPageRules) {
diff --git a/chromium/third_party/blink/renderer/core/css/style_engine.cc b/chromium/third_party/blink/renderer/core/css/style_engine.cc
index afe290fa889..80a40073853 100644
--- a/chromium/third_party/blink/renderer/core/css/style_engine.cc
+++ b/chromium/third_party/blink/renderer/core/css/style_engine.cc
@@ -2054,14 +2054,15 @@ void StyleEngine::ApplyRuleSetChanges(
// - If new sheets were appended to existing ones, start appending after the
// common prefix, and rebuild CascadeLayerMap only if layers are changed.
// - For other diffs, reset author style and re-add all sheets for the
- // TreeScope. If there is an existing CascadeLayerMap, rebuild it.
+ // TreeScope. If new sheets need a CascadeLayerMap, rebuild it.
if (new_style_sheets.IsEmpty()) {
rebuild_cascade_layer_map = false;
ResetAuthorStyle(tree_scope);
} else if (change == kActiveSheetsAppended) {
append_start_index = old_style_sheets.size();
} else {
- rebuild_cascade_layer_map = scoped_resolver->HasCascadeLayerMap();
+ rebuild_cascade_layer_map = (changed_rule_flags & kLayerRules) ||
+ scoped_resolver->HasCascadeLayerMap();
scoped_resolver->ResetStyle();
}
}
diff --git a/chromium/third_party/blink/renderer/core/frame/local_frame_view.cc b/chromium/third_party/blink/renderer/core/frame/local_frame_view.cc
index 0d754ddf2f3..8a9dabd602b 100644
--- a/chromium/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/chromium/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1892,6 +1892,8 @@ void LocalFrameView::PerformPostLayoutTasks(bool visual_viewport_size_changed) {
}
}
+ UpdateDocumentAnnotatedRegions();
+
GetLayoutView()->EnclosingLayer()->UpdateLayerPositionsAfterLayout();
frame_->Selection().DidLayout();
@@ -3237,7 +3239,6 @@ void LocalFrameView::UpdateStyleAndLayout() {
PerformPostLayoutTasks(visual_viewport_size_changed);
GetFrame().GetDocument()->LayoutUpdated();
}
- UpdateDocumentAnnotatedRegions();
UpdateGeometriesIfNeeded();
}
diff --git a/chromium/third_party/blink/renderer/core/layout/layout_box.cc b/chromium/third_party/blink/renderer/core/layout/layout_box.cc
index c9684117de0..cf438c59122 100644
--- a/chromium/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/chromium/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3874,9 +3874,11 @@ const NGLayoutResult* LayoutBox::CachedLayoutResult(
}
// Multi-cols behave differently between the initial column balancing
- // pass, and the regular pass (specifically when forced breaks are
- // present), we just miss the cache for these cases.
+ // pass, and the regular pass (specifically when forced breaks or OOFs
+ // are present), we just miss the cache for these cases.
if (old_space.IsInitialColumnBalancingPass()) {
+ if (physical_fragment.HasOutOfFlowInFragmentainerSubtree())
+ return nullptr;
if (auto* block = DynamicTo<LayoutBlock>(this)) {
if (block->IsFragmentationContextRoot())
return nullptr;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index d0c9eb1f0e6..23f4a0b503a 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -448,6 +448,12 @@ void NGBoxFragmentBuilder::PropagateBreakInfo(
} else {
DCHECK(!child_layout_result.ColumnSpannerPath());
}
+
+ if (!child_box_fragment->IsFragmentainerBox() &&
+ !HasOutOfFlowInFragmentainerSubtree()) {
+ SetHasOutOfFlowInFragmentainerSubtree(
+ child_box_fragment->HasOutOfFlowInFragmentainerSubtree());
+ }
}
void NGBoxFragmentBuilder::PropagateChildBreakValues(
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 271d70661cf..f6984a418c6 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -184,6 +184,16 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder {
has_out_of_flow_fragment_child_ = has_out_of_flow_fragment_child;
}
+ bool HasOutOfFlowInFragmentainerSubtree() const {
+ return has_out_of_flow_in_fragmentainer_subtree_;
+ }
+
+ void SetHasOutOfFlowInFragmentainerSubtree(
+ bool has_out_of_flow_in_fragmentainer_subtree) {
+ has_out_of_flow_in_fragmentainer_subtree_ =
+ has_out_of_flow_in_fragmentainer_subtree;
+ }
+
void SwapOutOfFlowPositionedCandidates(
HeapVector<NGLogicalOutOfFlowPositionedNode>* candidates);
@@ -435,6 +445,7 @@ class CORE_EXPORT NGContainerFragmentBuilder : public NGFragmentBuilder {
bool has_oof_candidate_that_needs_block_offset_adjustment_ = false;
bool has_out_of_flow_fragment_child_ = false;
+ bool has_out_of_flow_in_fragmentainer_subtree_ = false;
#if DCHECK_IS_ON()
bool is_may_have_descendant_above_block_start_explicitly_set_ = false;
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 352e63de43e..0e8779b6f93 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -753,20 +753,26 @@ void NGOutOfFlowLayoutPart::LayoutCandidates(
SaveStaticPositionOnPaintLayer(layout_box, candidate.static_position);
if (IsContainingBlockForCandidate(candidate) &&
(!only_layout || layout_box == only_layout)) {
- if (has_block_fragmentation_ &&
- !container_builder_->IsInitialColumnBalancingPass()) {
- // As an optimization, only populate legacy positioned objects lists
- // when inside a fragmentation context root, since otherwise we can
- // just look at the children in the fragment tree.
- if (layout_box != only_layout)
- container_builder_->InsertLegacyPositionedObject(candidate.Node());
- NGLogicalOOFNodeForFragmentation fragmentainer_descendant(candidate);
- container_builder_->AdjustFragmentainerDescendant(
- fragmentainer_descendant);
- container_builder_->AdjustFixedposContainingBlockForInnerMulticols();
- container_builder_->AddOutOfFlowFragmentainerDescendant(
- fragmentainer_descendant);
- continue;
+ if (has_block_fragmentation_) {
+ container_builder_->SetHasOutOfFlowInFragmentainerSubtree(true);
+ if (!container_builder_->IsInitialColumnBalancingPass()) {
+ // As an optimization, only populate legacy positioned objects lists
+ // when inside a fragmentation context root, since otherwise we can
+ // just look at the children in the fragment tree.
+ if (layout_box != only_layout) {
+ container_builder_->InsertLegacyPositionedObject(
+ candidate.Node());
+ }
+ NGLogicalOOFNodeForFragmentation fragmentainer_descendant(
+ candidate);
+ container_builder_->AdjustFragmentainerDescendant(
+ fragmentainer_descendant);
+ container_builder_
+ ->AdjustFixedposContainingBlockForInnerMulticols();
+ container_builder_->AddOutOfFlowFragmentainerDescendant(
+ fragmentainer_descendant);
+ continue;
+ }
}
NodeInfo node_info = SetupNodeInfo(candidate);
NodeToLayout node_to_layout = {node_info,
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index 2f59f0f3ad1..fca4867af65 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -357,6 +357,8 @@ NGPhysicalFragment::NGPhysicalFragment(NGContainerFragmentBuilder* builder,
!builder->oof_positioned_fragmentainer_descendants_.IsEmpty() ||
!builder->multicols_with_pending_oofs_.IsEmpty()),
has_out_of_flow_fragment_child_(builder->HasOutOfFlowFragmentChild()),
+ has_out_of_flow_in_fragmentainer_subtree_(
+ builder->HasOutOfFlowInFragmentainerSubtree()),
break_token_(std::move(builder->break_token_)),
oof_data_(builder->oof_positioned_descendants_.IsEmpty() &&
!has_fragmented_out_of_flow_data_
@@ -433,6 +435,8 @@ NGPhysicalFragment::NGPhysicalFragment(const NGPhysicalFragment& other,
has_last_baseline_(other.has_last_baseline_),
has_fragmented_out_of_flow_data_(other.has_fragmented_out_of_flow_data_),
has_out_of_flow_fragment_child_(other.has_out_of_flow_fragment_child_),
+ has_out_of_flow_in_fragmentainer_subtree_(
+ other.has_out_of_flow_in_fragmentainer_subtree_),
base_direction_(other.base_direction_),
break_token_(other.break_token_),
oof_data_(other.oof_data_ ? other.CloneOutOfFlowData() : nullptr) {
diff --git a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index 59f67c0c133..f3097e56ff8 100644
--- a/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/chromium/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -611,6 +611,13 @@ class CORE_EXPORT NGPhysicalFragment
return has_out_of_flow_fragment_child_;
}
+ // If there is an OOF contained within a fragmentation context, this will
+ // return true for all fragments in the chain from the OOF's CB to the
+ // fragmentainer that the CB resides in.
+ bool HasOutOfFlowInFragmentainerSubtree() const {
+ return has_out_of_flow_in_fragmentainer_subtree_;
+ }
+
bool HasOutOfFlowPositionedDescendants() const {
return oof_data_ && !oof_data_->oof_positioned_descendants.IsEmpty();
}
@@ -709,6 +716,7 @@ class CORE_EXPORT NGPhysicalFragment
unsigned has_last_baseline_ : 1;
const unsigned has_fragmented_out_of_flow_data_ : 1;
const unsigned has_out_of_flow_fragment_child_ : 1;
+ const unsigned has_out_of_flow_in_fragmentainer_subtree_ : 1;
// The following are only used by NGPhysicalLineBoxFragment.
unsigned base_direction_ : 1; // TextDirection
diff --git a/chromium/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc b/chromium/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
index 264791c56ca..5f2255cce8b 100644
--- a/chromium/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
+++ b/chromium/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
@@ -396,7 +396,8 @@ void GPUBuffer::DetachMappedArrayBuffers(v8::Isolate* isolate) {
DCHECK(array_buffer->IsDetachable(isolate));
array_buffer->DetachContents(isolate);
- DCHECK(array_buffer->IsDetached());
+ // TODO(crbug.com/1326210): Temporary CHECK to prevent aliased array buffers.
+ CHECK(array_buffer->IsDetached());
}
mapped_array_buffers_.clear();
}
diff --git a/chromium/third_party/crashpad/README.chromium b/chromium/third_party/crashpad/README.chromium
index f220d2d01a8..911f692bdc3 100644
--- a/chromium/third_party/crashpad/README.chromium
+++ b/chromium/third_party/crashpad/README.chromium
@@ -46,3 +46,7 @@ Local Modifications:
- CloseMultipleNowOrOnExec has been updated to invoke the new
base::subtle::ResetFDOwnership() API
- FALLTHROUGH macro has been replaced with C++17 attribute [[fallthrough]]
+ - M102 CP 4581a355b1
+ 94242690d57b ios: Check dyld_image_info->imageFilePath for nullptr
+ 4581a355b17e ios: Limit depth of intermediate dump parser
+
diff --git a/chromium/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc b/chromium/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
index 88d5eb0a6e5..2869c2174f4 100644
--- a/chromium/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
+++ b/chromium/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
@@ -963,10 +963,12 @@ void InProcessIntermediateDumpHandler::WriteModuleInfo(
return;
}
- WriteProperty(writer,
- IntermediateDumpKey::kName,
- image->imageFilePath,
- strlen(image->imageFilePath));
+ if (image->imageFilePath) {
+ WriteProperty(writer,
+ IntermediateDumpKey::kName,
+ image->imageFilePath,
+ strlen(image->imageFilePath));
+ }
uint64_t address = FromPointerCast<uint64_t>(image->imageLoadAddress);
WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
WriteProperty(
@@ -976,7 +978,12 @@ void InProcessIntermediateDumpHandler::WriteModuleInfo(
{
IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
- WriteProperty(writer, IntermediateDumpKey::kName, image_infos->dyldPath);
+ if (image_infos->dyldPath) {
+ WriteProperty(writer,
+ IntermediateDumpKey::kName,
+ image_infos->dyldPath,
+ strlen(image_infos->dyldPath));
+ }
uint64_t address =
FromPointerCast<uint64_t>(image_infos->dyldImageLoadAddress);
WriteProperty(writer, IntermediateDumpKey::kAddress, &address);
diff --git a/chromium/third_party/crashpad/crashpad/snapshot/BUILD.gn b/chromium/third_party/crashpad/crashpad/snapshot/BUILD.gn
index ea2412a0bf6..b331598283b 100644
--- a/chromium/third_party/crashpad/crashpad/snapshot/BUILD.gn
+++ b/chromium/third_party/crashpad/crashpad/snapshot/BUILD.gn
@@ -492,6 +492,7 @@ bundle_data("snapshot_test_ios_data") {
sources = [
"ios/testdata/crash-1fa088dda0adb41459d063078a0f384a0bb8eefa",
"ios/testdata/crash-5726011582644224",
+ "ios/testdata/crash-6605504629637120",
]
outputs = [ "{{bundle_resources_dir}}/crashpad_test_data/" +
diff --git a/chromium/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc b/chromium/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
index 5f56082eb14..1a994bdb834 100644
--- a/chromium/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
+++ b/chromium/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
@@ -198,14 +198,16 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
}
}
- void WriteModules(IOSIntermediateDumpWriter* writer) {
+ void WriteModules(IOSIntermediateDumpWriter* writer, bool has_module_path) {
IOSIntermediateDumpWriter::ScopedArray moduleArray(writer, Key::kModules);
for (uint32_t image_index = 0; image_index < 2; ++image_index) {
IOSIntermediateDumpWriter::ScopedArrayMap modules(writer);
- constexpr char image_file[] = "/path/to/module";
- EXPECT_TRUE(
- writer->AddProperty(Key::kName, image_file, strlen(image_file)));
+ if (has_module_path) {
+ constexpr char image_file[] = "/path/to/module";
+ EXPECT_TRUE(
+ writer->AddProperty(Key::kName, image_file, strlen(image_file)));
+ }
uint64_t address = 0;
uint64_t vmsize = 1;
@@ -241,12 +243,16 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
}
}
- void ExpectModules(const std::vector<const ModuleSnapshot*>& modules) {
+ void ExpectModules(const std::vector<const ModuleSnapshot*>& modules,
+ bool expect_module_path) {
for (auto module : modules) {
EXPECT_EQ(module->GetModuleType(),
ModuleSnapshot::kModuleTypeSharedLibrary);
- EXPECT_STREQ(module->Name().c_str(), "/path/to/module");
- EXPECT_STREQ(module->DebugFileName().c_str(), "module");
+
+ if (expect_module_path) {
+ EXPECT_STREQ(module->Name().c_str(), "/path/to/module");
+ EXPECT_STREQ(module->DebugFileName().c_str(), "module");
+ }
UUID uuid;
uint32_t age;
module->UUIDAndAge(&uuid, &age);
@@ -424,7 +430,8 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
EXPECT_STREQ(daylight_name.c_str(), "Daylight");
}
- void ExpectSnapshot(const ProcessSnapshot& snapshot) {
+ void ExpectSnapshot(const ProcessSnapshot& snapshot,
+ bool expect_module_path) {
EXPECT_EQ(snapshot.ProcessID(), 2);
EXPECT_EQ(snapshot.ParentProcessID(), 1);
@@ -447,7 +454,7 @@ class ProcessSnapshotIOSIntermediateDumpTest : public testing::Test {
ExpectSystem(*snapshot.System());
ExpectThreads(snapshot.Threads());
- ExpectModules(snapshot.Modules());
+ ExpectModules(snapshot.Modules(), expect_module_path);
ExpectMachException(*snapshot.Exception());
}
@@ -626,14 +633,14 @@ TEST_F(ProcessSnapshotIOSIntermediateDumpTest, ShortContext) {
WriteSystemInfo(writer());
WriteProcessInfo(writer());
WriteThreads(writer());
- WriteModules(writer());
+ WriteModules(writer(), /*has_module_path=*/false);
WriteMachException(writer(), true /* short_context=true*/);
}
ProcessSnapshotIOSIntermediateDump process_snapshot;
ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
EXPECT_FALSE(IsRegularFile(path()));
EXPECT_TRUE(DumpSnapshot(process_snapshot));
- ExpectSnapshot(process_snapshot);
+ ExpectSnapshot(process_snapshot, /*expect_module_path=*/false);
}
TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FullReport) {
@@ -644,14 +651,14 @@ TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FullReport) {
WriteSystemInfo(writer());
WriteProcessInfo(writer());
WriteThreads(writer());
- WriteModules(writer());
+ WriteModules(writer(), /*has_module_path=*/true);
WriteMachException(writer());
}
ProcessSnapshotIOSIntermediateDump process_snapshot;
ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), annotations()));
EXPECT_FALSE(IsRegularFile(path()));
EXPECT_TRUE(DumpSnapshot(process_snapshot));
- ExpectSnapshot(process_snapshot);
+ ExpectSnapshot(process_snapshot, /*expect_module_path=*/true);
}
TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FuzzTestCases) {
@@ -672,6 +679,11 @@ TEST_F(ProcessSnapshotIOSIntermediateDumpTest, FuzzTestCases) {
map = process_snapshot2.AnnotationsSimpleMap();
ASSERT_TRUE(map.find("crashpad_intermediate_dump_incomplete") != map.end());
EXPECT_EQ(map["crashpad_intermediate_dump_incomplete"], "yes");
+
+ fuzz_path = TestPaths::TestDataRoot().Append(
+ FILE_PATH_LITERAL("snapshot/ios/testdata/crash-6605504629637120"));
+ crashpad::internal::ProcessSnapshotIOSIntermediateDump process_snapshot3;
+ EXPECT_FALSE(process_snapshot3.InitializeWithFilePath(fuzz_path, {}));
}
} // namespace
diff --git a/chromium/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_reader.cc b/chromium/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_reader.cc
index 022133bce75..d9610f656fc 100644
--- a/chromium/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_reader.cc
+++ b/chromium/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_reader.cc
@@ -70,6 +70,12 @@ bool IOSIntermediateDumpReader::Parse(FileReaderInterface* reader,
}
while (reader->ReadExactly(&command, sizeof(Command))) {
+ constexpr int kMaxStackDepth = 10;
+ if (stack.size() > kMaxStackDepth) {
+ LOG(ERROR) << "Unexpected depth of intermediate dump data.";
+ return false;
+ }
+
IOSIntermediateDumpObject* parent = stack.top();
switch (command) {
case Command::kMapStart: {
diff --git a/chromium/third_party/node/node_modules.tar.gz.sha1 b/chromium/third_party/node/node_modules.tar.gz.sha1
index a12b8f05865..3eb591554f8 100644
--- a/chromium/third_party/node/node_modules.tar.gz.sha1
+++ b/chromium/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-799c063ed26f8754b5992e2dbc56d5e26d2e4476
+691ddf79fa889ba538a8e25f79dde7bc3175e409
diff --git a/chromium/third_party/node/node_modules/@types/d3-drag/index.d.ts b/chromium/third_party/node/node_modules/@types/d3-drag/index.d.ts
new file mode 100644
index 00000000000..c10d0bacafc
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3-drag/index.d.ts
@@ -0,0 +1,406 @@
+// Type definitions for D3JS d3-drag module 1.2
+// Project: https://github.com/d3/d3-drag/, https://d3js.org/d3-drag
+// Definitions by: Tom Wanzek <https://github.com/tomwanzek>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// TypeScript Version: 2.3
+
+// Last module patch version validated against: 1.2.5
+
+import { Selection, ValueFn } from 'd3-selection';
+
+// --------------------------------------------------------------------------
+// Shared Type Definitions and Interfaces
+// --------------------------------------------------------------------------
+
+/**
+ * DraggedElementBaseType serves as an alias for the 'minimal' data type which can be selected
+ * without 'd3-drag' (and related code in 'd3-selection') trying to use properties internally which would otherwise not
+ * be supported.
+ */
+export type DraggedElementBaseType = Element;
+
+/**
+ * Container element type usable for mouse/touch functions
+ */
+export type DragContainerElement = HTMLElement | SVGSVGElement | SVGGElement; // HTMLElement includes HTMLCanvasElement
+
+/**
+ * The subject datum should at a minimum expose x and y properties, so that the relative position
+ * of the subject and the pointer can be preserved during the drag gesture.
+ */
+export interface SubjectPosition {
+ /**
+ * x-coordinate
+ */
+ x: number;
+ /**
+ * y-coordinate
+ */
+ y: number;
+}
+
+/**
+ * A D3 Drag Behavior
+ *
+ * The first generic refers to the type of element to be dragged.
+ * The second generic refers to the type of the datum of the dragged element.
+ * The third generic refers to the type of the drag behavior subject.
+ *
+ * The subject of a drag gesture represents the thing being dragged.
+ * It is computed when an initiating input event is received,
+ * such as a mousedown or touchstart, immediately before the drag gesture starts.
+ * The subject is then exposed as event.subject on subsequent drag events for this gesture.
+ *
+ * The default subject is the datum of the element in the originating selection (see drag)
+ * that received the initiating input event; if this datum is undefined,
+ * an object representing the coordinates of the pointer is created.
+ * When dragging circle elements in SVG, the default subject is thus the datum of the circle being dragged.
+ * With Canvas, the default subject is the canvas element’s datum (regardless of where on the canvas you click).
+ * In this case, a custom subject accessor would be more appropriate,
+ * such as one that picks the closest circle to the mouse within a given search radius.
+ */
+export interface DragBehavior<GElement extends DraggedElementBaseType, Datum, Subject> extends Function {
+ /**
+ * Applies the drag behavior to the selected elements.
+ * This function is typically not invoked directly, and is instead invoked via selection.call.
+ *
+ * For details see: {@link https://github.com/d3/d3-drag#_drag}
+ *
+ * @param selection A D3 selection of elements.
+ * @param args Optional arguments to be passed in.
+ */
+ (selection: Selection<GElement, Datum, any, any>, ...args: any[]): void;
+
+ /**
+ * Returns the current container accessor function.
+ */
+ container(): ValueFn<GElement, Datum, DragContainerElement>;
+ /**
+ * Sets the container accessor to the specified function and returns the drag behavior.
+ *
+ * The container of a drag gesture determines the coordinate system of subsequent drag events,
+ * affecting event.x and event.y. The element returned by the container accessor is subsequently
+ * passed to d3.mouse or d3.touch, as appropriate, to determine the local coordinates of the pointer.
+ *
+ * The default container accessor returns the parent node of the element in the originating selection (see drag)
+ * that received the initiating input event. This is often appropriate when dragging SVG or HTML elements,
+ * since those elements are typically positioned relative to a parent. For dragging graphical elements with a Canvas,
+ * however, you may want to redefine the container as the initiating element itself, using "this" in the accessor
+ * function.
+ *
+ * @param accessor A container accessor function which is evaluated for each selected element,
+ * in order, being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element. The function returns the container element.
+ */
+ container(accessor: ValueFn<GElement, Datum, DragContainerElement>): this;
+ /**
+ * Sets the container accessor to the specified object and returns the drag behavior.
+ *
+ * The container of a drag gesture determines the coordinate system of subsequent drag events,
+ * affecting event.x and event.y. The element returned by the container accessor is subsequently
+ * passed to d3.mouse or d3.touch, as appropriate, to determine the local coordinates of the pointer.
+ *
+ * The default container accessor returns the parent node of the element in the originating selection (see drag)
+ * that received the initiating input event. This is often appropriate when dragging SVG or HTML elements,
+ * since those elements are typically positioned relative to a parent. For dragging graphical elements with a Canvas,
+ * however, you may want to redefine the container as the initiating element itself, such as drag.container(canvas).
+ *
+ * @param container Container element for the drag gesture.
+ */
+ container(container: DragContainerElement): this;
+
+ /**
+ * Returns the current filter function.
+ */
+ filter(): ValueFn<GElement, Datum, boolean>;
+
+ /**
+ * Sets the filter to the specified filter function and returns the drag behavior.
+ *
+ * If the filter returns falsey, the initiating event is ignored and no drag gesture is started.
+ * Thus, the filter determines which input events are ignored. The default filter ignores mousedown events on secondary buttons,
+ * since those buttons are typically intended for other purposes, such as the context menu.
+ *
+ * @param filterFn A filter function which is evaluated for each selected element,
+ * in order, being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element. The function returns a boolean value.
+ */
+ filter(filterFn: ValueFn<GElement, Datum, boolean>): this;
+
+ /**
+ * Returns the current touch support detector, which defaults to a function returning true,
+ * if the "ontouchstart" event is supported on the current element.
+ */
+ touchable(): ValueFn<GElement, Datum, boolean>;
+ /**
+ * Sets the touch support detector to the specified boolean value and returns the drag behavior.
+ *
+ * Touch event listeners are only registered if the detector returns truthy for the corresponding element when the drag behavior is applied.
+ * The default detector works well for most browsers that are capable of touch input, but not all; Chrome’s mobile device emulator, for example,
+ * fails detection.
+ *
+ * @param touchable A boolean value. true when touch event listeners should be applied to the corresponding element, otherwise false.
+ */
+ touchable(touchable: boolean): this;
+ /**
+ * Sets the touch support detector to the specified function and returns the drag behavior.
+ *
+ * Touch event listeners are only registered if the detector returns truthy for the corresponding element when the drag behavior is applied.
+ * The default detector works well for most browsers that are capable of touch input, but not all; Chrome’s mobile device emulator, for example,
+ * fails detection.
+ *
+ * @param touchable A touch support detector function, which returns true when touch event listeners should be applied to the corresponding element.
+ * The function is evaluated for each selected element to which the drag behavior was applied, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element. The function returns a boolean value.
+ */
+ touchable(touchable: ValueFn<GElement, Datum, boolean>): this;
+
+ /**
+ * Returns the current subject accessor functions.
+ */
+ subject(): ValueFn<GElement, Datum, Subject>;
+ /**
+ * Sets the subject accessor to the specified function and returns the drag behavior.
+ *
+ * The subject of a drag gesture represents the thing being dragged.
+ * It is computed when an initiating input event is received,
+ * such as a mousedown or touchstart, immediately before the drag gesture starts.
+ * The subject is then exposed as event.subject on subsequent drag events for this gesture.
+ *
+ * The default subject is the datum of the element in the originating selection (see drag)
+ * that received the initiating input event; if this datum is undefined,
+ * an object representing the coordinates of the pointer is created.
+ * When dragging circle elements in SVG, the default subject is thus the datum of the circle being dragged.
+ * With Canvas, the default subject is the canvas element’s datum (regardless of where on the canvas you click).
+ * In this case, a custom subject accessor would be more appropriate,
+ * such as one that picks the closest circle to the mouse within a given search radius.
+ *
+ *
+ *
+ * The subject of a drag gesture may not be changed after the gesture starts.
+ *
+ * During the evaluation of the subject accessor, d3.event is a beforestart drag event.
+ * Use event.sourceEvent to access the initiating input event and event.identifier to
+ * access the touch identifier. The event.x and event.y are relative to the container,
+ * and are computed using d3.mouse or d3.touch as appropriate.
+ *
+ * @param accessor An extent accessor function which is evaluated for each selected element,
+ * in order, being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element.The returned subject should be an object that exposes x and y properties,
+ * so that the relative position of the subject and the pointer can be preserved during the drag gesture.
+ * If the subject is null or undefined, no drag gesture is started for this pointer;
+ * however, other starting touches may yet start drag gestures.
+ */
+ subject(accessor: ValueFn<GElement, Datum, Subject>): this;
+
+ /**
+ * Return the current click distance threshold, which defaults to zero.
+ */
+ clickDistance(): number;
+ /**
+ * Set the maximum distance that the mouse can move between mousedown and mouseup that will trigger
+ * a subsequent click event. If at any point between mousedown and mouseup the mouse is greater than or equal to
+ * distance from its position on mousedown, the click event following mouseup will be suppressed.
+ *
+ * @param distance The distance threshold between mousedown and mouseup measured in client coordinates (event.clientX and event.clientY).
+ * The default is zero.
+ */
+ clickDistance(distance: number): this;
+
+ /**
+ * Return the first currently-assigned listener matching the specified typenames, if any.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ */
+ on(typenames: string): ValueFn<GElement, Datum, void> | undefined;
+ /**
+ * Remove the current event listeners for the specified typenames, if any, return the drag behavior.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ * @param listener Use null to remove the listener.
+ */
+ on(typenames: string, listener: null): this;
+ /**
+ * Set the event listener for the specified typenames and return the drag behavior.
+ * If an event listener was already registered for the same type and name,
+ * the existing listener is removed before the new listener is added.
+ * When a specified event is dispatched, each listener will be invoked with the same context and arguments as selection.on listeners.
+ *
+ * Changes to registered listeners via drag.on during a drag gesture do not affect the current drag gesture.
+ * Instead, you must use event.on, which also allows you to register temporary event listeners for the current drag gesture.
+ * Separate events are dispatched for each active pointer during a drag gesture.
+ * For example, if simultaneously dragging multiple subjects with multiple fingers, a start event is dispatched for each finger,
+ * even if both fingers start touching simultaneously.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ * @param listener An event listener function which is evaluated for each selected element,
+ * in order, being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element.
+ */
+ on(typenames: string, listener: ValueFn<GElement, Datum, void>): this;
+}
+
+/**
+ * Creates a new drag behavior. The returned behavior, drag, is both an object and a function, and is
+ * typically applied to selected elements via selection.call.
+ *
+ * Use this signature when using the default subject accessor.
+ *
+ * The first generic refers to the type of element to be dragged.
+ * The second generic refers to the type of the datum of the dragged element.
+ */
+export function drag<GElement extends DraggedElementBaseType, Datum>(): DragBehavior<GElement, Datum, Datum | SubjectPosition>;
+/**
+ * Creates a new drag behavior. The returned behavior, drag, is both an object and a function, and is
+ * typically applied to selected elements via selection.call.
+ *
+ * Use this signature when using a custom subject accessor.
+ *
+ * The first generic refers to the type of element to be dragged.
+ * The second generic refers to the type of the datum of the dragged element.
+ * The third generic refers to the type of the drag behavior subject.
+ */
+export function drag<GElement extends DraggedElementBaseType, Datum, Subject>(): DragBehavior<GElement, Datum, Subject>;
+
+/**
+ * D3 Drag event
+ *
+ * The first generic refers to the type of element to be dragged.
+ * The second generic refers to the type of the datum of the dragged element.
+ * The third generic refers to the type of the drag behavior subject.
+ */
+export interface D3DragEvent<GElement extends DraggedElementBaseType, Datum, Subject> {
+ /**
+ * The DragBehavior associated with the event
+ */
+ target: DragBehavior<GElement, Datum, Subject>;
+ /**
+ * The event type for the DragEvent
+ */
+ type: 'start' | 'drag' | 'end' | string; // Leave failsafe string type for cases like 'drag.foo'
+ /**
+ * The drag subject, defined by drag.subject.
+ */
+ subject: Subject;
+ /**
+ * The new x-coordinate of the subject, relative to the container
+ */
+ x: number;
+ /**
+ * The new y-coordinate of the subject, relative to the container
+ */
+ y: number;
+ /**
+ * The change in x-coordinate since the previous drag event.
+ */
+ dx: number;
+ /**
+ * The change in y-coordinate since the previous drag event.
+ */
+ dy: number;
+ /**
+ * The string “mouseâ€, or a numeric touch identifier.
+ */
+ identifier: 'mouse' | number;
+ /**
+ * The number of currently active drag gestures (on start and end, not including this one).
+ *
+ * The event.active field is useful for detecting the first start event and the last end event
+ * in a sequence of concurrent drag gestures: it is zero when the first drag gesture starts,
+ * and zero when the last drag gesture ends.
+ */
+ active: number;
+ /**
+ * The underlying input event, such as mousemove or touchmove.
+ */
+ sourceEvent: any;
+ /**
+ * Return the first currently-assigned listener matching the specified typenames, if any.
+ *
+ * Equivalent to drag.on, but only applies to the current drag gesture. Before the drag gesture starts,
+ * a copy of the current drag event listeners is made. This copy is bound to the current drag gesture
+ * and modified by event.on. This is useful for temporary listeners that only receive events for the current drag gesture.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ */
+ on(typenames: string): ValueFn<GElement, Datum, void> | undefined;
+ /**
+ * Remove the current event listeners for the specified typenames, if any, return the drag behavior.
+ *
+ * Equivalent to drag.on, but only applies to the current drag gesture. Before the drag gesture starts,
+ * a copy of the current drag event listeners is made. This copy is bound to the current drag gesture
+ * and modified by event.on. This is useful for temporary listeners that only receive events for the current drag gesture.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ * @param listener Use null to remove the listener.
+ */
+ on(typenames: string, listener: null): this;
+ /**
+ * Set the event listener for the specified typenames and return the drag behavior.
+ * If an event listener was already registered for the same type and name,
+ * the existing listener is removed before the new listener is added.
+ * When a specified event is dispatched, each listener will be invoked with the same context and arguments as selection.on listeners.
+ *
+ * Equivalent to drag.on, but only applies to the current drag gesture. Before the drag gesture starts,
+ * a copy of the current drag event listeners is made. This copy is bound to the current drag gesture
+ * and modified by event.on. This is useful for temporary listeners that only receive events for the current drag gesture.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace.
+ * Each typename is a type, optionally followed by a period (.) and a name, such as "drag.foo"" and "drag.bar";
+ * the name allows multiple listeners to be registered for the same type. The type must be one of the following:
+ * start (after a new pointer becomes active [on mousedown or touchstart]), drag (after an active pointer moves [on mousemove or touchmove], or
+ * end (after an active pointer becomes inactive [on mouseup, touchend or touchcancel].)
+ * @param listener An event listener function which is evaluated for each selected element,
+ * in order, being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element.
+ */
+ on(typenames: string, listener: ValueFn<GElement, Datum, void>): this;
+}
+
+/**
+ * Prevents native drag-and-drop and text selection on the specified window.
+ * As an alternative to preventing the default action of mousedown events,
+ * this method prevents undesirable default actions following mousedown. In supported browsers,
+ * this means capturing dragstart and selectstart events, preventing the associated default actions,
+ * and immediately stopping their propagation. In browsers that do not support selection events,
+ * the user-select CSS property is set to none on the document element.
+ * This method is intended to be called on mousedown, followed by d3.dragEnable on mouseup.
+ *
+ * @param window The window for which drag should be disabled.
+ */
+export function dragDisable(window: Window): void;
+
+/**
+ * Allows native drag-and-drop and text selection on the specified window; undoes the effect of d3.dragDisable.
+ * This method is intended to be called on mouseup, preceded by d3.dragDisable on mousedown.
+ * If noclick is true, this method also temporarily suppresses click events.
+ * The suppression of click events expires after a zero-millisecond timeout,
+ * such that it only suppress the click event that would immediately follow the current mouseup event, if any.
+ *
+ * @param window The window for which drag should be (re-)enabled.
+ * @param noClick An optional flag. If noclick is true, this method also temporarily suppresses click events.
+ */
+export function dragEnable(window: Window, noClick?: boolean): void;
diff --git a/chromium/third_party/node/node_modules/@types/d3-force/index.d.ts b/chromium/third_party/node/node_modules/@types/d3-force/index.d.ts
new file mode 100755
index 00000000000..a114693ab14
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3-force/index.d.ts
@@ -0,0 +1,1249 @@
+// Type definitions for D3JS d3-force module 1.2
+// Project: https://github.com/d3/d3-force/, https://d3js.org/d3-force
+// Definitions by: Tom Wanzek <https://github.com/tomwanzek>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// denisname <https://github.com/denisname>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+// Last module patch version validated against: 1.2.1
+
+// -----------------------------------------------------------------------
+// Force Simulation
+// -----------------------------------------------------------------------
+
+/**
+ * The base data structure for the datum of a Simulation Node.
+ * The optional properties contained in this data structure are internally assigned
+ * by the Simulation upon (re-)initialization.
+ *
+ * When defining a data type to use for node data, it should be an extension of this interface
+ * and respect the already "earmarked" properties used by the simulation.
+ *
+ * IMPORTANT: Prior to initialization, the following properties are optional: index, x, y, vx, and vy.
+ * After initialization they will be defined. The optional properties fx and fy are ONLY defined,
+ * if the node's position has been fixed.
+ */
+export interface SimulationNodeDatum {
+ /**
+ * Node’s zero-based index into nodes array. This property is set during the initialization process of a simulation.
+ */
+ index?: number | undefined;
+ /**
+ * Node’s current x-position
+ */
+ x?: number | undefined;
+ /**
+ * Node’s current y-position
+ */
+ y?: number | undefined;
+ /**
+ * Node’s current x-velocity
+ */
+ vx?: number | undefined;
+ /**
+ * Node’s current y-velocity
+ */
+ vy?: number | undefined;
+ /**
+ * Node’s fixed x-position (if position was fixed)
+ */
+ fx?: number | null | undefined;
+ /**
+ * Node’s fixed y-position (if position was fixed)
+ */
+ fy?: number | null | undefined;
+}
+
+/**
+ * The base data structure for the datum of a Simulation Link, as used by ForceLink.
+ * The optional properties contained in this data structure are internally assigned
+ * by when initializing with ForceLink.links(...)
+ *
+ *
+ * IMPORTANT: The source and target properties may be internally mutated in type during the
+ * ForceLink initialization process (possibly being changed from a node index in the nodes array,
+ * or a node id string to the simulation node object which was mapped in using the current
+ * ForceLink.id(...) accessor function.)
+ */
+export interface SimulationLinkDatum<NodeDatum extends SimulationNodeDatum> {
+ /**
+ * Link’s source node.
+ * For convenience, a link’s source and target properties may be initialized using numeric or string identifiers rather than object references; see link.id.
+ * When the link force is initialized (or re-initialized, as when the nodes or links change), any link.source or link.target property which is not an object
+ * is replaced by an object reference to the corresponding node with the given identifier.
+ * After initialization, the source property represents the source node object.
+ */
+ source: NodeDatum | string | number;
+ /**
+ * Link’s source link
+ * For convenience, a link’s source and target properties may be initialized using numeric or string identifiers rather than object references; see link.id.
+ * When the link force is initialized (or re-initialized, as when the nodes or links change), any link.source or link.target property which is not an object
+ * is replaced by an object reference to the corresponding node with the given identifier.
+ * After initialization, the target property represents the target node object.
+ */
+ target: NodeDatum | string | number;
+ /**
+ * The zero-based index into the links array. Internally generated when calling ForceLink.links(...)
+ */
+ index?: number | undefined;
+}
+
+/**
+ * A Force Simulation
+ *
+ * The first generic refers to the type of the datum associated with a node in the simulation.
+ * The second generic refers to the type of the datum associated with a link in the simulation, if applicable.
+ *
+ */
+export interface Simulation<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> {
+ /**
+ * Restart the simulation’s internal timer and return the simulation.
+ * In conjunction with simulation.alphaTarget or simulation.alpha, this method can be used to “reheat†the simulation during interaction,
+ * such as when dragging a node, or to resume the simulation after temporarily pausing it with simulation.stop.
+ */
+ restart(): this;
+
+ /**
+ * Stop the simulation’s internal timer, if it is running, and return the simulation. If the timer is already stopped, this method does nothing.
+ * This method is useful for running the simulation manually; see simulation.tick.
+ */
+ stop(): this;
+
+ /**
+ * Manually steps the simulation by the specified number of *iterations*, and returns the simulation. If *iterations* is not specified, it defaults to 1 (single step).
+ *
+ * For each iteration, it increments the current alpha by (alphaTarget - alpha) × alphaDecay; then invokes each registered force, passing the new alpha;
+ * then decrements each node’s velocity by velocity × velocityDecay; lastly increments each node’s position by velocity.
+ *
+ * This method does not dispatch events; events are only dispatched by the internal timer when the simulation is started automatically upon
+ * creation or by calling simulation.restart. The natural number of ticks when the simulation is started is
+ * ⌈log(alphaMin) / log(1 - alphaDecay)⌉; by default, this is 300.
+ */
+ tick(iterations?: number): this;
+
+ /**
+ * Returns the simulation’s array of nodes as specified to the constructor.
+ */
+ nodes(): NodeDatum[];
+ /**
+ * Set the simulation’s nodes to the specified array of objects, initialize their positions and velocities if necessary,
+ * and then re-initialize any bound forces; Returns the simulation.
+ *
+ * Each node must be an object. The following properties are assigned by the simulation:
+ * - index (the node’s zero-based index into nodes)
+ * - x (the node’s current x-position)
+ * - y (the node’s current y-position)
+ * - vx (the node’s current x-velocity)
+ * - vy (the node’s current y-velocity)
+ *
+ * The position [x,y] and velocity [vx,vy] may be subsequently modified by forces and by the simulation.
+ * If either vx or vy is NaN, the velocity is initialized to [0,0]. If either x or y is NaN, the position is initialized in a phyllotaxis arrangement,
+ * so chosen to ensure a deterministic, uniform distribution around the origin.
+ *
+ * To fix a node in a given position, you may specify two additional properties:
+ * - fx (the node’s fixed x-position)
+ * - fy (the node’s fixed y-position)
+ *
+ * At the end of each tick, after the application of any forces, a node with a defined node.fx has node.x reset to this value and node.vx set to zero;
+ * likewise, a node with a defined node.fy has node.y reset to this value and node.vy set to zero.
+ * To unfix a node that was previously fixed, set node.fx and node.fy to null, or delete these properties.
+ *
+ * If the specified array of nodes is modified, such as when nodes are added to or removed from the simulation,
+ * this method must be called again with the new (or changed) array to notify the simulation and bound forces of the change;
+ * the simulation does not make a defensive copy of the specified array.
+ */
+ nodes(nodesData: NodeDatum[]): this;
+
+ /**
+ * Return the current alpha of the simulation, which defaults to 1.
+ */
+ alpha(): number;
+ /**
+ * Set the current alpha to the specified number in the range [0,1] and return this simulation.
+ * The default is 1.
+ *
+ * @param alpha Current alpha of simulation.
+ */
+ alpha(alpha: number): this;
+
+ /**
+ * Return the current minimum alpha value, which defaults to 0.001.
+ */
+ alphaMin(): number;
+ /**
+ * Set the minimum alpha to the specified number in the range [0,1] and return this simulation.
+ * The default is 0.001. The simulation’s internal timer stops when the current alpha is less than the minimum alpha.
+ * The default alpha decay rate of ~0.0228 corresponds to 300 iterations.
+ *
+ * @param min Minimum alpha of simulation.
+ */
+ alphaMin(min: number): this;
+
+ /**
+ * Return the current alpha decay rate, which defaults to 0.0228… = 1 - pow(0.001, 1 / 300) where 0.001 is the default minimum alpha.
+ */
+ alphaDecay(): number;
+ /**
+ * Set the alpha decay rate to the specified number in the range [0,1] and return this simulation.
+ * The default is 0.0228… = 1 - pow(0.001, 1 / 300) where 0.001 is the default minimum alpha.
+ *
+ * The alpha decay rate determines how quickly the current alpha interpolates towards the desired target alpha;
+ * since the default target alpha is zero, by default this controls how quickly the simulation cools.
+ * Higher decay rates cause the simulation to stabilize more quickly, but risk getting stuck in a local minimum;
+ * lower values cause the simulation to take longer to run, but typically converge on a better layout.
+ * To have the simulation run forever at the current alpha, set the decay rate to zero;
+ * alternatively, set a target alpha greater than the minimum alpha.
+ *
+ * @param decay Alpha decay rate.
+ */
+ alphaDecay(decay: number): this;
+
+ /**
+ * Returns the current target alpha value, which defaults to 0.
+ */
+ alphaTarget(): number;
+ /**
+ * Set the current target alpha to the specified number in the range [0,1] and return this simulation.
+ * The default is 0.
+ *
+ * @param target Alpha target value.
+ */
+ alphaTarget(target: number): this;
+
+ /**
+ * Return the current target alpha value, which defaults to 0.4.
+ */
+ velocityDecay(): number;
+ /**
+ * Set the velocity decay factor to the specified number in the range [0,1] and return this simulation.
+ * The default is 0.4.
+ *
+ * The decay factor is akin to atmospheric friction; after the application of any forces during a tick,
+ * each node’s velocity is multiplied by 1 - decay. As with lowering the alpha decay rate,
+ * less velocity decay may converge on a better solution, but risks numerical instabilities and oscillation.
+ *
+ * @param decay Velocity Decay.
+ */
+ velocityDecay(decay: number): this;
+
+ /**
+ * Return the force with the specified name, or undefined if there is no such force.
+ * (By default, new simulations have no forces.)
+ *
+ * Given that it is in general not known, what type of force has been registered under
+ * a specified name, use the generic to cast the result to the appropriate type, if known.
+ *
+ * @param name Name of the registered force.
+ */
+ force<F extends Force<NodeDatum, LinkDatum>>(name: string): F| undefined;
+ /**
+ * Remove a previously registered force.
+ *
+ * @param name Name of the registered force.
+ * @param force Use null to remove force.
+ */
+ force(name: string, force: null): this;
+ /**
+ * Assign the force for the specified name and return this simulation.
+ * (By default, new simulations have no forces.)
+ *
+ * @param name Name to register the force under.
+ * @param force A force to use with the simulation.
+ */
+ force(name: string, force: Force<NodeDatum, LinkDatum>): this;
+
+ /**
+ * Return the node closest to the position [x,y] with the given search radius.
+ * If radius is not specified, it defaults to infinity.
+ * If there is no node within the search area, returns undefined.
+ *
+ * @param x x-coordinate
+ * @param y y-coordinate
+ * @param radius Optional search radius. Defaults to infinity.
+ */
+ find(x: number, y: number, radius?: number): NodeDatum | undefined;
+
+ /**
+ * Return the first currently-assigned listener matching the specified typenames, if any.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace. Each typename is a type,
+ * optionally followed by a period (.) and a name, such as "tick.foo" and "tick.bar"; the name allows multiple listeners to be registered for the same type.
+ * The type must be one of the following: "tick" (after each tick of the simulation’s internal timer) or
+ * "end" (after the simulation’s timer stops when alpha < alphaMin).
+ */
+ on(typenames: 'tick' | 'end' | string): ((this: Simulation<NodeDatum, LinkDatum>) => void) | undefined;
+ /**
+ * Remove the current event listeners for the specified typenames, if any, return the simulation.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace. Each typename is a type,
+ * optionally followed by a period (.) and a name, such as "tick.foo" and "tick.bar"; the name allows multiple listeners to be registered for the same type.
+ * The type must be one of the following: "tick" (after each tick of the simulation’s internal timer) or
+ * "end" (after the simulation’s timer stops when alpha < alphaMin).
+ * @param listener Use null to remove the listener.
+ */
+ on(typenames: 'tick' | 'end' | string, listener: null): this;
+ /**
+ * Set the event listener for the specified typenames and return this simulation.
+ * If an event listener was already registered for the same type and name,
+ * the existing listener is removed before the new listener is added.
+ * When a specified event is dispatched, each listener will be invoked with the this context as the simulation.
+ *
+ * The type must be one of the following:
+ * - tick [after each tick of the simulation’s internal timer]
+ * - end [after the simulation’s timer stops when alpha < alphaMin]
+ *
+ * Note that tick events are not dispatched when simulation.tick is called manually;
+ * events are only dispatched by the internal timer and are intended for interactive rendering of the simulation.
+ * To affect the simulation, register forces instead of modifying nodes’ positions or velocities inside a tick event listener.
+ *
+ * @param typenames The typenames is a string containing one or more typename separated by whitespace. Each typename is a type,
+ * optionally followed by a period (.) and a name, such as "tick.foo" and "tick.bar"; the name allows multiple listeners to be registered for the same type.
+ * The type must be one of the following: "tick" (after each tick of the simulation’s internal timer) or
+ * "end" (after the simulation’s timer stops when alpha < alphaMin).
+ * @param listener An event listener function which is invoked with the this context of the simulation.
+ */
+ on(typenames: 'tick' | 'end' | string, listener: (this: this) => void): this;
+}
+
+/**
+ * Create a new simulation with the specified array of nodes and no forces.
+ * If nodes is not specified, it defaults to the empty array.
+ * The simulator starts automatically; use simulation.on to listen for tick events as the simulation runs.
+ * If you wish to run the simulation manually instead, call simulation.stop, and then call simulation.tick as desired.
+ *
+ * Use this signature, when creating a simulation WITHOUT link force(s).
+ *
+ * The generic refers to the type of the data for a node.
+ *
+ * @param nodesData Optional array of nodes data, defaults to empty array.
+ */
+export function forceSimulation<NodeDatum extends SimulationNodeDatum>(nodesData?: NodeDatum[]): Simulation<NodeDatum, undefined>;
+/**
+ * Create a new simulation with the specified array of nodes and no forces.
+ * If nodes is not specified, it defaults to the empty array.
+ * The simulator starts automatically; use simulation.on to listen for tick events as the simulation runs.
+ * If you wish to run the simulation manually instead, call simulation.stop, and then call simulation.tick as desired.
+ *
+ * Use this signature, when creating a simulation WITH link force(s).
+ *
+ * The first generic refers to the type of data for a node.
+ * The second generic refers to the type of data for a link.
+ *
+ * @param nodesData Optional array of nodes data, defaults to empty array.
+ */
+export function forceSimulation<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum>>(nodesData?: NodeDatum[]): Simulation<NodeDatum, LinkDatum>;
+
+// ----------------------------------------------------------------------
+// Forces
+// ----------------------------------------------------------------------
+
+/**
+ * A force is simply a function that modifies nodes’ positions or velocities; in this context, a force can apply a classical physical force such as electrical charge or gravity,
+ * or it can resolve a geometric constraint, such as keeping nodes within a bounding box or keeping linked nodes a fixed distance apart.
+ *
+ * Forces typically read the node’s current position [x,y] and then add to (or subtract from) the node’s velocity [vx,vy].
+ * However, forces may also “peek ahead†to the anticipated next position of the node, [x + vx,y + vy]; this is necessary for resolving geometric constraints through iterative relaxation.
+ * Forces may also modify the position directly, which is sometimes useful to avoid adding energy to the simulation, such as when recentering the simulation in the viewport.
+ *
+ * Forces may optionally implement force.initialize to receive the simulation’s array of nodes.
+ */
+export interface Force<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum> | undefined> {
+ /**
+ * Apply this force, optionally observing the specified alpha.
+ * Typically, the force is applied to the array of nodes previously passed to force.initialize,
+ * however, some forces may apply to a subset of nodes, or behave differently.
+ * For example, d3.forceLink applies to the source and target of each link.
+ */
+ (alpha: number): void;
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize?(nodes: NodeDatum[]): void;
+}
+
+// Centering ------------------------------------------------------------
+
+/**
+ * The centering force translates nodes uniformly so that the mean position of all nodes
+ * (the center of mass if all nodes have equal weight) is at the given position [x,y].
+ * This force modifies the positions of nodes on each application; it does not modify velocities,
+ * as doing so would typically cause the nodes to overshoot and oscillate around the desired center.
+ * This force helps keeps nodes in the center of the viewport, and unlike the positioning force,
+ * it does not distort their relative positions.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceCenter<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Return the current x-coordinate of the centering position, which defaults to zero.
+ */
+ x(): number;
+ /**
+ * Set the x-coordinate of the centering position.
+ *
+ * @param x x-coordinate.
+ */
+ x(x: number): this;
+
+ /**
+ * Return the current y-coordinate of the centering position, which defaults to zero.
+ */
+ y(): number;
+ /**
+ * Set the y-coordinate of the centering position.
+ *
+ * @param y y-coordinate.
+ */
+ y(y: number): this;
+}
+
+/**
+ * Create a new centering force with the specified x- and y- coordinates.
+ * If x and y are not specified, they default to [0,0].
+ *
+ * The centering force translates nodes uniformly so that the mean position of all nodes
+ * (the center of mass if all nodes have equal weight) is at the given position [x,y].
+ * This force modifies the positions of nodes on each application; it does not modify velocities,
+ * as doing so would typically cause the nodes to overshoot and oscillate around the desired center.
+ * This force helps keeps nodes in the center of the viewport, and unlike the positioning force,
+ * it does not distort their relative positions.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param x An optional x-coordinate for the centering position, defaults to 0.
+ * @param y An optional y-coordinate for the centering position, defaults to 0.
+ */
+export function forceCenter<NodeDatum extends SimulationNodeDatum>(x?: number, y?: number): ForceCenter<NodeDatum>;
+
+// Collision ------------------------------------------------------------
+
+/**
+ * The collision force treats nodes as circles with a given radius, rather than points, and prevents nodes from overlapping.
+ * More formally, two nodes a and b are separated so that the distance between a and b is at least radius(a) + radius(b).
+ * To reduce jitter, this is by default a “soft†constraint with a configurable strength and iteration count.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceCollide<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Returns the current radius accessor function.
+ */
+ radius(): (node: NodeDatum, i: number, nodes: NodeDatum[]) => number;
+ /**
+ * Set the radius used in collision detection to a constant number for each node.
+ *
+ * The constant is internally wrapped into a radius accessor function.
+ *
+ * The radius accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the radius of each node is only recomputed
+ * when the force is initialized or when this method is called with a new radius, and not on every application of the force.
+ *
+ * @param radius A constant radius for each node.
+ */
+ radius(radius: number): this;
+ /**
+ * Set the radius accessor function determining the radius for each node in collision detection.
+ *
+ * The radius accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the radius of each node is only recomputed
+ * when the force is initialized or when this method is called with a new radius, and not on every application of the force.
+ *
+ * @param radius A radius accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns a radius.
+ */
+ radius(radius: (node: NodeDatum, i: number, nodes: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current strength, which defaults to 0.7.
+ */
+ strength(): number;
+ /**
+ * Set the force strength to the specified number in the range [0,1] and return this force.
+ * The default strength is 0.7.
+ *
+ * Overlapping nodes are resolved through iterative relaxation.
+ * For each node, the other nodes that are anticipated to overlap at the next tick (using the anticipated positions [x + vx,y + vy]) are determined;
+ * the node’s velocity is then modified to push the node out of each overlapping node.
+ * The change in velocity is dampened by the force’s strength such that the resolution of simultaneous overlaps can be blended together to find a stable solution.
+ *
+ * @param strength Strength.
+ */
+ strength(strength: number): this;
+
+ /**
+ * Return the current iteration count which defaults to 1.
+ */
+ iterations(): number;
+ /**
+ * Sets the number of iterations per application to the specified number and return this force.
+ *
+ * Increasing the number of iterations greatly increases the rigidity of the constraint and avoids partial overlap of nodes,
+ * but also increases the runtime cost to evaluate the force.
+ *
+ * @param iterations Number of iterations.
+ */
+ iterations(iterations: number): this;
+}
+
+/**
+ * Creates a new circle collision force with the default radius one for all nodes.
+ *
+ * The collision force treats nodes as circles with a given radius, rather than points, and prevents nodes from overlapping.
+ * More formally, two nodes a and b are separated so that the distance between a and b is at least radius(a) + radius(b).
+ * To reduce jitter, this is by default a “soft†constraint with a configurable strength and iteration count.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export function forceCollide<NodeDatum extends SimulationNodeDatum>(): ForceCollide<NodeDatum>;
+/**
+ * Create a new circle collision force with the specified constant radius for all nodes.
+ *
+ * The collision force treats nodes as circles with a given radius, rather than points, and prevents nodes from overlapping.
+ * More formally, two nodes a and b are separated so that the distance between a and b is at least radius(a) + radius(b).
+ * To reduce jitter, this is by default a “soft†constraint with a configurable strength and iteration count.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param radius A constant radius for each node.
+ */
+export function forceCollide<NodeDatum extends SimulationNodeDatum>(radius: number): ForceCollide<NodeDatum>;
+/**
+ * Creates a new circle collision force with the specified radius accessor function.
+ *
+ * The collision force treats nodes as circles with a given radius, rather than points, and prevents nodes from overlapping.
+ * More formally, two nodes a and b are separated so that the distance between a and b is at least radius(a) + radius(b).
+ * To reduce jitter, this is by default a “soft†constraint with a configurable strength and iteration count.
+ *
+ * The radius accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the radius of each node is only recomputed
+ * when the force is initialized or when this method is called with a new radius, and not on every application of the force.
+ *
+ * @param radius A radius accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns a radius.
+ */
+export function forceCollide<NodeDatum extends SimulationNodeDatum>(radius: (node: NodeDatum, i: number, nodes: NodeDatum[]) => number): ForceCollide<NodeDatum>;
+
+// Link ----------------------------------------------------------------
+
+/**
+ * The link force pushes linked nodes together or apart according to the desired link distance.
+ * The strength of the force is proportional to the difference between the linked nodes’ distance and the target distance, similar to a spring force.
+ *
+ * The first generic refers to the type of data for a node.
+ * The second generic refers to the type of data for a link.
+ */
+export interface ForceLink<NodeDatum extends SimulationNodeDatum, LinkDatum extends SimulationLinkDatum<NodeDatum>> extends Force<NodeDatum, LinkDatum> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Return the current array of links, which defaults to the empty array.
+ *
+ */
+ links(): LinkDatum[];
+ /**
+ * Set the array of links associated with this force, recompute the distance and strength parameters for each link, and return this force.
+ *
+ * Each link is an object with the following properties:
+ * * source - the link’s source node; see simulation.nodes
+ * * target - the link’s target node; see simulation.nodes
+ * * index - the zero-based index into links, assigned by this method
+ *
+ * For convenience, a link’s source and target properties may be initialized using numeric or string identifiers rather than object references; see link.id.
+ * When the link force is initialized (or re-initialized, as when the nodes or links change), any link.source or link.target property which is not an object
+ * is replaced by an object reference to the corresponding node with the given identifier.
+ * If the specified array of links is modified, such as when links are added to or removed from the simulation,
+ * this method must be called again with the new (or changed) array to notify the force of the change;
+ * the force does not make a defensive copy of the specified array.
+ *
+ * @param links An array of link data.
+ */
+ links(links: LinkDatum[]): this;
+
+ /**
+ * Return the current node id accessor, which defaults to the numeric node.index.
+ */
+ id(): (node: NodeDatum, i: number, nodesData: NodeDatum[]) => (string | number);
+ /**
+ * Set the node id accessor to the specified function and return this force.
+ *
+ * The default id accessor allows each link’s source and target to be specified as a zero-based index
+ * into the nodes array.
+ *
+ * The id accessor is invoked for each node whenever the force is initialized,
+ * as when the nodes or links change, being passed the node, the zero-based index of the node in the node array, and the node array.
+ *
+ * @param id A node id accessor function which is invoked for each node in the simulation,
+ * being passed the node, the zero-based index of the node in the node array, and the node array. It returns a string to represent the node id which can be used
+ * for matching link source and link target strings during the ForceLink initialization.
+ */
+ id(id: (node: NodeDatum, i: number, nodesData: NodeDatum[]) => string): this;
+
+ /**
+ * Return the current distance accessor, which defaults to implying a default distance of 30.
+ */
+ distance(): (link: LinkDatum, i: number, links: LinkDatum[]) => number;
+ /**
+ * Set the distance accessor to use the specified constant number for all links,
+ * re-evaluates the distance accessor for each link, and returns this force.
+ *
+ * The constant is internally wrapped into a distance accessor function.
+ *
+ * The distance accessor is invoked for each link, being passed the link, its zero-based index and the complete array of links.
+ * The resulting number is then stored internally, such that the distance of each link is only recomputed when the force is initialized or
+ * when this method is called with a new distance, and not on every application of the force.
+ *
+ * @param distance The constant distance to be used for all links.
+ */
+ distance(distance: number): this;
+ /**
+ * Set the distance accessor to use the specified function,
+ * re-evaluates the distance accessor for each link, and returns this force.
+ *
+ * The distance accessor is invoked for each link, being passed the link, its zero-based index and the complete array of links.
+ * The resulting number is then stored internally, such that the distance of each link is only recomputed when the force is initialized or
+ * when this method is called with a new distance, and not on every application of the force.
+ *
+ * @param distance A distance accessor function which is invoked for each link being passed the link,
+ * its zero-based index and the complete array of links. It returns the distance.
+ */
+ distance(distance: (link: LinkDatum, i: number, links: LinkDatum[]) => number): this;
+
+ /**
+ * Return the current strength accessor.
+ * For details regarding the default behavior see: {@link https://github.com/d3/d3-force#link_strength}
+ */
+ strength(): (link: LinkDatum, i: number, links: LinkDatum[]) => number;
+ /**
+ * Set the strength accessor to use the specified constant number for all links,
+ * re-evaluates the strength accessor for each link, and returns this force.
+ *
+ * The constant is internally wrapped into a strength accessor function.
+ *
+ * The strength accessor is invoked for each link, being passed the link, its zero-based index and the complete array of links.
+ * The resulting number is then stored internally, such that the strength of each link is only recomputed
+ * when the force is initialized or when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength The constant strength to be used for all links.
+ */
+ strength(strength: number): this;
+ /**
+ * Set the strength accessor to use the specified function,
+ * re-evaluates the strength accessor for each link, and returns this force.
+ *
+ * The strength accessor is invoked for each link, being passed the link, its zero-based index and the complete array of links.
+ * The resulting number is then stored internally, such that the strength of each link is only recomputed
+ * when the force is initialized or when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength A distance accessor function which is invoked for each link being passed the link,
+ * its zero-based index and the complete array of links. It returns the strength.
+ */
+ strength(strength: (link: LinkDatum, i: number, links: LinkDatum[]) => number): this;
+
+ /**
+ * Return the current iteration count which defaults to 1.
+ */
+ iterations(): number;
+ /**
+ * Sets the number of iterations per application to the specified number and return this force.
+ *
+ * Increasing the number of iterations greatly increases the rigidity of the constraint and is useful for complex structures such as lattices,
+ * but also increases the runtime cost to evaluate the force.
+ *
+ * @param iterations Number of iterations.
+ */
+ iterations(iterations: number): this;
+}
+
+/**
+ * Creates a new link force with the defaulting links to an empty array.
+ *
+ * The link force pushes linked nodes together or apart according to the desired link distance.
+ * The strength of the force is proportional to the difference between the linked nodes’ distance and the target distance, similar to a spring force.
+ *
+ * The first generic refers to the type of data for a node.
+ * The second generic refers to the type of data for a link.
+ */
+export function forceLink<NodeDatum extends SimulationNodeDatum, LinksDatum extends SimulationLinkDatum<NodeDatum>>(): ForceLink<NodeDatum, LinksDatum>;
+/**
+ * Creates a new link force with the specified links array.
+ *
+ * The link force pushes linked nodes together or apart according to the desired link distance.
+ * The strength of the force is proportional to the difference between the linked nodes’ distance and the target distance, similar to a spring force.
+ *
+ * The first generic refers to the type of data for a node.
+ * The second generic refers to the type of data for a link.
+ *
+ * @param links An array of link data.
+ */
+export function forceLink<NodeDatum extends SimulationNodeDatum, LinksDatum extends SimulationLinkDatum<NodeDatum>>(links: LinksDatum[]): ForceLink<NodeDatum, LinksDatum>;
+
+// Many Body ----------------------------------------------------------------
+
+/**
+ * The many-body (or n-body) force applies mutually amongst all nodes. It can be used to simulate gravity (attraction) if the strength is positive,
+ * or electrostatic charge (repulsion) if the strength is negative. This implementation uses quadtrees and the Barnes–Hut approximation to greatly
+ * improve performance; the accuracy can be customized using the theta parameter.
+ *
+ * Unlike links, which only affect two linked nodes, the charge force is global: every node affects every other node, even if they are on disconnected subgraphs.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceManyBody<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Return the current strength accessor.
+ *
+ * For details regarding the default behavior see: {@link https://github.com/d3/d3-force#manyBody_strength}
+ */
+ strength(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the strength accessor to the specified constant strength for all nodes, re-evaluates the strength accessor for each node, and
+ * returns this force.
+ *
+ * A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other,
+ * similar to electrostatic charge.
+ *
+ * The default represents a constant value of -30.
+ *
+ * The constant is internally wrapped into a strength accessor function.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength The constant strength to be used for all nodes.
+ */
+ strength(strength: number): this;
+ /**
+ * Set the strength accessor to the specified function, re-evaluates the strength accessor for each node, and
+ * returns this force.
+ *
+ * A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other,
+ * similar to electrostatic charge.
+ *
+ * The default represents a constant value of -30.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength A strength accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the strength.
+ */
+ strength(strength: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current value of the Barnes–Hut approximation criterion , which defaults to 0.9
+ */
+ theta(): number;
+ /**
+ * Set the Barnes–Hut approximation criterion to the specified number and returns this force.
+ *
+ * To accelerate computation, this force implements the Barnes–Hut approximation which takes O(n log n) per application
+ * where n is the number of nodes. For each application, a quadtree stores the current node positions;
+ * then for each node, the combined force of all other nodes on the given node is computed.
+ * For a cluster of nodes that is far away, the charge force can be approximated by treating the cluster as a single, larger node.
+ * The theta parameter determines the accuracy of the approximation:
+ * if the ratio w / l of the width w of the quadtree cell to the distance l from the node to the cell’s center of mass is less than theta,
+ * all nodes in the given cell are treated as a single node rather than individually.
+ *
+ * The default value is 0.9.
+ *
+ * @param theta Value for the theta parameter.
+ */
+ theta(theta: number): this;
+
+ /**
+ * Returns the current minimum distance over which this force is considered, which defaults to 1.
+ */
+ distanceMin(): number;
+ /**
+ * Sets the minimum distance between nodes over which this force is considered.
+ *
+ * A minimum distance establishes an upper bound on the strength of the force between two nearby nodes, avoiding instability.
+ * In particular, it avoids an infinitely-strong force if two nodes are exactly coincident; in this case, the direction of the force is random.
+ *
+ * The default value is 1.
+ *
+ * @param distance The minimum distance between nodes over which this force is considered.
+ */
+ distanceMin(distance: number): this;
+
+ /**
+ * Returns the current maximum distance over which this force is considered, which defaults to infinity.
+ */
+ distanceMax(): number;
+ /**
+ * Sets the maximum distance between nodes over which this force is considered.
+ *
+ * Specifying a finite maximum distance improves performance and produces a more localized layout.
+ *
+ * The default value is infinity.
+ *
+ * @param distance The maximum distance between nodes over which this force is considered.
+ */
+ distanceMax(distance: number): this;
+}
+
+/**
+ * Creates a new many-body force with the default parameters.
+ *
+ * The many-body (or n-body) force applies mutually amongst all nodes. It can be used to simulate gravity (attraction) if the strength is positive,
+ * or electrostatic charge (repulsion) if the strength is negative. This implementation uses quadtrees and the Barnes–Hut approximation to greatly
+ * improve performance; the accuracy can be customized using the theta parameter.
+ *
+ * Unlike links, which only affect two linked nodes, the charge force is global: every node affects every other node, even if they are on disconnected subgraphs.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export function forceManyBody<NodeDatum extends SimulationNodeDatum>(): ForceManyBody<NodeDatum>;
+
+// Positioning ----------------------------------------------------------------
+
+/**
+ * The x-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceX<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Returns the current strength accessor, which defaults to a constant strength for all nodes of 0.1.
+ */
+ strength(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the strength accessor to the specified constant strength for all nodes, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The constant is internally wrapped into a strength accessor function.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength Constant value of strength to be used for all nodes.
+ */
+ strength(strength: number): this;
+ /**
+ * Set the strength accessor to the specified function, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength A strength accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the strength.
+ */
+ strength(strength: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current x-accessor, which defaults to a function returning 0 for all nodes.
+ */
+ x(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the x-coordinate accessor to the specified number, re-evaluates the x-accessor for each node,
+ * and returns this force.
+ *
+ * The constant is internally wrapped into an x-coordinate accessor function.
+ *
+ * The x-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the target x-coordinate of each node is only recomputed when the force is initialized or
+ * when this method is called with a new x, and not on every application of the force.
+ *
+ * @param x Constant x-coordinate to be used for all nodes.
+ */
+ x(x: number): this;
+ /**
+ * Set the x-coordinate accessor to the specified function, re-evaluates the x-accessor for each node,
+ * and returns this force.
+ *
+ * The x-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the target x-coordinate of each node is only recomputed when the force is initialized or
+ * when this method is called with a new x, and not on every application of the force.
+ *
+ * @param x A x-coordinate accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the x-coordinate.
+ */
+ x(x: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+}
+
+/**
+ * Create a new positioning force along the x-axis towards the given position x which is defaulted to a constant 0 for all nodes.
+ *
+ * The x-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export function forceX<NodeDatum extends SimulationNodeDatum>(): ForceX<NodeDatum>;
+/**
+ * Create a new positioning force along the x-axis towards the given position x which is constant for all nodes.
+ *
+ * The x-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param x Constant x-coordinate to be used for all nodes.
+ */
+export function forceX<NodeDatum extends SimulationNodeDatum>(x: number): ForceX<NodeDatum>;
+/**
+ * Create a new positioning force along the x-axis towards the position x given by evaluating the specified x-coordinate accessor
+ * for each node.
+ *
+ * The x-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param x A x-coordinate accessor function which is invoked for each node in the simulation, being passed the node and its zero-based index.
+ * The function returns the x-coordinate.
+ */
+export function forceX<NodeDatum extends SimulationNodeDatum>(x: (d: NodeDatum, i: number, data: NodeDatum[]) => number): ForceX<NodeDatum>;
+
+/**
+ * The y-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceY<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Returns the current strength accessor, which defaults to a constant strength for all nodes of 0.1.
+ */
+ strength(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the strength accessor to the specified constant strength for all nodes, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s y-velocity: (y - node.y) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current y-position to the target y-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The constant is internally wrapped into a strength accessor function.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength Constant value of strength to be used for all nodes.
+ */
+ strength(strength: number): this;
+ /**
+ * Set the strength accessor to the specified function, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s y-velocity: (y - node.y) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current y-position to the target y-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength A strength accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the strength.
+ */
+ strength(strength: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current y-accessor, which defaults to a function returning 0 for all nodes.
+ */
+ y(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the y-coordinate accessor to the specified number, re-evaluates the y-accessor for each node,
+ * and returns this force.
+ *
+ * The constant is internally wrapped into a y-coordinate accessor function.
+ *
+ * The y-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the target y-coordinate of each node is only recomputed when the force is initialized or
+ * when this method is called with a new y, and not on every application of the force.
+ *
+ * @param y Constant y-coordinate to be used for all nodes.
+ */
+ y(y: number): this;
+ /**
+ * Set the y-coordinate accessor to the specified function, re-evaluates the y-accessor for each node,
+ * and returns this force.
+ *
+ * The y-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the target y-coordinate of each node is only recomputed when the force is initialized or
+ * when this method is called with a new y, and not on every application of the force.
+ *
+ * @param y A y-coordinate accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the y-coordinate.
+ */
+ y(y: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+}
+
+/**
+ * Create a new positioning force along the y-axis towards the given position y which is defaulted to a constant 0 for all nodes.
+ *
+ * The y-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export function forceY<NodeDatum extends SimulationNodeDatum>(): ForceY<NodeDatum>;
+/**
+ * Create a new positioning force along the y-axis towards the given position y which is constant for all nodes.
+ *
+ * The y-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param y Constant y-coordinate to be used for all nodes.
+ */
+export function forceY<NodeDatum extends SimulationNodeDatum>(y: number): ForceY<NodeDatum>;
+/**
+ * Create a new positioning force along the y-axis towards the position y given by evaluating the specified y-coordinate accessor
+ * for each node.
+ *
+ * The y-positioning force pushes nodes towards a desired position along the given dimension with a configurable strength.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ *
+ * @param y A y-coordinate accessor function which is invoked for each node in the simulation, being passed the node and its zero-based index.
+ * The function returns the y-coordinate.
+ */
+export function forceY<NodeDatum extends SimulationNodeDatum>(y: (d: NodeDatum, i: number, data: NodeDatum[]) => number): ForceY<NodeDatum>;
+
+/**
+ * The radial force is similar to the x- and y-positioning forces, except it pushes nodes towards the closest point on a given circle.
+ * The circle is of the specified radius centered at ⟨x,y⟩. If x and y are not specified, they default to ⟨0,0⟩.
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export interface ForceRadial<NodeDatum extends SimulationNodeDatum> extends Force<NodeDatum, any> {
+ /**
+ * Assign the array of nodes to this force. This method is called when a force is bound to a simulation via simulation.force
+ * and when the simulation’s nodes change via simulation.nodes.
+ *
+ * A force may perform necessary work during initialization, such as evaluating per-node parameters, to avoid repeatedly performing work during each application of the force.
+ */
+ initialize(nodes: NodeDatum[]): void;
+
+ /**
+ * Returns the current strength accessor, which defaults to a constant strength for all nodes of 0.1.
+ */
+ strength(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the strength accessor to the specified constant strength for all nodes, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The constant is internally wrapped into a strength accessor function.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength Constant value of strength to be used for all nodes.
+ */
+ strength(strength: number): this;
+ /**
+ * Set the strength accessor to the specified function, re-evaluates the strength accessor for each node, and returns this force.
+ *
+ * The strength determines how much to increment the node’s x-velocity: (x - node.x) × strength.
+ *
+ * For example, a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application.
+ * Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints.
+ *
+ * A value outside the range [0,1] is not recommended.
+ *
+ * The strength accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the strength of each node is only recomputed when the force is initialized or
+ * when this method is called with a new strength, and not on every application of the force.
+ *
+ * @param strength A strength accessor function which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the strength.
+ */
+ strength(strength: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current radius accessor for the circle.
+ */
+ radius(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the radius accessor for the circle to the specified number, re-evaluates the radius accessor for each node,
+ * and returns this force.
+ *
+ * The constant is internally wrapped into a radius accessor function.
+ *
+ * The radius accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that radius of the circle for each node is only recomputed when the force is initialized or
+ * when this method is called with a new radius, and not on every application of the force.
+ *
+ * @param radius Constant radius of the circle to be used for all nodes.
+ */
+ radius(radius: number): this;
+ /**
+ * Set the radius accessor for the circle to the specified function, re-evaluates the radius accessor for each node,
+ * and returns this force.
+ *
+ * The radius accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that radius of the circle for each node is only recomputed when the force is initialized or
+ * when this method is called with a new radius, and not on every application of the force.
+ *
+ * @param radius A radius accessor function for the circle which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the radius of the circle.
+ */
+ radius(radius: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current x-accessor for the circle center, which defaults to a function returning 0 for all nodes.
+ */
+ x(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the x-coordinate accessor for the circle center to the specified number, re-evaluates the x-accessor for each node,
+ * and returns this force.
+ *
+ * The constant is internally wrapped into an x-coordinate accessor function.
+ *
+ * The x-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the x-coordinate of the circle center for each node is only recomputed when the force is initialized or
+ * when this method is called with a new x, and not on every application of the force.
+ *
+ * @param x Constant x-coordinate of the circle center to be used for all nodes.
+ */
+ x(x: number): this;
+ /**
+ * Set the x-coordinate accessor to the specified function, re-evaluates the x-accessor for each node,
+ * and returns this force.
+ *
+ * The x-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the x-coordinate of the circle center for each node is only recomputed when the force is initialized or
+ * when this method is called with a new x, and not on every application of the force.
+ *
+ * @param x A x-coordinate accessor function for the circle center which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the x-coordinate of the circle center.
+ */
+ x(x: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+
+ /**
+ * Return the current y-accessor for the circle center, which defaults to a function returning 0 for all nodes.
+ */
+ y(): (d: NodeDatum, i: number, data: NodeDatum[]) => number;
+ /**
+ * Set the y-coordinate accessor for the circle center to the specified number, re-evaluates the y-accessor for each node,
+ * and returns this force.
+ *
+ * The constant is internally wrapped into an y-coordinate accessor function.
+ *
+ * The y-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the y-coordinate of the circle center for each node is only recomputed when the force is initialized or
+ * when this method is called with a new y, and not on every application of the force.
+ *
+ * @param y Constant y-coordinate of the circle center to be used for all nodes.
+ */
+ y(y: number): this;
+ /**
+ * Set the y-coordinate accessor to the specified function, re-evaluates the y-accessor for each node,
+ * and returns this force.
+ *
+ * The y-accessor is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The resulting number is then stored internally, such that the y-coordinate of the circle center for each node is only recomputed when the force is initialized or
+ * when this method is called with a new y, and not on every application of the force.
+ *
+ * @param y A y-coordinate accessor function for the circle center which is invoked for each node in the simulation, being passed the node, its zero-based index and the complete array of nodes.
+ * The function returns the y-coordinate of the circle center.
+ */
+ y(y: (d: NodeDatum, i: number, data: NodeDatum[]) => number): this;
+}
+
+/**
+ * Create a new radial positioning force towards a circle of the specified radius centered at ⟨x,y⟩.
+ * If x and y are not specified, they default to ⟨0,0⟩.
+ *
+ * The strength of the force is proportional to the one-dimensional distance between the node’s position and the target position.
+ * While this force can be used to position individual nodes, it is intended primarily for global forces that apply to all (or most) nodes.
+ *
+ * The generic refers to the type of data for a node.
+ */
+export function forceRadial<NodeDatum extends SimulationNodeDatum>(radius: number | ((d: NodeDatum, i: number, data: NodeDatum[]) => number),
+ x?: number | ((d: NodeDatum, i: number, data: NodeDatum[]) => number), y?: number | ((d: NodeDatum, i: number, data: NodeDatum[]) => number)): ForceRadial<NodeDatum>;
diff --git a/chromium/third_party/node/node_modules/@types/d3-scale-chromatic/index.d.ts b/chromium/third_party/node/node_modules/@types/d3-scale-chromatic/index.d.ts
new file mode 100644
index 00000000000..2095710d473
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3-scale-chromatic/index.d.ts
@@ -0,0 +1,525 @@
+// Type definitions for D3JS d3-scale-chromatic module 1.5
+// Project: https://github.com/d3/d3-scale-chromatic/, https://d3js.org/d3-scale-chromatic
+// Definitions by: Hugues Stefanski <https://github.com/Ledragon>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// Henrique Machado <https://github.com/henriquefm>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+// Last module patch version validated against: 1.5.0
+
+// -----------------------------------------------------------------------
+// Categorical
+// -----------------------------------------------------------------------
+/**
+ * An array of ten categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeCategory10: ReadonlyArray<string>;
+/**
+ * An array of eight categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeAccent: ReadonlyArray<string>;
+/**
+ * An array of eight categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeDark2: ReadonlyArray<string>;
+/**
+ * An array of twelve categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemePaired: ReadonlyArray<string>;
+/**
+ * An array of nine categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemePastel1: ReadonlyArray<string>;
+/**
+ * An array of eight categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemePastel2: ReadonlyArray<string>;
+/**
+ * An array of nine categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeSet1: ReadonlyArray<string>;
+/**
+ * An array of eight categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeSet2: ReadonlyArray<string>;
+/**
+ * An array of twelve categorical colors represented as RGB hexadecimal strings.
+ */
+export const schemeSet3: ReadonlyArray<string>;
+/**
+ * An array of ten categorical colors authored by Tableau as part of Tableau 10 represented as RGB hexadecimal strings.
+ */
+export const schemeTableau10: ReadonlyArray<string>;
+
+// -----------------------------------------------------------------------
+// Diverging
+// -----------------------------------------------------------------------
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “BrBG†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateBrBG(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “BrBG†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeBrBG[9] contains an array of nine strings representing the nine colors of the
+ * brown-blue-green diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeBrBG: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PRGn†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePRGn(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PRGn†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePRGn[9] contains an array of nine strings representing the nine colors of the
+ * purple-green diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemePRGn: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PiYG†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePiYG(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PiYG†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePiYG[9] contains an array of nine strings representing the nine colors of the
+ * pink-yellow-green diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemePiYG: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PuOr†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePuOr(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PuOr†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePuOr[9] contains an array of nine strings representing the nine colors of the
+ * purple-orange diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemePuOr: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “RdBu†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateRdBu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “RdBu†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeRdBu[9] contains an array of nine strings representing the nine colors of the
+ * red-blue diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeRdBu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “RdGy†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateRdGy(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “RdGy†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeRdGy[9] contains an array of nine strings representing the nine colors of the
+ * red-grey diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeRdGy: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “RdYlBu†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateRdYlBu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “RdYlBu†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeRdYlBu[9] contains an array of nine strings representing the nine colors of the
+ * red-yellow-blue diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeRdYlBu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “RdYlGn†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateRdYlGn(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “RdYlGn†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeRdYlGn[9] contains an array of nine strings representing the nine colors of the
+ * red-yellow-green diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeRdYlGn: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Spectral†diverging color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateSpectral(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Spectral†diverging color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeSpectral[9] contains an array of nine strings representing the nine colors of the
+ * spectral diverging color scheme. Diverging color schemes support a size k ranging from 3 to 11.
+ */
+export const schemeSpectral: ReadonlyArray<ReadonlyArray<string>>;
+
+// -----------------------------------------------------------------------
+// Sequential
+// -----------------------------------------------------------------------
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Blues†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateBlues(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Blues†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeBlues[9] contains an array of nine strings representing the nine colors of the
+ * blue sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeBlues: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Greens†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateGreens(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Greens†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeGreens[9] contains an array of nine strings representing the nine colors of the
+ * green sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeGreens: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Greys†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateGreys(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Greys†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeGreys[9] contains an array of nine strings representing the nine colors of the
+ * grey sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeGreys: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Oranges†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateOranges(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Oranges†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeOranges[9] contains an array of nine strings representing the nine colors of the
+ * orange sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeOranges: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Purples†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePurples(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Purples†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePurples[9] contains an array of nine strings representing the nine colors of the
+ * purple sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemePurples: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “Reds†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateReds(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “Reds†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeReds[9] contains an array of nine strings representing the nine colors of the
+ * red sequential color scheme. Sequential, single-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeReds: ReadonlyArray<ReadonlyArray<string>>;
+
+// -----------------------------------------------------------------------
+// Sequential(Multi-Hue)
+// -----------------------------------------------------------------------
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “turbo†color scheme by Anton Mikhailov.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateTurbo(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “viridis†perceptually-uniform color scheme designed by van der Walt, Smith and Firing for matplotlib,
+ * represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateViridis(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “inferno†perceptually-uniform color scheme designed by van der Walt and Smith for matplotlib,
+ * represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateInferno(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “magma†perceptually-uniform color scheme designed by van der Walt and Smith for matplotlib,
+ * represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateMagma(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “plasma†perceptually-uniform color scheme designed by van der Walt and Smith for matplotlib,
+ * represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolatePlasma(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “plasma†perceptually-uniform color scheme designed by van der Walt and Smith for matplotlib,
+ * represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateCividis(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from a 180° rotation of Niccoli’s perceptual rainbow, represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateWarm(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from Niccoli’s perceptual rainbow, represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateCool(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from d3.interpolateWarm scale from [0.0, 0.5] followed by the d3.interpolateCool scale from [0.5, 1.0],
+ * thus implementing the cyclical less-angry rainbow color scheme.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateRainbow(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “sinebow†color scheme by Jim Bumgardner and Charlie Loyd.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateSinebow(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from Green’s default Cubehelix represented as an RGB string.
+ *
+ * @param t A number in the interval [0, 1].
+ */
+export function interpolateCubehelixDefault(t: number): string;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “BuGn†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateBuGn(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “BuGn†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeBuGn[9] contains an array of nine strings representing the nine colors of the
+ * blue-green sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeBuGn: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “BuPu†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateBuPu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “BuPu†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeBuPu[9] contains an array of nine strings representing the nine colors of the
+ * blue-purple sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeBuPu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “GnBu†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateGnBu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “GnBu†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeGnBu[9] contains an array of nine strings representing the nine colors of the
+ * green-blue sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeGnBu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “OrRd†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateOrRd(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “OrRd†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeOrRd[9] contains an array of nine strings representing the nine colors of the
+ * orange-red sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeOrRd: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PuBuGn†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePuBuGn(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PuBuGn†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePuBuGn[9] contains an array of nine strings representing the nine colors of the
+ * purple-blue-green sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemePuBuGn: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PuBu†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePuBu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PuBu†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePuBu[9] contains an array of nine strings representing the nine colors of the
+ * purple-blue sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemePuBu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “PuRd†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolatePuRd(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “PuRd†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemePuRd[9] contains an array of nine strings representing the nine colors of the
+ * purple-red sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemePuRd: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “RdPu†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateRdPu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “RdPu†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeRdPu[9] contains an array of nine strings representing the nine colors of the
+ * red-purple sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeRdPu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “YlGnBu†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateYlGnBu(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “YlGnBu†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeYlGnBu[9] contains an array of nine strings representing the nine colors of the
+ * yellow-green-blue sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeYlGnBu: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “YlGn†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateYlGn(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “YlGn†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeYlGn[9] contains an array of nine strings representing the nine colors of the
+ * yellow-green sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeYlGn: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “YlOrBr†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateYlOrBr(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “YlOrBr†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeYlOrBr[9] contains an array of nine strings representing the nine colors of the
+ * yellow-orange-brown sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeYlOrBr: ReadonlyArray<ReadonlyArray<string>>;
+
+/**
+ * Given a number t in the range [0,1], returns the corresponding color from the “YlOrRd†sequential color scheme represented as an RGB string.
+ *
+ * @param t Number in the range [0, 1].
+ */
+export function interpolateYlOrRd(t: number): string;
+
+/**
+ * An array of arrays of hexadecimal color strings from the “YlOrRd†sequential color scheme. The kth element of this array contains
+ * the color scheme of size k; for example, d3.schemeYlOrRd[9] contains an array of nine strings representing the nine colors of the
+ * yellow-orange-red sequential color scheme. Sequential, multi-hue color schemes support a size k ranging from 3 to 9.
+ */
+export const schemeYlOrRd: ReadonlyArray<ReadonlyArray<string>>;
diff --git a/chromium/third_party/node/node_modules/@types/d3-selection/index.d.ts b/chromium/third_party/node/node_modules/@types/d3-selection/index.d.ts
new file mode 100644
index 00000000000..e72ce071eb8
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3-selection/index.d.ts
@@ -0,0 +1,1256 @@
+// Type definitions for D3JS d3-selection module 1.4
+// Project: https://github.com/d3/d3-selection/, https://d3js.org/d3-selection
+// Definitions by: Tom Wanzek <https://github.com/tomwanzek>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// denisname <https://github.com/denisname>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// TypeScript Version: 2.3
+
+// Last module patch version validated against: 1.4.1
+
+// --------------------------------------------------------------------------
+// Shared Type Definitions and Interfaces
+// --------------------------------------------------------------------------
+
+/**
+ * BaseType serves as an alias for the 'minimal' data type which can be selected
+ * without 'd3-selection' trying to use properties internally which would otherwise not
+ * be supported.
+ */
+export type BaseType = Element | EnterElement | Document | Window | null;
+
+/**
+ * KeyType serves as alias for valid types that d3 supports as key for data binding
+ */
+export type KeyType = string | number;
+
+/**
+ * A helper interface which covers arguments like NodeListOf<T> or HTMLCollectionOf<T>
+ * argument types
+ */
+export interface ArrayLike<T> {
+ length: number;
+ item(index: number): T | null;
+ [index: number]: T;
+}
+
+/**
+ * An interface describing the element type of the Enter Selection group elements
+ * created when invoking selection.enter().
+ */
+export interface EnterElement {
+ ownerDocument: Document;
+ namespaceURI: string;
+ appendChild(newChild: Node): Node;
+ insertBefore(newChild: Node, refChild: Node): Node;
+ querySelector(selectors: string): Element;
+ querySelectorAll(selectors: string): NodeListOf<Element>;
+}
+
+/**
+ * Container element type usable for mouse/touch functions
+ */
+export type ContainerElement = HTMLElement | SVGSVGElement | SVGGElement;
+
+/**
+ * A User interface event (e.g. mouse event, touch or MSGestureEvent) with captured clientX and clientY properties.
+ */
+export interface ClientPointEvent {
+ clientX: number;
+ clientY: number;
+}
+
+/**
+ * Interface for optional parameters map, when dispatching custom events
+ * on a selection
+ */
+export interface CustomEventParameters {
+ /**
+ * If true, the event is dispatched to ancestors in reverse tree order
+ */
+ bubbles: boolean;
+ /**
+ * If true, event.preventDefault is allowed
+ */
+ cancelable: boolean;
+ /**
+ * Any custom data associated with the event
+ */
+ detail: any;
+}
+
+/**
+ * Callback type for selections and transitions
+ */
+export type ValueFn<T extends BaseType, Datum, Result> = (this: T, datum: Datum, index: number, groups: T[] | ArrayLike<T>) => Result;
+
+/**
+ * TransitionLike is a helper interface to represent a quasi-Transition, without specifying the full Transition interface in this file.
+ * For example, wherever d3-zoom allows a Transition to be passed in as an argument, it internally immediately invokes its `selection()`
+ * method to retrieve the underlying Selection object before proceeding.
+ * d3-brush uses a subset of Transition methods internally.
+ * The use of this interface instead of the full imported Transition interface is [referred] to achieve
+ * two things:
+ * (1) the d3-transition module may not be required by a projects use case,
+ * (2) it avoids possible complications from 'module augmentation' from d3-transition to Selection.
+ */
+export interface TransitionLike<GElement extends BaseType, Datum> {
+ selection(): Selection<GElement, Datum, any, any>;
+ on(type: string, listener: null): TransitionLike<GElement, Datum>;
+ on(type: string, listener: ValueFn<GElement, Datum, void>): TransitionLike<GElement, Datum>;
+ tween(name: string, tweenFn: null): TransitionLike<GElement, Datum>;
+ tween(name: string, tweenFn: ValueFn<GElement, Datum, ((t: number) => void)>): TransitionLike<GElement, Datum>;
+}
+
+// --------------------------------------------------------------------------
+// All Selection related interfaces and function
+// --------------------------------------------------------------------------
+
+/**
+ * Select the first element that matches the specified selector string. If no elements match the selector, returns an empty selection.
+ * If multiple elements match the selector, only the first matching element (in document order) will be selected.
+ *
+ * The first generic "GElement" refers to the type of element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, on the selected element. This is useful when re-selecting an element with a previously set, know datum type.
+ *
+ * @param selector CSS selector string
+ */
+export function select<GElement extends BaseType, OldDatum>(selector: string): Selection<GElement, OldDatum, HTMLElement, any>;
+/**
+ * Select the specified node element.
+ *
+ * The first generic "GElement" refers to the type of element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, on the selected element. This is useful when re-selecting an element with a previously set, know datum type.
+ *
+ * @param node An element to be selected
+ */
+export function select<GElement extends BaseType, OldDatum>(node: GElement): Selection<GElement, OldDatum, null, undefined>;
+
+/**
+ * Create an empty selection.
+ */
+export function selectAll(): Selection<null, undefined, null, undefined>;
+/**
+ * Create an empty selection.
+ */
+export function selectAll(selector: null): Selection<null, undefined, null, undefined>;
+/**
+ * Create an empty selection.
+ */
+export function selectAll(selector: undefined): Selection<null, undefined, null, undefined>;
+/**
+ * Select all elements that match the specified selector string. The elements will be selected in document order (top-to-bottom).
+ * If no elements in the document match the selector, returns an empty selection.
+ *
+ * The first generic "GElement" refers to the type of element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param selector CSS selector string
+ */
+export function selectAll<GElement extends BaseType, OldDatum>(selector: string): Selection<GElement, OldDatum, HTMLElement, any>;
+/**
+ * Select the specified array of nodes.
+ *
+ * The first generic "GElement" refers to the type of element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param nodes An Array of nodes
+ */
+export function selectAll<GElement extends BaseType, OldDatum>(nodes: GElement[]): Selection<GElement, OldDatum, null, undefined>;
+/**
+ * Select the specified nodes. This signature allows the selection of nodes contained in a NodeList, HTMLCollection or similar data structure.
+ *
+ * The first generic "GElement" refers to the type of element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param nodes An Array-like collection of nodes
+ */
+export function selectAll<GElement extends BaseType, OldDatum>(nodes: ArrayLike<GElement>): Selection<GElement, OldDatum, null, undefined>;
+
+/**
+ * A D3 Selection of elements.
+ *
+ * The first generic "GElement" refers to the type of the selected element(s).
+ * The second generic "Datum" refers to the type of the datum of a selected element(s).
+ * The third generic "PElement" refers to the type of the parent element(s) in the D3 selection.
+ * The fourth generic "PDatum" refers to the type of the datum of the parent element(s).
+ */
+export interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
+ // Sub-selection -------------------------
+
+ /**
+ * For each selected element, select the first descendant element that matches the specified selector string.
+ * If no element matches the specified selector for the current element, the element at the current index will
+ * be null in the returned selection. If multiple elements match the selector, only the first matching element
+ * in document order is selected. Selection.select does not affect grouping: it preserves the existing group
+ * structure and indexes, and propagates data (if any) to selected children.
+ *
+ * If the current element has associated data, this data is propagated to the
+ * corresponding selected element.
+ *
+ * The generic represents the type of the descendant element to be selected.
+ *
+ * @param selector CSS selector string
+ */
+ select<DescElement extends BaseType>(selector: string): Selection<DescElement, Datum, PElement, PDatum>;
+ /**
+ * Create an empty sub-selection. Selection.select does not affect grouping: it preserves the existing group
+ * structure and indexes.
+ */
+ select<DescElement extends BaseType>(selector: null): Selection<null, undefined, PElement, PDatum>;
+ /**
+ * For each selected element, select the descendant element returned by the selector function.
+ * If no element is returned by the selector function for the current element, the element at the
+ * current index will be null in the returned selection. Selection.select does not affect grouping:
+ * it preserves the existing group structure and indexes, and propagates data (if any) to selected children.
+ *
+ * If the current element has associated data, this data is propagated to the
+ * corresponding selected element.
+ *
+ * The generic represents the type of the descendant element to be selected.
+ *
+ * @param selector A selector function, which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * It must return an element, or null if there is no matching element.
+ */
+ select<DescElement extends BaseType>(selector: ValueFn<GElement, Datum, DescElement>): Selection<DescElement, Datum, PElement, PDatum>;
+
+ /**
+ * Create an empty sub-selection. Selection.selectAll does affect grouping: The elements in the returned
+ * selection are grouped by their corresponding parent node in this selection, the group at the current index will be empty.
+ */
+ selectAll(): Selection<null, undefined, GElement, Datum>;
+ /**
+ * Create an empty sub-selection. Selection.selectAll does affect grouping: The elements in the returned
+ * selection are grouped by their corresponding parent node in this selection, the group at the current index will be empty.
+ */
+ selectAll(selector: null): Selection<null, undefined, GElement, Datum>;
+ /**
+ * Create an empty sub-selection. Selection.selectAll does affect grouping: The elements in the returned
+ * selection are grouped by their corresponding parent node in this selection, the group at the current index will be empty.
+ */
+ selectAll(selector: undefined): Selection<null, undefined, GElement, Datum>;
+ /**
+ * For each selected element, selects the descendant elements that match the specified selector string. The elements in the returned
+ * selection are grouped by their corresponding parent node in this selection. If no element matches the specified selector
+ * for the current element, the group at the current index will be empty. Selection.selectAll does affect grouping: each selected descendant
+ * is grouped by the parent element in the originating selection.
+ *
+ * The selected elements do not inherit data from this selection; use selection.data to propagate data to children.
+ *
+ * The first generic "DescElement" refers to the type of descendant element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param selector CSS selector string
+ */
+ selectAll<DescElement extends BaseType, OldDatum>(selector: string): Selection<DescElement, OldDatum, GElement, Datum>;
+
+ /**
+ * For each selected element, selects the descendant elements returned by the selector function. The elements in the returned
+ * selection are grouped by their corresponding parent node in this selection. If no element matches the specified selector
+ * for the current element, the group at the current index will be empty. Selection.selectAll does affect grouping: each selected descendant
+ * is grouped by the parent element in the originating selection.
+ *
+ * The selected elements do not inherit data from this selection; use selection.data to propagate data to children.
+ *
+ * The first generic "DescElement" refers to the type of descendant element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param selector A selector function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). It must return an array of elements
+ * (or a pseudo-array, such as a NodeList), or the empty array if there are no matching elements.
+ */
+ selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Selection<DescElement, OldDatum, GElement, Datum>;
+
+ // Modifying -------------------------------
+
+ /**
+ * Return the current value of the specified attribute for the first (non-null) element in the selection.
+ * This is generally useful only if you know that the selection contains exactly one element.
+ *
+ * @param name Name of the attribute
+ */
+ attr(name: string): string;
+ /**
+ * Clear the attribute with the specified name for the selected elements and returns this selection.
+ *
+ * @param name Name of the attribute
+ * @param value null,to clear the attribute
+ */
+ attr(name: string, value: null): this;
+ /**
+ * Sets the value of the attribute with the specified name for the selected elements and returns this selection.
+ * All elements are given the same attribute value.
+ *
+ * @param name Name of the attribute
+ * @param value Constant value for the attribute
+ */
+ attr(name: string, value: string | number | boolean): this;
+ /**
+ * Sets the value of the attribute with the specified name for the selected elements and returns this selection.
+ * The value for the individual selected elements is determined by the value function.
+ *
+ * @param name Name of the attribute
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). A null value will clear the attribute.
+ */
+ attr(name: string, value: ValueFn<GElement, Datum, string | number | boolean | null>): this;
+
+ /**
+ * Returns true if and only if the first (non-null) selected element has the specified classes.
+ * This is generally useful only if you know the selection contains exactly one element.
+ *
+ * @param name A string of space-separated class names.
+ */
+ classed(names: string): boolean;
+ /**
+ * Assigns or unassigns the specified CSS class names on the selected elements by setting
+ * the class attribute or modifying the classList property and returns this selection.
+ * If the constant value is truthy, then all elements are assigned the specified classes; otherwise, the classes are unassigned.
+ *
+ * @param names A string of space-separated class names.
+ * @param value A boolean flag (true = assign / false = unassign)
+ */
+ classed(names: string, value: boolean): this;
+ /**
+ * Assigns or unassigns the specified CSS class names on the selected elements by setting
+ * the class attribute or modifying the classList property and returns this selection.
+ * The assign/unassign status for the individual selected elements is determined by the boolean return
+ * value of the value function.
+ *
+ * @param names A string of space-separated class names.
+ * @param value A value function which is evaluated for each selected element, in order,
+ * being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * The function’s return value is then used to assign or unassign classes on each element.
+ */
+ classed(names: string, value: ValueFn<GElement, Datum, boolean>): this;
+
+ /**
+ * Returns the current value of the specified style property for the first (non-null) element in the selection.
+ * The current value is defined as the element’s inline value, if present, and otherwise its computed value.
+ * Accessing the current style value is generally useful only if you know the selection contains exactly one element.
+ *
+ * @param name Name of the style
+ */
+ style(name: string): string;
+ /**
+ * Clear the style with the specified name for the selected elements and returns this selection.
+ *
+ * @param name Name of the style
+ * @param value null,to clear the style
+ */
+ style(name: string, value: null): this;
+ /**
+ * Sets the value of the style with the specified name for the selected elements and returns this selection.
+ * All elements are given the same style value.
+ *
+ * @param name Name of the style
+ * @param value Constant value for the style
+ * @param priority An optional priority flag, either null or the string important (without the exclamation point)
+ */
+ style(name: string, value: string | number | boolean, priority?: null | 'important'): this;
+ /**
+ * Sets the value of the style with the specified name for the selected elements and returns this selection.
+ * The value for the individual selected elements is determined by the value function.
+ *
+ * @param name Name of the style
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). A null value will clear the style.
+ * @param priority An optional priority flag, either null or the string important (without the exclamation point)
+ */
+ style(name: string, value: ValueFn<GElement, Datum, string | number | boolean | null>, priority?: null | 'important'): this;
+
+ /**
+ * Return the current value of the specified property for the first (non-null) element in the selection.
+ * This is generally useful only if you know that the selection contains exactly one element.
+ *
+ * @param name Name of the property
+ */
+ property(name: string): any;
+ /**
+ * Look up a local variable on the first node of this selection. Note that this is not equivalent to `local.get(selection.node())` in that it will not look up locals set on the parent node(s).
+ *
+ * @param name The `d3.local` variable to look up.
+ */
+ property<T>(name: Local<T>): T | undefined;
+ /**
+ * Sets the value of the property with the specified name for the selected elements and returns this selection.
+ * The value for the individual selected elements is determined by the value function.
+ *
+ * Some HTML elements have special properties that are not addressable using attributes or styles,
+ * such as a form field’s text value and a checkbox’s checked boolean. Use this method to get or set these properties.
+ *
+ * @param name Name of the property
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). A null value will clear the property.
+ */
+ property(name: string, value: ValueFn<GElement, Datum, any>): this;
+ /**
+ * Clears the property with the specified name for the selected elements and returns this selection.
+ *
+ * @param name Name of the property
+ * @param value null,to clear the property
+ */
+ property(name: string, value: null): this;
+ /**
+ * Sets the value of the property with the specified name for the selected elements and returns this selection.
+ * All elements are given the same property value.
+ *
+ * @param name Name of the property
+ * @param value Constant value for the property
+ */
+ property(name: string, value: any): this;
+ /**
+ * Store a value in a `d3.local` variable.
+ * This is equivalent to `selection.each(function (d, i, g) { name.set(this, value.call(this, d, i, g)); })` but more concise.
+ *
+ * @param name A `d3.local` variable
+ * @param value A callback that returns the value to store
+ */
+ property<T>(name: Local<T>, value: ValueFn<GElement, Datum, T>): this;
+ /**
+ * Store a value in a `d3.local` variable for each node in the selection.
+ * This is equivalent to `selection.each(function () { name.set(this, value); })` but more concise.
+ *
+ * @param name A `d3.local` variable
+ * @param value A callback that returns the value to store
+ */
+ property<T>(name: Local<T>, value: T): this;
+
+ /**
+ * Returns the text content for the first (non-null) element in the selection.
+ * This is generally useful only if you know the selection contains exactly one element.
+ */
+ text(): string;
+ /**
+ * Clear the text content of the selected elements and return the selection.
+ */
+ text(value: null): this;
+ /**
+ * Sets the text content to the specified value on all selected elements, replacing any existing child elements.
+ * All elements are given the same text content.
+ *
+ * @param value Text content value for the elements.
+ */
+ text(value: string | number | boolean): this;
+ /**
+ * Sets the text content to the specified value on all selected elements, replacing any existing child elements.
+ * All elements are given the same text content.
+ *
+ * @param value A value unction which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * The function’s return value is then used to set each element’s text content. A null value will clear the content.
+ */
+ text(value: ValueFn<GElement, Datum, string | number | boolean | null>): this;
+
+ /**
+ * Returns a string representation of the inner HTML for the first (non-null) element in the selection.
+ * This is generally useful only if you know the selection contains exactly one element.
+ */
+ html(): string;
+ /**
+ * Clear the html content of the selected elements and return the selection.
+ */
+ html(value: null): this;
+ /**
+ * Sets the inner HTML to the specified value on all selected elements, replacing any existing child elements.
+ * All elements are given the same inner HTML
+ *
+ * @param value String representation of inner HTML.
+ */
+ html(value: string): this;
+ /**
+ * Sets the inner HTML to the specified value on all selected elements, replacing any existing child elements.
+ * The inner HTML is determined for each individual element using a value function.
+ *
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current
+ * datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * The function’s return value is then used to set each element’s inner HTML. A null value will clear the content.
+ */
+ html(value: ValueFn<GElement, Datum, string | null>): this;
+
+ /**
+ * Appends a new element of this type (tag name) as the last child of each selected element,
+ * or before the next following sibling in the update selection if this is an enter selection.
+ * The latter behavior for enter selections allows you to insert elements into the DOM in an order consistent with the new bound data;
+ * however, note that selection.order may still be required if updating elements change order
+ * (i.e., if the order of new data is inconsistent with old data).
+ *
+ * This method returns a new selection containing the appended elements.
+ * Each new element inherits the data of the current elements, if any.
+ *
+ * @param type A string representing the tag name.
+ */
+ append<K extends keyof ElementTagNameMap>(type: K): Selection<ElementTagNameMap[K], Datum, PElement, PDatum>;
+ /**
+ * Appends a new element of this type (tag name) as the last child of each selected element,
+ * or before the next following sibling in the update selection if this is an enter selection.
+ * The latter behavior for enter selections allows you to insert elements into the DOM in an order consistent with the new bound data;
+ * however, note that selection.order may still be required if updating elements change order
+ * (i.e., if the order of new data is inconsistent with old data).
+ *
+ * This method returns a new selection containing the appended elements.
+ * Each new element inherits the data of the current elements, if any.
+ *
+ * The generic refers to the type of the child element to be appended.
+ *
+ * @param type A string representing the tag name. The specified name may have a namespace prefix, such as svg:text
+ * to specify a text attribute in the SVG namespace. If no namespace is specified, the namespace will be inherited
+ * from the parent element; or, if the name is one of the known prefixes, the corresponding namespace will be used
+ * (for example, svg implies svg:svg)
+ */
+ append<ChildElement extends BaseType>(type: string): Selection<ChildElement, Datum, PElement, PDatum>;
+
+ /**
+ * Appends a new element of the type provided by the element creator function as the last child of each selected element,
+ * or before the next following sibling in the update selection if this is an enter selection.
+ * The latter behavior for enter selections allows you to insert elements into the DOM in an order consistent with the new bound data;
+ * however, note that selection.order may still be required if updating elements change order
+ * (i.e., if the order of new data is inconsistent with old data).
+ *
+ * This method returns a new selection containing the appended elements.
+ * Each new element inherits the data of the current elements, if any.
+ *
+ * The generic refers to the type of the child element to be appended.
+ *
+ * @param type A creator function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return
+ * an element to be appended. (The function typically creates a new element, but it may instead return an existing element.)
+ */
+ append<ChildElement extends BaseType>(type: ValueFn<GElement, Datum, ChildElement>): Selection<ChildElement, Datum, PElement, PDatum>;
+
+ /**
+ * Inserts a new element of the specified type (tag name) before the first element matching the specified
+ * before selector for each selected element. For example, a before selector :first-child will prepend nodes before the first child.
+ * If before is not specified, it defaults to null. (To append elements in an order consistent with bound data, use selection.append.)
+ *
+ * This method returns a new selection containing the appended elements.
+ * Each new element inherits the data of the current elements, if any.
+ *
+ * The generic refers to the type of the child element to be appended.
+ *
+ * @param type A string representing the tag name for the element type to be inserted.
+ * @param before One of:
+ * * A CSS selector string for the element before which the insertion should occur.
+ * * A child selector function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return the child element
+ * before which the element should be inserted.
+ */
+ insert<K extends keyof ElementTagNameMap>(
+ type: K,
+ before?: string | ValueFn<GElement, Datum, BaseType>
+ ): Selection<ElementTagNameMap[K], Datum, PElement, PDatum>;
+ /**
+ * Inserts a new element of the specified type (tag name) before the first element matching the specified
+ * before selector for each selected element. For example, a before selector :first-child will prepend nodes before the first child.
+ * If before is not specified, it defaults to null. (To append elements in an order consistent with bound data, use selection.append.)
+ *
+ * This method returns a new selection containing the appended elements.
+ * Each new element inherits the data of the current elements, if any.
+ *
+ * The generic refers to the type of the child element to be appended.
+ *
+ * @param type One of:
+ * * A string representing the tag name for the element type to be inserted. The specified name may have a namespace prefix,
+ * such as svg:text to specify a text attribute in the SVG namespace. If no namespace is specified, the namespace will be inherited
+ * from the parent element; or, if the name is one of the known prefixes, the corresponding namespace will be used
+ * (for example, svg implies svg:svg)
+ * * A creator function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return
+ * an element to be inserted. (The function typically creates a new element, but it may instead return an existing element.)
+ * @param before One of:
+ * * A CSS selector string for the element before which the insertion should occur.
+ * * A child selector function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return the child element
+ * before which the element should be inserted.
+ */
+ insert<ChildElement extends BaseType>(
+ type: string | ValueFn<GElement, Datum, ChildElement>,
+ before?: string | ValueFn<GElement, Datum, BaseType>
+ ): Selection<ChildElement, Datum, PElement, PDatum>;
+
+ /**
+ * Removes the selected elements from the document.
+ * Returns this selection (the removed elements) which are now detached from the DOM.
+ */
+ remove(): this;
+
+ /**
+ * Inserts clones of the selected elements immediately following the selected elements and returns a selection of the newly
+ * added clones. If deep is true, the descendant nodes of the selected elements will be cloned as well. Otherwise, only the elements
+ * themselves will be cloned.
+ *
+ * @param deep Perform deep cloning if this flag is set to true.
+ */
+ clone(deep?: boolean): Selection<GElement, Datum, PElement, PDatum>;
+
+ /**
+ * Returns a new selection merging this selection with the specified other selection.
+ * The returned selection has the same number of groups and the same parents as this selection.
+ * Any missing (null) elements in this selection are filled with the corresponding element,
+ * if present (not null), from the specified selection. (If the other selection has additional groups or parents,
+ * they are ignored.)
+ *
+ * This method is commonly used to merge the enter and update selections after a data-join.
+ * After modifying the entering and updating elements separately, you can merge the two selections and
+ * perform operations on both without duplicate code.
+ *
+ * This method is not intended for concatenating arbitrary selections, however: if both this selection
+ * and the specified other selection have (non-null) elements at the same index, this selection’s element
+ * is returned in the merge and the other selection’s element is ignored.
+ *
+ * @param other Selection to be merged.
+ */
+ merge(other: Selection<GElement, Datum, PElement, PDatum>): Selection<GElement, Datum, PElement, PDatum>;
+
+ /**
+ * Filters the selection, returning a new selection that contains only the elements for
+ * which the specified filter is true.
+ *
+ * The returned filtered selection preserves the parents of this selection, but like array.filter,
+ * it does not preserve indexes as some elements may be removed; use selection.select to preserve the index, if needed.
+ *
+ * @param selector A CSS selector string to match when filtering.
+ */
+ filter(selector: string): Selection<GElement, Datum, PElement, PDatum>;
+ /**
+ * Filters the selection, returning a new selection that contains only the elements for
+ * which the specified filter is true.
+ *
+ * The returned filtered selection preserves the parents of this selection, but like array.filter,
+ * it does not preserve indexes as some elements may be removed; use selection.select to preserve the index, if needed.
+ *
+ * The generic refers to the type of element which will be selected after applying the filter, i.e. if the element types
+ * contained in a pre-filter selection are narrowed to a subset as part of the filtering.
+ *
+ * @param selector A CSS selector string to match when filtering.
+ */
+ filter<FilteredElement extends BaseType>(selector: string): Selection<FilteredElement, Datum, PElement, PDatum>;
+ /**
+ * Filter the selection, returning a new selection that contains only the elements for
+ * which the specified filter is true.
+ *
+ * The returned filtered selection preserves the parents of this selection, but like array.filter,
+ * it does not preserve indexes as some elements may be removed; use selection.select to preserve the index, if needed.
+ *
+ * @param selector A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return true
+ * for an element to be included, and false otherwise.
+ */
+ filter(selector: ValueFn<GElement, Datum, boolean>): Selection<GElement, Datum, PElement, PDatum>;
+ /**
+ * Filter the selection, returning a new selection that contains only the elements for
+ * which the specified filter is true.
+ *
+ * The returned filtered selection preserves the parents of this selection, but like array.filter,
+ * it does not preserve indexes as some elements may be removed; use selection.select to preserve the index, if needed.
+ *
+ * @param selector A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). This function should return true
+ * for an element to be included, and false otherwise.
+ */
+ filter<FilteredElement extends BaseType>(selector: ValueFn<GElement, Datum, boolean>): Selection<FilteredElement, Datum, PElement, PDatum>;
+
+ /**
+ * Return a new selection that contains a copy of each group in this selection sorted according
+ * to the compare function. After sorting, re-inserts elements to match the resulting order (per selection.order).
+ *
+ * Note that sorting is not guaranteed to be stable; however, it is guaranteed to have the same
+ * behavior as your browser’s built-in sort method on arrays.
+ *
+ * @param comparator An optional comparator function, which defaults to "ascending". The function is passed
+ * two elements’ data a and b to compare. It should return either a negative, positive, or zero value.
+ * If negative, then a should be before b; if positive, then a should be after b; otherwise, a and b are
+ * considered equal and the order is arbitrary.
+ */
+ sort(comparator?: (a: Datum, b: Datum) => number): this;
+
+ /**
+ * Re-insert elements into the document such that the document order of each group matches the selection order.
+ * This is equivalent to calling selection.sort if the data is already sorted, but much faster.
+ */
+ order(): this;
+
+ /**
+ * Re-insert each selected element, in order, as the last child of its parent.
+ */
+ raise(): this;
+
+ /**
+ * Re-insert each selected element, in order, as the first child of its parent.
+ */
+ lower(): this;
+
+ // Data Join ---------------------------------
+
+ /**
+ * Returns the bound datum for the first (non-null) element in the selection.
+ * This is generally useful only if you know the selection contains exactly one element.
+ */
+ datum(): Datum;
+ /**
+ * Delete the bound data for each element in the selection.
+ */
+ datum(value: null): Selection<GElement, undefined, PElement, PDatum>;
+ /**
+ * Sets the element’s bound data using the specified value function on all selected elements.
+ * Unlike selection.data, this method does not compute a join and does not affect
+ * indexes or the enter and exit selections.
+ *
+ * The generic refers to the type of the new datum to be used for the selected elements.
+ *
+ * @param value A value function which is evaluated for each selected element, in order,
+ * being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element (nodes[i]). The function is then used to set each element’s new data.
+ * A null value will delete the bound data.
+ */
+ datum<NewDatum>(value: ValueFn<GElement, Datum, NewDatum>): Selection<GElement, NewDatum, PElement, PDatum>;
+ /**
+ * Sets the element’s bound data to the specified value on all selected elements.
+ * Unlike selection.data, this method does not compute a join and does not affect
+ * indexes or the enter and exit selections.
+ *
+ * The generic refers to the type of the new datum to be used for the selected elements.
+ *
+ * @param value A value object to be used as the datum for each element.
+ */
+ datum<NewDatum>(value: NewDatum): Selection<GElement, NewDatum, PElement, PDatum>;
+
+ /**
+ * Returns the array of data for the selected elements.
+ */
+ data(): Datum[];
+ /**
+ * Joins the specified array of data with the selected elements, returning a new selection that represents
+ * the update selection: the elements successfully bound to data. Also defines the enter and exit selections on
+ * the returned selection, which can be used to add or remove elements to correspond to the new data.
+ *
+ * The data is specified for each group in the selection. If the selection has multiple groups
+ * (such as d3.selectAll followed by selection.selectAll), then data should typically be specified as a function.
+ *
+ * If a key function is not specified, then the first datum in data is assigned to the first selected element,
+ * the second datum to the second selected element, and so on.
+ * A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index,
+ * by computing a string identifier for each datum and element.
+ *
+ * The update and enter selections are returned in data order, while the exit selection preserves the selection
+ * order prior to the join. If a key function is specified, the order of elements in the selection may not match
+ * their order in the document; use selection.order or selection.sort as needed.
+ *
+ * This method cannot be used to clear bound data; use selection.datum instead.
+ *
+ * For details see: {@link https://github.com/d3/d3-selection#joining-data }
+ *
+ * The generic refers to the type of the new datum to be used for the selected elements.
+ *
+ * @param data The specified data is an array of arbitrary values (e.g., numbers or objects).
+ * @param key An optional key function which is evaluated for each selected element, in order, being passed the
+ * current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]); the returned string is the element’s key.
+ * The key function is then also evaluated for each new datum in data, being passed the current datum (d),
+ * the current index (i), and the group’s new data, with this as the group’s parent DOM element (nodes[i]); the returned string is the datum’s key.
+ * The datum for a given key is assigned to the element with the matching key. If multiple elements have the same key,
+ * the duplicate elements are put into the exit selection; if multiple data have the same key, the duplicate data are put into the enter selection.
+ */
+ data<NewDatum>(data: NewDatum[], key?: ValueFn<GElement | PElement, Datum | NewDatum, KeyType>): Selection<GElement, NewDatum, PElement, PDatum>;
+ /**
+ * Joins the data returned by the specified value function with the selected elements, returning a new selection that it represents
+ * the update selection: the elements successfully bound to data. Also defines the enter and exit selections on
+ * the returned selection, which can be used to add or remove elements to correspond to the new data.
+ *
+ * The data is specified for each group in the selection.
+ *
+ * If a key function is not specified, then the first datum in data is assigned to the first selected element,
+ * the second datum to the second selected element, and so on.
+ * A key function may be specified to control which datum is assigned to which element, replacing the default join-by-index,
+ * by computing a string identifier for each datum and element.
+ *
+ * The update and enter selections are returned in data order, while the exit selection preserves the selection
+ * order prior to the join. If a key function is specified, the order of elements in the selection may not match
+ * their order in the document; use selection.order or selection.sort as needed.
+ *
+ * This method cannot be used to clear bound data; use selection.datum instead.
+ *
+ * For details see: {@link https://github.com/d3/d3-selection#joining-data }
+ *
+ * The generic refers to the type of the new datum to be used for the selected elements.
+ *
+ * @param data A value function which will be evaluated for each group in order, being passed the group’s parent datum
+ * (d, which may be undefined), the group index (i), and the selection’s parent nodes (nodes),
+ * with this as the group’s parent element. The function returns an array of values for each group.
+ * @param key An optional key function which is evaluated for each selected element, in order, being passed the
+ * current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]); the returned string is the element’s key.
+ * The key function is then also evaluated for each new datum in data, being passed the current datum (d),
+ * the current index (i), and the group’s new data, with this as the group’s parent DOM element (nodes[i]); the returned string is the datum’s key.
+ * The datum for a given key is assigned to the element with the matching key. If multiple elements have the same key,
+ * the duplicate elements are put into the exit selection; if multiple data have the same key, the duplicate data are put into the enter selection.
+ */
+ data<NewDatum>(data: ValueFn<PElement, PDatum, NewDatum[]>, key?: ValueFn<GElement | PElement, Datum | NewDatum, KeyType>): Selection<GElement, NewDatum, PElement, PDatum>;
+
+ /**
+ * Appends, removes and reorders elements as necessary to match the data that was previously bound by `selection.data`, returning the merged enter and update selection.
+ * This method is a convenient alternative to the more explicit `selection.enter`, `selection.exit`, `selection.append` and `selection.remove`.
+ *
+ * The "matching" logic is determined by the key function passed to `selection.data`.
+ */
+ join<K extends keyof ElementTagNameMap, OldDatum = Datum>(
+ enter: K,
+ update?: (elem: Selection<GElement, Datum, PElement, PDatum>) => Selection<GElement, Datum, PElement, PDatum> | undefined,
+ exit?: (elem: Selection<GElement, OldDatum, PElement, PDatum>) => void
+ ): Selection<GElement | ElementTagNameMap[K], Datum, PElement, PDatum>;
+ /**
+ * Appends, removes and reorders elements as necessary to match the data that was previously bound by `selection.data`, returning the merged enter and update selection.
+ * This method is a convenient alternative to the more explicit `selection.enter`, `selection.exit`, `selection.append` and `selection.remove`.
+ *
+ * The "matching" logic is determined by the key function passed to `selection.data`.
+ */
+ join<ChildElement extends BaseType, OldDatum = Datum>(
+ enter: string,
+ update?: (elem: Selection<GElement, Datum, PElement, PDatum>) => Selection<GElement, Datum, PElement, PDatum> | undefined,
+ exit?: (elem: Selection<GElement, OldDatum, PElement, PDatum>) => void
+ ): Selection<ChildElement | GElement, Datum, PElement, PDatum>;
+ /**
+ * Appends, removes and reorders elements as necessary to match the data that was previously bound by `selection.data`, returning the merged enter and update selection.
+ * This method is a convenient alternative to the more explicit `selection.enter`, `selection.exit`, `selection.append` and `selection.remove`.
+ *
+ * The "matching" logic is determined by the key function passed to `selection.data`.
+ */
+ join<ChildElement extends BaseType, OldDatum = Datum>(
+ enter: (elem: Selection<EnterElement, Datum, PElement, PDatum>) => Selection<ChildElement, Datum, PElement, PDatum>,
+ update?: (elem: Selection<GElement, Datum, PElement, PDatum>) => Selection<GElement, Datum, PElement, PDatum> | undefined,
+ exit?: (elem: Selection<GElement, OldDatum, PElement, PDatum>) => void
+ ): Selection<ChildElement | GElement, Datum, PElement, PDatum>;
+
+ /**
+ * Return the enter selection: placeholder nodes for each datum that had no corresponding DOM element
+ * in the selection. (The enter selection is empty for selections not returned by selection.data.)
+ */
+ enter(): Selection<EnterElement, Datum, PElement, PDatum>;
+
+ /**
+ * Returns the exit selection: existing DOM elements in the selection for which no new datum was found.
+ * (The exit selection is empty for selections not returned by selection.data.)
+ *
+ * IMPORTANT: The generic refers to the type of the old datum associated with the exit selection elements.
+ * Ensure you set the generic to the correct type, if you need to access the data on the exit selection in
+ * follow-up steps, e.g. to set styles as part of an exit transition before removing them.
+ */
+ exit<OldDatum>(): Selection<GElement, OldDatum, PElement, PDatum>;
+
+ // Event Handling -------------------
+
+ /**
+ * Return the currently-assigned listener for the specified event typename on the first (non-null) selected element,
+ * if any, If multiple typenames are specified, the first matching listener is returned.
+ *
+ * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
+ * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
+ * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
+ * such as "input change"" or "click.foo click.bar".
+ */
+ on(typenames: string): ValueFn<GElement, Datum, void> | undefined;
+ /**
+ * Remove a listener for the specified event type names. To remove all listeners for a given name,
+ * pass null as the listener and ".foo" as the typename, where foo is the name; to remove all listeners with no name, specify "." as the typename.
+ *
+ * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
+ * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
+ * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
+ * such as "input change"" or "click.foo click.bar".
+ * @param listener null to indicate removal of listener
+ */
+ on(typenames: string, listener: null): this;
+ /**
+ * Add an event listener for the specified event type names. If an event listener was previously registered for the same typename
+ * on a selected element, the old listener is removed before the new listener is added.
+ *
+ * When a specified event is dispatched on a selected node, the specified listener will be evaluated for each selected element.
+ *
+ * An optional capture flag may be specified which corresponds to the W3C useCapture flag:
+ * "After initiating capture, all events of the specified type will be dispatched to the registered EventListener before being
+ * dispatched to any EventTargets beneath them in the tree. Events which are bubbling upward through the tree will not
+ * trigger an EventListener designated to use capture."
+ *
+ * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
+ * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
+ * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
+ * such as "input change"" or "click.foo click.bar".
+ * @param listener A listener function which will be evaluated for each selected element, being passed the current datum (d), the current index (i),
+ * and the current group (nodes), with this as the current DOM element (nodes[i]). Listeners always see the latest datum for their element,
+ * but the index is a property of the selection and is fixed when the listener is assigned; to update the index, re-assign the listener.
+ * To access the current event within a listener, use d3.event.
+ * @param capture An optional capture flag which corresponds to the W3C useCapture flag.
+ */
+ on(typenames: string, listener: ValueFn<GElement, Datum, void>, capture?: boolean): this;
+
+ /**
+ * Dispatches a custom event of the specified type to each selected element, in order.
+ * An optional parameters map may be specified to set additional properties of the event.
+ *
+ * @param type Name of event to dispatch
+ * @param parameters An optional value map with custom event parameters
+ */
+ dispatch(type: string, parameters?: CustomEventParameters): this;
+ /**
+ * Dispatches a custom event of the specified type to each selected element, in order.
+ * An optional value function returning a parameters map for each element in the selection may be specified to set additional properties of the event.
+ *
+ * @param type Name of event to dispatch
+ * @param parameters A value function which is evaluated for each selected element, in order,
+ * being passed the current datum (d), the current index (i), and the current group (nodes),
+ * with this as the current DOM element (nodes[i]). It must return the parameters map for the current element.
+ */
+ dispatch(type: string, parameters?: ValueFn<GElement, Datum, CustomEventParameters>): this;
+
+ // Control Flow ----------------------
+
+ /**
+ * Invoke the specified function for each selected element, passing in the current datum (d),
+ * the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]).
+ * This method can be used to invoke arbitrary code for each selected element, and is useful for creating a context to access parent and child data simultaneously.
+ *
+ * @param func A function which is invoked for each selected element,
+ * being passed the current datum (d), the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]).
+ */
+ each(func: ValueFn<GElement, Datum, void>): this;
+
+ /**
+ * Invoke the specified function exactly once, passing in this selection along with any optional arguments.
+ * Returns this selection.
+ *
+ * @param func A function which is passed this selection as the first argument along with any optional arguments.
+ * @param args List of optional arguments to be passed to the callback function.
+ */
+ call(func: (selection: Selection<GElement, Datum, PElement, PDatum>, ...args: any[]) => void, ...args: any[]): this;
+
+ /**
+ * Return true if this selection contains no (non-null) elements.
+ */
+ empty(): boolean;
+
+ /**
+ * Return the first (non-null) element in this selection. If the selection is empty, returns null.
+ */
+ node(): GElement | null;
+
+ /**
+ * Return an array of all (non-null) elements in this selection.
+ */
+ nodes(): GElement[];
+
+ /**
+ * Returns the total number of elements in this selection.
+ */
+ size(): number;
+}
+
+/**
+ * Selects the root element, document.documentElement. This function can also be used to test for selections
+ * (instanceof d3.selection) or to extend the selection prototype.
+ */
+export type SelectionFn = () => Selection<HTMLElement, any, null, undefined>;
+
+/**
+ * Selects the root element, document.documentElement. This function can also be used to test for selections
+ * (instanceof d3.selection) or to extend the selection prototype.
+ */
+export const selection: SelectionFn;
+
+// ---------------------------------------------------------------------------
+// on.js event and customEvent related
+// ---------------------------------------------------------------------------
+
+/**
+ * A D3 Base Event
+ */
+export interface BaseEvent {
+ /**
+ * Event type
+ */
+ type: string;
+ /**
+ * The prior value of d3.event, allowing custom events to retain a reference to the originating native event.
+ */
+ sourceEvent?: any; // Could be of all sorts of types, too general: BaseEvent | Event | MouseEvent | TouchEvent | ... | OwnCustomEventType;
+}
+
+/**
+ * The current event, if any. This is set during the invocation of an event listener, and is reset after the listener terminates.
+ * Use this to access standard event fields such as event.timeStamp and methods such as event.preventDefault.
+ * While you can use the native event.pageX and event.pageY, it is often more convenient to transform the event position to
+ * the local coordinate system of the container that received the event using d3.mouse, d3.touch or d3.touches.
+ *
+ * If you use Babel, Webpack, or another ES6-to-ES5 bundler, be aware that the value of d3.event changes during an event!
+ * An import of d3.event must be a live binding, so you may need to configure the bundler to import from D3’s ES6 modules
+ * rather than from the generated UMD bundle; not all bundlers observe jsnext:main.
+ * Also beware of conflicts with the window.event global.
+ */
+export const event: any; // Could be of all sorts of types, too general: BaseEvent | Event | MouseEvent | TouchEvent | ... | OwnCustomEventType;
+
+/**
+ * Invokes the specified listener, using the specified "that" as "this" context and passing the specified arguments, if any.
+ * During the invocation, d3.event is set to the specified event; after the listener returns (or throws an error),
+ * d3.event is restored to its previous value.
+ * In addition, sets event.sourceEvent to the prior value of d3.event, allowing custom events to retain a reference to the originating native event.
+ * Returns the value returned by the listener.
+ *
+ * The first generic "Context" refers to the "this" context type in which the listener will be invoked.
+ * The second generic "Result" specifies the return type of the listener.
+ *
+ * @param event The event to which d3.event will be set during the listener invocation.
+ * @param listener The event listener function to be invoked. This function will be invoked with the "this" context, provided
+ * by the "that" argument of customEvent(...). It will be passed all optional arguments passed to customEvent(...). The function returns
+ * a value corresponding to the type of the second generic type.
+ * @param that The "this"" context which will be used for the invocation of listener.
+ * @param args A list of optional arguments, which will be passed to listener.
+ */
+export function customEvent<Context, Result>(event: BaseEvent, listener: (this: Context, ...args: any[]) => Result, that: Context, ...args: any[]): Result;
+
+// ---------------------------------------------------------------------------
+// mouse.js related
+// ---------------------------------------------------------------------------
+
+/**
+ * Get (x, y)-coordinates of the current event relative to the specified container element.
+ * The container may be an HTML or SVG container element, such as a G element or an SVG element.
+ * The coordinates are returned as a two-element array of numbers [x, y].
+ *
+ * @param container Container element relative to which coordinates are calculated.
+ */
+export function mouse(container: ContainerElement): [number, number];
+
+// ---------------------------------------------------------------------------
+// touch.js and touches.js related
+// ---------------------------------------------------------------------------
+
+/**
+ * Returns the x and y coordinates of the touch with the specified identifier associated
+ * with the current event relative to the specified container.
+ * The container may be an HTML or SVG container element, such as a G element or an SVG element.
+ * The coordinates are returned as a two-element array of numbers [x, y] or null if there is no touch with
+ * the specified identifier in touches, returns null; this can be useful for ignoring touchmove events
+ * where the only some touches have moved.
+ *
+ * If touches is not specified, it defaults to the current event’s changedTouches property.
+ *
+ * @param container Container element relative to which coordinates are calculated.
+ * @param identifier Touch Identifier associated with the current event.
+ */
+export function touch(container: ContainerElement, identifier: number): [number, number] | null;
+
+/**
+ * Return the x and y coordinates of the touch with the specified identifier associated
+ * with the current event relative to the specified container.
+ * The container may be an HTML or SVG container element, such as a G element or an SVG element.
+ * The coordinates are returned as a two-element array of numbers [x, y] or null if there is no touch with
+ * the specified identifier in touches, returns null; this can be useful for ignoring touchmove events
+ * where the only some touches have moved.
+ *
+ * If touches is not specified, it defaults to the current event’s changedTouches property.
+ *
+ * @param container Container element relative to which coordinates are calculated.
+ * @param touches TouchList to be used when identifying the touch.
+ * @param identifier Touch Identifier associated with the current event.
+ */
+export function touch(container: ContainerElement, touches: TouchList, identifier: number): [number, number] | null;
+
+/**
+ * Return the x and y coordinates of the touches associated with the current event relative to the specified container.
+ * The container may be an HTML or SVG container element, such as a G element or an SVG element.
+ * The coordinates are returned as an array of two-element arrays of numbers [[x1, y1], [x2, y2], …].
+ *
+ * If touches is not specified, it defaults to the current event’s touches property.
+ *
+ * @param container Container element relative to which coordinates are calculated.
+ * @param touches TouchList to be used.
+ */
+export function touches(container: ContainerElement, touches?: TouchList): Array<[number, number]>;
+
+/**
+ * Returns the x and y coordinates of the specified event relative to the specified container.
+ * (The event may also be a touch.) The container may be an HTML or SVG container element, such as a G element or an SVG element.
+ * The coordinates are returned as a two-element array of numbers [x, y].
+ *
+ * @param container Container element relative to which coordinates are calculated.
+ * @param event A User interface event (e.g. mouse event, touch or MSGestureEvent) with captured clientX and clientY properties.
+ */
+export function clientPoint(container: ContainerElement, event: ClientPointEvent): [number, number];
+
+// ---------------------------------------------------------------------------
+// style
+// ---------------------------------------------------------------------------
+
+/**
+ * Returns the value of the style property with the specified name for the specified node.
+ * If the node has an inline style with the specified name, its value is returned; otherwise, the computed property value is returned.
+ * See also selection.style.
+ *
+ * @param node A DOM node (e.g. HTMLElement, SVGElement) for which to retrieve the style property.
+ * @param name Style property name.
+ */
+export function style(node: Element, name: string): string;
+
+// ---------------------------------------------------------------------------
+// local.js related
+// ---------------------------------------------------------------------------
+
+export interface Local<T> {
+ /**
+ * Retrieves a local variable stored on the node (or one of its parents).
+ *
+ * @param node A node element.
+ */
+ get(node: Element): T | undefined;
+ /**
+ * Deletes the value associated with the given node. Values stored on ancestors are not affected, meaning that child nodes will still see inherited values.
+ *
+ * This function returns true if there was a value stored directly on the node, and false otherwise.
+ *
+ * @param node A node element.
+ */
+ remove(node: Element): boolean;
+ /**
+ * Store a value for this local variable. Calling `.get()` on children of this node will also retrieve the variable's value.
+ *
+ * @param node A node element.
+ * @param value Value to store locally
+ */
+ set(node: Element, value: T): Element;
+ /**
+ * Obtain a string with the internally assigned property name for the local
+ * which is used to store the value on a node
+ */
+ toString(): string;
+}
+
+/**
+ * Obtain a new local variable
+ *
+ * The generic refers to the type of the variable to store locally.
+ */
+export function local<T>(): Local<T>;
+
+// ---------------------------------------------------------------------------
+// namespace.js related
+// ---------------------------------------------------------------------------
+
+/**
+ * Interface for object literal containing local name with related fully qualified namespace
+ */
+export interface NamespaceLocalObject {
+ /**
+ * Fully qualified namespace
+ */
+ space: string;
+ /**
+ * Name of the local to be namespaced.
+ */
+ local: string;
+}
+
+/**
+ * Obtain an object with properties of fully qualified namespace string and
+ * name of local by parsing a shorthand string "prefix:local". If the prefix
+ * does not exist in the "namespaces" object provided by d3-selection, then
+ * the local name is returned as a simple string.
+ *
+ * @param prefixedLocal A string composed of the namespace prefix and local
+ * name separated by colon, e.g. "svg:text".
+ */
+export function namespace(prefixedLocal: string): NamespaceLocalObject | string;
+
+// ---------------------------------------------------------------------------
+// namespaces.js related
+// ---------------------------------------------------------------------------
+
+/**
+ * Interface for maps of namespace prefixes to corresponding fully qualified namespace strings
+ */
+export interface NamespaceMap { [prefix: string]: string; }
+
+/**
+ * Map of namespace prefixes to corresponding fully qualified namespace strings
+ */
+export const namespaces: NamespaceMap;
+
+// ---------------------------------------------------------------------------
+// window.js related
+// ---------------------------------------------------------------------------
+
+/**
+ * Returns the owner window for the specified node. If node is a node, returns the owner document’s default view;
+ * if node is a document, returns its default view; otherwise returns the node.
+ *
+ * @param DOMNode A DOM element
+ */
+export function window(DOMNode: Window | Document | Element): Window;
+
+// ---------------------------------------------------------------------------
+// creator.js and matcher.js Complex helper closure generating functions
+// for explicit bound-context dependent use
+// ---------------------------------------------------------------------------
+
+/**
+ * Given the specified element name, returns a single-element selection containing
+ * a detached element of the given name in the current document.
+ *
+ * @param name tag name of the element to be added.
+ */
+export function create<K extends keyof ElementTagNameMap>(name: K): Selection<ElementTagNameMap[K], undefined, null, undefined>;
+/**
+ * Given the specified element name, returns a single-element selection containing
+ * a detached element of the given name in the current document.
+ *
+ * @param name Tag name of the element to be added. See "namespace" for details on supported namespace prefixes,
+ * such as for SVG elements.
+ */
+export function create<NewGElement extends Element>(name: string): Selection<NewGElement, undefined, null, undefined>;
+
+/**
+ * Given the specified element name, returns a function which creates an element of the given name,
+ * assuming that "this" is the parent element.
+ *
+ * @param name Tag name of the element to be added.
+ */
+export function creator<K extends keyof ElementTagNameMap>(name: K): (this: BaseType) => ElementTagNameMap[K];
+/**
+ * Given the specified element name, returns a function which creates an element of the given name,
+ * assuming that "this" is the parent element.
+ *
+ * The generic refers to the type of the new element to be returned by the creator function.
+ *
+ * @param name Tag name of the element to be added. See "namespace" for details on supported namespace prefixes,
+ * such as for SVG elements.
+ */
+export function creator<NewGElement extends Element>(name: string): (this: BaseType) => NewGElement;
+
+/**
+ * Given the specified selector, returns a function which returns true if "this" element matches the specified selector.
+ *
+ * @param selector A CSS selector string.
+ */
+export function matcher(selector: string): (this: BaseType) => boolean;
+
+// ----------------------------------------------------------------------------
+// selector.js and selectorAll.js related functions
+// ----------------------------------------------------------------------------
+
+/**
+ * Given the specified selector, returns a function which returns the first descendant of "this" element
+ * that matches the specified selector.
+ *
+ * The generic refers to the type of the returned descendant element.
+ *
+ * @param selector A CSS selector string.
+ */
+export function selector<DescElement extends Element>(selector: string): (this: BaseType) => DescElement;
+
+/**
+ * Given the specified selector, returns a function which returns all descendants of "this" element that match the specified selector.
+ *
+ * The generic refers to the type of the returned descendant element.
+ *
+ * @param selector A CSS selector string.
+ */
+export function selectorAll<DescElement extends Element>(selector: string): (this: BaseType) => NodeListOf<DescElement>;
diff --git a/chromium/third_party/node/node_modules/@types/d3-transition/index.d.ts b/chromium/third_party/node/node_modules/@types/d3-transition/index.d.ts
new file mode 100755
index 00000000000..41e2b1c9340
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3-transition/index.d.ts
@@ -0,0 +1,643 @@
+// Type definitions for D3JS d3-transition module 3.0
+// Project: https://github.com/d3/d3-transition/, https://d3js.org/d3-transition
+// Definitions by: Tom Wanzek <https://github.com/tomwanzek>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// Robert Moura <https://github.com/robertmoura>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+// Last module patch version validated against: 3.0.1
+
+import { ArrayLike, BaseType, Selection, ValueFn } from 'd3-selection';
+
+/**
+ * Extend interface 'Selection' by declaration merging with 'd3-selection'
+ */
+declare module 'd3-selection' {
+ /**
+ * A D3 Selection of elements.
+ *
+ * The first generic "GElement" refers to the type of the selected element(s).
+ * The second generic "Datum" refers to the type of the datum of a selected element(s).
+ * The third generic "PElement" refers to the type of the parent element(s) in the D3 selection.
+ * The fourth generic "PDatum" refers to the type of the datum of the parent element(s).
+ */
+ interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
+ /**
+ * Interrupts the active transition of the specified name on the selected elements, and cancels any pending transitions with the specified name, if any.
+ * If a name is not specified, null is used.
+ *
+ * IMPORTANT: Interrupting a transition on an element has no effect on any transitions on any descendant elements.
+ * For example, an axis transition consists of multiple independent, synchronized transitions on the descendants of the axis G element
+ * (the tick lines, the tick labels, the domain path, etc.). To interrupt the axis transition, you must therefore interrupt the descendants.
+ *
+ * @param name Name of the transition.
+ */
+ interrupt(name?: string): this;
+ /**
+ * Returns a new transition on the given selection with the specified name. If a name is not specified, null is used.
+ * The new transition is only exclusive with other transitions of the same name.
+ *
+ * @param name Name of the transition.
+ */
+ transition(name?: string): Transition<GElement, Datum, PElement, PDatum>;
+ /**
+ * Returns a new transition on the given selection.
+ *
+ * When using a transition instance, the returned transition has the same id and name as the specified transition.
+ * If a transition with the same id already exists on a selected element, the existing transition is returned for that element.
+ * Otherwise, the timing of the returned transition is inherited from the existing transition of the same id on the nearest ancestor of each selected element.
+ * Thus, this method can be used to synchronize a transition across multiple selections,
+ * or to re-select a transition for specific elements and modify its configuration.
+ *
+ * If the specified transition is not found on a selected node or its ancestors (such as if the transition already ended),
+ * the default timing parameters are used; however, in a future release, this will likely be changed to throw an error.
+ *
+ * @param transition A transition instance.
+ */
+ transition(transition: Transition<BaseType, any, any, any>): Transition<GElement, Datum, PElement, PDatum>;
+ }
+}
+
+/**
+ * Return the active transition on the specified node with the specified name, if any.
+ * If no name is specified, null is used. Returns null if there is no such active transition on the specified node.
+ * This method is useful for creating chained transitions.
+ *
+ * The first generic "GElement" refers to the type of element on which the returned active transition was defined. The second generic "Datum" refers to the type of the
+ * datum, of a selected element on which the transition is defined. The third generic refers to the type of the parent elements in the returned Transition.
+ * The fourth generic refers to the type of the datum defined on the parent elements in the returned Transition.
+ *
+ * @param node Element for which the active transition should be returned.
+ * @param name Name of the transition.
+ */
+// tslint:disable-next-line:no-unnecessary-generics
+export function active<GElement extends BaseType, Datum, PElement extends BaseType, PDatum>(node: GElement, name?: string): Transition<GElement, Datum, PElement, PDatum> | null;
+
+/**
+ * Interrupts the active transition of the specified name on the specified node, and cancels any pending transitions with the specified name, if any.
+ * If a name is not specified, null is used.
+ *
+ * @param node Element for which the transition should be interrupted.
+ * @param name Name of the transition to be interrupted. If a name is not specified, null is used.
+ */
+export function interrupt(node: BaseType, name?: string): void;
+
+/**
+ * A D3 Transition.
+ *
+ * The first generic "GElement" refers to the type of the selected element(s) in the Transition.
+ * The second generic "Datum" refers to the type of the datum of a selected element(s) in the Transition.
+ * The third generic "PElement" refers to the type of the parent element(s) in the D3 selection in the Transition.
+ * The fourth generic "PDatum" refers to the type of the datum of the parent element(s) in the Transition.
+ */
+export interface Transition<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
+ // Sub-selection -------------------------
+
+ /**
+ * For each selected element, select the first descendant element that matches the specified selector string, if any,
+ * and returns a transition on the resulting selection. The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * The generic represents the type of the descendant element to be selected.
+ *
+ * @param selector CSS selector string
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ select<DescElement extends BaseType>(selector: string): Transition<DescElement, Datum, PElement, PDatum>;
+ /**
+ * For each selected element, select the descendant element returned by the selector function, if any,
+ * and returns a transition on the resulting selection. The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * The generic represents the type of the descendant element to be selected.
+ *
+ * @param selector A selector function, which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * It must return an element, or null if there is no matching element.
+ */
+ select<DescElement extends BaseType>(selector: ValueFn<GElement, Datum, DescElement>): Transition<DescElement, Datum, PElement, PDatum>;
+
+ /**
+ * For each selected element, select all descendant elements that match the specified selector string, if any,
+ * and returns a transition on the resulting selection. The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element, the existing transition is returned for that element.
+ *
+ * The first generic "DescElement" refers to the type of descendant element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param selector CSS selector string
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ selectAll<DescElement extends BaseType, OldDatum>(selector: string): Transition<DescElement, OldDatum, GElement, Datum>;
+ /**
+ * For each selected element, select all descendant elements returned by the selector function, if any,
+ * and returns a transition on the resulting selection. The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element, the existing transition is returned for that element.
+ *
+ * The first generic "DescElement" refers to the type of descendant element to be selected. The second generic "OldDatum" refers to the type of the
+ * datum, of a selected element. This is useful when re-selecting elements with a previously set, know datum type.
+ *
+ * @param selector A selector function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). It must return an array of elements
+ * (or a pseudo-array, such as a NodeList), or the empty array if there are no matching elements.
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Transition<DescElement, OldDatum, GElement, Datum>;
+
+ /**
+ * For each selected element, selects the first child element that matches the specified selector string, if any, and returns a transition on the resulting selection.
+ * The selector may be specified either as a selector string or a function.
+ * If a function, it is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element.
+ * The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element, the existing transition is returned for that element.
+ * This method is equivalent to deriving the selection for this transition via transition.selection,
+ * creating a subselection via selection.selectChild, and then creating a new transition via selection.transition.
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ selectChild<DescElement extends BaseType, OldDatum>(selector?: string | ValueFn<GElement, Datum, DescElement>): Transition<DescElement, OldDatum, GElement, Datum>;
+
+ /**
+ * For each selected element, selects all children that match the specified selector string, if any, and returns a transition on the resulting selection.
+ * The selector may be specified either as a selector string or a function.
+ * If a function, it is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element.
+ * The new transition has the same id, name and timing as this transition;
+ * however, if a transition with the same id already exists on a selected element, the existing transition is returned for that element.
+ * This method is equivalent to deriving the selection for this transition via transition.selection,
+ * creating a subselection via selection.selectChildren, and then creating a new transition via selection.transition.
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ selectChildren<DescElement extends BaseType, OldDatum>(selector?: string | ValueFn<GElement, Datum, DescElement>): Transition<DescElement, OldDatum, GElement, Datum>;
+
+ /**
+ * Return the selection corresponding to this transition.
+ */
+ selection(): Selection<GElement, Datum, PElement, PDatum>;
+
+ /**
+ * Returns a new transition on the same selected elements as this transition, scheduled to start when this transition ends.
+ * The new transition inherits a reference time equal to this transition’s time plus its delay and duration.
+ * The new transition also inherits this transition’s name, duration, and easing.
+ * This method can be used to schedule a sequence of chained transitions.
+ *
+ * A delay configured for the new transition will be relative to the previous transition.
+ */
+ transition(): Transition<GElement, Datum, PElement, PDatum>;
+
+ // Modifying -------------------------------
+
+ /**
+ * For each selected element, assigns the attribute tween for the attribute with the specified name to the specified target value.
+ * The starting value of the tween is the attribute’s value when the transition starts.
+ * If the target value is null, the attribute is removed when the transition starts.
+ */
+ attr(name: string, value: null | string | number | boolean): this;
+ /**
+ * For each selected element, assigns the attribute tween for the attribute with the specified name to the specified target value.
+ * The starting value of the tween is the attribute’s value when the transition starts.
+ * The target value is return value of the value function evaluated for the selected element.
+ *
+ * An interpolator is chosen based on the type of the target value, using the following algorithm:
+ * 1.) If value is a number, use interpolateNumber.
+ * 2.) If value is a color or a string coercible to a color, use interpolateRgb.
+ * 3.) Use interpolateString.
+ *
+ * To apply a different interpolator, use transition.attrTween.
+ *
+ * @param name Name of the attribute.
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * A null value will clear the attribute at the start of the transition.
+ */
+ attr(name: string, value: ValueFn<GElement, Datum, string | number | boolean | null>): this;
+
+ /**
+ * Return the current interpolator factory for attribute with the specified name, or undefined if no such tween exists.
+ *
+ * @param name Name of attribute.
+ */
+ attrTween(name: string): ValueFn<GElement, Datum, (this: GElement, t: number) => string> | undefined;
+ /**
+ * Remove the previously-assigned attribute tween of the specified name, if any.
+ *
+ * @param name Name of attribute.
+ * @param factory Use null to remove previously-assigned attribute tween.
+ */
+ attrTween(name: string, factory: null): this;
+ /**
+ * Assign the attribute tween for the attribute with the specified name to the specified interpolator factory.
+ * An interpolator factory is a function that returns an interpolator; when the transition starts, the factory is evaluated for each selected element.
+ * The returned interpolator will then be invoked for each frame of the transition, in order,
+ * being passed the eased time t, typically in the range [0, 1]. Lastly, the return value of the interpolator will be used to set the attribute value.
+ * The interpolator must return a string.
+ *
+ * @param name Name of attribute.
+ * @param factory An interpolator factory which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). The interpolator factory returns a string interpolator,
+ * which takes as its argument eased time t, typically in the range [0, 1] and returns the interpolated string.
+ */
+ attrTween(name: string, factory: ValueFn<GElement, Datum, (this: GElement, t: number) => string>): this;
+
+ /**
+ * For each selected element, the style with the specified name will be cleared at the start of the transition.
+ *
+ * @param name Name of the style.
+ * @param value Use null to clear the style.
+ */
+ style(name: string, value: null): this;
+ /**
+ * For each selected element, assigns the style tween for the style with the specified name to the specified target value with the
+ * specified priority.
+ * The starting value of the tween is the style’s inline value if present, and otherwise its computed value.
+ * The target value is the specified constant value for all elements.
+ *
+ * An interpolator is chosen based on the type of the target value, using the following algorithm:
+ * 1.) If value is a number, use interpolateNumber.
+ * 2.) If value is a color or a string coercible to a color, use interpolateRgb.
+ * 3.) Use interpolateString.
+ *
+ * To apply a different interpolator, use transition.attrTween.
+ *
+ * @param name Name of the style.
+ * @param value Target value for the style.
+ * @param priority An optional priority flag, either null or the string important (without the exclamation point)
+ */
+ style(name: string, value: string | number | boolean, priority?: null | 'important'): this;
+ /**
+ * For each selected element, assigns the style tween for the style with the specified name to the specified target value with the
+ * specified priority.
+ * The starting value of the tween is the style's inline value if present, and otherwise its computed value.
+ * The target value is return value of the value function evaluated for the selected element.
+ *
+ * An interpolator is chosen based on the type of the target value, using the following algorithm:
+ * 1.) If value is a number, use interpolateNumber.
+ * 2.) If value is a color or a string coercible to a color, use interpolateRgb.
+ * 3.) Use interpolateString.
+ *
+ * To apply a different interpolator, use transition.attrTween.
+ *
+ * @param name Name of the style.
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * A null value will clear the style at the start of the transition.
+ * @param priority An optional priority flag, either null or the string important (without the exclamation point)
+ */
+ style(name: string, value: ValueFn<GElement, Datum, string | number | boolean | null>, priority?: null | 'important'): this;
+
+ /**
+ * Return the current interpolator factory for style with the specified name, or undefined if no such tween exists.
+ *
+ * @param name Name of style.
+ */
+ styleTween(name: string): ValueFn<GElement, Datum, (this: GElement, t: number) => string> | undefined;
+ /**
+ * Remove the previously-assigned style tween of the specified name, if any.
+ *
+ * @param name Name of style.
+ * @param factory Use null to remove previously-assigned style tween.
+ */
+ styleTween(name: string, factory: null): this;
+ /**
+ * Assign the style tween for the style with the specified name to the specified interpolator factory.
+ * An interpolator factory is a function that returns an interpolator; when the transition starts, the factory is evaluated for each selected element.
+ * The returned interpolator will then be invoked for each frame of the transition, in order,
+ * being passed the eased time t, typically in the range [0, 1]. Lastly, the return value of the interpolator will be used to set the style value.
+ * The interpolator must return a string.
+ *
+ * @param name Name of style.
+ * @param factory An interpolator factory which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). The interpolator factory returns a string interpolator,
+ * which takes as its argument eased time t, typically in the range [0, 1] and returns the interpolated string.
+ * @param priority An optional priority flag, either null or the string important (without the exclamation point)
+ */
+ styleTween(name: string, factory: ValueFn<GElement, Datum, (this: GElement, t: number) => string>, priority?: null | 'important'): this;
+
+ /**
+ * For each selected element, sets the text content to the specified target value when the transition starts.
+ * A null value will clear the content.
+ */
+ text(value: null | string | number | boolean): this;
+ /**
+ * For each selected element, sets the text content returned by the value function for each selected element when the transition starts.
+ *
+ * To interpolate text rather than to set it on start, use transition.textTween (for example) or append a replacement element and cross-fade opacity (for example).
+ * Text is not interpolated by default because it is usually undesirable.
+ *
+ * @param value A value function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
+ * A null value will clear the text content at the start of the transition.
+ */
+ text(value: ValueFn<GElement, Datum, string | number | boolean>): this;
+
+ /**
+ * Returns the current interpolator factory for text, or undefined if no such tween exists.
+ */
+ textTween(): ValueFn<GElement, Datum, (this: GElement, t: number) => string> | undefined;
+ /**
+ * Removes the previously-assigned text tween, if any
+ *
+ * @param factory Use null to remove previously-assigned text tween.
+ */
+ textTween(factory: null): this;
+ /**
+ * Assigns the text tween to the specified interpolator factory.
+ * An interpolator factory is a function that returns an interpolator; when the transition starts, the factory is evaluated for each selected element,
+ * in order, being passed the current datum d and index i, with the this context as the current DOM element.
+ * The returned interpolator will then be invoked for each frame of the transition, in order, being passed the eased time t, typically in the range [0, 1].
+ * Lastly, the return value of the interpolator will be used to set the text.
+ * The interpolator must return a string.
+ *
+ * @param factory An interpolator factory is a function that returns an interpolator; when the transition starts, the factory is evaluated for each selected element,
+ * in order, being passed the current datum d and index i, with the this context as the current DOM element.
+ * The returned interpolator will then be invoked for each frame of the transition, in order, being passed the eased time t, typically in the range [0, 1].
+ * Lastly, the return value of the interpolator will be used to set the text.
+ * The interpolator must return a string.
+ */
+ textTween(factory: ValueFn<GElement, Datum, (this: GElement, t: number) => string>): this;
+
+ /**
+ * For each selected element, removes the element when the transition ends, as long as the element has no other active or pending transitions.
+ * If the element has other active or pending transitions, does nothing.
+ */
+ remove(): this;
+
+ /**
+ * Returns the tween with the specified name, or undefined, if no tween was previously assigned to
+ * that name.
+ *
+ * @param name Name of tween.
+ */
+ tween(name: string): ValueFn<GElement, Datum, (this: GElement, t: number) => void> | undefined;
+ /**
+ * Removes the tween with the specified name, if a tween was previously assigned to
+ * that name.
+ *
+ * @param name Name of tween.
+ * @param tweenFn Use null to remove a previously-assigned tween.
+ */
+ tween(name: string, tweenFn: null): this;
+ /**
+ * For each selected element, assigns the tween with the specified name with the specified value function.
+ * The value must be specified as a function that returns a function.
+ * When the transition starts, the value function is evaluated for each selected element.
+ * The returned function is then invoked for each frame of the transition, in order,
+ * being passed the eased time t, typically in the range [0, 1].
+ *
+ * @param name Name of tween.
+ * @param tweenFn A tween function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). The tween function returns a function
+ * which takes as its argument eased time t, typically in the range [0, 1] and performs the tweening activities for each transition frame.
+ */
+ tween(name: string, tweenFn: ValueFn<GElement, Datum, (this: GElement, t: number) => void>): this;
+
+ /**
+ * Returns a new transition merging this transition with the specified other transition,
+ * which must have the same id as this transition. The returned transition has the same number of groups,
+ * the same parents, the same name and the same id as this transition.
+ * Any missing (null) elements in this transition are filled with the corresponding element, if present (not null), from the other transition.
+ *
+ * @param other The transition to be merged.
+ */
+ merge(other: Transition<GElement, Datum, PElement, PDatum>): Transition<GElement, Datum, PElement, PDatum>;
+
+ /**
+ * For each selected element, selects only the elements that match the specified filter, and returns a transition on the resulting selection.
+ *
+ * The new transition has the same id, name and timing as this transition; however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * @param filter A CSS selector string.
+ */
+ filter(filter: string): Transition<GElement, Datum, PElement, PDatum>;
+ /**
+ * For each selected element, selects only the elements that match the specified filter, and returns a transition on the resulting selection.
+ *
+ * The new transition has the same id, name and timing as this transition; however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * The generic refers to the type of element which will be selected after applying the filter, i.e. if the element types
+ * contained in a pre-filter selection are narrowed to a subset as part of the filtering.
+ *
+ * @param filter A CSS selector string.
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ filter<FilteredElement extends BaseType>(filter: string): Transition<FilteredElement, Datum, PElement, PDatum>;
+ /**
+ * For each selected element, selects only the elements that match the specified filter, and returns a transition on the resulting selection.
+ *
+ * The new transition has the same id, name and timing as this transition; however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * @param filter A filter function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). The filter function returns a boolean indicating,
+ * whether the selected element matches.
+ */
+ filter(filter: ValueFn<GElement, Datum, boolean>): Transition<GElement, Datum, PElement, PDatum>;
+ /**
+ * For each selected element, selects only the elements that match the specified filter, and returns a transition on the resulting selection.
+ *
+ * The new transition has the same id, name and timing as this transition; however, if a transition with the same id already exists on a selected element,
+ * the existing transition is returned for that element.
+ *
+ * The generic refers to the type of element which will be selected after applying the filter, i.e. if the element types
+ * contained in a pre-filter selection are narrowed to a subset as part of the filtering.
+ *
+ * @param filter A filter function which is evaluated for each selected element, in order, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]). The filter function returns a boolean indicating,
+ * whether the selected element matches.
+ */
+ // tslint:disable-next-line:no-unnecessary-generics
+ filter<FilteredElement extends BaseType>(filter: ValueFn<GElement, Datum, boolean>): Transition<FilteredElement, Datum, PElement, PDatum>;
+
+ // Event Handling -------------------
+
+ /**
+ * Return the currently-assigned listener for the specified event typename on the first (non-null) selected element, if any.
+ * If multiple typenames are specified, the first matching listener is returned.
+ *
+ * @param typenames The typenames is one of the following string event types: start (when the transition starts), end (when the transition ends),
+ * interrupt (when the transition is interrupted), cancel(when the transition is cancelled).
+ * Note that these are not native DOM events. The type may be optionally followed by a period (.) and a name;
+ * the optional name allows multiple callbacks to be registered to receive events of the same type, such as "start.foo"" and "start.bar".
+ * To specify multiple typenames, separate typenames with spaces, such as "interrupt end"" or "start.foo start.bar".
+ */
+ on(typenames: string): ValueFn<GElement, Datum, void> | undefined;
+ /**
+ * Remove all listeners for a given name.
+ *
+ * @param typenames Name of the event type for which the listener should be removed. To remove all listeners for a given name use ".foo"
+ * as the typename, where foo is the name; to remove all listeners with no name, specify "." as the typename.
+ * @param listener Use null to remove listeners.
+ */
+ on(typenames: string, listener: null): this;
+ /**
+ * Add a listener to each selected element for the specified event typenames.
+ *
+ * When a specified transition event is dispatched on a selected node, the specified listener will be invoked for each transitioning element.
+ * Listeners always see the latest datum for their element, but the index is a property of the selection and is fixed when the listener is assigned;
+ * to update the index, re-assign the listener.
+ *
+ * @param typenames The typenames is one of the following string event types: start (when the transition starts), end (when the transition ends),
+ * interrupt (when the transition is interrupted), cancel(when the transition is cancelled).
+ * Note that these are not native DOM events. The type may be optionally followed by a period (.) and a name;
+ * the optional name allows multiple callbacks to be registered to receive events of the same type, such as "start.foo"" and "start.bar".
+ * To specify multiple typenames, separate typenames with spaces, such as "interrupt end"" or "start.foo start.bar".
+ * @param listener A listener function which will be evaluated for each selected element, being passed the current datum (d), the current index (i),
+ * and the current group (nodes), with this as the current DOM element (nodes[i]). Listeners always see the latest datum for their element,
+ * but the index is a property of the selection and is fixed when the listener is assigned; to update the index, re-assign the listener.
+ */
+ on(typenames: string, listener: ValueFn<GElement, Datum, void>): this;
+
+ /**
+ * Returns a promise that resolves when every selected element finishes transitioning. If any element’s transition is cancelled or interrupted, the promise rejects.
+ */
+ end(): Promise<void>;
+
+ // Control Flow ----------------------
+
+ /**
+ * Invoke the specified function for each selected element, passing the current datum (d),
+ * the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]).
+ * This method can be used to invoke arbitrary code for each selected element, and is useful for creating a context to access parent and child data simultaneously.
+ *
+ * @param func A function which is invoked for each selected element,
+ * being passed the current datum (d), the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]).
+ */
+ each(func: ValueFn<GElement, Datum, void>): this;
+
+ /**
+ * Invoke the specified function exactly once, passing in this transition along with any optional arguments.
+ * Returns this transition.
+ *
+ * @param func A function which is passed this transition as the first argument along with any optional arguments.
+ * @param args List of optional arguments to be passed to the callback function.
+ */
+ call(func: (transition: Transition<GElement, Datum, PElement, PDatum>, ...args: any[]) => any, ...args: any[]): this;
+
+ /**
+ * Return true if this transition contains no (non-null) elements.
+ */
+ empty(): boolean;
+
+ /**
+ * Return the first (non-null) element in this transition. If the transition is empty, returns null.
+ */
+ node(): GElement | null;
+
+ /**
+ * Return an array of all (non-null) elements in this transition.
+ */
+ nodes(): GElement[];
+
+ /**
+ * Returns the total number of elements in this transition.
+ */
+ size(): number;
+
+ // Transition Configuration ----------------------
+
+ /**
+ * Returns the current value of the delay for the first (non-null) element in the transition.
+ * This is generally useful only if you know that the transition contains exactly one element.
+ */
+ delay(): number;
+ /**
+ * For each selected element, sets the transition delay to the specified value in milliseconds.
+ * If a delay is not specified, it defaults to zero.
+ *
+ * @param milliseconds Number of milliseconds for the delay.
+ */
+ delay(milliseconds: number): this;
+ /**
+ * For each selected element, sets the transition delay to the value in milliseconds returned by the
+ * value function.
+ *
+ * @param milliseconds A value function which is evaluated for each selected element, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]). The return value is a number
+ * specifying the delay in milliseconds.
+ */
+ delay(milliseconds: ValueFn<GElement, Datum, number>): this;
+
+ /**
+ * Returns the current value of the duration for the first (non-null) element in the transition.
+ * This is generally useful only if you know that the transition contains exactly one element.
+ */
+ duration(): number;
+ /**
+ * For each selected element, sets the transition duration to the specified value in milliseconds.
+ * If a duration is not specified, it defaults to 250ms.
+ *
+ * @param duration Number of milliseconds for the duration.
+ */
+ duration(milliseconds: number): this;
+ /**
+ * For each selected element, sets the transition duration to the value in milliseconds returned by the
+ * value function.
+ *
+ * @param milliseconds A value function which is evaluated for each selected element, being passed the current datum (d),
+ * the current index (i), and the current group (nodes), with this of the current DOM element (nodes[i]). The return value is a number
+ * specifying the duration in milliseconds.
+ */
+ duration(milliseconds: ValueFn<GElement, Datum, number>): this;
+
+ /**
+ * Returns the current easing function for the first (non-null) element in the transition.
+ * This is generally useful only if you know that the transition contains exactly one element.
+ */
+ ease(): (normalizedTime: number) => number;
+ /**
+ * Specifies the transition easing function for all selected elements. The value must be specified as a function.
+ * The easing function is invoked for each frame of the animation, being passed the normalized time t in the range [0, 1];
+ * it must then return the eased time tʹ which is typically also in the range [0, 1].
+ * A good easing function should return 0 if t = 0 and 1 if t = 1. If an easing function is not specified,
+ * it defaults to d3.easeCubic.
+ *
+ * @param easingFn An easing function which is passed the normalized time t in the range [0, 1];
+ * it must then return the eased time tʹ which is typically also in the range [0, 1].
+ * A good easing function should return 0 if t = 0 and 1 if t = 1.
+ */
+ ease(easingFn: (normalizedTime: number) => number): this;
+
+ /**
+ * Specifies a factory for the transition easing function.
+ *
+ * @param factory The factory must be a function.
+ * It is invoked for each node of the selection, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element.
+ * It must return an easing function.
+ */
+ easeVarying(factory: ValueFn<GElement, Datum, (normalizedTime: number) => number>): this;
+}
+
+/**
+ * Represents the union of the Selection and Transition types for any usages that operate on both.
+ * Typically used for functions which take in either a selection or transition and set or update attributes.
+ */
+export type SelectionOrTransition<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> = Selection<GElement, Datum, PElement, PDatum> | Transition<GElement, Datum, PElement, PDatum>;
+
+/**
+ * Returns a new transition with the specified name. If a name is not specified, null is used.
+ * The new transition is only exclusive with other transitions of the same name.
+ *
+ * The generic "OldDatum" refers to the type of a previously-set datum of the selected HTML element in the Transition.
+ *
+ * @param name Name of the transition.
+ */
+// tslint:disable-next-line:no-unnecessary-generics
+export function transition<OldDatum>(name?: string): Transition<HTMLElement, OldDatum, null, undefined>;
+
+/**
+ * Returns a new transition from an existing transition.
+ *
+ * When using a transition instance, the returned transition has the same id and name as the specified transition.
+ *
+ * The generic "OldDatum" refers to the type of a previously-set datum of the selected HTML element in the Transition.
+ *
+ * @param transition A transition instance.
+ */
+// tslint:disable-next-line:no-unnecessary-generics
+export function transition<OldDatum>(transition: Transition<BaseType, any, BaseType, any>): Transition<HTMLElement, OldDatum, null, undefined>;
diff --git a/chromium/third_party/node/node_modules/@types/d3/index.d.ts b/chromium/third_party/node/node_modules/@types/d3/index.d.ts
new file mode 100644
index 00000000000..bba9cde4c24
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/d3/index.d.ts
@@ -0,0 +1,27 @@
+// Type definitions for D3JS d3 standard bundle 5.16
+// Project: https://github.com/d3/d3, https://d3js.org
+// Definitions by: Tom Wanzek <https://github.com/tomwanzek>
+// Alex Ford <https://github.com/gustavderdrache>
+// Boris Yankov <https://github.com/borisyankov>
+// denisname <https://github.com/denisname>
+// Nathan Bierema <https://github.com/Methuselah96>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// TypeScript Version: 2.3
+
+// Last module patch version validated against: 5.16.0
+
+// NOTE TO MAINTAINERS: Review D3 v4.x module dependencies (see v4 sub-folder) and update its path-mappings in tsconfig (v4 folder),
+// if new MAJOR version of D3 v4 modules are released!!!
+
+export as namespace d3;
+
+/**
+ * Version number in format _Major.Minor.BugFix_, like 5.0.0.
+ */
+export const version: string;
+
+export * from 'd3-drag';
+export * from 'd3-force';
+export * from 'd3-scale-chromatic';
+export * from 'd3-selection';
+export * from 'd3-transition';
diff --git a/chromium/third_party/node/node_modules/@types/trusted-types/index.d.ts b/chromium/third_party/node/node_modules/@types/trusted-types/index.d.ts
new file mode 100644
index 00000000000..a2d9fecf318
--- /dev/null
+++ b/chromium/third_party/node/node_modules/@types/trusted-types/index.d.ts
@@ -0,0 +1,75 @@
+// Type definitions for trusted-types 1.0
+// Project: https://github.com/WICG/trusted-types
+// Definitions by: Jakub Vrana <https://github.com/vrana>
+// Damien Engels <https://github.com/engelsdamien>
+// Emanuel Tesar <https://github.com/siegrift>
+// Bjarki <https://github.com/bjarkler>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// TypeScript Version: 3.1
+
+type FnNames = keyof TrustedTypePolicyOptions;
+type Args<Options extends TrustedTypePolicyOptions, K extends FnNames> =
+ Parameters<NonNullable<Options[K]>>;
+
+declare global {
+ class TrustedHTML {
+ private constructor(); // To prevent instantiting with 'new'.
+ private brand: true; // To prevent structural typing.
+ }
+
+ class TrustedScript {
+ private constructor(); // To prevent instantiting with 'new'.
+ private brand: true; // To prevent structural typing.
+ }
+
+ class TrustedScriptURL {
+ private constructor(); // To prevent instantiting with 'new'.
+ private brand: true; // To prevent structural typing.
+ }
+
+ interface TrustedTypePolicyFactory {
+ createPolicy<Options extends TrustedTypePolicyOptions>(
+ policyName: string,
+ policyOptions?: Options,
+ ): Pick<TrustedTypePolicy<Options>, 'name'|Extract<keyof Options, FnNames>>;
+ isHTML(value: unknown): value is TrustedHTML;
+ isScript(value: unknown): value is TrustedScript;
+ isScriptURL(value: unknown): value is TrustedScriptURL;
+ readonly emptyHTML: TrustedHTML;
+ readonly emptyScript: TrustedScript;
+ getAttributeType(tagName: string, attribute: string, elementNs?: string, attrNs?: string): string | null;
+ getPropertyType(tagName: string, property: string, elementNs?: string): string | null;
+ readonly defaultPolicy: TrustedTypePolicy | null;
+ }
+
+ interface TrustedTypePolicy<Options extends TrustedTypePolicyOptions = TrustedTypePolicyOptions> {
+ readonly name: string;
+ createHTML(...args: Args<Options, 'createHTML'>): TrustedHTML;
+ createScript(...args: Args<Options, 'createScript'>): TrustedScript;
+ createScriptURL(...args: Args<Options, 'createScriptURL'>): TrustedScriptURL;
+ }
+
+ interface TrustedTypePolicyOptions {
+ createHTML?: (input: string, ...arguments: any[]) => string;
+ createScript?: (input: string, ...arguments: any[]) => string;
+ createScriptURL?: (input: string, ...arguments: any[]) => string;
+ }
+
+ interface Window {
+ // `trustedTypes` is left intentionally optional to make sure that
+ // people handle the case when their code is running in a browser not
+ // supporting trustedTypes.
+ trustedTypes?: TrustedTypePolicyFactory;
+ TrustedHTML: TrustedHTML;
+ TrustedScript: TrustedScript;
+ TrustedScriptURL: TrustedScriptURL;
+ TrustedTypePolicyFactory: TrustedTypePolicyFactory;
+ TrustedTypePolicy: TrustedTypePolicy;
+ }
+}
+
+// This is not available in global scope. It's only used for the export. This is
+// necessary to be able to use these types from nodejs (for SSR).
+declare const trustedTypes: TrustedTypePolicyFactory;
+
+export default trustedTypes;
diff --git a/chromium/third_party/webrtc/modules/desktop_capture/desktop_frame.cc b/chromium/third_party/webrtc/modules/desktop_capture/desktop_frame.cc
index 9e4a899fd26..39e1d46ba47 100644
--- a/chromium/third_party/webrtc/modules/desktop_capture/desktop_frame.cc
+++ b/chromium/third_party/webrtc/modules/desktop_capture/desktop_frame.cc
@@ -45,9 +45,13 @@ void DesktopFrame::CopyPixelsFrom(const uint8_t* src_buffer,
RTC_CHECK(DesktopRect::MakeSize(size()).ContainsRect(dest_rect));
uint8_t* dest = GetFrameDataAtPos(dest_rect.top_left());
- libyuv::CopyPlane(src_buffer, src_stride, dest, stride(),
- DesktopFrame::kBytesPerPixel * dest_rect.width(),
- dest_rect.height());
+ // TODO(crbug.com/1330019): Temporary workaround for a known libyuv crash when
+ // the height or width is 0. Remove this once this change has been merged.
+ if (dest_rect.width() && dest_rect.height()) {
+ libyuv::CopyPlane(src_buffer, src_stride, dest, stride(),
+ DesktopFrame::kBytesPerPixel * dest_rect.width(),
+ dest_rect.height());
+ }
}
void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame,
@@ -157,9 +161,13 @@ BasicDesktopFrame::~BasicDesktopFrame() {
// static
DesktopFrame* BasicDesktopFrame::CopyOf(const DesktopFrame& frame) {
DesktopFrame* result = new BasicDesktopFrame(frame.size());
- libyuv::CopyPlane(frame.data(), frame.stride(), result->data(),
- result->stride(), frame.size().width() * kBytesPerPixel,
- frame.size().height());
+ // TODO(crbug.com/1330019): Temporary workaround for a known libyuv crash when
+ // the height or width is 0. Remove this once this change has been merged.
+ if (frame.size().width() && frame.size().height()) {
+ libyuv::CopyPlane(frame.data(), frame.stride(), result->data(),
+ result->stride(), frame.size().width() * kBytesPerPixel,
+ frame.size().height());
+ }
result->CopyFrameInfoFrom(frame);
return result;
}
diff --git a/chromium/tools/metrics/histograms/enums.xml b/chromium/tools/metrics/histograms/enums.xml
index 0561eb9250d..d07d4eb20d4 100644
--- a/chromium/tools/metrics/histograms/enums.xml
+++ b/chromium/tools/metrics/histograms/enums.xml
@@ -70047,6 +70047,7 @@ Called by update_net_trust_anchors.py.-->
<int value="50" label="SharedImageProvider no access"/>
<int value="51" label="SharedImageProvider SkImage creation failed"/>
<int value="52" label="Zero SkColorFilter bytes"/>
+ <int value="53" label="Insufficient Pixel Data"/>
</enum>
<enum name="PaletteModeCancelType">
diff --git a/chromium/ui/base/ime/win/tsf_text_store.cc b/chromium/ui/base/ime/win/tsf_text_store.cc
index f52d0f737dd..ceec7a1397e 100644
--- a/chromium/ui/base/ime/win/tsf_text_store.cc
+++ b/chromium/ui/base/ime/win/tsf_text_store.cc
@@ -68,6 +68,14 @@ HRESULT TSFTextStore::Initialize() {
return hr;
}
+ hr = ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&input_processor_profile_mgr_));
+ if (FAILED(hr)) {
+ DVLOG(1) << "Failed to initialize InputProcessorProfileMgr.";
+ return hr;
+ }
+
return S_OK;
}
@@ -649,8 +657,10 @@ HRESULT TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
is_tic_write_in_progress_ = false;
} else {
composition_start_ = selection_.start();
- if (!selection_.EqualsIgnoringDirection(selection_from_client_))
+ if (!selection_.EqualsIgnoringDirection(selection_from_client_) &&
+ !IsInputIME()) {
text_input_client_->SetEditableSelectionRange(selection_);
+ }
CalculateTextandSelectionDiffAndNotifyIfNeeded();
}
ResetCacheAfterEditSession();
@@ -1493,7 +1503,7 @@ void TSFTextStore::CommitTextAndEndCompositionIfAny(size_t old_size,
text_input_client_->ClearCompositionText();
}
- if (!selection_.is_empty() && !is_selection_interim_char_ &&
+ if (!selection_.is_empty() && !IsInputIME() &&
selection_.GetMax() <= string_buffer_document_.size()) {
text_input_client_->SetEditableSelectionRange(selection_);
}
@@ -1595,10 +1605,6 @@ void TSFTextStore::GetStyle(const TF_DISPLAYATTRIBUTE& attribute,
void TSFTextStore::ResetCacheAfterEditSession() {
// reset the flag since we've already inserted/replaced the text.
new_text_inserted_ = false;
- if (is_selection_interim_char_) {
- // Need to reset |selection_| for interim char selection.
- selection_ = gfx::Range(selection_.GetMax());
- }
is_selection_interim_char_ = false;
// reset |on_start_composition_called_| for next edit session.
on_start_composition_called_ = false;
@@ -1608,4 +1614,14 @@ void TSFTextStore::ResetCacheAfterEditSession() {
string_pending_insertion_.clear();
}
+bool TSFTextStore::IsInputIME() const {
+ TF_INPUTPROCESSORPROFILE profile;
+ if (SUCCEEDED(input_processor_profile_mgr_->GetActiveProfile(
+ GUID_TFCAT_TIP_KEYBOARD, &profile))) {
+ return profile.hkl == NULL &&
+ profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR;
+ }
+ return false;
+}
+
} // namespace ui
diff --git a/chromium/ui/base/ime/win/tsf_text_store.h b/chromium/ui/base/ime/win/tsf_text_store.h
index 8f1f8225e3a..9bca2cf4472 100644
--- a/chromium/ui/base/ime/win/tsf_text_store.h
+++ b/chromium/ui/base/ime/win/tsf_text_store.h
@@ -317,6 +317,9 @@ class COMPONENT_EXPORT(UI_BASE_IME_WIN) TSFTextStore
// Reset all cached flags when |TSFTextStore::RequestLock| returns.
void ResetCacheAfterEditSession();
+ // Returns if current input method is an IME.
+ bool IsInputIME() const;
+
// Gets the style information from the display attribute for the actively
// composed text.
void GetStyle(const TF_DISPLAYATTRIBUTE& attribute, ImeTextSpan* span);
@@ -459,6 +462,8 @@ class COMPONENT_EXPORT(UI_BASE_IME_WIN) TSFTextStore
Microsoft::WRL::ComPtr<ITfCategoryMgr> category_manager_;
Microsoft::WRL::ComPtr<ITfDisplayAttributeMgr> display_attribute_manager_;
Microsoft::WRL::ComPtr<ITfContext> context_;
+ Microsoft::WRL::ComPtr<ITfInputProcessorProfileMgr>
+ input_processor_profile_mgr_;
// Current list of requested supported attribute values.
// Currently the supported attributes are URL and InputScope.
diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl.cc b/chromium/ui/events/gestures/gesture_recognizer_impl.cc
index be062ddc1a2..e48876dd7bd 100644
--- a/chromium/ui/events/gestures/gesture_recognizer_impl.cc
+++ b/chromium/ui/events/gestures/gesture_recognizer_impl.cc
@@ -322,12 +322,11 @@ void GestureRecognizerImpl::CancelActiveTouchesExceptImpl(
// Do not iterate directly over |consumer_gesture_provider_| because canceling
// active touches may cause the consumer to be removed from
// |consumer_gesture_provider_|. See https://crbug.com/651258 for more info.
- std::vector<GestureConsumer*> consumers(consumer_gesture_provider_.size());
+ std::vector<GestureConsumer*> consumers;
+ consumers.reserve(consumer_gesture_provider_.size());
for (const auto& entry : consumer_gesture_provider_) {
- if (entry.first == not_cancelled)
- continue;
-
- consumers.push_back(entry.first);
+ if (entry.first != not_cancelled)
+ consumers.push_back(entry.first);
}
for (auto* consumer : consumers)
diff --git a/chromium/ui/gfx/x/window_cache.cc b/chromium/ui/gfx/x/window_cache.cc
index 0f1fa29f2e6..c12bea05497 100644
--- a/chromium/ui/gfx/x/window_cache.cc
+++ b/chromium/ui/gfx/x/window_cache.cc
@@ -152,13 +152,13 @@ Window WindowCache::GetWindowAtPoint(gfx::Point point_px,
}
}
- if (info->has_wm_name)
- return window;
for (Window child : base::Reversed(info->children)) {
Window ret = GetWindowAtPoint(point_px, child, ignore);
if (ret != Window::None)
return ret;
}
+ if (info->has_wm_name)
+ return window;
return Window::None;
}
diff --git a/chromium/ui/gfx/x/window_cache_unittest.cc b/chromium/ui/gfx/x/window_cache_unittest.cc
index a4c23784e6c..c88dbfd5c28 100644
--- a/chromium/ui/gfx/x/window_cache_unittest.cc
+++ b/chromium/ui/gfx/x/window_cache_unittest.cc
@@ -418,4 +418,19 @@ TEST_F(WindowCacheTest, GetWindowAtPoint) {
EXPECT_EQ(cache()->GetWindowAtPoint({150, 150}, root()), c);
}
+// Regression test for https://crbug.com/1316735
+// If both a parent and child window have a WM_NAME, the child window
+// should be returned by GetWindowAtPoint().
+TEST_F(WindowCacheTest, NestedWmName) {
+ connection()->MapWindow(root());
+ SetStringProperty(root(), Atom::WM_NAME, Atom::STRING, "root");
+
+ Window a = CreateWindow(root());
+ connection()->MapWindow(a);
+ SetStringProperty(a, Atom::WM_NAME, Atom::STRING, "a");
+
+ cache()->SyncForTest();
+ EXPECT_EQ(cache()->GetWindowAtPoint({100, 100}, root()), a);
+}
+
} // namespace x11
diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_screen.cc b/chromium/ui/ozone/platform/wayland/host/wayland_screen.cc
index 241b037b5b3..54fb4cf6487 100644
--- a/chromium/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/chromium/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -129,7 +129,15 @@ void WaylandScreen::OnOutputRemoved(uint32_t output_id) {
}
}
}
- display_list_.RemoveDisplay(output_id);
+ // TODO(https://crbug.com/1299403): Work around the symptoms of a common
+ // crash. Unclear if this is the proper long term solution.
+ auto it = display_list_.FindDisplayById(output_id);
+ DCHECK(it != display_list_.displays().end());
+ if (it != display_list_.displays().end()) {
+ display_list_.RemoveDisplay(output_id);
+ } else {
+ LOG(ERROR) << "output_id is not associated with a Display.";
+ }
}
void WaylandScreen::AddOrUpdateDisplay(uint32_t output_id,
diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc b/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc
index 0ff844dda1d..43e6152cc2b 100644
--- a/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc
+++ b/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc
@@ -26,6 +26,7 @@
#include "ui/ozone/platform/wayland/host/wayland_buffer_handle.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_output.h"
+#include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_subsurface.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
@@ -681,10 +682,15 @@ void WaylandSurface::Enter(void* data,
auto* wayland_output =
static_cast<WaylandOutput*>(wl_output_get_user_data(output));
+
+ DCHECK_NE(surface->connection_->wayland_output_manager()->GetOutput(
+ wayland_output->output_id()),
+ nullptr);
+
surface->entered_outputs_.emplace_back(wayland_output->output_id());
if (surface->root_window_)
- surface->root_window_->OnEnteredOutputIdAdded();
+ surface->root_window_->OnEnteredOutput();
}
// static
@@ -700,27 +706,21 @@ void WaylandSurface::Leave(void* data,
}
void WaylandSurface::RemoveEnteredOutput(uint32_t output_id) {
- if (entered_outputs().empty())
- return;
-
auto entered_outputs_it_ =
std::find_if(entered_outputs_.begin(), entered_outputs_.end(),
[&output_id](uint32_t id) { return id == output_id; });
+ if (entered_outputs_it_ == entered_outputs_.end())
+ return;
- // The `entered_outputs_` list should be updated,
- // 1. for wl_surface::leave, when a user switches physical output between two
- // displays, a surface does not necessarily receive enter events immediately
- // or until a user resizes/moves it. This means that switching output between
- // displays in a single output mode results in leave events, but the surface
- // might not have received enter event before. Thus, remove the id of the
- // output that the surface leaves only if it was stored before.
- // 2. for wl_registry::global_remove, when wl_output is removed by a server
- // after the display is unplugged or switched off.
- if (entered_outputs_it_ != entered_outputs_.end())
- entered_outputs_.erase(entered_outputs_it_);
+ // In certain use cases, such as switching outputs in the single output
+ // configuration, the compositor may move the surface from one output to
+ // another one, send wl_surface::leave event to it, but defer sending
+ // wl_surface::enter until the user moves or resizes the surface on the new
+ // output.
+ entered_outputs_.erase(entered_outputs_it_);
if (root_window_)
- root_window_->OnEnteredOutputIdRemoved();
+ root_window_->OnLeftOutput();
}
void WaylandSurface::SetOverlayPriority(
diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window.cc
index 273f4b514ad..bbfbb210c53 100644
--- a/chromium/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/chromium/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -175,6 +175,18 @@ uint32_t WaylandWindow::GetPreferredEnteredOutputId() {
auto* output_manager = connection_->wayland_output_manager();
auto* output = output_manager->GetOutput(output_id);
auto* preferred_output = output_manager->GetOutput(preferred_output_id);
+ // The compositor may have told the surface to enter the output that the
+ // client is not aware of. In such an event, we cannot evaluate scales, and
+ // can only return the default, which means falling back to the primary
+ // display in the code that calls this.
+ // DCHECKS below are kept for trying to catch the situation in developer's
+ // builds and find the way to reproduce the issue.
+ // See crbug.com/1323635
+ DCHECK(output) << " output " << output_id << " not found!";
+ DCHECK(preferred_output)
+ << " output " << preferred_output_id << " not found!";
+ if (!output || !preferred_output)
+ return 0;
if (output->scale_factor() > preferred_output->scale_factor())
preferred_output_id = output_id;
}
@@ -632,7 +644,7 @@ WaylandWindow* WaylandWindow::GetRootParentWindow() {
return parent_window_ ? parent_window_->GetRootParentWindow() : this;
}
-void WaylandWindow::OnEnteredOutputIdAdded() {
+void WaylandWindow::OnEnteredOutput() {
// Wayland does weird things for menus so instead of tracking outputs that
// we entered or left, we take that from the parent window and ignore this
// event.
@@ -642,7 +654,7 @@ void WaylandWindow::OnEnteredOutputIdAdded() {
UpdateWindowScale(true);
}
-void WaylandWindow::OnEnteredOutputIdRemoved() {
+void WaylandWindow::OnLeftOutput() {
// Wayland does weird things for menus so instead of tracking outputs that
// we entered or left, we take that from the parent window and ignore this
// event.
diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window.h b/chromium/ui/ozone/platform/wayland/host/wayland_window.h
index 52472f360ae..5eb89b3917a 100644
--- a/chromium/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/chromium/ui/ozone/platform/wayland/host/wayland_window.h
@@ -278,12 +278,13 @@ class WaylandWindow : public PlatformWindow,
WaylandWindow* GetTopMostChildWindow();
// Called by the WaylandSurface attached to this window when that surface
- // becomes partially or fully within the scanout region of |output|.
- void OnEnteredOutputIdAdded();
+ // becomes partially or fully within the scanout region of an output that it
+ // wasn't before.
+ void OnEnteredOutput();
// Called by the WaylandSurface attached to this window when that surface
- // becomes fully outside of the scanout region of |output|.
- void OnEnteredOutputIdRemoved();
+ // becomes fully outside of one of outputs that it previously resided on.
+ void OnLeftOutput();
// Returns true iff this window is opaque.
bool IsOpaqueWindow() const;
diff --git a/chromium/ui/strings/translations/ax_strings_te.xtb b/chromium/ui/strings/translations/ax_strings_te.xtb
index 47c420b52d3..7437716cbb0 100644
--- a/chromium/ui/strings/translations/ax_strings_te.xtb
+++ b/chromium/ui/strings/translations/ax_strings_te.xtb
@@ -30,7 +30,7 @@
<translation id="2723001399770238859">ఆడియో</translation>
<translation id="2759744352195237655">పాపౠఅపౠబటనà±</translation>
<translation id="2844350028562914727">వివరాలà±</translation>
-<translation id="2896972712917208084">రేడియో సమూహం</translation>
+<translation id="2896972712917208084">రేడియో à°—à±à°°à±‚à°ªà±â€Œ</translation>
<translation id="2931838996092594335">à°•à±à°²à°¿à°•à± చేయి</translation>
<translation id="2940813599313844715">ఆబà±à°œà±†à°•à±à°Ÿà±</translation>
<translation id="3040011195152428237">లింకà±</translation>
diff --git a/chromium/ui/strings/translations/ui_strings_fa.xtb b/chromium/ui/strings/translations/ui_strings_fa.xtb
index cd0ff067ffc..e0075ad5bd3 100644
--- a/chromium/ui/strings/translations/ui_strings_fa.xtb
+++ b/chromium/ui/strings/translations/ui_strings_fa.xtb
@@ -73,7 +73,7 @@
<translation id="3126026824346185272">Ctrl</translation>
<translation id="3157931365184549694">بازیابی</translation>
<translation id="3183922693828471536">پیمایش به اینجا</translation>
-<translation id="3234408098842461169">پیکان پایین</translation>
+<translation id="3234408098842461169">کلید پایین‌بر</translation>
<translation id="3291688615589870984">{DAYS,plural, =1{۱ روز}one{# روز}other{# روز}}</translation>
<translation id="3295886253693811851">برقراری تماس از</translation>
<translation id="3306688585798492231">نمایشگر داخلی</translation>
diff --git a/chromium/v8/include/v8-version.h b/chromium/v8/include/v8-version.h
index 37abe105b74..f47ac397a88 100644
--- a/chromium/v8/include/v8-version.h
+++ b/chromium/v8/include/v8-version.h
@@ -11,7 +11,7 @@
#define V8_MAJOR_VERSION 10
#define V8_MINOR_VERSION 2
#define V8_BUILD_NUMBER 154
-#define V8_PATCH_LEVEL 4
+#define V8_PATCH_LEVEL 10
// Use 1 for candidates and 0 otherwise.
// (Boolean macro values are not supported by all preprocessors.)
diff --git a/chromium/v8/src/builtins/arm/builtins-arm.cc b/chromium/v8/src/builtins/arm/builtins-arm.cc
index 38a88c24efb..2fda6b75369 100644
--- a/chromium/v8/src/builtins/arm/builtins-arm.cc
+++ b/chromium/v8/src/builtins/arm/builtins-arm.cc
@@ -2609,8 +2609,7 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in a register by the jump table trampoline.
// Convert to Smi for the runtime call.
- __ SmiTag(kWasmCompileLazyFuncIndexRegister,
- kWasmCompileLazyFuncIndexRegister);
+ __ SmiTag(kWasmCompileLazyFuncIndexRegister);
{
HardAbortScope hard_abort(masm); // Avoid calls to Abort.
FrameAndConstantPoolScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
@@ -2640,22 +2639,34 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ stm(db_w, sp, gp_regs);
__ vstm(db_w, sp, lowest_fp_reg, highest_fp_reg);
- // Pass instance and function index as explicit arguments to the runtime
+ // Push the Wasm instance for loading the jump table address after the
+ // runtime call.
+ __ push(kWasmInstanceRegister);
+
+ // Push the Wasm instance again as an explicit argument to the runtime
// function.
__ push(kWasmInstanceRegister);
+ // Push the function index as second argument.
__ push(kWasmCompileLazyFuncIndexRegister);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Move(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- // The entrypoint address is the return value.
- __ mov(r8, kReturnRegister0);
+ // The runtime function returns the jump table slot offset as a Smi. Use
+ // that to compute the jump target in r8.
+ __ pop(kWasmInstanceRegister);
+ __ ldr(r8, MemOperand(
+ kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ add(r8, r8, Operand::SmiUntag(kReturnRegister0));
+ // r8 now holds the jump table slot where we want to jump to in the end.
// Restore registers.
__ vldm(ia_w, sp, lowest_fp_reg, highest_fp_reg);
__ ldm(ia_w, sp, gp_regs);
}
- // Finally, jump to the entrypoint.
+
+ // Finally, jump to the jump table slot for the function.
__ Jump(r8);
}
diff --git a/chromium/v8/src/builtins/arm64/builtins-arm64.cc b/chromium/v8/src/builtins/arm64/builtins-arm64.cc
index 7bfd4f81906..c281c93ff63 100644
--- a/chromium/v8/src/builtins/arm64/builtins-arm64.cc
+++ b/chromium/v8/src/builtins/arm64/builtins-arm64.cc
@@ -3018,41 +3018,50 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Sign extend and convert to Smi for the runtime call.
__ sxtw(kWasmCompileLazyFuncIndexRegister,
kWasmCompileLazyFuncIndexRegister.W());
- __ SmiTag(kWasmCompileLazyFuncIndexRegister,
- kWasmCompileLazyFuncIndexRegister);
-
- UseScratchRegisterScope temps(masm);
- {
- HardAbortScope hard_abort(masm); // Avoid calls to Abort.
- FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
-
- // Save all parameter registers (see wasm-linkage.h). They might be
- // overwritten in the runtime call below. We don't have any callee-saved
- // registers in wasm, so no need to store anything else.
- RegList gp_regs;
+ __ SmiTag(kWasmCompileLazyFuncIndexRegister);
+
+ // Compute register lists for parameters to be saved. We save all parameter
+ // registers (see wasm-linkage.h). They might be overwritten in the runtime
+ // call below. We don't have any callee-saved registers in wasm, so no need to
+ // store anything else.
+ constexpr RegList kSavedGpRegs = ([]() constexpr {
+ RegList saved_gp_regs;
for (Register gp_param_reg : wasm::kGpParamRegisters) {
- gp_regs.set(gp_param_reg);
+ saved_gp_regs.set(gp_param_reg);
}
// Also push x1, because we must push multiples of 16 bytes (see
// {TurboAssembler::PushCPURegList}.
- CHECK_EQ(1, gp_regs.Count() % 2);
- gp_regs.set(x1);
- CHECK_EQ(0, gp_regs.Count() % 2);
+ saved_gp_regs.set(x1);
+ // All set registers were unique.
+ CHECK_EQ(saved_gp_regs.Count(), arraysize(wasm::kGpParamRegisters) + 1);
+ // We push a multiple of 16 bytes.
+ CHECK_EQ(0, saved_gp_regs.Count() % 2);
+ // The Wasm instance must be part of the saved registers.
+ CHECK(saved_gp_regs.has(kWasmInstanceRegister));
+ CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
+ saved_gp_regs.Count());
+ return saved_gp_regs;
+ })();
- DoubleRegList fp_regs;
+ constexpr DoubleRegList kSavedFpRegs = ([]() constexpr {
+ DoubleRegList saved_fp_regs;
for (DoubleRegister fp_param_reg : wasm::kFpParamRegisters) {
- fp_regs.set(fp_param_reg);
+ saved_fp_regs.set(fp_param_reg);
}
- CHECK_EQ(gp_regs.Count(), arraysize(wasm::kGpParamRegisters) + 1);
- CHECK_EQ(fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
- CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
- gp_regs.Count());
+ CHECK_EQ(saved_fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs,
- fp_regs.Count());
+ saved_fp_regs.Count());
+ return saved_fp_regs;
+ })();
- __ PushXRegList(gp_regs);
- __ PushQRegList(fp_regs);
+ {
+ HardAbortScope hard_abort(masm); // Avoid calls to Abort.
+ FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
+
+ // Save registers that we need to keep alive across the runtime call.
+ __ PushXRegList(kSavedGpRegs);
+ __ PushQRegList(kSavedFpRegs);
// Pass instance and function index as explicit arguments to the runtime
// function.
@@ -3062,17 +3071,23 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Mov(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- // Exclude x17 from the scope, there are hardcoded uses of it below.
- temps.Exclude(x17);
-
- // The entrypoint address is the return value.
- __ Mov(x17, kReturnRegister0);
+ // Untag the returned Smi into into x17, for later use.
+ static_assert(!kSavedGpRegs.has(x17));
+ __ SmiUntag(x17, kReturnRegister0);
// Restore registers.
- __ PopQRegList(fp_regs);
- __ PopXRegList(gp_regs);
+ __ PopQRegList(kSavedFpRegs);
+ __ PopXRegList(kSavedGpRegs);
}
- // Finally, jump to the entrypoint.
+
+ // The runtime function returned the jump table slot offset as a Smi (now in
+ // x17). Use that to compute the jump target.
+ static_assert(!kSavedGpRegs.has(x18));
+ __ ldr(x18, MemOperand(
+ kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ add(x17, x18, Operand(x17));
+ // Finally, jump to the jump table slot for the function.
__ Jump(x17);
}
diff --git a/chromium/v8/src/builtins/ia32/builtins-ia32.cc b/chromium/v8/src/builtins/ia32/builtins-ia32.cc
index 7f32853d1f7..58035700186 100644
--- a/chromium/v8/src/builtins/ia32/builtins-ia32.cc
+++ b/chromium/v8/src/builtins/ia32/builtins-ia32.cc
@@ -2878,20 +2878,28 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
offset += kSimd128Size;
}
- // Push the Wasm instance as an explicit argument to WasmCompileLazy.
+ // Push the Wasm instance for loading the jump table address after the
+ // runtime call.
+ __ Push(kWasmInstanceRegister);
+
+ // Push the Wasm instance again as an explicit argument to the runtime
+ // function.
__ Push(kWasmInstanceRegister);
// Push the function index as second argument.
__ Push(kWasmCompileLazyFuncIndexRegister);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Move(kContextRegister, Smi::zero());
- {
- // At this point, ebx has been spilled to the stack but is not yet
- // overwritten with another value. We can still use it as kRootRegister.
- __ CallRuntime(Runtime::kWasmCompileLazy, 2);
- }
- // The entrypoint address is the return value.
- __ mov(edi, kReturnRegister0);
+ __ CallRuntime(Runtime::kWasmCompileLazy, 2);
+ // The runtime function returns the jump table slot offset as a Smi. Use
+ // that to compute the jump target in edi.
+ __ Pop(kWasmInstanceRegister);
+ __ mov(edi, MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset -
+ kHeapObjectTag));
+ __ SmiUntag(kReturnRegister0);
+ __ add(edi, kReturnRegister0);
+ // edi now holds the jump table slot where we want to jump to in the end.
// Restore registers.
for (DoubleRegister reg : base::Reversed(wasm::kFpParamRegisters)) {
@@ -2904,7 +2912,8 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Pop(reg);
}
}
- // Finally, jump to the entrypoint.
+
+ // Finally, jump to the jump table slot for the function.
__ jmp(edi);
}
diff --git a/chromium/v8/src/builtins/loong64/builtins-loong64.cc b/chromium/v8/src/builtins/loong64/builtins-loong64.cc
index b94128f409d..8c668ee3e98 100644
--- a/chromium/v8/src/builtins/loong64/builtins-loong64.cc
+++ b/chromium/v8/src/builtins/loong64/builtins-loong64.cc
@@ -2648,37 +2648,50 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in t0 by the jump table trampoline.
// Convert to Smi for the runtime call
__ SmiTag(kWasmCompileLazyFuncIndexRegister);
- {
- HardAbortScope hard_abort(masm); // Avoid calls to Abort.
- FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
- // Save all parameter registers (see wasm-linkage.h). They might be
- // overwritten in the runtime call below. We don't have any callee-saved
- // registers in wasm, so no need to store anything else.
- RegList gp_regs;
+ // Compute register lists for parameters to be saved. We save all parameter
+ // registers (see wasm-linkage.h). They might be overwritten in the runtime
+ // call below. We don't have any callee-saved registers in wasm, so no need to
+ // store anything else.
+ constexpr RegList kSavedGpRegs = ([]() constexpr {
+ RegList saved_gp_regs;
for (Register gp_param_reg : wasm::kGpParamRegisters) {
- gp_regs.set(gp_param_reg);
+ saved_gp_regs.set(gp_param_reg);
}
- DoubleRegList fp_regs;
+ // All set registers were unique.
+ CHECK_EQ(saved_gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
+ // The Wasm instance must be part of the saved registers.
+ CHECK(saved_gp_regs.has(kWasmInstanceRegister));
+ CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
+ saved_gp_regs.Count());
+ return saved_gp_regs;
+ })();
+
+ constexpr DoubleRegList kSavedFpRegs = ([]() constexpr {
+ DoubleRegList saved_fp_regs;
for (DoubleRegister fp_param_reg : wasm::kFpParamRegisters) {
- fp_regs.set(fp_param_reg);
+ saved_fp_regs.set(fp_param_reg);
}
- CHECK_EQ(gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
- CHECK_EQ(fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
- CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
- gp_regs.Count());
+ CHECK_EQ(saved_fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs,
- fp_regs.Count());
+ saved_fp_regs.Count());
+ return saved_fp_regs;
+ })();
+
+ {
+ HardAbortScope hard_abort(masm); // Avoid calls to Abort.
+ FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
- __ MultiPush(gp_regs);
- __ MultiPushFPU(fp_regs);
+ // Save registers that we need to keep alive across the runtime call.
+ __ MultiPush(kSavedGpRegs);
+ __ MultiPushFPU(kSavedFpRegs);
// kFixedFrameSizeFromFp is hard coded to include space for Simd
// registers, so we still need to allocate extra (unused) space on the stack
// as if they were saved.
- __ Sub_d(sp, sp, fp_regs.Count() * kDoubleSize);
+ __ Sub_d(sp, sp, kSavedFpRegs.Count() * kDoubleSize);
// Pass instance and function index as an explicit arguments to the runtime
// function.
@@ -2687,15 +2700,27 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// set the current context on the isolate.
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- __ mov(t8, a0);
- __ Add_d(sp, sp, fp_regs.Count() * kDoubleSize);
+ // Untag the returned Smi into into t7, for later use.
+ static_assert(!kSavedGpRegs.has(t7));
+ __ SmiUntag(t7, a0);
+
+ __ Add_d(sp, sp, kSavedFpRegs.Count() * kDoubleSize);
// Restore registers.
- __ MultiPopFPU(fp_regs);
- __ MultiPop(gp_regs);
+ __ MultiPopFPU(kSavedFpRegs);
+ __ MultiPop(kSavedGpRegs);
}
- // Finally, jump to the entrypoint.
- __ Jump(t8);
+
+ // The runtime function returned the jump table slot offset as a Smi (now in
+ // t7). Use that to compute the jump target.
+ static_assert(!kSavedGpRegs.has(t8));
+ __ Ld_d(t8, MemOperand(
+ kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ Add_d(t7, t8, Operand(t7));
+
+ // Finally, jump to the jump table slot for the function.
+ __ Jump(t7);
}
void Builtins::Generate_WasmDebugBreak(MacroAssembler* masm) {
diff --git a/chromium/v8/src/builtins/mips/builtins-mips.cc b/chromium/v8/src/builtins/mips/builtins-mips.cc
index 379af4b2648..29240177a35 100644
--- a/chromium/v8/src/builtins/mips/builtins-mips.cc
+++ b/chromium/v8/src/builtins/mips/builtins-mips.cc
@@ -2592,32 +2592,45 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in t0 by the jump table trampoline.
// Convert to Smi for the runtime call.
__ SmiTag(kWasmCompileLazyFuncIndexRegister);
- {
- HardAbortScope hard_abort(masm); // Avoid calls to Abort.
- FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
- // Save all parameter registers (see wasm-linkage.h). They might be
- // overwritten in the runtime call below. We don't have any callee-saved
- // registers in wasm, so no need to store anything else.
- RegList gp_regs;
+ // Compute register lists for parameters to be saved. We save all parameter
+ // registers (see wasm-linkage.h). They might be overwritten in the runtime
+ // call below. We don't have any callee-saved registers in wasm, so no need to
+ // store anything else.
+ constexpr RegList kSavedGpRegs = ([]() constexpr {
+ RegList saved_gp_regs;
for (Register gp_param_reg : wasm::kGpParamRegisters) {
- gp_regs.set(gp_param_reg);
+ saved_gp_regs.set(gp_param_reg);
}
- DoubleRegList fp_regs;
+ // All set registers were unique.
+ CHECK_EQ(saved_gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
+ // The Wasm instance must be part of the saved registers.
+ CHECK(saved_gp_regs.has(kWasmInstanceRegister));
+ CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
+ saved_gp_regs.Count());
+ return saved_gp_regs;
+ })();
+
+ constexpr DoubleRegList kSavedFpRegs = ([]() constexpr {
+ DoubleRegList saved_fp_regs;
for (DoubleRegister fp_param_reg : wasm::kFpParamRegisters) {
- fp_regs.set(fp_param_reg);
+ saved_fp_regs.set(fp_param_reg);
}
- CHECK_EQ(gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
- CHECK_EQ(fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
- CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
- gp_regs.Count());
+ CHECK_EQ(saved_fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs,
- fp_regs.Count());
+ saved_fp_regs.Count());
+ return saved_fp_regs;
+ })();
- __ MultiPush(gp_regs);
- __ MultiPushFPU(fp_regs);
+ {
+ HardAbortScope hard_abort(masm); // Avoid calls to Abort.
+ FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
+
+ // Save registers that we need to keep alive across the runtime call.
+ __ MultiPush(kSavedGpRegs);
+ __ MultiPushFPU(kSavedFpRegs);
// Pass instance and function index as an explicit arguments to the runtime
// function.
@@ -2628,11 +2641,24 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
// Restore registers.
- __ MultiPopFPU(fp_regs);
- __ MultiPop(gp_regs);
+ __ MultiPopFPU(kSavedFpRegs);
+ __ MultiPop(kSavedGpRegs);
}
- // Finally, jump to the entrypoint.
- __ Jump(kScratchReg, v0, 0);
+
+ // Untag the returned Smi, for later use.
+ static_assert(!kSavedGpRegs.has(v0));
+ __ SmiUntag(v0);
+
+ // The runtime function returned the jump table slot offset as a Smi (now in
+ // t8). Use that to compute the jump target.
+ static_assert(!kSavedGpRegs.has(t8));
+ __ Lw(t8,
+ MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ Addu(t8, v0, t8);
+
+ // Finally, jump to the jump table slot for the function.
+ __ Jump(t8);
}
void Builtins::Generate_WasmDebugBreak(MacroAssembler* masm) {
diff --git a/chromium/v8/src/builtins/mips64/builtins-mips64.cc b/chromium/v8/src/builtins/mips64/builtins-mips64.cc
index f1eb2e1847f..ee19a288fc0 100644
--- a/chromium/v8/src/builtins/mips64/builtins-mips64.cc
+++ b/chromium/v8/src/builtins/mips64/builtins-mips64.cc
@@ -2643,31 +2643,44 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in t0 by the jump table trampoline.
// Convert to Smi for the runtime call
__ SmiTag(kWasmCompileLazyFuncIndexRegister);
- {
- HardAbortScope hard_abort(masm); // Avoid calls to Abort.
- FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
- // Save all parameter registers (see wasm-linkage.h). They might be
- // overwritten in the runtime call below. We don't have any callee-saved
- // registers in wasm, so no need to store anything else.
- RegList gp_regs;
+ // Compute register lists for parameters to be saved. We save all parameter
+ // registers (see wasm-linkage.h). They might be overwritten in the runtime
+ // call below. We don't have any callee-saved registers in wasm, so no need to
+ // store anything else.
+ constexpr RegList kSavedGpRegs = ([]() constexpr {
+ RegList saved_gp_regs;
for (Register gp_param_reg : wasm::kGpParamRegisters) {
- gp_regs.set(gp_param_reg);
+ saved_gp_regs.set(gp_param_reg);
}
- DoubleRegList fp_regs;
+ // All set registers were unique.
+ CHECK_EQ(saved_gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
+ // The Wasm instance must be part of the saved registers.
+ CHECK(saved_gp_regs.has(kWasmInstanceRegister));
+ CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
+ saved_gp_regs.Count());
+ return saved_gp_regs;
+ })();
+
+ constexpr DoubleRegList kSavedFpRegs = ([]() constexpr {
+ DoubleRegList saved_fp_regs;
for (DoubleRegister fp_param_reg : wasm::kFpParamRegisters) {
- fp_regs.set(fp_param_reg);
+ saved_fp_regs.set(fp_param_reg);
}
- CHECK_EQ(gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
- CHECK_EQ(fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
- CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
- gp_regs.Count());
+ CHECK_EQ(saved_fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs,
- fp_regs.Count());
+ saved_fp_regs.Count());
+ return saved_fp_regs;
+ })();
- __ MultiPush(gp_regs);
+ {
+ HardAbortScope hard_abort(masm); // Avoid calls to Abort.
+ FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
+
+ // Save registers that we need to keep alive across the runtime call.
+ __ MultiPush(kSavedGpRegs);
// Check if machine has simd enabled, if so push vector registers. If not
// then only push double registers.
Label push_doubles, simd_pushed;
@@ -2679,15 +2692,15 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
{
CpuFeatureScope msa_scope(
masm, MIPS_SIMD, CpuFeatureScope::CheckPolicy::kDontCheckSupported);
- __ MultiPushMSA(fp_regs);
+ __ MultiPushMSA(kSavedFpRegs);
}
__ Branch(&simd_pushed);
__ bind(&push_doubles);
- __ MultiPushFPU(fp_regs);
+ __ MultiPushFPU(kSavedFpRegs);
// kFixedFrameSizeFromFp is hard coded to include space for Simd
// registers, so we still need to allocate extra (unused) space on the stack
// as if they were saved.
- __ Dsubu(sp, sp, fp_regs.Count() * kDoubleSize);
+ __ Dsubu(sp, sp, kSavedFpRegs.Count() * kDoubleSize);
__ bind(&simd_pushed);
// Pass instance and function index as an explicit arguments to the runtime
// function.
@@ -2707,17 +2720,30 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
{
CpuFeatureScope msa_scope(
masm, MIPS_SIMD, CpuFeatureScope::CheckPolicy::kDontCheckSupported);
- __ MultiPopMSA(fp_regs);
+ __ MultiPopMSA(kSavedFpRegs);
}
__ Branch(&simd_popped);
__ bind(&pop_doubles);
- __ Daddu(sp, sp, fp_regs.Count() * kDoubleSize);
- __ MultiPopFPU(fp_regs);
+ __ Daddu(sp, sp, kSavedFpRegs.Count() * kDoubleSize);
+ __ MultiPopFPU(kSavedFpRegs);
__ bind(&simd_popped);
- __ MultiPop(gp_regs);
+ __ MultiPop(kSavedGpRegs);
}
- // Finally, jump to the entrypoint.
- __ Jump(v0);
+
+ // Untag the returned Smi, for later use.
+ static_assert(!kSavedGpRegs.has(v0));
+ __ SmiUntag(v0);
+
+ // The runtime function returned the jump table slot offset as a Smi (now in
+ // t8). Use that to compute the jump target.
+ static_assert(!kSavedGpRegs.has(t8));
+ __ Ld(t8,
+ MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ Daddu(t8, v0, t8);
+
+ // Finally, jump to the jump table slot for the function.
+ __ Jump(t8);
}
void Builtins::Generate_WasmDebugBreak(MacroAssembler* masm) {
diff --git a/chromium/v8/src/builtins/ppc/builtins-ppc.cc b/chromium/v8/src/builtins/ppc/builtins-ppc.cc
index 64b45555a1c..c9d275ca93c 100644
--- a/chromium/v8/src/builtins/ppc/builtins-ppc.cc
+++ b/chromium/v8/src/builtins/ppc/builtins-ppc.cc
@@ -2832,8 +2832,8 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in a register by the jump table trampoline.
// Convert to Smi for the runtime call.
- __ SmiTag(kWasmCompileLazyFuncIndexRegister,
- kWasmCompileLazyFuncIndexRegister);
+ __ SmiTag(kWasmCompileLazyFuncIndexRegister);
+
{
HardAbortScope hard_abort(masm); // Avoid calls to Abort.
FrameAndConstantPoolScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
@@ -2867,21 +2867,37 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ MultiPush(gp_regs);
__ MultiPushF64AndV128(fp_regs, simd_regs);
- // Pass instance and function index as explicit arguments to the runtime
+ // Push the Wasm instance for loading the jump table address after the
+ // runtime call.
+ __ Push(kWasmInstanceRegister);
+
+ // Push the Wasm instance again as an explicit argument to the runtime
// function.
- __ Push(kWasmInstanceRegister, kWasmCompileLazyFuncIndexRegister);
+ __ Push(kWasmInstanceRegister);
+ // Push the function index as second argument.
+ __ Push(kWasmCompileLazyFuncIndexRegister);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ LoadSmiLiteral(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- // The entrypoint address is the return value.
- __ mr(r11, kReturnRegister0);
+ // The runtime function returns the jump table slot offset as a Smi. Use
+ // that to compute the jump target in r11.
+ __ Pop(kWasmInstanceRegister);
+ __ LoadU64(
+ r11,
+ MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag),
+ r0);
+ __ SmiUntag(kReturnRegister0);
+ __ AddS64(r11, r11, kReturnRegister0);
+ // r11 now holds the jump table slot where we want to jump to in the end.
// Restore registers.
__ MultiPopF64AndV128(fp_regs, simd_regs);
__ MultiPop(gp_regs);
}
- // Finally, jump to the entrypoint.
+
+ // Finally, jump to the jump table slot for the function.
__ Jump(r11);
}
diff --git a/chromium/v8/src/builtins/riscv64/builtins-riscv64.cc b/chromium/v8/src/builtins/riscv64/builtins-riscv64.cc
index d6fb8d279aa..f9e2685326a 100644
--- a/chromium/v8/src/builtins/riscv64/builtins-riscv64.cc
+++ b/chromium/v8/src/builtins/riscv64/builtins-riscv64.cc
@@ -2751,37 +2751,40 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in t0 by the jump table trampoline.
// Convert to Smi for the runtime call
__ SmiTag(kWasmCompileLazyFuncIndexRegister);
- {
- HardAbortScope hard_abort(masm); // Avoid calls to Abort.
- FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
- // Save all parameter registers (see kGpParamRegisters in wasm-linkage.cc).
- // They might be overwritten in the runtime call below. We don't have any
- // callee-saved registers in wasm, so no need to store anything else.
- RegList gp_regs;
+ RegList kSavedGpRegs = ([]() constexpr {
+ RegList saved_gp_regs;
for (Register gp_param_reg : wasm::kGpParamRegisters) {
- gp_regs.set(gp_param_reg);
+ saved_gp_regs.set(gp_param_reg);
}
- // Also push a1, because we must push multiples of 16 bytes (see
- // {TurboAssembler::PushCPURegList}.
- CHECK_EQ(1, gp_regs.Count() % 2);
- gp_regs.set(a1);
- // Ensure that A1 will not be repeated.
- CHECK_EQ(0, gp_regs.Count() % 2);
-
- DoubleRegList fp_regs;
+
+ // All set registers were unique.
+ CHECK_EQ(saved_gp_regs.Count(), arraysize(wasm::kGpParamRegisters));
+ // The Wasm instance must be part of the saved registers.
+ CHECK(saved_gp_regs.has(kWasmInstanceRegister));
+ CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
+ saved_gp_regs.Count());
+ return saved_gp_regs;
+ })();
+
+ DoubleRegList kSavedFpRegs = ([]() constexpr {
+ DoubleRegList saved_fp_regs;
for (DoubleRegister fp_param_reg : wasm::kFpParamRegisters) {
- fp_regs.set(fp_param_reg);
+ saved_fp_regs.set(fp_param_reg);
}
- CHECK_EQ(gp_regs.Count(), arraysize(wasm::kGpParamRegisters) + 1);
- CHECK_EQ(fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
- CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
- gp_regs.Count());
+ CHECK_EQ(saved_fp_regs.Count(), arraysize(wasm::kFpParamRegisters));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs,
- fp_regs.Count());
- __ MultiPush(gp_regs);
- __ MultiPushFPU(fp_regs);
+ saved_fp_regs.Count());
+ return saved_fp_regs;
+ })();
+
+ {
+ HardAbortScope hard_abort(masm); // Avoid calls to Abort.
+ FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
+
+ __ MultiPush(kSavedGpRegs);
+ __ MultiPushFPU(kSavedFpRegs);
// Pass instance and function index as an explicit arguments to the runtime
// function.
@@ -2791,13 +2794,21 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- __ Move(s1, a0); // move return value to s1 since a0 will be restored to
- // the value before the call
+ __ SmiUntag(s1, a0); // move return value to s1 since a0 will be restored
+ // to the value before the call
+ CHECK(!kSavedGpRegs.has(s1));
// Restore registers.
- __ MultiPopFPU(fp_regs);
- __ MultiPop(gp_regs);
+ __ MultiPopFPU(kSavedFpRegs);
+ __ MultiPop(kSavedGpRegs);
}
+
+ // The runtime function returned the jump table slot offset as a Smi (now in
+ // x17). Use that to compute the jump target.
+ __ Ld(kScratchReg,
+ MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset - kHeapObjectTag));
+ __ Add64(s1, s1, Operand(kScratchReg));
// Finally, jump to the entrypoint.
__ Jump(s1);
}
diff --git a/chromium/v8/src/builtins/s390/builtins-s390.cc b/chromium/v8/src/builtins/s390/builtins-s390.cc
index afaae01b2a9..8faa9b121eb 100644
--- a/chromium/v8/src/builtins/s390/builtins-s390.cc
+++ b/chromium/v8/src/builtins/s390/builtins-s390.cc
@@ -2877,8 +2877,8 @@ void Builtins::Generate_Construct(MacroAssembler* masm) {
void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// The function index was put in a register by the jump table trampoline.
// Convert to Smi for the runtime call.
- __ SmiTag(kWasmCompileLazyFuncIndexRegister,
- kWasmCompileLazyFuncIndexRegister);
+ __ SmiTag(kWasmCompileLazyFuncIndexRegister);
+
{
HardAbortScope hard_abort(masm); // Avoid calls to Abort.
FrameAndConstantPoolScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
@@ -2906,21 +2906,35 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ MultiPush(gp_regs);
__ MultiPushF64OrV128(fp_regs, ip);
- // Pass instance and function index as explicit arguments to the runtime
+ // Push the Wasm instance for loading the jump table address after the
+ // runtime call.
+ __ Push(kWasmInstanceRegister);
+
+ // Push the Wasm instance again as an explicit argument to the runtime
// function.
- __ Push(kWasmInstanceRegister, r7);
+ __ Push(kWasmInstanceRegister);
+ // Push the function index as second argument.
+ __ Push(kWasmCompileLazyFuncIndexRegister);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ LoadSmiLiteral(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- // The entrypoint address is the return value.
- __ mov(ip, r2);
+ // The runtime function returns the jump table slot offset as a Smi. Use
+ // that to compute the jump target in ip.
+ __ Pop(kWasmInstanceRegister);
+ __ LoadU64(ip, MemOperand(kWasmInstanceRegister,
+ WasmInstanceObject::kJumpTableStartOffset -
+ kHeapObjectTag));
+ __ SmiUntag(kReturnRegister0);
+ __ AddS64(ip, ip, kReturnRegister0);
+ // ip now holds the jump table slot where we want to jump to in the end.
// Restore registers.
__ MultiPopF64OrV128(fp_regs, ip);
__ MultiPop(gp_regs);
}
- // Finally, jump to the entrypoint.
+
+ // Finally, jump to the jump table slot for the function.
__ Jump(ip);
}
diff --git a/chromium/v8/src/builtins/x64/builtins-x64.cc b/chromium/v8/src/builtins/x64/builtins-x64.cc
index a59143273c0..6b52a175d2e 100644
--- a/chromium/v8/src/builtins/x64/builtins-x64.cc
+++ b/chromium/v8/src/builtins/x64/builtins-x64.cc
@@ -2786,6 +2786,7 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Pop(r15);
// Convert to Smi for the runtime call.
__ SmiTag(r15);
+
{
HardAbortScope hard_abort(masm); // Avoid calls to Abort.
FrameScope scope(masm, StackFrame::WASM_COMPILE_LAZY);
@@ -2809,7 +2810,12 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
offset += kSimd128Size;
}
- // Push the Wasm instance as an explicit argument to WasmCompileLazy.
+ // Push the Wasm instance for loading the jump table address after the
+ // runtime call.
+ __ Push(kWasmInstanceRegister);
+
+ // Push the Wasm instance again as an explicit argument to the runtime
+ // function.
__ Push(kWasmInstanceRegister);
// Push the function index as second argument.
__ Push(r15);
@@ -2817,8 +2823,15 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// set the current context on the isolate.
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
- // The entrypoint address is the return value.
- __ movq(r15, kReturnRegister0);
+ // The runtime function returns the jump table slot offset as a Smi. Use
+ // that to compute the jump target in r15.
+ __ Pop(kWasmInstanceRegister);
+ __ movq(r15, MemOperand(kWasmInstanceRegister,
+ wasm::ObjectAccess::ToTagged(
+ WasmInstanceObject::kJumpTableStartOffset)));
+ __ SmiUntag(kReturnRegister0);
+ __ addq(r15, kReturnRegister0);
+ // r15 now holds the jump table slot where we want to jump to in the end.
// Restore registers.
for (DoubleRegister reg : base::Reversed(wasm::kFpParamRegisters)) {
@@ -2831,7 +2844,8 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Pop(reg);
}
}
- // Finally, jump to the entrypoint.
+
+ // Finally, jump to the jump table slot for the function.
__ jmp(r15);
}
diff --git a/chromium/v8/src/compiler/pipeline.cc b/chromium/v8/src/compiler/pipeline.cc
index cd546fe9c81..a71427f5682 100644
--- a/chromium/v8/src/compiler/pipeline.cc
+++ b/chromium/v8/src/compiler/pipeline.cc
@@ -3486,8 +3486,11 @@ bool PipelineImpl::SelectInstructions(Linkage* linkage) {
const RegisterConfiguration* config = RegisterConfiguration::Default();
std::unique_ptr<const RegisterConfiguration> restricted_config;
+ // The mid-tier register allocator keeps values in stack slots for too long.
+ // This is incompatible with left-trimming, therefore we cannot enable it for
+ // JS functions.
bool use_mid_tier_register_allocator =
- !CodeKindIsStaticallyCompiled(data->info()->code_kind()) &&
+ data->info()->code_kind() == CodeKind::WASM_FUNCTION &&
(FLAG_turbo_force_mid_tier_regalloc ||
(FLAG_turbo_use_mid_tier_regalloc_for_huge_functions &&
data->sequence()->VirtualRegisterCount() >
diff --git a/chromium/v8/src/execution/riscv64/frame-constants-riscv64.h b/chromium/v8/src/execution/riscv64/frame-constants-riscv64.h
index 6b70815ea40..d5e3165956b 100644
--- a/chromium/v8/src/execution/riscv64/frame-constants-riscv64.h
+++ b/chromium/v8/src/execution/riscv64/frame-constants-riscv64.h
@@ -24,14 +24,15 @@ class EntryFrameConstants : public AllStatic {
class WasmCompileLazyFrameConstants : public TypedFrameConstants {
public:
static constexpr int kNumberOfSavedGpParamRegs =
- arraysize(wasm::kGpParamRegisters) + 1;
+ arraysize(wasm::kGpParamRegisters);
static constexpr int kNumberOfSavedFpParamRegs =
arraysize(wasm::kFpParamRegisters);
static constexpr int kNumberOfSavedAllParamRegs =
kNumberOfSavedGpParamRegs + kNumberOfSavedFpParamRegs;
// FP-relative.
- // See Generate_WasmCompileLazy in builtins-mips64.cc.
+ // See Generate_WasmCompileLazy in builtins-riscv64.cc.
+ // TODO(riscv): add rvv v reg save
static constexpr int kWasmInstanceOffset =
TYPED_FRAME_PUSHED_VALUE_OFFSET(kNumberOfSavedAllParamRegs);
static constexpr int kFixedFrameSizeFromFp =
diff --git a/chromium/v8/src/heap/mark-compact.cc b/chromium/v8/src/heap/mark-compact.cc
index 49a05b6a137..ef0b67ca2b6 100644
--- a/chromium/v8/src/heap/mark-compact.cc
+++ b/chromium/v8/src/heap/mark-compact.cc
@@ -3054,14 +3054,11 @@ void MarkCompactCollector::ClearJSWeakRefs() {
// unregister_token field set to undefined when processing the first
// WeakCell. Like above, we're modifying pointers during GC, so record the
// slots.
- HeapObject undefined = ReadOnlyRoots(isolate()).undefined_value();
JSFinalizationRegistry finalization_registry =
JSFinalizationRegistry::cast(weak_cell.finalization_registry());
finalization_registry.RemoveUnregisterToken(
JSReceiver::cast(unregister_token), isolate(),
- [undefined](WeakCell matched_cell) {
- matched_cell.set_unregister_token(undefined);
- },
+ JSFinalizationRegistry::kKeepMatchedCellsInRegistry,
gc_notify_updated_slot);
} else {
// The unregister_token is alive.
diff --git a/chromium/v8/src/ic/ic.cc b/chromium/v8/src/ic/ic.cc
index b0572bc23e4..1fdf72440a4 100644
--- a/chromium/v8/src/ic/ic.cc
+++ b/chromium/v8/src/ic/ic.cc
@@ -1849,19 +1849,10 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
IsAnyDefineOwn() ? LookupIterator::OWN : LookupIterator::DEFAULT);
if (name->IsPrivate()) {
- bool exists = it.IsFound();
- if (name->IsPrivateName() && exists == IsDefineKeyedOwnIC()) {
- Handle<String> name_string(
- String::cast(Symbol::cast(*name).description()), isolate());
- if (exists) {
- MessageTemplate message =
- name->IsPrivateBrand()
- ? MessageTemplate::kInvalidPrivateBrandReinitialization
- : MessageTemplate::kInvalidPrivateFieldReinitialization;
- return TypeError(message, object, name_string);
- } else {
- return TypeError(MessageTemplate::kInvalidPrivateMemberWrite, object,
- name_string);
+ if (name->IsPrivateName()) {
+ DCHECK(!IsDefineNamedOwnIC());
+ if (!JSReceiver::CheckPrivateNameStore(&it, IsDefineKeyedOwnIC())) {
+ return MaybeHandle<Object>();
}
}
diff --git a/chromium/v8/src/objects/code-kind.h b/chromium/v8/src/objects/code-kind.h
index b43affdc2d9..32c3b025757 100644
--- a/chromium/v8/src/objects/code-kind.h
+++ b/chromium/v8/src/objects/code-kind.h
@@ -57,10 +57,6 @@ inline constexpr bool CodeKindIsBaselinedJSFunction(CodeKind kind) {
return kind == CodeKind::BASELINE;
}
-inline constexpr bool CodeKindIsStaticallyCompiled(CodeKind kind) {
- return kind == CodeKind::BYTECODE_HANDLER || kind == CodeKind::BUILTIN;
-}
-
inline constexpr bool CodeKindIsUnoptimizedJSFunction(CodeKind kind) {
STATIC_ASSERT(static_cast<int>(CodeKind::INTERPRETED_FUNCTION) + 1 ==
static_cast<int>(CodeKind::BASELINE));
diff --git a/chromium/v8/src/objects/js-objects.cc b/chromium/v8/src/objects/js-objects.cc
index 3f806f5a090..4335a7cf0e4 100644
--- a/chromium/v8/src/objects/js-objects.cc
+++ b/chromium/v8/src/objects/js-objects.cc
@@ -186,6 +186,55 @@ Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate,
}
// static
+bool JSReceiver::CheckPrivateNameStore(LookupIterator* it, bool is_define) {
+ DCHECK(it->GetName()->IsPrivateName());
+ Isolate* isolate = it->isolate();
+ Handle<String> name_string(
+ String::cast(Handle<Symbol>::cast(it->GetName())->description()),
+ isolate);
+ bool should_throw = GetShouldThrow(isolate, Nothing<ShouldThrow>()) ==
+ ShouldThrow::kThrowOnError;
+ for (; it->IsFound(); it->Next()) {
+ switch (it->state()) {
+ case LookupIterator::TRANSITION:
+ case LookupIterator::INTERCEPTOR:
+ case LookupIterator::JSPROXY:
+ case LookupIterator::NOT_FOUND:
+ case LookupIterator::INTEGER_INDEXED_EXOTIC:
+ case LookupIterator::ACCESSOR:
+ UNREACHABLE();
+ case LookupIterator::ACCESS_CHECK:
+ if (!it->HasAccess()) {
+ isolate->ReportFailedAccessCheck(
+ Handle<JSObject>::cast(it->GetReceiver()));
+ RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, false);
+ return false;
+ }
+ break;
+ case LookupIterator::DATA:
+ if (is_define && should_throw) {
+ MessageTemplate message =
+ it->GetName()->IsPrivateBrand()
+ ? MessageTemplate::kInvalidPrivateBrandReinitialization
+ : MessageTemplate::kInvalidPrivateFieldReinitialization;
+ isolate->Throw(*(isolate->factory()->NewTypeError(
+ message, name_string, it->GetReceiver())));
+ return false;
+ }
+ return true;
+ }
+ }
+ DCHECK(!it->IsFound());
+ if (!is_define && should_throw) {
+ isolate->Throw(*(isolate->factory()->NewTypeError(
+ MessageTemplate::kInvalidPrivateMemberWrite, name_string,
+ it->GetReceiver())));
+ return false;
+ }
+ return true;
+}
+
+// static
Maybe<bool> JSReceiver::CheckIfCanDefine(Isolate* isolate, LookupIterator* it,
Handle<Object> value,
Maybe<ShouldThrow> should_throw) {
diff --git a/chromium/v8/src/objects/js-objects.h b/chromium/v8/src/objects/js-objects.h
index d6a96a8fe2d..4edb34d5c9f 100644
--- a/chromium/v8/src/objects/js-objects.h
+++ b/chromium/v8/src/objects/js-objects.h
@@ -161,6 +161,11 @@ class JSReceiver : public TorqueGeneratedJSReceiver<JSReceiver, HeapObject> {
Isolate* isolate, Handle<JSReceiver> object, Handle<Object> key,
PropertyDescriptor* desc, Maybe<ShouldThrow> should_throw);
+ // Check if private name property can be store on the object. It will return
+ // false with an error when it cannot.
+ V8_WARN_UNUSED_RESULT static bool CheckPrivateNameStore(LookupIterator* it,
+ bool is_define);
+
// Check if a data property can be created on the object. It will fail with
// an error when it cannot.
V8_WARN_UNUSED_RESULT static Maybe<bool> CheckIfCanDefine(
diff --git a/chromium/v8/src/objects/js-weak-refs-inl.h b/chromium/v8/src/objects/js-weak-refs-inl.h
index acce7b72b94..76e6e075e5d 100644
--- a/chromium/v8/src/objects/js-weak-refs-inl.h
+++ b/chromium/v8/src/objects/js-weak-refs-inl.h
@@ -60,16 +60,14 @@ bool JSFinalizationRegistry::Unregister(
// key. Each WeakCell will be in the "active_cells" or "cleared_cells" list of
// its FinalizationRegistry; remove it from there.
return finalization_registry->RemoveUnregisterToken(
- *unregister_token, isolate,
- [isolate](WeakCell matched_cell) {
- matched_cell.RemoveFromFinalizationRegistryCells(isolate);
- },
+ *unregister_token, isolate, kRemoveMatchedCellsFromRegistry,
[](HeapObject, ObjectSlot, Object) {});
}
-template <typename MatchCallback, typename GCNotifyUpdatedSlotCallback>
+template <typename GCNotifyUpdatedSlotCallback>
bool JSFinalizationRegistry::RemoveUnregisterToken(
- JSReceiver unregister_token, Isolate* isolate, MatchCallback match_callback,
+ JSReceiver unregister_token, Isolate* isolate,
+ RemoveUnregisterTokenMode removal_mode,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
// This method is called from both FinalizationRegistry#unregister and for
// removing weakly-held dead unregister tokens. The latter is during GC so
@@ -107,7 +105,16 @@ bool JSFinalizationRegistry::RemoveUnregisterToken(
value = weak_cell.key_list_next();
if (weak_cell.unregister_token() == unregister_token) {
// weak_cell has the same unregister token; remove it from the key list.
- match_callback(weak_cell);
+ switch (removal_mode) {
+ case kRemoveMatchedCellsFromRegistry:
+ weak_cell.RemoveFromFinalizationRegistryCells(isolate);
+ break;
+ case kKeepMatchedCellsInRegistry:
+ // Do nothing.
+ break;
+ }
+ // Clear unregister token-related fields.
+ weak_cell.set_unregister_token(undefined);
weak_cell.set_key_list_prev(undefined);
weak_cell.set_key_list_next(undefined);
was_present = true;
diff --git a/chromium/v8/src/objects/js-weak-refs.h b/chromium/v8/src/objects/js-weak-refs.h
index 57f765b282e..f678234ff81 100644
--- a/chromium/v8/src/objects/js-weak-refs.h
+++ b/chromium/v8/src/objects/js-weak-refs.h
@@ -43,10 +43,14 @@ class JSFinalizationRegistry
// it modifies slots in key_map and WeakCells and the normal write barrier is
// disabled during GC, we need to tell the GC about the modified slots via the
// gc_notify_updated_slot function.
- template <typename MatchCallback, typename GCNotifyUpdatedSlotCallback>
+ enum RemoveUnregisterTokenMode {
+ kRemoveMatchedCellsFromRegistry,
+ kKeepMatchedCellsInRegistry
+ };
+ template <typename GCNotifyUpdatedSlotCallback>
inline bool RemoveUnregisterToken(
JSReceiver unregister_token, Isolate* isolate,
- MatchCallback match_callback,
+ RemoveUnregisterTokenMode removal_mode,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot);
// Returns true if the cleared_cells list is non-empty.
diff --git a/chromium/v8/src/objects/lookup.cc b/chromium/v8/src/objects/lookup.cc
index 81f83302e7b..df9e219d33b 100644
--- a/chromium/v8/src/objects/lookup.cc
+++ b/chromium/v8/src/objects/lookup.cc
@@ -1264,7 +1264,9 @@ LookupIterator::State LookupIterator::LookupInSpecialHolder(
}
#endif // V8_ENABLE_WEBASSEMBLY
if (map.is_access_check_needed()) {
- if (is_element || !name_->IsPrivate(isolate_)) return ACCESS_CHECK;
+ if (is_element || !name_->IsPrivate(isolate_) ||
+ name_->IsPrivateName(isolate_))
+ return ACCESS_CHECK;
}
V8_FALLTHROUGH;
case ACCESS_CHECK:
diff --git a/chromium/v8/src/objects/objects.cc b/chromium/v8/src/objects/objects.cc
index 4616ef7ab74..d16146114e8 100644
--- a/chromium/v8/src/objects/objects.cc
+++ b/chromium/v8/src/objects/objects.cc
@@ -6981,7 +6981,7 @@ void JSFinalizationRegistry::RemoveCellFromUnregisterTokenMap(
}
// weak_cell is now removed from the unregister token map, so clear its
- // unregister token-related fields for heap verification.
+ // unregister token-related fields.
weak_cell.set_unregister_token(undefined);
weak_cell.set_key_list_prev(undefined);
weak_cell.set_key_list_next(undefined);
diff --git a/chromium/v8/src/profiler/profile-generator.cc b/chromium/v8/src/profiler/profile-generator.cc
index afbe860cb24..c70e4e50cf1 100644
--- a/chromium/v8/src/profiler/profile-generator.cc
+++ b/chromium/v8/src/profiler/profile-generator.cc
@@ -570,6 +570,8 @@ void ContextFilter::OnMoveEvent(Address from_address, Address to_address) {
using v8::tracing::TracedValue;
+std::atomic<ProfilerId> CpuProfilesCollection::last_id_{0};
+
CpuProfile::CpuProfile(CpuProfiler* profiler, ProfilerId id, const char* title,
CpuProfilingOptions options,
std::unique_ptr<DiscardedSamplesDelegate> delegate)
@@ -892,10 +894,7 @@ size_t CodeMap::GetEstimatedMemoryUsage() const {
}
CpuProfilesCollection::CpuProfilesCollection(Isolate* isolate)
- : profiler_(nullptr),
- current_profiles_semaphore_(1),
- last_id_(0),
- isolate_(isolate) {
+ : profiler_(nullptr), current_profiles_semaphore_(1), isolate_(isolate) {
USE(isolate_);
}
diff --git a/chromium/v8/src/profiler/profile-generator.h b/chromium/v8/src/profiler/profile-generator.h
index 2c3b44e6b09..360d4c23aa2 100644
--- a/chromium/v8/src/profiler/profile-generator.h
+++ b/chromium/v8/src/profiler/profile-generator.h
@@ -587,7 +587,7 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
// Accessed by VM thread and profile generator thread.
std::vector<std::unique_ptr<CpuProfile>> current_profiles_;
base::Semaphore current_profiles_semaphore_;
- ProfilerId last_id_;
+ static std::atomic<ProfilerId> last_id_;
Isolate* isolate_;
};
diff --git a/chromium/v8/src/runtime/runtime-object.cc b/chromium/v8/src/runtime/runtime-object.cc
index e4da1da95c5..4241abe151e 100644
--- a/chromium/v8/src/runtime/runtime-object.cc
+++ b/chromium/v8/src/runtime/runtime-object.cc
@@ -48,6 +48,9 @@ MaybeHandle<Object> Runtime::GetObjectProperty(
LookupIterator(isolate, receiver, lookup_key, lookup_start_object);
MaybeHandle<Object> result = Object::GetProperty(&it);
+ if (result.is_null()) {
+ return result;
+ }
if (is_found) *is_found = it.IsFound();
if (!it.IsFound() && key->IsSymbol() &&
@@ -572,15 +575,9 @@ MaybeHandle<Object> Runtime::SetObjectProperty(
PropertyKey lookup_key(isolate, key, &success);
if (!success) return MaybeHandle<Object>();
LookupIterator it(isolate, object, lookup_key);
-
- if (!it.IsFound() && key->IsSymbol() &&
- Symbol::cast(*key).is_private_name()) {
- Handle<Object> name_string(Symbol::cast(*key).description(), isolate);
- DCHECK(name_string->IsString());
- THROW_NEW_ERROR(isolate,
- NewTypeError(MessageTemplate::kInvalidPrivateMemberWrite,
- name_string, object),
- Object);
+ if (key->IsSymbol() && Symbol::cast(*key).is_private_name() &&
+ !JSReceiver::CheckPrivateNameStore(&it, false)) {
+ return MaybeHandle<Object>();
}
MAYBE_RETURN_NULL(
@@ -589,10 +586,11 @@ MaybeHandle<Object> Runtime::SetObjectProperty(
return value;
}
-MaybeHandle<Object> Runtime::DefineObjectOwnProperty(
- Isolate* isolate, Handle<Object> object, Handle<Object> key,
- Handle<Object> value, StoreOrigin store_origin,
- Maybe<ShouldThrow> should_throw) {
+MaybeHandle<Object> Runtime::DefineObjectOwnProperty(Isolate* isolate,
+ Handle<Object> object,
+ Handle<Object> key,
+ Handle<Object> value,
+ StoreOrigin store_origin) {
if (object->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR(
isolate,
@@ -607,20 +605,15 @@ MaybeHandle<Object> Runtime::DefineObjectOwnProperty(
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
if (key->IsSymbol() && Symbol::cast(*key).is_private_name()) {
- Handle<Symbol> private_symbol = Handle<Symbol>::cast(key);
- if (it.IsFound()) {
- Handle<Object> name_string(private_symbol->description(), isolate);
- DCHECK(name_string->IsString());
- MessageTemplate message =
- private_symbol->is_private_brand()
- ? MessageTemplate::kInvalidPrivateBrandReinitialization
- : MessageTemplate::kInvalidPrivateFieldReinitialization;
- THROW_NEW_ERROR(isolate, NewTypeError(message, name_string), Object);
- } else {
- MAYBE_RETURN_NULL(JSReceiver::AddPrivateField(&it, value, should_throw));
+ if (!JSReceiver::CheckPrivateNameStore(&it, true)) {
+ return MaybeHandle<Object>();
}
+ DCHECK(!it.IsFound());
+ MAYBE_RETURN_NULL(
+ JSReceiver::AddPrivateField(&it, value, Nothing<ShouldThrow>()));
} else {
- MAYBE_RETURN_NULL(JSReceiver::CreateDataProperty(&it, value, should_throw));
+ MAYBE_RETURN_NULL(
+ JSReceiver::CreateDataProperty(&it, value, Nothing<ShouldThrow>()));
}
return value;
diff --git a/chromium/v8/src/runtime/runtime-wasm.cc b/chromium/v8/src/runtime/runtime-wasm.cc
index a6712673c0d..501f665927d 100644
--- a/chromium/v8/src/runtime/runtime-wasm.cc
+++ b/chromium/v8/src/runtime/runtime-wasm.cc
@@ -224,14 +224,11 @@ RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
bool success = wasm::CompileLazy(isolate, instance, func_index);
if (!success) {
DCHECK(isolate->has_pending_exception());
- return ReadOnlyRoots(isolate).exception();
+ return ReadOnlyRoots{isolate}.exception();
}
- Address entrypoint =
- instance->module_object().native_module()->GetCallTargetForFunction(
- func_index);
-
- return Object(entrypoint);
+ auto* native_module = instance->module_object().native_module();
+ return Smi::FromInt(native_module->GetJumpTableOffset(func_index));
}
namespace {
diff --git a/chromium/v8/src/runtime/runtime.h b/chromium/v8/src/runtime/runtime.h
index a140f9b5269..877b277a5e2 100644
--- a/chromium/v8/src/runtime/runtime.h
+++ b/chromium/v8/src/runtime/runtime.h
@@ -821,10 +821,9 @@ class Runtime : public AllStatic {
// private field definition), this method throws if the field already exists
// on object.
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
- DefineObjectOwnProperty(
- Isolate* isolate, Handle<Object> object, Handle<Object> key,
- Handle<Object> value, StoreOrigin store_origin,
- Maybe<ShouldThrow> should_throw = Nothing<ShouldThrow>());
+ DefineObjectOwnProperty(Isolate* isolate, Handle<Object> object,
+ Handle<Object> key, Handle<Object> value,
+ StoreOrigin store_origin);
// When "receiver" is not passed, it defaults to "lookup_start_object".
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
diff --git a/chromium/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h b/chromium/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
index b4b238421d2..36413545c60 100644
--- a/chromium/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
+++ b/chromium/v8/src/wasm/baseline/mips64/liftoff-assembler-mips64.h
@@ -748,13 +748,13 @@ void LiftoffAssembler::AtomicStore(Register dst_addr, Register offset_reg,
Daddu(temp0, dst_op.rm(), dst_op.offset()); \
switch (type.value()) { \
case StoreType::kI64Store8: \
- ASSEMBLE_ATOMIC_BINOP_EXT(Ll, Sc, 8, inst64, 7); \
+ ASSEMBLE_ATOMIC_BINOP_EXT(Lld, Scd, 8, inst64, 7); \
break; \
case StoreType::kI32Store8: \
ASSEMBLE_ATOMIC_BINOP_EXT(Ll, Sc, 8, inst32, 3); \
break; \
case StoreType::kI64Store16: \
- ASSEMBLE_ATOMIC_BINOP_EXT(Ll, Sc, 16, inst64, 7); \
+ ASSEMBLE_ATOMIC_BINOP_EXT(Lld, Scd, 16, inst64, 7); \
break; \
case StoreType::kI32Store16: \
ASSEMBLE_ATOMIC_BINOP_EXT(Ll, Sc, 16, inst32, 3); \
@@ -823,13 +823,13 @@ void LiftoffAssembler::AtomicExchange(Register dst_addr, Register offset_reg,
Daddu(temp0, dst_op.rm(), dst_op.offset());
switch (type.value()) {
case StoreType::kI64Store8:
- ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Ll, Sc, 8, 7);
+ ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Lld, Scd, 8, 7);
break;
case StoreType::kI32Store8:
ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Ll, Sc, 8, 3);
break;
case StoreType::kI64Store16:
- ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Ll, Sc, 16, 7);
+ ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Lld, Scd, 16, 7);
break;
case StoreType::kI32Store16:
ASSEMBLE_ATOMIC_EXCHANGE_INTEGER_EXT(Ll, Sc, 16, 3);
@@ -899,13 +899,13 @@ void LiftoffAssembler::AtomicCompareExchange(
Daddu(temp0, dst_op.rm(), dst_op.offset());
switch (type.value()) {
case StoreType::kI64Store8:
- ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Ll, Sc, 8, 7);
+ ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Lld, Scd, 8, 7);
break;
case StoreType::kI32Store8:
ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Ll, Sc, 8, 3);
break;
case StoreType::kI64Store16:
- ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Ll, Sc, 16, 7);
+ ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Lld, Scd, 16, 7);
break;
case StoreType::kI32Store16:
ASSEMBLE_ATOMIC_COMPARE_EXCHANGE_INTEGER_EXT(Ll, Sc, 16, 3);
diff --git a/chromium/v8/src/wasm/wasm-linkage.h b/chromium/v8/src/wasm/wasm-linkage.h
index 77ed549c900..88115462bed 100644
--- a/chromium/v8/src/wasm/wasm-linkage.h
+++ b/chromium/v8/src/wasm/wasm-linkage.h
@@ -143,6 +143,8 @@ constexpr DoubleRegister kFpReturnRegisters[] = {};
// The parameter index where the instance parameter should be placed in wasm
// call descriptors. This is used by the Int64Lowering::LowerNode method.
constexpr int kWasmInstanceParameterIndex = 0;
+static_assert(kWasmInstanceRegister ==
+ kGpParamRegisters[kWasmInstanceParameterIndex]);
class LinkageAllocator {
public: