1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/chromeos/extensions/echo_private_api.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/files/file_util.h"
13 #include "base/location.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/task/post_task.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chromeos/settings/cros_settings.h"
21 #include "chrome/browser/chromeos/ui/echo_dialog_view.h"
22 #include "chrome/browser/extensions/chrome_extension_function_details.h"
23 #include "chrome/browser/extensions/extension_tab_util.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_navigator_params.h"
26 #include "chrome/common/extensions/api/echo_private.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/common/url_constants.h"
29 #include "chromeos/system/statistics_provider.h"
30 #include "components/prefs/pref_registry_simple.h"
31 #include "components/prefs/pref_service.h"
32 #include "components/prefs/scoped_user_pref_update.h"
33 #include "content/public/browser/web_contents.h"
34 #include "extensions/browser/extension_file_task_runner.h"
35 #include "extensions/browser/view_type_utils.h"
36 #include "extensions/common/extension.h"
37 
38 namespace echo_api = extensions::api::echo_private;
39 
40 namespace chromeos {
41 
42 namespace echo_offer {
43 
RegisterPrefs(PrefRegistrySimple * registry)44 void RegisterPrefs(PrefRegistrySimple* registry) {
45   registry->RegisterDictionaryPref(prefs::kEchoCheckedOffers);
46 }
47 
48 }  // namespace echo_offer
49 
50 }  // namespace chromeos
51 
52 EchoPrivateGetRegistrationCodeFunction::
EchoPrivateGetRegistrationCodeFunction()53     EchoPrivateGetRegistrationCodeFunction() {}
54 
55 EchoPrivateGetRegistrationCodeFunction::
~EchoPrivateGetRegistrationCodeFunction()56     ~EchoPrivateGetRegistrationCodeFunction() {}
57 
58 ExtensionFunction::ResponseValue
GetRegistrationCode(const std::string & type)59 EchoPrivateGetRegistrationCodeFunction::GetRegistrationCode(
60     const std::string& type) {
61   // Possible ECHO code type and corresponding key name in StatisticsProvider.
62   const std::string kCouponType = "COUPON_CODE";
63   const std::string kGroupType = "GROUP_CODE";
64 
65   chromeos::system::StatisticsProvider* provider =
66       chromeos::system::StatisticsProvider::GetInstance();
67   std::string result;
68   if (type == kCouponType) {
69     provider->GetMachineStatistic(chromeos::system::kOffersCouponCodeKey,
70                                   &result);
71   } else if (type == kGroupType) {
72     provider->GetMachineStatistic(chromeos::system::kOffersGroupCodeKey,
73                                   &result);
74   }
75 
76   return ArgumentList(echo_api::GetRegistrationCode::Results::Create(result));
77 }
78 
79 ExtensionFunction::ResponseAction
Run()80 EchoPrivateGetRegistrationCodeFunction::Run() {
81   std::unique_ptr<echo_api::GetRegistrationCode::Params> params =
82       echo_api::GetRegistrationCode::Params::Create(*args_);
83   EXTENSION_FUNCTION_VALIDATE(params);
84   return RespondNow(GetRegistrationCode(params->type));
85 }
86 
EchoPrivateSetOfferInfoFunction()87 EchoPrivateSetOfferInfoFunction::EchoPrivateSetOfferInfoFunction() {}
88 
~EchoPrivateSetOfferInfoFunction()89 EchoPrivateSetOfferInfoFunction::~EchoPrivateSetOfferInfoFunction() {}
90 
Run()91 ExtensionFunction::ResponseAction EchoPrivateSetOfferInfoFunction::Run() {
92   std::unique_ptr<echo_api::SetOfferInfo::Params> params =
93       echo_api::SetOfferInfo::Params::Create(*args_);
94   EXTENSION_FUNCTION_VALIDATE(params);
95 
96   const std::string& service_id = params->id;
97   std::unique_ptr<base::DictionaryValue> dict =
98       params->offer_info.additional_properties.DeepCopyWithoutEmptyChildren();
99 
100   PrefService* local_state = g_browser_process->local_state();
101   DictionaryPrefUpdate offer_update(local_state, prefs::kEchoCheckedOffers);
102   offer_update->SetWithoutPathExpansion("echo." + service_id, std::move(dict));
103   return RespondNow(NoArguments());
104 }
105 
EchoPrivateGetOfferInfoFunction()106 EchoPrivateGetOfferInfoFunction::EchoPrivateGetOfferInfoFunction() {}
107 
~EchoPrivateGetOfferInfoFunction()108 EchoPrivateGetOfferInfoFunction::~EchoPrivateGetOfferInfoFunction() {}
109 
Run()110 ExtensionFunction::ResponseAction EchoPrivateGetOfferInfoFunction::Run() {
111   std::unique_ptr<echo_api::GetOfferInfo::Params> params =
112       echo_api::GetOfferInfo::Params::Create(*args_);
113   EXTENSION_FUNCTION_VALIDATE(params);
114 
115   const std::string& service_id = params->id;
116   PrefService* local_state = g_browser_process->local_state();
117   const base::DictionaryValue* offer_infos = local_state->
118       GetDictionary(prefs::kEchoCheckedOffers);
119 
120   const base::DictionaryValue* offer_info = NULL;
121   if (!offer_infos->GetDictionaryWithoutPathExpansion(
122          "echo." + service_id, &offer_info)) {
123     return RespondNow(Error("Not found"));
124   }
125 
126   echo_api::GetOfferInfo::Results::Result result;
127   result.additional_properties.MergeDictionary(offer_info);
128   return RespondNow(
129       ArgumentList(echo_api::GetOfferInfo::Results::Create(result)));
130 }
131 
EchoPrivateGetOobeTimestampFunction()132 EchoPrivateGetOobeTimestampFunction::EchoPrivateGetOobeTimestampFunction() {
133 }
134 
~EchoPrivateGetOobeTimestampFunction()135 EchoPrivateGetOobeTimestampFunction::~EchoPrivateGetOobeTimestampFunction() {
136 }
137 
Run()138 ExtensionFunction::ResponseAction EchoPrivateGetOobeTimestampFunction::Run() {
139   base::PostTaskAndReplyWithResult(
140       extensions::GetExtensionFileTaskRunner().get(), FROM_HERE,
141       base::BindOnce(
142           &EchoPrivateGetOobeTimestampFunction::GetOobeTimestampOnFileSequence,
143           this),
144       base::BindOnce(&EchoPrivateGetOobeTimestampFunction::RespondWithResult,
145                      this));
146   return RespondLater();
147 }
148 
149 // Get the OOBE timestamp from file /home/chronos/.oobe_completed.
150 // The timestamp is used to determine when the user first activates the device.
151 // If we can get the timestamp info, return it as yyyy-mm-dd, otherwise, return
152 // an empty string.
153 std::unique_ptr<base::Value>
GetOobeTimestampOnFileSequence()154 EchoPrivateGetOobeTimestampFunction::GetOobeTimestampOnFileSequence() {
155   DCHECK(
156       extensions::GetExtensionFileTaskRunner()->RunsTasksInCurrentSequence());
157 
158   const char kOobeTimestampFile[] = "/home/chronos/.oobe_completed";
159   std::string timestamp;
160   base::File::Info fileInfo;
161   if (base::GetFileInfo(base::FilePath(kOobeTimestampFile), &fileInfo)) {
162     base::Time::Exploded ctime;
163     fileInfo.creation_time.UTCExplode(&ctime);
164     timestamp += base::StringPrintf("%u-%u-%u",
165                                     ctime.year,
166                                     ctime.month,
167                                     ctime.day_of_month);
168   }
169   return std::make_unique<base::Value>(timestamp);
170 }
171 
RespondWithResult(std::unique_ptr<base::Value> result)172 void EchoPrivateGetOobeTimestampFunction::RespondWithResult(
173     std::unique_ptr<base::Value> result) {
174   Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
175 }
176 
EchoPrivateGetUserConsentFunction()177 EchoPrivateGetUserConsentFunction::EchoPrivateGetUserConsentFunction()
178     : redeem_offers_allowed_(false) {
179 }
180 
181 // static
182 scoped_refptr<EchoPrivateGetUserConsentFunction>
CreateForTest(const DialogShownTestCallback & dialog_shown_callback)183 EchoPrivateGetUserConsentFunction::CreateForTest(
184       const DialogShownTestCallback& dialog_shown_callback) {
185   scoped_refptr<EchoPrivateGetUserConsentFunction> function(
186       new EchoPrivateGetUserConsentFunction());
187   function->dialog_shown_callback_ = dialog_shown_callback;
188   return function;
189 }
190 
~EchoPrivateGetUserConsentFunction()191 EchoPrivateGetUserConsentFunction::~EchoPrivateGetUserConsentFunction() {}
192 
Run()193 ExtensionFunction::ResponseAction EchoPrivateGetUserConsentFunction::Run() {
194   CheckRedeemOffersAllowed();
195   return RespondLater();
196 }
197 
OnAccept()198 void EchoPrivateGetUserConsentFunction::OnAccept() {
199   Finalize(true);
200 }
201 
OnCancel()202 void EchoPrivateGetUserConsentFunction::OnCancel() {
203   Finalize(false);
204 }
205 
OnMoreInfoLinkClicked()206 void EchoPrivateGetUserConsentFunction::OnMoreInfoLinkClicked() {
207   ChromeExtensionFunctionDetails details(this);
208   NavigateParams params(details.GetProfile(), GURL(chrome::kEchoLearnMoreURL),
209                         ui::PAGE_TRANSITION_LINK);
210   // Open the link in a new window. The echo dialog is modal, so the current
211   // window is useless until the dialog is closed.
212   params.disposition = WindowOpenDisposition::NEW_WINDOW;
213   Navigate(&params);
214 }
215 
CheckRedeemOffersAllowed()216 void EchoPrivateGetUserConsentFunction::CheckRedeemOffersAllowed() {
217   chromeos::CrosSettingsProvider::TrustedStatus status =
218       chromeos::CrosSettings::Get()->PrepareTrustedValues(base::BindOnce(
219           &EchoPrivateGetUserConsentFunction::CheckRedeemOffersAllowed, this));
220   if (status == chromeos::CrosSettingsProvider::TEMPORARILY_UNTRUSTED)
221     return;
222 
223   bool allow = true;
224   chromeos::CrosSettings::Get()->GetBoolean(
225       chromeos::kAllowRedeemChromeOsRegistrationOffers, &allow);
226 
227   OnRedeemOffersAllowedChecked(allow);
228 }
229 
OnRedeemOffersAllowedChecked(bool is_allowed)230 void EchoPrivateGetUserConsentFunction::OnRedeemOffersAllowedChecked(
231     bool is_allowed) {
232   redeem_offers_allowed_ = is_allowed;
233 
234   std::unique_ptr<echo_api::GetUserConsent::Params> params =
235       echo_api::GetUserConsent::Params::Create(*args_);
236 
237   // Verify that the passed origin URL is valid.
238   GURL service_origin = GURL(params->consent_requester.origin);
239   if (!service_origin.is_valid()) {
240     Respond(Error("Invalid origin."));
241     return;
242   }
243 
244   content::WebContents* web_contents = nullptr;
245   if (!params->consent_requester.tab_id) {
246     web_contents = GetSenderWebContents();
247 
248     if (!web_contents || extensions::GetViewType(web_contents) !=
249                              extensions::VIEW_TYPE_APP_WINDOW) {
250       Respond(Error("Not called from an app window - the tabId is required."));
251       return;
252     }
253   } else {
254     TabStripModel* tab_strip = nullptr;
255     int tab_index = -1;
256     if (!extensions::ExtensionTabUtil::GetTabById(
257             *params->consent_requester.tab_id, browser_context(),
258             false /*incognito_enabled*/, nullptr /*browser*/, &tab_strip,
259             &web_contents, &tab_index)) {
260       Respond(Error("Tab not found."));
261       return;
262     }
263 
264     // Bail out if the requested tab is not active - the dialog is modal to the
265     // window, so showing it for a request from an inactive tab could be
266     // misleading/confusing to the user.
267     if (tab_index != tab_strip->active_index()) {
268       Respond(Error("Consent requested from an inactive tab."));
269       return;
270     }
271   }
272 
273   DCHECK(web_contents);
274 
275   // Add ref to ensure the function stays around until the dialog listener is
276   // called. The reference is release in |Finalize|.
277   AddRef();
278 
279   // Create and show the dialog.
280   chromeos::EchoDialogView::Params dialog_params;
281   dialog_params.echo_enabled = redeem_offers_allowed_;
282   if (dialog_params.echo_enabled) {
283     dialog_params.service_name =
284         base::UTF8ToUTF16(params->consent_requester.service_name);
285     dialog_params.origin = base::UTF8ToUTF16(params->consent_requester.origin);
286   }
287 
288   chromeos::EchoDialogView* dialog =
289       new chromeos::EchoDialogView(this, dialog_params);
290   dialog->Show(web_contents->GetTopLevelNativeWindow());
291 
292   // If there is a dialog_shown_callback_, invoke it with the created dialog.
293   if (!dialog_shown_callback_.is_null())
294     dialog_shown_callback_.Run(dialog);
295 }
296 
Finalize(bool consent)297 void EchoPrivateGetUserConsentFunction::Finalize(bool consent) {
298   // Consent should not be true if offers redeeming is disabled.
299   CHECK(redeem_offers_allowed_ || !consent);
300   Respond(OneArgument(base::Value(consent)));
301 
302   // Release the reference added in |OnRedeemOffersAllowedChecked|, before
303   // showing the dialog.
304   Release();
305 }
306