Implement the update engine portion for new enterprise policies.

Enterprises need the ability to stop the auto updates and pin clients
to a given target version. This CL adds support for these features in
the update_engine.

BUG=27307: Implement StopAutoUpdate based on enterprise policy
TEST=Added new unit tests, manually tested all cases on ZGB.
CQ-DEPEND=I523c3f67e0cb07fd24744dc0a30382ff2fe2128a
Change-Id: Id576401afc6d2c93f0e9ece7c6c0ddcf4b1bc00d
Reviewed-on: https://gerrit.chromium.org/gerrit/17867
Commit-Ready: Jay Srinivasan <jaysri@chromium.org>
Reviewed-by: Jay Srinivasan <jaysri@chromium.org>
Tested-by: Jay Srinivasan <jaysri@chromium.org>
diff --git a/action_processor.h b/action_processor.h
index 57b1d37..c45c3c8 100644
--- a/action_processor.h
+++ b/action_processor.h
@@ -43,6 +43,7 @@
   kActionCodeSignedDeltaPayloadExpectedError = 17,
   kActionCodeDownloadPayloadPubKeyVerificationError = 18,
   kActionCodePostinstallBootedFromFirmwareB = 19,
+  kActionCodeOmahaUpdateIgnoredPerPolicy = 20,
   kActionCodeOmahaRequestEmptyResponseError = 200,
   kActionCodeOmahaRequestXMLParseError = 201,
   kActionCodeOmahaRequestNoUpdateCheckNode = 202,
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index bbcc00b..c97a9a7 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -100,7 +100,14 @@
   if (event == NULL) {
     body = GetPingBody(ping_active_days, ping_roll_call_days);
     if (!ping_only) {
-      body += "        <o:updatecheck></o:updatecheck>\n";
+      body += StringPrintf(
+          "        <o:updatecheck"
+          " updatedisabled=\"%s\""
+          " targetversionprefix=\"%s\""
+          "></o:updatecheck>\n",
+          params.update_disabled ? "true" : "false",
+          XmlEncode(params.target_version_prefix).c_str());
+
       // If this is the first update check after a reboot following a previous
       // update, generate an event containing the previous version number. If
       // the previous version preference file doesn't exist the event is still
@@ -233,6 +240,7 @@
                                     ping_active_days_,
                                     ping_roll_call_days_,
                                     prefs_));
+
   http_fetcher_->SetPostData(request_post.data(), request_post.size(),
                              kHttpContentTypeTextXml);
   LOG(INFO) << "Posting an Omaha request to " << params_.update_url;
@@ -451,6 +459,12 @@
     return;
   }
 
+  if (params_.update_disabled) {
+    LOG(ERROR) << "Ignoring Omaha updates as updates are disabled by policy.";
+    completer.set_code(kActionCodeOmahaUpdateIgnoredPerPolicy);
+    return;
+  }
+
   // In best-effort fashion, fetch the rest of the expected attributes
   // from the updatecheck node, then return the object
   output_object.update_exists = true;
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 776e5e7..e92fb03 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -46,7 +46,9 @@
     "unittest",
     "OEM MODEL 09235 7471",
     false,  // delta okay
