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