1// Copyright 2013 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/signin/signin_promo.h"
6
7#include "base/prefs/pref_service.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/string_util.h"
10#include "base/strings/stringprintf.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/browser_process.h"
13#include "chrome/browser/extensions/signin/gaia_auth_extension_loader.h"
14#include "chrome/browser/first_run/first_run.h"
15#include "chrome/browser/google/google_brand.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/profiles/profile_info_cache.h"
18#include "chrome/browser/profiles/profile_manager.h"
19#include "chrome/browser/signin/signin_manager_factory.h"
20#include "chrome/browser/ui/webui/options/core_options_handler.h"
21#include "chrome/browser/ui/webui/theme_source.h"
22#include "chrome/common/net/url_util.h"
23#include "chrome/common/pref_names.h"
24#include "chrome/common/url_constants.h"
25#include "components/google/core/browser/google_util.h"
26#include "components/pref_registry/pref_registry_syncable.h"
27#include "components/signin/core/browser/signin_manager.h"
28#include "components/signin/core/common/profile_management_switches.h"
29#include "content/public/browser/url_data_source.h"
30#include "content/public/browser/web_contents.h"
31#include "content/public/browser/web_ui.h"
32#include "content/public/browser/web_ui_data_source.h"
33#include "google_apis/gaia/gaia_urls.h"
34#include "net/base/escape.h"
35#include "net/base/network_change_notifier.h"
36#include "net/base/url_util.h"
37#include "url/gurl.h"
38
39using content::WebContents;
40
41namespace {
42
43// Gaia cannot support about:blank as a continue URL, so using a hosted blank
44// page instead.
45const char kSignInLandingUrlPrefix[] =
46    "https://www.google.com/intl/%s/chrome/blank.html";
47
48// The maximum number of times we want to show the sign in promo at startup.
49const int kSignInPromoShowAtStartupMaximum = 10;
50
51// Forces the web based signin flow when set.
52bool g_force_web_based_signin_flow = false;
53
54// Checks we want to show the sign in promo for the given brand.
55bool AllowPromoAtStartupForCurrentBrand() {
56  std::string brand;
57  google_brand::GetBrand(&brand);
58
59  if (brand.empty())
60    return true;
61
62  if (google_brand::IsInternetCafeBrandCode(brand))
63    return false;
64
65  // Enable for both organic and distribution.
66  return true;
67}
68
69// Returns true if a user has seen the sign in promo at startup previously.
70bool HasShownPromoAtStartup(Profile* profile) {
71  return profile->GetPrefs()->HasPrefPath(prefs::kSignInPromoStartupCount);
72}
73
74// Returns true if the user has previously skipped the sign in promo.
75bool HasUserSkippedPromo(Profile* profile) {
76  return profile->GetPrefs()->GetBoolean(prefs::kSignInPromoUserSkipped);
77}
78
79}  // namespace
80
81namespace signin {
82
83bool ShouldShowPromo(Profile* profile) {
84#if defined(OS_CHROMEOS)
85  // There's no need to show the sign in promo on cros since cros users are
86  // already logged in.
87  return false;
88#else
89
90  // Don't bother if we don't have any kind of network connection.
91  if (net::NetworkChangeNotifier::IsOffline())
92    return false;
93
94  // Don't show for supervised profiles.
95  if (profile->IsSupervised())
96    return false;
97
98  // Display the signin promo if the user is not signed in.
99  SigninManager* signin = SigninManagerFactory::GetForProfile(
100      profile->GetOriginalProfile());
101  return !signin->AuthInProgress() && signin->IsSigninAllowed() &&
102      !signin->IsAuthenticated();
103#endif
104}
105
106bool ShouldShowPromoAtStartup(Profile* profile, bool is_new_profile) {
107  DCHECK(profile);
108
109  // Don't show if the profile is an incognito.
110  if (profile->IsOffTheRecord())
111    return false;
112
113  if (!ShouldShowPromo(profile))
114    return false;
115
116  if (!is_new_profile) {
117    if (!HasShownPromoAtStartup(profile))
118      return false;
119  }
120
121  if (HasUserSkippedPromo(profile))
122    return false;
123
124  // For Chinese users skip the sign in promo.
125  if (g_browser_process->GetApplicationLocale() == "zh-CN")
126    return false;
127
128  PrefService* prefs = profile->GetPrefs();
129  int show_count = prefs->GetInteger(prefs::kSignInPromoStartupCount);
130  if (show_count >= kSignInPromoShowAtStartupMaximum)
131    return false;
132
133  // This pref can be set in the master preferences file to allow or disallow
134  // showing the sign in promo at startup.
135  if (prefs->HasPrefPath(prefs::kSignInPromoShowOnFirstRunAllowed))
136    return prefs->GetBoolean(prefs::kSignInPromoShowOnFirstRunAllowed);
137
138  // For now don't show the promo for some brands.
139  if (!AllowPromoAtStartupForCurrentBrand())
140    return false;
141
142  // Default to show the promo for Google Chrome builds.
143#if defined(GOOGLE_CHROME_BUILD)
144  return true;
145#else
146  return false;
147#endif
148}
149
150void DidShowPromoAtStartup(Profile* profile) {
151  int show_count = profile->GetPrefs()->GetInteger(
152      prefs::kSignInPromoStartupCount);
153  show_count++;
154  profile->GetPrefs()->SetInteger(prefs::kSignInPromoStartupCount, show_count);
155}
156
157void SetUserSkippedPromo(Profile* profile) {
158  profile->GetPrefs()->SetBoolean(prefs::kSignInPromoUserSkipped, true);
159}
160
161GURL GetLandingURL(const char* option, int value) {
162  std::string url;
163  if (switches::IsEnableWebBasedSignin()) {
164    const std::string& locale = g_browser_process->GetApplicationLocale();
165    url = base::StringPrintf(kSignInLandingUrlPrefix, locale.c_str());
166  } else {
167    url = base::StringPrintf(
168        "%s/success.html", extensions::kGaiaAuthExtensionOrigin);
169  }
170  base::StringAppendF(&url, "?%s=%d", option, value);
171  return GURL(url);
172}
173
174GURL GetPromoURL(Source source, bool auto_close) {
175  return GetPromoURL(source, auto_close, false /* is_constrained */);
176}
177
178GURL GetPromoURL(Source source, bool auto_close, bool is_constrained) {
179  DCHECK_NE(SOURCE_UNKNOWN, source);
180
181  if (!switches::IsEnableWebBasedSignin()) {
182    std::string url(chrome::kChromeUIChromeSigninURL);
183    base::StringAppendF(&url, "?%s=%d", kSignInPromoQueryKeySource, source);
184    if (auto_close)
185      base::StringAppendF(&url, "&%s=1", kSignInPromoQueryKeyAutoClose);
186    if (is_constrained)
187      base::StringAppendF(&url, "&%s=1", kSignInPromoQueryKeyConstrained);
188    return GURL(url);
189  }
190
191  // Build a Gaia-based URL that can be used to sign the user into chrome.
192  // There are required request parameters:
193  //
194  //  - tell Gaia which service the user is signing into.  In this case,
195  //    a chrome sign in uses the service "chromiumsync"
196  //  - provide a continue URL.  This is the URL that Gaia will redirect to
197  //    once the sign is complete.
198  //
199  // The continue URL includes a source parameter that can be extracted using
200  // the function GetSourceForSignInPromoURL() below.  This is used to know
201  // which of the chrome sign in access points was used to sign the user in.
202  // It is also parsed for the |auto_close| flag, which indicates that the tab
203  // must be closed after sync setup is successful.
204  // See OneClickSigninHelper for details.
205  std::string query_string = "?service=chromiumsync&sarp=1";
206
207  std::string continue_url = GetLandingURL(kSignInPromoQueryKeySource,
208                                           static_cast<int>(source)).spec();
209  if (auto_close)
210    base::StringAppendF(&continue_url, "&%s=1", kSignInPromoQueryKeyAutoClose);
211
212  base::StringAppendF(
213      &query_string,
214      "&%s=%s",
215      kSignInPromoQueryKeyContinue,
216      net::EscapeQueryParamValue(continue_url, false).c_str());
217
218  return GaiaUrls::GetInstance()->service_login_url().Resolve(query_string);
219}
220
221GURL GetReauthURL(Profile* profile, const std::string& account_id) {
222  if (switches::IsEnableWebBasedSignin()) {
223    return net::AppendQueryParameter(
224        signin::GetPromoURL(signin::SOURCE_SETTINGS, true),
225        "Email",
226        account_id);
227  }
228
229  signin::Source source = switches::IsNewAvatarMenu() ?
230      signin::SOURCE_REAUTH : signin::SOURCE_SETTINGS;
231
232  GURL url = signin::GetPromoURL(
233      source, true /* auto_close */,
234      switches::IsNewAvatarMenu() /* is_constrained */);
235  url = net::AppendQueryParameter(url, "email", account_id);
236  url = net::AppendQueryParameter(url, "validateEmail", "1");
237  return net::AppendQueryParameter(url, "readOnlyEmail", "1");
238}
239
240GURL GetNextPageURLForPromoURL(const GURL& url) {
241  std::string value;
242  if (net::GetValueForKeyInQuery(url, kSignInPromoQueryKeyContinue, &value)) {
243    GURL continue_url = GURL(value);
244    if (continue_url.is_valid())
245      return continue_url;
246  }
247
248  return GURL();
249}
250
251Source GetSourceForPromoURL(const GURL& url) {
252  std::string value;
253  if (net::GetValueForKeyInQuery(url, kSignInPromoQueryKeySource, &value)) {
254    int source = 0;
255    if (base::StringToInt(value, &source) && source >= SOURCE_START_PAGE &&
256        source < SOURCE_UNKNOWN) {
257      return static_cast<Source>(source);
258    }
259  }
260  return SOURCE_UNKNOWN;
261}
262
263bool IsAutoCloseEnabledInURL(const GURL& url) {
264  std::string value;
265  if (net::GetValueForKeyInQuery(url, kSignInPromoQueryKeyAutoClose, &value)) {
266    int enabled = 0;
267    if (base::StringToInt(value, &enabled) && enabled == 1)
268      return true;
269  }
270  return false;
271}
272
273bool ShouldShowAccountManagement(const GURL& url) {
274  std::string value;
275  if (net::GetValueForKeyInQuery(
276          url, kSignInPromoQueryKeyShowAccountManagement, &value)) {
277    int enabled = 0;
278    if (base::StringToInt(value, &enabled) && enabled == 1)
279      return true;
280  }
281  return false;
282}
283
284bool IsContinueUrlForWebBasedSigninFlow(const GURL& url) {
285  GURL::Replacements replacements;
286  replacements.ClearQuery();
287  const std::string& locale = g_browser_process->GetApplicationLocale();
288  GURL continue_url =
289      GURL(base::StringPrintf(kSignInLandingUrlPrefix, locale.c_str()));
290  return (
291      google_util::IsGoogleDomainUrl(
292          url,
293          google_util::ALLOW_SUBDOMAIN,
294          google_util::DISALLOW_NON_STANDARD_PORTS) &&
295      url.ReplaceComponents(replacements).path() ==
296        continue_url.ReplaceComponents(replacements).path());
297}
298
299void ForceWebBasedSigninFlowForTesting(bool force) {
300  g_force_web_based_signin_flow = force;
301}
302
303void RegisterProfilePrefs(
304    user_prefs::PrefRegistrySyncable* registry) {
305  registry->RegisterIntegerPref(
306      prefs::kSignInPromoStartupCount,
307      0,
308      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
309  registry->RegisterBooleanPref(
310      prefs::kSignInPromoUserSkipped,
311      false,
312      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
313  registry->RegisterBooleanPref(
314      prefs::kSignInPromoShowOnFirstRunAllowed,
315      true,
316      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
317  registry->RegisterBooleanPref(
318      prefs::kSignInPromoShowNTPBubble,
319      false,
320      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
321}
322
323}  // namespace signin
324