Enable support of endpoints override.

Endpoints can be overridden only during device registration.
New endpoints will be stored in device config only on successful registration.
Device will keep endpoints as pending during registration process.

BUG:23907593
BUG:26525138

Change-Id: I2a2ddcbad19746d631a78b33f7305da1c0bb07fb
Reviewed-on: https://weave-review.googlesource.com/2203
Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/examples/daemon/common/daemon.h b/examples/daemon/common/daemon.h
index 985c5e5..0b3c25a 100644
--- a/examples/daemon/common/daemon.h
+++ b/examples/daemon/common/daemon.h
@@ -19,10 +19,11 @@
 class Daemon {
  public:
   struct Options {
-    bool force_bootstrapping_{false};
-    bool disable_privet_{false};
-    std::string registration_ticket_;
-    std::string model_id_{"AAAAA"};
+    bool force_bootstrapping{false};
+    bool disable_privet{false};
+    std::string registration_ticket;
+    std::string model_id{"AAAAA"};
+    std::string service_url;
 
     static void ShowUsage(const std::string& name) {
       LOG(ERROR) << "\nUsage: " << name << " <option(s)>"
@@ -30,8 +31,10 @@
                  << "\t-h,--help                    Show this help message\n"
                  << "\t--v=LEVEL                    Logging level\n"
                  << "\t-b,--bootstrapping           Force WiFi bootstrapping\n"
-                 << "\t--registration_ticket=TICKET Register device with the "
-                    "given ticket\n"
+                 << "\t-r,--registration_ticket=TICKET Register device with "
+                    "the given ticket\n"
+                 << "\t-s,--staging                 Use staging server. Use "
+                    "only with -r.\n"
                  << "\t--disable_privet             Disable local privet\n";
     }
 
@@ -41,15 +44,19 @@
         if (arg == "-h" || arg == "--help") {
           return false;
         } else if (arg == "-b" || arg == "--bootstrapping") {
-          force_bootstrapping_ = true;
+          force_bootstrapping = true;
+        } else if (arg == "-s" || arg == "--staging") {
+          service_url =
+              "https://www-googleapis-staging.sandbox.google.com/weave/v1/";
         } else if (arg == "--disable_privet") {
-          disable_privet_ = true;
-        } else if (arg.find("--registration_ticket") != std::string::npos) {
+          disable_privet = true;
+        } else if (arg.find("--registration_ticket=") != std::string::npos ||
+                   arg.find("-r=") != std::string::npos) {
           auto pos = arg.find("=");
           if (pos == std::string::npos) {
             return false;
           }
-          registration_ticket_ = arg.substr(pos + 1);
+          registration_ticket = arg.substr(pos + 1);
         } else if (arg.find("--v") != std::string::npos) {
           auto pos = arg.find("=");
           if (pos == std::string::npos) {
@@ -66,14 +73,13 @@
 
   Daemon(const Options& opts)
       : task_runner_{new weave::examples::EventTaskRunner},
-        config_store_{
-            new weave::examples::FileConfigStore(opts.model_id_,
-                                                 task_runner_.get())},
+        config_store_{new weave::examples::FileConfigStore(opts.model_id,
+                                                           task_runner_.get())},
         http_client_{new weave::examples::CurlHttpClient(task_runner_.get())},
         network_{new weave::examples::EventNetworkImpl(task_runner_.get())},
         bluetooth_{new weave::examples::BluetoothImpl} {
-    if (!opts.disable_privet_) {
-      network_->SetSimulateOffline(opts.force_bootstrapping_);
+    if (!opts.disable_privet) {
+      network_->SetSimulateOffline(opts.force_bootstrapping);
 
       dns_sd_.reset(new weave::examples::AvahiClient);
       http_server_.reset(
@@ -87,9 +93,11 @@
                                     dns_sd_.get(), http_server_.get(),
                                     wifi_.get(), bluetooth_.get());
 
-    if (!opts.registration_ticket_.empty()) {
-      device_->Register(opts.registration_ticket_,
-                        base::Bind(&OnRegisterDeviceDone, device_.get()));
+    if (!opts.registration_ticket.empty()) {
+      weave::RegistrationData data;
+      data.ticket_id = opts.registration_ticket;
+      data.service_url = opts.service_url;
+      device_->Register(data, base::Bind(&OnRegisterDeviceDone, device_.get()));
     }
   }
 
diff --git a/examples/daemon/light/light.cc b/examples/daemon/light/light.cc
index a78231f..4cbf9b4 100644
--- a/examples/daemon/light/light.cc
+++ b/examples/daemon/light/light.cc
@@ -304,7 +304,7 @@
 
 int main(int argc, char** argv) {
   Daemon::Options opts;
-  opts.model_id_ = "AIAAA";
+  opts.model_id = "AIAAA";
   if (!opts.Parse(argc, argv)) {
     Daemon::Options::ShowUsage(argv[0]);
     return 1;
diff --git a/examples/daemon/lock/lock.cc b/examples/daemon/lock/lock.cc
index fcc6ad7..a2da9cf 100644
--- a/examples/daemon/lock/lock.cc
+++ b/examples/daemon/lock/lock.cc
@@ -134,7 +134,7 @@
 
 int main(int argc, char** argv) {
   Daemon::Options opts;
-  opts.model_id_ = "AOAAA";
+  opts.model_id = "AOAAA";
   if (!opts.Parse(argc, argv)) {
     Daemon::Options::ShowUsage(argv[0]);
     return 1;
diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc
index b215023..af3f047 100644
--- a/examples/provider/file_config_store.cc
+++ b/examples/provider/file_config_store.cc
@@ -48,6 +48,7 @@
   settings->model_id = model_id_;
   settings->pairing_modes = {PairingType::kEmbeddedCode};
   settings->embedded_code = "0000";
+  settings->allow_endpoints_override = true;
 
   // Keys owners:
   //   avakulenko@google.com
diff --git a/include/weave/device.h b/include/weave/device.h
index 05e3b78..9a29574 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -31,6 +31,31 @@
   kInvalidCredentials,  // Our registration has been revoked.
 };
 
+struct RegistrationData {
+  explicit RegistrationData(const std::string& ticket = {})
+      : ticket_id{ticket} {}
+
+  std::string ticket_id;
+
+  std::string oauth_url;
+  std::string client_id;
+  std::string client_secret;
+  std::string api_key;
+  std::string service_url;
+  std::string xmpp_endpoint;
+};
+
+inline bool operator==(const RegistrationData& l, const RegistrationData& r) {
+  return l.ticket_id == r.ticket_id && l.oauth_url == r.oauth_url &&
+         l.client_id == r.client_id && l.client_secret == r.client_secret &&
+         l.api_key == r.api_key && l.service_url == r.service_url &&
+         l.xmpp_endpoint == r.xmpp_endpoint;
+}
+
+inline bool operator!=(const RegistrationData& l, const RegistrationData& r) {
+  return !(l == r);
+}
+
 class Device {
  public:
   virtual ~Device() {}
@@ -141,7 +166,7 @@
 
   // Registers the device.
   // This is testing method and should not be used by applications.
-  virtual void Register(const std::string& ticket_id,
+  virtual void Register(const RegistrationData& registration_data,
                         const DoneCallback& callback) = 0;
 
   // Handler should display pin code to the user.
diff --git a/include/weave/settings.h b/include/weave/settings.h
index 7cb798d..2ba619c 100644
--- a/include/weave/settings.h
+++ b/include/weave/settings.h
@@ -71,6 +71,7 @@
 
   // Internal options to tweak some library functionality. External code should
   // avoid using them.
+  bool allow_endpoints_override{false};
   bool wifi_auto_setup_enabled{true};
   std::string test_privet_ssid;
 };
diff --git a/include/weave/test/mock_device.h b/include/weave/test/mock_device.h
index 2f380e0..8e75d44 100644
--- a/include/weave/test/mock_device.h
+++ b/include/weave/test/mock_device.h
@@ -63,7 +63,7 @@
   MOCK_METHOD1(AddGcdStateChangedCallback,
                void(const GcdStateChangedCallback& callback));
   MOCK_METHOD2(Register,
-               void(const std::string& ticket_id,
+               void(const RegistrationData& registration_data,
                     const DoneCallback& callback));
   MOCK_METHOD2(AddPairingChangedCallbacks,
                void(const PairingBeginCallback& begin_callback,
diff --git a/src/config.cc b/src/config.cc
index 21a1c1f..5ae3fed 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -67,7 +67,7 @@
     dict->Set(config_keys::kCloudId, std::move(tmp));
 }
 
-Config::Settings CreateDefaultSettings() {
+Config::Settings CreateDefaultSettings(provider::ConfigStore* config_store) {
   Config::Settings result;
   result.oauth_url = "https://accounts.google.com/o/oauth2/";
   result.service_url = kWeaveUrl;
@@ -75,6 +75,36 @@
   result.local_anonymous_access_role = AuthScope::kViewer;
   result.pairing_modes.insert(PairingType::kPinCode);
   result.device_id = base::GenerateGUID();
+
+  if (!config_store)
+    return result;
+
+  // Crash on any mistakes in defaults.
+  CHECK(config_store->LoadDefaults(&result));
+
+  CHECK(!result.client_id.empty());
+  CHECK(!result.client_secret.empty());
+  CHECK(!result.api_key.empty());
+  CHECK(!result.oauth_url.empty());
+  CHECK(!result.service_url.empty());
+  CHECK(!result.xmpp_endpoint.empty());
+  CHECK(!result.oem_name.empty());
+  CHECK(!result.model_name.empty());
+  CHECK(!result.model_id.empty());
+  CHECK(!result.name.empty());
+  CHECK(!result.device_id.empty());
+  CHECK_EQ(result.embedded_code.empty(),
+           (result.pairing_modes.find(PairingType::kEmbeddedCode) ==
+            result.pairing_modes.end()));
+
+  // Values below will be generated at runtime.
+  CHECK(result.cloud_id.empty());
+  CHECK(result.refresh_token.empty());
+  CHECK(result.robot_account.empty());
+  CHECK(result.last_configured_ssid.empty());
+  CHECK(result.secret.empty());
+  CHECK(result.root_client_token_owner == RootClientTokenOwner::kNone);
+
   return result;
 }
 
@@ -91,8 +121,12 @@
     : EnumToStringMap(kRootClientTokenOwnerMap) {}
 
 Config::Config(provider::ConfigStore* config_store)
-    : settings_{CreateDefaultSettings()}, config_store_{config_store} {
-  Load();
+    : defaults_{CreateDefaultSettings(config_store)},
+      settings_{defaults_},
+      config_store_{config_store} {
+  Transaction change{this};
+  change.save_ = false;
+  change.LoadState();
 }
 
 void Config::AddOnChangedCallback(const OnChangedCallback& callback) {
@@ -105,42 +139,8 @@
   return settings_;
 }
 
-void Config::Load() {
-  Transaction change{this};
-  change.save_ = false;
-
-  settings_ = CreateDefaultSettings();
-
-  if (!config_store_)
-    return;
-
-  // Crash on any mistakes in defaults.
-  CHECK(config_store_->LoadDefaults(&settings_));
-
-  CHECK(!settings_.client_id.empty());
-  CHECK(!settings_.client_secret.empty());
-  CHECK(!settings_.api_key.empty());
-  CHECK(!settings_.oauth_url.empty());
-  CHECK(!settings_.service_url.empty());
-  CHECK(!settings_.xmpp_endpoint.empty());
-  CHECK(!settings_.oem_name.empty());
-  CHECK(!settings_.model_name.empty());
-  CHECK(!settings_.model_id.empty());
-  CHECK(!settings_.name.empty());
-  CHECK(!settings_.device_id.empty());
-  CHECK_EQ(settings_.embedded_code.empty(),
-           (settings_.pairing_modes.find(PairingType::kEmbeddedCode) ==
-              settings_.pairing_modes.end()));
-
-  // Values below will be generated at runtime.
-  CHECK(settings_.cloud_id.empty());
-  CHECK(settings_.refresh_token.empty());
-  CHECK(settings_.robot_account.empty());
-  CHECK(settings_.last_configured_ssid.empty());
-  CHECK(settings_.secret.empty());
-  CHECK(settings_.root_client_token_owner == RootClientTokenOwner::kNone);
-
-  change.LoadState();
+const Config::Settings& Config::GetDefaults() const {
+  return defaults_;
 }
 
 void Config::Transaction::LoadState() {
diff --git a/src/config.h b/src/config.h
index 8e0a8f3..692d9c5 100644
--- a/src/config.h
+++ b/src/config.h
@@ -45,6 +45,7 @@
 
   void AddOnChangedCallback(const OnChangedCallback& callback);
   const Config::Settings& GetSettings() const;
+  const Config::Settings& GetDefaults() const;
 
   // Allows editing of config. Makes sure that callbacks were called and changes
   // were saved.
@@ -121,9 +122,9 @@
   };
 
  private:
-  void Load();
   void Save();
 
+  const Settings defaults_;
   Settings settings_;
   provider::ConfigStore* config_store_{nullptr};
   std::vector<OnChangedCallback> on_changed_;
diff --git a/src/device_manager.cc b/src/device_manager.cc
index deb5404..4c0d3ee 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -262,9 +262,9 @@
   return component_manager_->GetLegacyState();
 }
 
-void DeviceManager::Register(const std::string& ticket_id,
+void DeviceManager::Register(const RegistrationData& registration_data,
                              const DoneCallback& callback) {
-  device_info_->RegisterDevice(ticket_id, callback);
+  device_info_->RegisterDevice(registration_data, callback);
 }
 
 void DeviceManager::AddPairingChangedCallbacks(
diff --git a/src/device_manager.h b/src/device_manager.h
index d77bacc..f0ad464 100644
--- a/src/device_manager.h
+++ b/src/device_manager.h
@@ -69,7 +69,7 @@
                   ErrorPtr* error) override;
   Command* FindCommand(const std::string& id) override;
   void AddStateChangedCallback(const base::Closure& callback) override;
-  void Register(const std::string& ticket_id,
+  void Register(const RegistrationData& registration_data,
                 const DoneCallback& callback) override;
   GcdState GetGcdState() const override;
   void AddGcdStateChangedCallback(
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index 0dc1f54..b692f06 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -28,6 +28,7 @@
 #include "src/json_error_codes.h"
 #include "src/notification/xmpp_channel.h"
 #include "src/privet/auth_manager.h"
+#include "src/privet/constants.h"
 #include "src/string_utils.h"
 #include "src/utils.h"
 
@@ -281,8 +282,8 @@
     const std::string& subpath,
     const WebParamList& params) const {
   CHECK(!GetSettings().cloud_id.empty()) << "Must have a valid device ID";
-  return BuildURL(GetSettings().service_url,
-                  "devices/" + GetSettings().cloud_id + "/" + subpath, params);
+  return GetServiceURL("devices/" + GetSettings().cloud_id + "/" + subpath,
+                       params);
 }
 
 std::string DeviceRegistrationInfo::GetOAuthURL(
@@ -515,8 +516,47 @@
                                 base::Bind(callback, base::Passed(&error)), {});
 }
 
-void DeviceRegistrationInfo::RegisterDevice(const std::string& ticket_id,
+void DeviceRegistrationInfo::RegisterDevice(RegistrationData registration_data,
                                             const DoneCallback& callback) {
+  if (!GetSettings().allow_endpoints_override &&
+      registration_data != RegistrationData{registration_data.ticket_id}) {
+    ErrorPtr error;
+    Error::AddTo(&error, FROM_HERE, privet::errors::kInvalidParams,
+                 "Endpoint change is not permitted");
+    return RegisterDeviceError(callback, std::move(error));
+  }
+
+  // Reset OAuth to defaults, if device was unregistered values can be
+  // customized. These muse be replaced all together.
+  if (registration_data.oauth_url.empty() ||
+      registration_data.client_id.empty() ||
+      registration_data.client_secret.empty() ||
+      registration_data.api_key.empty()) {
+    registration_data.oauth_url = GetDefaults().oauth_url;
+    registration_data.client_id = GetDefaults().client_id;
+    registration_data.client_secret = GetDefaults().client_secret;
+    registration_data.api_key = GetDefaults().api_key;
+  }
+
+  // Reset Server URL to default, if device was unregistered value can be
+  // customized.
+  if (registration_data.service_url.empty())
+    registration_data.service_url = GetDefaults().service_url;
+
+  // Reset XMPP to default, if device was unregistered value can be
+  // customized.
+  if (registration_data.xmpp_endpoint.empty())
+    registration_data.xmpp_endpoint = GetDefaults().xmpp_endpoint;
+
+  VLOG(1) << "RegisterDevice: "
+          << "ticket_id: " << registration_data.ticket_id
+          << ", oauth_url: " << registration_data.oauth_url
+          << ", client_id: " << registration_data.client_id
+          << ", client_secret: " << registration_data.client_secret
+          << ", api_key: " << registration_data.api_key
+          << ", service_url: " << registration_data.service_url
+          << ", xmpp_endpoint: " << registration_data.xmpp_endpoint;
+
   if (HaveRegistrationCredentials()) {
     ErrorPtr error;
     Error::AddTo(&error, FROM_HERE, kErrorAlreayRegistered,
@@ -528,21 +568,23 @@
   CHECK(device_draft);
 
   base::DictionaryValue req_json;
-  req_json.SetString("id", ticket_id);
-  req_json.SetString("oauthClientId", GetSettings().client_id);
+  req_json.SetString("id", registration_data.ticket_id);
+  req_json.SetString("oauthClientId", registration_data.client_id);
   req_json.Set("deviceDraft", device_draft.release());
 
-  auto url = GetServiceURL("registrationTickets/" + ticket_id,
-                           {{"key", GetSettings().api_key}});
+  auto url = BuildURL(registration_data.service_url,
+                      "registrationTickets/" + registration_data.ticket_id,
+                      {{"key", registration_data.api_key}});
 
   RequestSender sender{HttpClient::Method::kPatch, url, http_client_};
   sender.SetJsonData(req_json);
   sender.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketSent,
-                         weak_factory_.GetWeakPtr(), ticket_id, callback));
+                         weak_factory_.GetWeakPtr(), registration_data,
+                         callback));
 }
 
 void DeviceRegistrationInfo::RegisterDeviceOnTicketSent(
-    const std::string& ticket_id,
+    const RegistrationData& registration_data,
     const DoneCallback& callback,
     std::unique_ptr<provider::HttpClient::Response> response,
     ErrorPtr error) {
@@ -557,15 +599,17 @@
     return RegisterDeviceError(callback, std::move(error));
   }
 
-  std::string url =
-      GetServiceURL("registrationTickets/" + ticket_id + "/finalize",
-                    {{"key", GetSettings().api_key}});
+  std::string url = BuildURL(
+      registration_data.service_url,
+      "registrationTickets/" + registration_data.ticket_id + "/finalize",
+      {{"key", registration_data.api_key}});
   RequestSender{HttpClient::Method::kPost, url, http_client_}.Send(
       base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized,
-                 weak_factory_.GetWeakPtr(), callback));
+                 weak_factory_.GetWeakPtr(), registration_data, callback));
 }
 
 void DeviceRegistrationInfo::RegisterDeviceOnTicketFinalized(
+    const RegistrationData& registration_data,
     const DoneCallback& callback,
     std::unique_ptr<provider::HttpClient::Response> response,
     ErrorPtr error) {
@@ -595,19 +639,21 @@
   UpdateDeviceInfoTimestamp(*device_draft_response);
 
   // Now get access_token and refresh_token
-  RequestSender sender2{HttpClient::Method::kPost, GetOAuthURL("token"),
+  RequestSender sender2{HttpClient::Method::kPost,
+                        BuildURL(registration_data.oauth_url, "token", {}),
                         http_client_};
   sender2.SetFormData({{"code", auth_code},
-                       {"client_id", GetSettings().client_id},
-                       {"client_secret", GetSettings().client_secret},
+                       {"client_id", registration_data.client_id},
+                       {"client_secret", registration_data.client_secret},
                        {"redirect_uri", "oob"},
                        {"grant_type", "authorization_code"}});
   sender2.Send(base::Bind(&DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent,
-                          weak_factory_.GetWeakPtr(), cloud_id, robot_account,
-                          callback));
+                          weak_factory_.GetWeakPtr(), registration_data,
+                          cloud_id, robot_account, callback));
 }
 
 void DeviceRegistrationInfo::RegisterDeviceOnAuthCodeSent(
+    const RegistrationData& registration_data,
     const std::string& cloud_id,
     const std::string& robot_account,
     const DoneCallback& callback,
@@ -631,9 +677,18 @@
       base::Time::Now() + base::TimeDelta::FromSeconds(expires_in);
 
   Config::Transaction change{config_};
+
   change.set_cloud_id(cloud_id);
   change.set_robot_account(robot_account);
   change.set_refresh_token(refresh_token);
+
+  change.set_oauth_url(registration_data.oauth_url);
+  change.set_client_id(registration_data.client_id);
+  change.set_client_secret(registration_data.client_secret);
+  change.set_api_key(registration_data.api_key);
+  change.set_service_url(registration_data.service_url);
+  change.set_xmpp_endpoint(registration_data.xmpp_endpoint);
+
   change.Commit();
 
   task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
@@ -828,34 +883,6 @@
   change.set_local_pairing_enabled(local_pairing_enabled);
 }
 
-bool DeviceRegistrationInfo::UpdateServiceConfig(
-    const std::string& client_id,
-    const std::string& client_secret,
-    const std::string& api_key,
-    const std::string& oauth_url,
-    const std::string& service_url,
-    const std::string& xmpp_endpoint,
-    ErrorPtr* error) {
-  if (HaveRegistrationCredentials()) {
-    return Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered,
-                        "Unable to change config for registered device");
-  }
-  Config::Transaction change{config_};
-  if (!client_id.empty())
-    change.set_client_id(client_id);
-  if (!client_secret.empty())
-    change.set_client_secret(client_secret);
-  if (!api_key.empty())
-    change.set_api_key(api_key);
-  if (!oauth_url.empty())
-    change.set_oauth_url(oauth_url);
-  if (!service_url.empty())
-    change.set_service_url(service_url);
-  if (!xmpp_endpoint.empty())
-    change.set_xmpp_endpoint(xmpp_endpoint);
-  return true;
-}
-
 void DeviceRegistrationInfo::UpdateCommand(
     const std::string& command_id,
     const base::DictionaryValue& command_patch,
diff --git a/src/device_registration_info.h b/src/device_registration_info.h
index a296258..ef79268 100644
--- a/src/device_registration_info.h
+++ b/src/device_registration_info.h
@@ -64,7 +64,8 @@
 
   void AddGcdStateChangedCallback(
       const Device::GcdStateChangedCallback& callback);
-  void RegisterDevice(const std::string& ticket_id,
+
+  void RegisterDevice(RegistrationData registration_data,
                       const DoneCallback& callback);
 
   void UpdateDeviceInfo(const std::string& name,
@@ -73,13 +74,6 @@
   void UpdateBaseConfig(AuthScope anonymous_access_role,
                         bool local_discovery_enabled,
                         bool local_pairing_enabled);
-  bool UpdateServiceConfig(const std::string& client_id,
-                           const std::string& client_secret,
-                           const std::string& api_key,
-                           const std::string& oauth_url,
-                           const std::string& service_url,
-                           const std::string& xmpp_endpoint,
-                           ErrorPtr* error);
 
   void GetDeviceInfo(const CloudRequestDoneCallback& callback);
 
@@ -124,6 +118,8 @@
  private:
   friend class DeviceRegistrationInfoTest;
 
+  const Config::Settings& GetDefaults() const { return config_->GetDefaults(); }
+
   base::WeakPtr<DeviceRegistrationInfo> AsWeakPtr() {
     return weak_factory_.GetWeakPtr();
   }
@@ -276,15 +272,17 @@
 
   void RegisterDeviceError(const DoneCallback& callback, ErrorPtr error);
   void RegisterDeviceOnTicketSent(
-      const std::string& ticket_id,
+      const RegistrationData& registration_data,
       const DoneCallback& callback,
       std::unique_ptr<provider::HttpClient::Response> response,
       ErrorPtr error);
   void RegisterDeviceOnTicketFinalized(
+      const RegistrationData& registration_data,
       const DoneCallback& callback,
       std::unique_ptr<provider::HttpClient::Response> response,
       ErrorPtr error);
   void RegisterDeviceOnAuthCodeSent(
+      const RegistrationData& registration_data,
       const std::string& cloud_id,
       const std::string& robot_account,
       const DoneCallback& callback,
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index bbc167e..d3d0c26 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -127,12 +127,12 @@
   void SetUp() override {
     EXPECT_CALL(clock_, Now())
         .WillRepeatedly(Return(base::Time::FromTimeT(1450000000)));
-    ReloadDefaults();
+    ReloadDefaults(true);
   }
 
-  void ReloadDefaults() {
+  void ReloadDefaults(bool allow_endpoints_override) {
     EXPECT_CALL(config_store_, LoadDefaults(_))
-        .WillOnce(Invoke([](Settings* settings) {
+        .WillOnce(Invoke([allow_endpoints_override](Settings* settings) {
           settings->client_id = test_data::kClientId;
           settings->client_secret = test_data::kClientSecret;
           settings->api_key = test_data::kApiKey;
@@ -146,6 +146,7 @@
           settings->oauth_url = test_data::kOAuthURL;
           settings->service_url = test_data::kServiceURL;
           settings->xmpp_endpoint = test_data::kXmppEndpoint;
+          settings->allow_endpoints_override = allow_endpoints_override;
           return true;
         }));
     config_.reset(new Config{&config_store_});
@@ -155,7 +156,7 @@
     dev_reg_->Start();
   }
 
-  void ReloadSettings(bool registered = true) {
+  void ReloadSettings(bool registered, bool allow_endpoints_override) {
     base::DictionaryValue dict;
     dict.SetInteger("version", 1);
     if (registered) {
@@ -168,7 +169,7 @@
     base::JSONWriter::WriteWithOptions(
         dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
     EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(json_string));
-    ReloadDefaults();
+    ReloadDefaults(allow_endpoints_override);
   }
 
   void PublishCommands(const base::ListValue& commands) {
@@ -196,6 +197,9 @@
     return dev_reg_->HaveRegistrationCredentials();
   }
 
+  void RegisterDevice(const RegistrationData registration_data,
+                      const RegistrationData& expected_data);
+
   provider::test::FakeTaskRunner task_runner_;
   provider::test::MockConfigStore config_store_;
   StrictMock<MockHttpClient> http_client_;
@@ -245,7 +249,7 @@
 
 TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
   EXPECT_FALSE(HaveRegistrationCredentials());
-  ReloadSettings();
+  ReloadSettings(true, false);
 
   EXPECT_CALL(
       http_client_,
@@ -287,7 +291,7 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
-  ReloadSettings();
+  ReloadSettings(true, false);
   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
 
   EXPECT_CALL(
@@ -316,7 +320,7 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) {
-  ReloadSettings();
+  ReloadSettings(true, false);
   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
 
   EXPECT_CALL(
@@ -346,7 +350,7 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
-  ReloadSettings();
+  ReloadSettings(true, false);
   SetAccessToken();
 
   EXPECT_CALL(
@@ -377,9 +381,32 @@
   EXPECT_TRUE(succeeded);
 }
 
-TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
-  ReloadSettings(false);
+TEST_F(DeviceRegistrationInfoTest, ReRegisterDevice) {
+  ReloadSettings(true, false);
 
+  bool done = false;
+  dev_reg_->RegisterDevice(RegistrationData{test_data::kClaimTicketId},
+                           base::Bind([this, &done](ErrorPtr error) {
+                             EXPECT_TRUE(error->HasError("already_registered"));
+                             done = true;
+                             task_runner_.Break();
+                             EXPECT_EQ(GcdState::kConnecting, GetGcdState());
+
+                             // Validate the device info saved to storage...
+                             EXPECT_EQ(test_data::kCloudId,
+                                       dev_reg_->GetSettings().cloud_id);
+                             EXPECT_EQ(test_data::kRefreshToken,
+                                       dev_reg_->GetSettings().refresh_token);
+                             EXPECT_EQ(test_data::kRobotAccountEmail,
+                                       dev_reg_->GetSettings().robot_account);
+                           }));
+  task_runner_.Run();
+  EXPECT_TRUE(done);
+}
+
+void DeviceRegistrationInfoTest::RegisterDevice(
+    const RegistrationData registration_data,
+    const RegistrationData& expected_data) {
   auto json_traits = CreateDictionaryValue(R"({
     'base': {
       'commands': {
@@ -408,25 +435,25 @@
   EXPECT_TRUE(component_manager_.SetStateProperty(
       "comp", "base.firmwareVersion", ver, nullptr));
 
-  std::string ticket_url = dev_reg_->GetServiceURL("registrationTickets/") +
-                           test_data::kClaimTicketId;
+  std::string ticket_url = expected_data.service_url + "registrationTickets/" +
+                           expected_data.ticket_id;
   EXPECT_CALL(http_client_,
               SendRequest(HttpClient::Method::kPatch,
-                          ticket_url + "?key=" + test_data::kApiKey,
+                          ticket_url + "?key=" + expected_data.api_key,
                           HttpClient::Headers{GetJsonHeader()}, _, _))
-      .WillOnce(WithArgs<3, 4>(
-          Invoke([](const std::string& data,
-                    const HttpClient::SendRequestCallback& callback) {
+      .WillOnce(WithArgs<3, 4>(Invoke(
+          [&expected_data](const std::string& data,
+                           const HttpClient::SendRequestCallback& callback) {
             auto json = test::CreateDictionaryValue(data);
             EXPECT_NE(nullptr, json.get());
             std::string value;
             EXPECT_TRUE(json->GetString("id", &value));
-            EXPECT_EQ(test_data::kClaimTicketId, value);
+            EXPECT_EQ(expected_data.ticket_id, value);
             EXPECT_TRUE(
                 json->GetString("deviceDraft.channel.supportedType", &value));
             EXPECT_EQ("pull", value);
             EXPECT_TRUE(json->GetString("oauthClientId", &value));
-            EXPECT_EQ(test_data::kClientId, value);
+            EXPECT_EQ(expected_data.client_id, value);
             EXPECT_TRUE(json->GetString("deviceDraft.description", &value));
             EXPECT_EQ("Easy to clean", value);
             EXPECT_TRUE(json->GetString("deviceDraft.location", &value));
@@ -489,7 +516,7 @@
 
   EXPECT_CALL(http_client_,
               SendRequest(HttpClient::Method::kPost,
-                          ticket_url + "/finalize?key=" + test_data::kApiKey,
+                          ticket_url + "/finalize?key=" + expected_data.api_key,
                           HttpClient::Headers{}, _, _))
       .WillOnce(WithArgs<4>(
           Invoke([](const HttpClient::SendRequestCallback& callback) {
@@ -509,15 +536,15 @@
 
   EXPECT_CALL(
       http_client_,
-      SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
+      SendRequest(HttpClient::Method::kPost, expected_data.oauth_url + "token",
                   HttpClient::Headers{GetFormHeader()}, _, _))
-      .WillOnce(WithArgs<3, 4>(Invoke([](
+      .WillOnce(WithArgs<3, 4>(Invoke([&expected_data](
           const std::string& data,
           const HttpClient::SendRequestCallback& callback) {
         EXPECT_EQ("authorization_code", GetFormField(data, "grant_type"));
         EXPECT_EQ(test_data::kRobotAccountAuthCode, GetFormField(data, "code"));
-        EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
-        EXPECT_EQ(test_data::kClientSecret,
+        EXPECT_EQ(expected_data.client_id, GetFormField(data, "client_id"));
+        EXPECT_EQ(expected_data.client_secret,
                   GetFormField(data, "client_secret"));
         EXPECT_EQ("oob", GetFormField(data, "redirect_uri"));
 
@@ -532,7 +559,9 @@
 
   EXPECT_CALL(
       http_client_,
-      SendRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
+      SendRequest(HttpClient::Method::kPost,
+                  expected_data.service_url + "devices/" + test_data::kCloudId +
+                      "/upsertLocalAuthInfo",
                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
       .WillOnce(WithArgs<3, 4>(
           Invoke([](const std::string& data,
@@ -546,10 +575,12 @@
 
   bool done = false;
   dev_reg_->RegisterDevice(
-      test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) {
-        EXPECT_FALSE(error);
+      registration_data,
+      base::Bind([this, &done, &expected_data](ErrorPtr error) {
         done = true;
         task_runner_.Break();
+
+        EXPECT_FALSE(error);
         EXPECT_EQ(GcdState::kConnecting, GetGcdState());
 
         // Validate the device info saved to storage...
@@ -558,29 +589,66 @@
                   dev_reg_->GetSettings().refresh_token);
         EXPECT_EQ(test_data::kRobotAccountEmail,
                   dev_reg_->GetSettings().robot_account);
+        EXPECT_EQ(expected_data.oauth_url, dev_reg_->GetSettings().oauth_url);
+        EXPECT_EQ(expected_data.client_id, dev_reg_->GetSettings().client_id);
+        EXPECT_EQ(expected_data.client_secret,
+                  dev_reg_->GetSettings().client_secret);
+        EXPECT_EQ(expected_data.api_key, dev_reg_->GetSettings().api_key);
+        EXPECT_EQ(expected_data.service_url,
+                  dev_reg_->GetSettings().service_url);
+        EXPECT_EQ(expected_data.xmpp_endpoint,
+                  dev_reg_->GetSettings().xmpp_endpoint);
       }));
   task_runner_.Run();
   EXPECT_TRUE(done);
 }
 
-TEST_F(DeviceRegistrationInfoTest, ReRegisterDevice) {
-  ReloadSettings();
+TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
+  ReloadSettings(false, true);
+
+  RegistrationData registration_data;
+  registration_data.ticket_id = "test_ticked_id";
+  registration_data.oauth_url = "https://test.oauth/";
+  registration_data.client_id = "test_client_id";
+  registration_data.client_secret = "test_client_secret";
+  registration_data.api_key = "test_api_key";
+  registration_data.service_url = "https://test.service/";
+  registration_data.xmpp_endpoint = "test.xmpp:1234";
+
+  RegisterDevice(registration_data, registration_data);
+}
+
+TEST_F(DeviceRegistrationInfoTest, RegisterDeviceWithDefaultEndpoints) {
+  ReloadSettings(false, true);
+
+  RegistrationData registration_data;
+  registration_data.ticket_id = "test_ticked_id";
+
+  RegistrationData expected_data = registration_data;
+  expected_data.oauth_url = test_data::kOAuthURL;
+  expected_data.client_id = test_data::kClientId;
+  expected_data.client_secret = test_data::kClientSecret;
+  expected_data.api_key = test_data::kApiKey;
+  expected_data.service_url = test_data::kServiceURL;
+  expected_data.xmpp_endpoint = test_data::kXmppEndpoint;
+
+  RegisterDevice(registration_data, expected_data);
+}
+
+TEST_F(DeviceRegistrationInfoTest, RegisterDeviceEndpointsOverrideNotAllowed) {
+  ReloadSettings(false, false);
+
+  RegistrationData registration_data;
+  registration_data.ticket_id = "test_ticked_id";
+  registration_data.service_url = "https://test.service/";
 
   bool done = false;
-  dev_reg_->RegisterDevice(
-      test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) {
-        EXPECT_TRUE(error->HasError("already_registered"));
-        done = true;
-        task_runner_.Break();
-        EXPECT_EQ(GcdState::kConnecting, GetGcdState());
-
-        // Validate the device info saved to storage...
-        EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
-        EXPECT_EQ(test_data::kRefreshToken,
-                  dev_reg_->GetSettings().refresh_token);
-        EXPECT_EQ(test_data::kRobotAccountEmail,
-                  dev_reg_->GetSettings().robot_account);
-      }));
+  dev_reg_->RegisterDevice(registration_data,
+                           base::Bind([this, &done](ErrorPtr error) {
+                             done = true;
+                             task_runner_.Break();
+                             EXPECT_TRUE(error->HasError("invalidParams"));
+                           }));
   task_runner_.Run();
   EXPECT_TRUE(done);
 }
@@ -590,7 +658,7 @@
   // unregistered, depending on whether or not we've found credentials.
   EXPECT_EQ(GcdState::kUnconfigured, GetGcdState());
   // Put some credentials into our state, make sure we call that offline.
-  ReloadSettings();
+  ReloadSettings(true, false);
   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
 }
 
@@ -600,7 +668,7 @@
   void SetUp() override {
     DeviceRegistrationInfoTest::SetUp();
 
-    ReloadSettings();
+    ReloadSettings(true, false);
     SetAccessToken();
 
     auto json_traits = CreateDictionaryValue(R"({
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index 49fceaa..3266ef9 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -12,6 +12,7 @@
 #include <base/memory/weak_ptr.h>
 #include <base/values.h>
 #include <weave/error.h>
+#include <weave/device.h>
 #include <weave/provider/task_runner.h>
 
 #include "src/backoff_entry.h"
@@ -109,15 +110,13 @@
 
   const SetupState& GetSetupState() const override { return setup_state_; }
 
-  bool Setup(const std::string& ticket_id,
-             const std::string& user,
+  bool Setup(const RegistrationData& registration_data,
              ErrorPtr* error) override {
-    VLOG(1) << "GCD Setup started. ticket_id: " << ticket_id
-            << ", user:" << user;
+    VLOG(1) << "GCD Setup started. ";
     // Set (or reset) the retry counter, since we are starting a new
     // registration process.
     registation_retry_count_ = kMaxDeviceRegistrationRetries;
-    ticket_id_ = ticket_id;
+    registration_data_ = registration_data;
     if (setup_state_.IsStatusEqual(SetupState::kInProgress)) {
       // Another registration is in progress. In case it fails, we will use
       // the new ticket ID when retrying the request.
@@ -273,7 +272,7 @@
       return;
     }
 
-    device_->RegisterDevice(ticket_id_,
+    device_->RegisterDevice(registration_data_,
                             base::Bind(&CloudDelegateImpl::RegisterDeviceDone,
                                        setup_weak_factory_.GetWeakPtr()));
   }
@@ -337,8 +336,8 @@
   // State of the current or last setup.
   SetupState setup_state_{SetupState::kNone};
 
-  // Ticket ID for registering the device.
-  std::string ticket_id_;
+  // Registration data for current registration process.
+  RegistrationData registration_data_;
 
   // Number of remaining retries for device registration process.
   int registation_retry_count_{0};
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h
index 9f053d8..01d2c30 100644
--- a/src/privet/cloud_delegate.h
+++ b/src/privet/cloud_delegate.h
@@ -12,6 +12,7 @@
 #include <base/callback.h>
 #include <base/memory/ref_counted.h>
 #include <base/observer_list.h>
+#include <weave/device.h>
 
 #include "src/privet/privet_types.h"
 #include "src/privet/security_delegate.h"
@@ -88,8 +89,7 @@
   virtual const SetupState& GetSetupState() const = 0;
 
   // Starts GCD setup.
-  virtual bool Setup(const std::string& ticket_id,
-                     const std::string& user,
+  virtual bool Setup(const RegistrationData& registration_data,
                      ErrorPtr* error) = 0;
 
   // Returns cloud id if the registered device or empty string if unregistered.
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h
index c2e9a89..5b5bd2f 100644
--- a/src/privet/mock_delegates.h
+++ b/src/privet/mock_delegates.h
@@ -183,7 +183,7 @@
   MOCK_CONST_METHOD0(GetAnonymousMaxScope, AuthScope());
   MOCK_CONST_METHOD0(GetConnectionState, const ConnectionState&());
   MOCK_CONST_METHOD0(GetSetupState, const SetupState&());
-  MOCK_METHOD3(Setup, bool(const std::string&, const std::string&, ErrorPtr*));
+  MOCK_METHOD2(Setup, bool(const RegistrationData&, ErrorPtr*));
   MOCK_CONST_METHOD0(GetCloudId, std::string());
   MOCK_CONST_METHOD0(GetLegacyState, const base::DictionaryValue&());
   MOCK_CONST_METHOD0(GetLegacyCommandDef, const base::DictionaryValue&());
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index e8b1c77..517c60f 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -14,6 +14,7 @@
 #include <base/location.h>
 #include <base/strings/stringprintf.h>
 #include <base/values.h>
+#include <weave/device.h>
 #include <weave/enum_to_string.h>
 #include <weave/provider/task_runner.h>
 
@@ -801,7 +802,7 @@
   if (!ssid.empty() && !wifi_->ConfigureCredentials(ssid, passphrase, &error))
     return ReturnError(*error, callback);
 
-  if (!ticket.empty() && !cloud_->Setup(ticket, user, &error))
+  if (!ticket.empty() && !cloud_->Setup(RegistrationData{ticket}, &error))
     return ReturnError(*error, callback);
 
   ReplyWithSetupStatus(callback);
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc
index 20f5aa0..9e900df 100644
--- a/src/privet/privet_handler_unittest.cc
+++ b/src/privet/privet_handler_unittest.cc
@@ -15,6 +15,7 @@
 #include <base/values.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <weave/device.h>
 #include <weave/test/unittest_utils.h>
 
 #include "src/privet/constants.h"
@@ -134,12 +135,11 @@
     EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return(""));
     EXPECT_CALL(cloud_, GetConnectionState())
         .WillRepeatedly(ReturnRef(gcd_disabled_state_));
-    auto set_error = [](const std::string&, const std::string&,
-                        ErrorPtr* error) {
+    auto set_error = [](ErrorPtr* error) {
       Error::AddTo(error, FROM_HERE, "setupUnavailable", "");
     };
-    EXPECT_CALL(cloud_, Setup(_, _, _))
-        .WillRepeatedly(DoAll(Invoke(set_error), Return(false)));
+    EXPECT_CALL(cloud_, Setup(_, _))
+        .WillRepeatedly(DoAll(WithArgs<1>(Invoke(set_error)), Return(false)));
   }
 
   test::MockClock clock_;
@@ -638,10 +638,10 @@
     }
   })";
 
-  auto set_error = [](const std::string&, const std::string&, ErrorPtr* error) {
+  auto set_error = [](ErrorPtr* error) {
     return Error::AddTo(error, FROM_HERE, "deviceBusy", "");
   };
-  EXPECT_CALL(cloud_, Setup(_, _, _)).WillOnce(Invoke(set_error));
+  EXPECT_CALL(cloud_, Setup(_, _)).WillOnce(WithArgs<1>(Invoke(set_error)));
   EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"),
                HandleRequest("/privet/v3/setup/start", kInput));
 
@@ -651,7 +651,7 @@
     }
   })";
   cloud_.setup_state_ = SetupState{SetupState::kInProgress};
-  EXPECT_CALL(cloud_, Setup("testTicket", "testUser", _))
+  EXPECT_CALL(cloud_, Setup(RegistrationData{"testTicket"}, _))
       .WillOnce(Return(true));
   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/start", kInput));
 }
diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc
index b300f57..452ac78 100644
--- a/src/weave_unittest.cc
+++ b/src/weave_unittest.cc
@@ -421,7 +421,8 @@
   InitDnsSdPublishing(true, "DB");
 
   bool done = false;
-  device_->Register("TICKET_ID", base::Bind([this, &done](ErrorPtr error) {
+  device_->Register(RegistrationData{"TICKET_ID"},
+                    base::Bind([this, &done](ErrorPtr error) {
                       EXPECT_FALSE(error);
                       done = true;
                       task_runner_.Break();
@@ -431,7 +432,8 @@
   EXPECT_TRUE(done);
 
   done = false;
-  device_->Register("TICKET_ID2", base::Bind([this, &done](ErrorPtr error) {
+  device_->Register(RegistrationData{"TICKET_ID2"},
+                    base::Bind([this, &done](ErrorPtr error) {
                       EXPECT_TRUE(error->HasError("already_registered"));
                       done = true;
                       task_runner_.Break();