• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_header_helper.h"
6 
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/prefs/incognito_mode_prefs.h"
11 #include "chrome/browser/profiles/profile_io_data.h"
12 #include "chrome/browser/tab_contents/tab_util.h"
13 #include "chrome/browser/ui/browser_window.h"
14 #include "chrome/common/url_constants.h"
15 #include "components/google/core/browser/google_util.h"
16 #include "components/signin/core/common/profile_management_switches.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/web_contents.h"
19 #include "google_apis/gaia/gaia_auth_util.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/url_request/url_request.h"
22 
23 #if defined(OS_ANDROID)
24 #include "chrome/browser/android/signin/account_management_screen_helper.h"
25 #else
26 #include "chrome/browser/ui/browser_commands.h"
27 #include "chrome/browser/ui/browser_finder.h"
28 #endif  // defined(OS_ANDROID)
29 
30 namespace {
31 
32 // Dictionary of fields in a mirror response header.
33 typedef std::map<std::string, std::string> MirrorResponseHeaderDictionary;
34 
35 const char kChromeConnectedHeader[] = "X-Chrome-Connected";
36 const char kChromeManageAccountsHeader[] = "X-Chrome-Manage-Accounts";
37 const char kGaiaIdAttrName[] = "id";
38 const char kProfileModeAttrName[] = "mode";
39 const char kEnableAccountConsistencyAttrName[] = "enable_account_consistency";
40 
41 const char kServiceTypeAttrName[] = "action";
42 const char kEmailAttrName[] = "email";
43 const char kIsSamlAttrName[] = "is_saml";
44 const char kContinueUrlAttrName[] = "continue_url";
45 const char kIsSameTabAttrName[] = "is_same_tab";
46 
47 // Determines the service type that has been passed from GAIA in the header.
GetGAIAServiceTypeFromHeader(const std::string & header_value)48 signin::GAIAServiceType GetGAIAServiceTypeFromHeader(
49     const std::string& header_value) {
50   if (header_value == "SIGNOUT")
51     return signin::GAIA_SERVICE_TYPE_SIGNOUT;
52   else if (header_value == "INCOGNITO")
53     return signin::GAIA_SERVICE_TYPE_INCOGNITO;
54   else if (header_value == "ADDSESSION")
55     return signin::GAIA_SERVICE_TYPE_ADDSESSION;
56   else if (header_value == "REAUTH")
57     return signin::GAIA_SERVICE_TYPE_REAUTH;
58   else if (header_value == "DEFAULT")
59     return signin::GAIA_SERVICE_TYPE_DEFAULT;
60   else
61     return signin::GAIA_SERVICE_TYPE_NONE;
62 }
63 
64 // Parses the mirror response header. Its expected format is
65 // "key1=value1,key2=value2,...".
ParseMirrorResponseHeader(const std::string & header_value)66 MirrorResponseHeaderDictionary ParseMirrorResponseHeader(
67     const std::string& header_value) {
68   std::vector<std::string> fields;
69   if (!Tokenize(header_value, std::string(","), &fields))
70     return MirrorResponseHeaderDictionary();
71 
72   MirrorResponseHeaderDictionary dictionary;
73   for (std::vector<std::string>::iterator i = fields.begin();
74        i != fields.end(); ++i) {
75     std::string field(*i);
76     std::vector<std::string> tokens;
77     if (Tokenize(field, "=", &tokens) != 2) {
78       DLOG(WARNING) << "Unexpected GAIA header field '" << field << "'.";
79       continue;
80     }
81     dictionary[tokens[0]] = tokens[1];
82   }
83   return dictionary;
84 }
85 
86 // Returns the parameters contained in the X-Chrome-Manage-Accounts response
87 // header.
BuildManageAccountsParams(const std::string & header_value)88 signin::ManageAccountsParams BuildManageAccountsParams(
89     const std::string& header_value) {
90   signin::ManageAccountsParams params;
91   MirrorResponseHeaderDictionary header_dictionary =
92       ParseMirrorResponseHeader(header_value);
93   MirrorResponseHeaderDictionary::const_iterator it = header_dictionary.begin();
94   for (; it != header_dictionary.end(); ++it) {
95     const std::string key_name(it->first);
96     if (key_name == kServiceTypeAttrName) {
97       params.service_type =
98           GetGAIAServiceTypeFromHeader(header_dictionary[kServiceTypeAttrName]);
99     } else if (key_name == kEmailAttrName) {
100       params.email = header_dictionary[kEmailAttrName];
101     } else if (key_name == kIsSamlAttrName) {
102       params.is_saml = header_dictionary[kIsSamlAttrName] == "true";
103     } else if (key_name == kContinueUrlAttrName) {
104       params.continue_url = header_dictionary[kContinueUrlAttrName];
105     } else if (key_name == kIsSameTabAttrName) {
106       params.is_same_tab = header_dictionary[kIsSameTabAttrName] == "true";
107     } else {
108       DLOG(WARNING) << "Unexpected GAIA header attribute '" << key_name << "'.";
109     }
110   }
111   return params;
112 }
113 
114 #if !defined(OS_IOS)
115 // Processes the mirror response header on the UI thread. Currently depending
116 // on the value of |header_value|, it either shows the profile avatar menu, or
117 // opens an incognito window/tab.
ProcessMirrorHeaderUIThread(int child_id,int route_id,signin::ManageAccountsParams manage_accounts_params)118 void ProcessMirrorHeaderUIThread(
119     int child_id, int route_id,
120     signin::ManageAccountsParams manage_accounts_params) {
121   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
122 
123   signin::GAIAServiceType service_type = manage_accounts_params.service_type;
124   DCHECK_NE(signin::GAIA_SERVICE_TYPE_NONE, service_type);
125 
126   content::WebContents* web_contents =
127       tab_util::GetWebContentsByID(child_id, route_id);
128   if (!web_contents)
129     return;
130 
131 #if !defined(OS_ANDROID)
132   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
133   if (browser) {
134     BrowserWindow::AvatarBubbleMode bubble_mode;
135     switch (service_type) {
136       case signin::GAIA_SERVICE_TYPE_INCOGNITO:
137         chrome::NewIncognitoWindow(browser);
138         return;
139       case signin::GAIA_SERVICE_TYPE_ADDSESSION:
140         bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN;
141         break;
142       case signin::GAIA_SERVICE_TYPE_REAUTH:
143         bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH;
144         break;
145       default:
146         bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT;
147     }
148     browser->window()->ShowAvatarBubbleFromAvatarButton(
149         bubble_mode, manage_accounts_params);
150   }
151 #else  // defined(OS_ANDROID)
152   if (service_type == signin::GAIA_SERVICE_TYPE_INCOGNITO) {
153     web_contents->OpenURL(content::OpenURLParams(
154         GURL(chrome::kChromeUINativeNewTabURL), content::Referrer(),
155         OFF_THE_RECORD, content::PAGE_TRANSITION_AUTO_TOPLEVEL, false));
156   } else {
157     AccountManagementScreenHelper::OpenAccountManagementScreen(
158         Profile::FromBrowserContext(web_contents->GetBrowserContext()),
159         service_type);
160   }
161 #endif // OS_ANDROID
162 }
163 #endif // !defined(OS_IOS)
164 
IsDriveOrigin(const GURL & url)165 bool IsDriveOrigin(const GURL& url) {
166   if (!url.SchemeIsSecure())
167     return false;
168 
169   const GURL kGoogleDriveURL("https://drive.google.com");
170   const GURL kGoogleDocsURL("https://docs.google.com");
171   return url == kGoogleDriveURL || url == kGoogleDocsURL;
172 }
173 
174 } // empty namespace
175 
176 namespace signin {
177 
ManageAccountsParams()178 ManageAccountsParams::ManageAccountsParams() :
179     service_type(GAIA_SERVICE_TYPE_NONE),
180     email(""),
181     is_saml(false),
182     continue_url(""),
183     is_same_tab(false),
184     child_id(0),
185     route_id(0) {}
186 
AppendMirrorRequestHeaderIfPossible(net::URLRequest * request,const GURL & redirect_url,ProfileIOData * io_data,int child_id,int route_id)187 bool AppendMirrorRequestHeaderIfPossible(
188     net::URLRequest* request,
189     const GURL& redirect_url,
190     ProfileIOData* io_data,
191     int child_id,
192     int route_id) {
193   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
194 
195   if (io_data->IsOffTheRecord() ||
196       io_data->google_services_username()->GetValue().empty()) {
197     return false;
198   }
199 
200   // Only set the header for Drive always, and other Google properties if
201   // new-profile-management is enabled.
202   // Vasquette, which is integrated with most Google properties, needs the
203   // header to redirect certain user actions to Chrome native UI. Drive needs
204   // the header to tell if the current user is connected. The drive path is a
205   // temporary workaround until the more generic chrome.principals API is
206   // available.
207   const GURL& url = redirect_url.is_empty() ? request->url() : redirect_url;
208   GURL origin(url.GetOrigin());
209   bool is_new_profile_management = switches::IsNewProfileManagement();
210   bool is_google_url =
211       !switches::IsEnableWebBasedSignin() &&
212       is_new_profile_management &&
213       (google_util::IsGoogleDomainUrl(
214            url,
215            google_util::ALLOW_SUBDOMAIN,
216            google_util::DISALLOW_NON_STANDARD_PORTS) ||
217        google_util::IsYoutubeDomainUrl(
218            url,
219            google_util::ALLOW_SUBDOMAIN,
220            google_util::DISALLOW_NON_STANDARD_PORTS));
221   if (!is_google_url && !IsDriveOrigin(origin))
222     return false;
223 
224   std::string account_id(io_data->google_services_account_id()->GetValue());
225 
226   int profile_mode_mask = PROFILE_MODE_DEFAULT;
227   if (io_data->incognito_availibility()->GetValue() ==
228           IncognitoModePrefs::DISABLED ||
229       IncognitoModePrefs::ArePlatformParentalControlsEnabledCached()) {
230     profile_mode_mask |= PROFILE_MODE_INCOGNITO_DISABLED;
231   }
232 
233   // TODO(guohui): needs to make a new flag for enabling account consistency.
234   std::string header_value(base::StringPrintf("%s=%s,%s=%s,%s=%s",
235       kGaiaIdAttrName, account_id.c_str(),
236       kProfileModeAttrName, base::IntToString(profile_mode_mask).c_str(),
237       kEnableAccountConsistencyAttrName,
238       is_new_profile_management ? "true" : "false"));
239   request->SetExtraRequestHeaderByName(
240       kChromeConnectedHeader, header_value, false);
241   return true;
242 }
243 
ProcessMirrorResponseHeaderIfExists(net::URLRequest * request,ProfileIOData * io_data,int child_id,int route_id)244 void ProcessMirrorResponseHeaderIfExists(
245     net::URLRequest* request,
246     ProfileIOData* io_data,
247     int child_id,
248     int route_id) {
249 #if defined(OS_IOS)
250   NOTREACHED();
251 #else
252   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
253   if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
254     return;
255 
256   std::string header_value;
257   if (!request->response_headers()->GetNormalizedHeader(
258           kChromeManageAccountsHeader, &header_value)) {
259     return;
260   }
261 
262   DCHECK(switches::IsNewProfileManagement() && !io_data->IsOffTheRecord());
263   ManageAccountsParams params(BuildManageAccountsParams(header_value));
264   if (params.service_type == GAIA_SERVICE_TYPE_NONE)
265     return;
266 
267   params.child_id = child_id;
268   params.route_id = route_id;
269   content::BrowserThread::PostTask(
270       content::BrowserThread::UI, FROM_HERE,
271       base::Bind(ProcessMirrorHeaderUIThread, child_id, route_id, params));
272 #endif  // defined(OS_IOS)
273 }
274 
275 } // namespace signin
276