-    "http://url");
+    "http://url",
+    false, // update_disabled
+    "");   // target_version_prefix
 
 string GetNoUpdateResponse(const string& app_id) {
   return string(
@@ -65,11 +67,13 @@
                          const string& needsadmin,
                          const string& size,
                          const string& deadline) {
-  return string("<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
-                "xmlns=\"http://www.google.com/update2/response\" "
-                "protocol=\"2.0\"><app "
-                "appid=\"") + app_id + "\" status=\"ok\"><ping "
+  return string(
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" "
+      "protocol=\"2.0\"><app "
+      "appid=\"") + app_id + "\" status=\"ok\"><ping "
       "status=\"ok\"/><updatecheck DisplayVersion=\"" + display_version + "\" "
+      "ChromeOSVersion=\"" + display_version + "\" "
       "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
       "IsDelta=\"true\" "
       "codebase=\"" + codebase + "\" hash=\"not-applicable\" "
@@ -264,6 +268,48 @@
   EXPECT_EQ("20101020", response.deadline);
 }
 
+TEST(OmahaRequestActionTest, ValidUpdateBlockedByPolicyTest) {
+  OmahaResponse response;
+  OmahaRequestParams params = kDefaultTestParams;
+  params.update_disabled = true;
+  ASSERT_FALSE(
+      TestUpdateCheck(NULL,  // prefs
+                      params,
+                      GetUpdateResponse(OmahaRequestParams::kAppId,
+                                        "1.2.3.4",  // version
+                                        "http://more/info",
+                                        "true",  // prompt
+                                        "http://code/base",  // dl url
+                                        "HASH1234=",  // checksum
+                                        "false",  // needs admin
+                                        "123",  // size
+                                        "20101020"),  // deadline
+                      -1,
+                      false,  // ping_only
+                      kActionCodeOmahaUpdateIgnoredPerPolicy,
+                      &response,
+                      NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+
+TEST(OmahaRequestActionTest, NoUpdatesSentWhenBlockedByPolicyTest) {
+  OmahaResponse response;
+  OmahaRequestParams params = kDefaultTestParams;
+  params.update_disabled = true;
+  ASSERT_TRUE(
+      TestUpdateCheck(NULL,  // prefs
+                      params,
+                      GetNoUpdateResponse(OmahaRequestParams::kAppId),
+                      -1,
+                      false,  // ping_only
+                      kActionCodeSuccess,
+                      &response,
+                      NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+
 TEST(OmahaRequestActionTest, NoOutputPipeTest) {
   const string http_response(GetNoUpdateResponse(OmahaRequestParams::kAppId));
 
@@ -379,6 +425,7 @@
                               + "\" status=\"ok\"><ping "
                               "status=\"ok\"/><updatecheck "
                               "DisplayVersion=\"1.2.3.4\" "
+                              "ChromeOSVersion=\"1.2.3.4\" "
                               "Prompt=\"false\" "
                               "IsDelta=\"true\" "
                               "codebase=\"http://code/base\" hash=\"foo\" "
@@ -461,7 +508,9 @@
                             "unittest_track&lt;",
                             "<OEM MODEL>",
                             false,  // delta okay
-                            "http://url");
+                            "http://url",
+                            false,   // update_disabled
+                            ""); // target_version_prefix
   OmahaResponse response;
   ASSERT_FALSE(
       TestUpdateCheck(NULL,  // prefs
@@ -550,23 +599,28 @@
   // convert post_data to string
   string post_str(&post_data[0], post_data.size());
   EXPECT_NE(post_str.find(
-                "        <o:ping active=\"1\" a=\"-1\" r=\"-1\"></o:ping>\n"
-                "        <o:updatecheck></o:updatecheck>\n"),
-            string::npos);
+      "        <o:ping active=\"1\" a=\"-1\" r=\"-1\"></o:ping>\n"
+      "        <o:updatecheck"
+      " updatedisabled=\"false\""
+      " targetversionprefix=\"\""
+      "></o:updatecheck>\n"),
+      string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
   EXPECT_EQ(post_str.find("o:event"), string::npos);
 }
 
-TEST(OmahaRequestActionTest, FormatUpdateCheckPrevVersionOutputTest) {
+
+TEST(OmahaRequestActionTest, FormatUpdateDisabledTest) {
   vector<char> post_data;
   NiceMock<PrefsMock> prefs;
   EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
-      .WillOnce(DoAll(SetArgumentPointee<1>(string("1.2>3.4")), Return(true)));
-  EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, ""))
-      .WillOnce(Return(true));
+      .WillOnce(DoAll(SetArgumentPointee<1>(string("")), Return(true)));
+  EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(0);
+  OmahaRequestParams params = kDefaultTestParams;
+  params.update_disabled = true;
   ASSERT_FALSE(TestUpdateCheck(&prefs,
-                               kDefaultTestParams,
+                               params,
                                "invalid xml>",
                                -1,
                                false,  // ping_only
@@ -576,17 +630,15 @@
   // convert post_data to string
   string post_str(&post_data[0], post_data.size());
   EXPECT_NE(post_str.find(
-                "        <o:ping active=\"1\" a=\"-1\" r=\"-1\"></o:ping>\n"
-                "        <o:updatecheck></o:updatecheck>\n"),
-            string::npos);
+      "        <o:ping active=\"1\" a=\"-1\" r=\"-1\"></o:ping>\n"
+      "        <o:updatecheck"
+      " updatedisabled=\"true\""
+      " targetversionprefix=\"\""
+      "></o:updatecheck>\n"),
+      string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
-  string prev_version_event = StringPrintf(
-      "        <o:event eventtype=\"%d\" eventresult=\"%d\" "
-      "previousversion=\"1.2&gt;3.4\"></o:event>\n",
-      OmahaEvent::kTypeUpdateComplete,
-      OmahaEvent::kResultSuccessReboot);
-  EXPECT_NE(post_str.find(prev_version_event), string::npos);
+  EXPECT_EQ(post_str.find("o:event"), string::npos);
 }
 
 TEST(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
@@ -665,7 +717,9 @@
                               "unittest_track",
                               "OEM MODEL REV 1234",
                               delta_okay,
-                              "http://url");
+                              "http://url",
+                              false, // update_disabled
+                              "");   // target_version_prefix
     ASSERT_FALSE(TestUpdateCheck(NULL,  // prefs
                                  params,
                                  "invalid xml>",
diff --git a/omaha_request_params.h b/omaha_request_params.h
index da091c0..482a8b7 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -18,8 +18,14 @@
 // This struct encapsulates the data Omaha gets for the request.
 // These strings in this struct should not be XML escaped.
 struct OmahaRequestParams {
+
   OmahaRequestParams()
-      : os_platform(kOsPlatform), os_version(kOsVersion), app_id(kAppId) {}
+      : os_platform(kOsPlatform),
+        os_version(kOsVersion),
+        app_id(kAppId),
+        delta_okay(true),
+        update_disabled(false) {}
+
   OmahaRequestParams(const std::string& in_os_platform,
                      const std::string& in_os_version,
                      const std::string& in_os_sp,
@@ -29,8 +35,10 @@
                      const std::string& in_app_lang,
                      const std::string& in_app_track,
                      const std::string& in_hardware_class,
-                     const bool in_delta_okay,
-                     const std::string& in_update_url)
+                     bool in_delta_okay,
+                     const std::string& in_update_url,
+                     bool in_update_disabled,
+                     const std::string& in_target_version_prefix)
       : os_platform(in_os_platform),
         os_version(in_os_version),
         os_sp(in_os_sp),
@@ -41,7 +49,9 @@
         app_track(in_app_track),
         hardware_class(in_hardware_class),
         delta_okay(in_delta_okay),
-        update_url(in_update_url) {}
+        update_url(in_update_url),
+        update_disabled(in_update_disabled),
+        target_version_prefix(in_target_version_prefix) {}
 
   std::string os_platform;
   std::string os_version;
@@ -58,6 +68,9 @@
 
   static const char kUpdateTrackKey[];
 
+  bool update_disabled;
+  std::string target_version_prefix;
+
   // Suggested defaults
   static const char* const kAppId;
   static const char* const kOsPlatform;
diff --git a/update_attempter.cc b/update_attempter.cc
index 31bc47b..c23135b 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -168,8 +168,14 @@
 
   // If the release_track is specified by policy, that takes precedence.
   string release_track;
-  if (policy_provider_->device_policy_is_loaded())
-    policy_provider_->GetDevicePolicy().GetReleaseChannel(&release_track);
+  if (policy_provider_->device_policy_is_loaded()) {
+    const policy::DevicePolicy& device_policy =
+                                policy_provider_->GetDevicePolicy();
+    device_policy.GetReleaseChannel(&release_track);
+    device_policy.GetUpdateDisabled(&omaha_request_params_.update_disabled);
+    device_policy.GetTargetVersionPrefix(
+      &omaha_request_params_.target_version_prefix);
+  }
 
   // Determine whether an alternative test address should be used.
   string omaha_url_to_use = omaha_url;
@@ -178,12 +184,18 @@
     LOG(INFO) << "using alternative server address: " << omaha_url_to_use;
   }
 
-  if (!omaha_request_params_.Init(app_version, omaha_url_to_use,
+  if (!omaha_request_params_.Init(app_version,
+                                  omaha_url_to_use,
                                   release_track)) {
     LOG(ERROR) << "Unable to initialize Omaha request device params.";
     return;
   }
 
+  LOG(INFO) << "update_disabled = "
+            << (omaha_request_params_.update_disabled ? "true" : "false")
+            << ", target_version_prefix = "
+            << omaha_request_params_.target_version_prefix;
+
   obeying_proxies_ = true;
   if (obey_proxies || proxy_manual_checks_ == 0) {
     LOG(INFO) << "forced to obey proxies";
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 463d5a6..6402bb6 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -75,6 +75,13 @@
   void ReadTrackFromPolicyTestStart();
   static gboolean StaticReadTrackFromPolicyTestStart(gpointer data);
 
+  void ReadUpdateDisabledFromPolicyTestStart();
+  static gboolean StaticReadUpdateDisabledFromPolicyTestStart(gpointer data);
+
+  void ReadTargetVersionPrefixFromPolicyTestStart();
+  static gboolean StaticReadTargetVersionPrefixFromPolicyTestStart(
+      gpointer data);
+
   MockDbusGlib dbus_;
   UpdateAttempterUnderTest attempter_;
   ActionProcessorMock* processor_;
@@ -272,6 +279,20 @@
   return FALSE;
 }
 
+gboolean UpdateAttempterTest::StaticReadUpdateDisabledFromPolicyTestStart(
+    gpointer data) {
+  UpdateAttempterTest* ua_test = reinterpret_cast<UpdateAttempterTest*>(data);
+  ua_test->ReadUpdateDisabledFromPolicyTestStart();
+  return FALSE;
+}
+
+gboolean UpdateAttempterTest::StaticReadTargetVersionPrefixFromPolicyTestStart(
+    gpointer data) {
+  UpdateAttempterTest* ua_test = reinterpret_cast<UpdateAttempterTest*>(data);
+  ua_test->ReadTargetVersionPrefixFromPolicyTestStart();
+  return FALSE;
+}
+
 namespace {
 const string kActionTypes[] = {
   OmahaRequestAction::StaticType(),
@@ -404,4 +425,64 @@
   g_idle_add(&StaticQuitMainLoop, this);
 }
 
+TEST_F(UpdateAttempterTest, ReadUpdateDisabledFromPolicy) {
+  loop_ = g_main_loop_new(g_main_context_default(), FALSE);
+  g_idle_add(&StaticReadUpdateDisabledFromPolicyTestStart, this);
+  g_main_loop_run(loop_);
+  g_main_loop_unref(loop_);
+  loop_ = NULL;
+}
+
+void UpdateAttempterTest::ReadUpdateDisabledFromPolicyTestStart() {
+  // Tests that the update_disbled flag is properly fetched
+  // from the device policy.
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+
+  EXPECT_CALL(*device_policy, GetUpdateDisabled(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(true),
+          Return(true)));
+
+  attempter_.Update("", "", false, false, false);
+  EXPECT_TRUE(attempter_.omaha_request_params_.update_disabled);
+
+  g_idle_add(&StaticQuitMainLoop, this);
+}
+
+TEST_F(UpdateAttempterTest, ReadTargetVersionPrefixFromPolicy) {
+  loop_ = g_main_loop_new(g_main_context_default(), FALSE);
+  g_idle_add(&StaticReadTargetVersionPrefixFromPolicyTestStart, this);
+  g_main_loop_run(loop_);
+  g_main_loop_unref(loop_);
+  loop_ = NULL;
+}
+
+void UpdateAttempterTest::ReadTargetVersionPrefixFromPolicyTestStart() {
+  // Tests that the target_version_prefix value is properly fetched
+  // from the device policy.
+
+  const std::string target_version_prefix = "1412.";
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+
+  EXPECT_CALL(*device_policy, GetTargetVersionPrefix(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(target_version_prefix),
+          Return(true)));
+
+  attempter_.Update("", "", false, false, false);
+  EXPECT_EQ(target_version_prefix.c_str(),
+            attempter_.omaha_request_params_.target_version_prefix);
+
+  g_idle_add(&StaticQuitMainLoop, this);
+}
+
+
 }  // namespace chromeos_update_engine