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.
5 #include "chrome/browser/signin/signin_header_helper.h"
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/signin/chrome_signin_client.h"
13 #include "chrome/browser/tab_contents/tab_util.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/common/url_constants.h"
16 #include "components/google/core/browser/google_util.h"
17 #include "components/signin/core/common/profile_management_switches.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/resource_request_info.h"
20 #include "content/public/browser/web_contents.h"
21 #include "google_apis/gaia/gaia_auth_util.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/url_request/url_request.h"
25 #if defined(OS_ANDROID)
26 #include "chrome/browser/android/signin/account_management_screen_helper.h"
27 #elif !defined(OS_IOS)
28 #include "chrome/browser/ui/browser_commands.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
31 #endif // defined(OS_ANDROID)
35 // Dictionary of fields in a mirror response header.
36 typedef std::map
<std::string
, std::string
> MirrorResponseHeaderDictionary
;
38 const char kChromeConnectedHeader
[] = "X-Chrome-Connected";
39 const char kChromeManageAccountsHeader
[] = "X-Chrome-Manage-Accounts";
40 const char kGaiaIdAttrName
[] = "id";
41 const char kProfileModeAttrName
[] = "mode";
42 const char kEnableAccountConsistencyAttrName
[] = "enable_account_consistency";
44 const char kServiceTypeAttrName
[] = "action";
45 const char kEmailAttrName
[] = "email";
46 const char kIsSamlAttrName
[] = "is_saml";
47 const char kContinueUrlAttrName
[] = "continue_url";
48 const char kIsSameTabAttrName
[] = "is_same_tab";
50 // Determines the service type that has been passed from GAIA in the header.
51 signin::GAIAServiceType
GetGAIAServiceTypeFromHeader(
52 const std::string
& header_value
) {
53 if (header_value
== "SIGNOUT")
54 return signin::GAIA_SERVICE_TYPE_SIGNOUT
;
55 else if (header_value
== "INCOGNITO")
56 return signin::GAIA_SERVICE_TYPE_INCOGNITO
;
57 else if (header_value
== "ADDSESSION")
58 return signin::GAIA_SERVICE_TYPE_ADDSESSION
;
59 else if (header_value
== "REAUTH")
60 return signin::GAIA_SERVICE_TYPE_REAUTH
;
61 else if (header_value
== "SIGNUP")
62 return signin::GAIA_SERVICE_TYPE_SIGNUP
;
63 else if (header_value
== "DEFAULT")
64 return signin::GAIA_SERVICE_TYPE_DEFAULT
;
66 return signin::GAIA_SERVICE_TYPE_NONE
;
69 // Parses the mirror response header. Its expected format is
70 // "key1=value1,key2=value2,...".
71 MirrorResponseHeaderDictionary
ParseMirrorResponseHeader(
72 const std::string
& header_value
) {
73 std::vector
<std::string
> fields
;
74 if (!Tokenize(header_value
, std::string(","), &fields
))
75 return MirrorResponseHeaderDictionary();
77 MirrorResponseHeaderDictionary dictionary
;
78 for (std::vector
<std::string
>::iterator i
= fields
.begin();
79 i
!= fields
.end(); ++i
) {
80 std::string
field(*i
);
81 std::vector
<std::string
> tokens
;
82 size_t delim
= field
.find_first_of('=');
83 if (delim
== std::string::npos
) {
84 DLOG(WARNING
) << "Unexpected GAIA header field '" << field
<< "'.";
87 dictionary
[field
.substr(0, delim
)] = net::UnescapeURLComponent(
88 field
.substr(delim
+ 1), net::UnescapeRule::URL_SPECIAL_CHARS
);
93 // Returns the parameters contained in the X-Chrome-Manage-Accounts response
95 signin::ManageAccountsParams
BuildManageAccountsParams(
96 const std::string
& header_value
) {
97 signin::ManageAccountsParams params
;
98 MirrorResponseHeaderDictionary header_dictionary
=
99 ParseMirrorResponseHeader(header_value
);
100 MirrorResponseHeaderDictionary::const_iterator it
= header_dictionary
.begin();
101 for (; it
!= header_dictionary
.end(); ++it
) {
102 const std::string
key_name(it
->first
);
103 if (key_name
== kServiceTypeAttrName
) {
104 params
.service_type
=
105 GetGAIAServiceTypeFromHeader(header_dictionary
[kServiceTypeAttrName
]);
106 } else if (key_name
== kEmailAttrName
) {
107 params
.email
= header_dictionary
[kEmailAttrName
];
108 } else if (key_name
== kIsSamlAttrName
) {
109 params
.is_saml
= header_dictionary
[kIsSamlAttrName
] == "true";
110 } else if (key_name
== kContinueUrlAttrName
) {
111 params
.continue_url
= header_dictionary
[kContinueUrlAttrName
];
112 } else if (key_name
== kIsSameTabAttrName
) {
113 params
.is_same_tab
= header_dictionary
[kIsSameTabAttrName
] == "true";
115 DLOG(WARNING
) << "Unexpected GAIA header attribute '" << key_name
<< "'.";
122 // Processes the mirror response header on the UI thread. Currently depending
123 // on the value of |header_value|, it either shows the profile avatar menu, or
124 // opens an incognito window/tab.
125 void ProcessMirrorHeaderUIThread(
126 int child_id
, int route_id
,
127 signin::ManageAccountsParams manage_accounts_params
) {
128 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
130 signin::GAIAServiceType service_type
= manage_accounts_params
.service_type
;
131 DCHECK_NE(signin::GAIA_SERVICE_TYPE_NONE
, service_type
);
133 content::WebContents
* web_contents
=
134 tab_util::GetWebContentsByID(child_id
, route_id
);
138 #if !defined(OS_ANDROID)
139 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
141 BrowserWindow::AvatarBubbleMode bubble_mode
;
142 switch (service_type
) {
143 case signin::GAIA_SERVICE_TYPE_INCOGNITO
:
144 chrome::NewIncognitoWindow(browser
);
146 case signin::GAIA_SERVICE_TYPE_ADDSESSION
:
147 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_ADD_ACCOUNT
;
149 case signin::GAIA_SERVICE_TYPE_REAUTH
:
150 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH
;
153 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT
;
155 browser
->window()->ShowAvatarBubbleFromAvatarButton(
156 bubble_mode
, manage_accounts_params
);
158 #else // defined(OS_ANDROID)
159 if (service_type
== signin::GAIA_SERVICE_TYPE_INCOGNITO
) {
160 GURL
url(manage_accounts_params
.continue_url
.empty() ?
161 chrome::kChromeUINativeNewTabURL
:
162 manage_accounts_params
.continue_url
);
163 web_contents
->OpenURL(content::OpenURLParams(
164 url
, content::Referrer(), OFF_THE_RECORD
,
165 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, false));
167 AccountManagementScreenHelper::OpenAccountManagementScreen(
168 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
173 #endif // !defined(OS_IOS)
175 bool IsDriveOrigin(const GURL
& url
) {
176 if (!url
.SchemeIsSecure())
179 const GURL
kGoogleDriveURL("https://drive.google.com");
180 const GURL
kGoogleDocsURL("https://docs.google.com");
181 return url
== kGoogleDriveURL
|| url
== kGoogleDocsURL
;
188 ManageAccountsParams::ManageAccountsParams() :
189 service_type(GAIA_SERVICE_TYPE_NONE
),
197 bool AppendMirrorRequestHeaderIfPossible(
198 net::URLRequest
* request
,
199 const GURL
& redirect_url
,
200 ProfileIOData
* io_data
,
203 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
205 if (io_data
->IsOffTheRecord())
208 std::string
account_id(io_data
->google_services_account_id()->GetValue());
210 if (account_id
.empty())
213 // If signin cookies are not allowed, don't add the header.
214 if (!ChromeSigninClient::SettingsAllowSigninCookies(
215 io_data
->GetCookieSettings())) {
219 // Only set the header for Drive and Gaia always, and other Google properties
220 // if account consistency is enabled.
221 // Vasquette, which is integrated with most Google properties, needs the
222 // header to redirect certain user actions to Chrome native UI. Drive and Gaia
223 // need the header to tell if the current user is connected. The drive path is
224 // a temporary workaround until the more generic chrome.principals API is
226 const GURL
& url
= redirect_url
.is_empty() ? request
->url() : redirect_url
;
227 GURL
origin(url
.GetOrigin());
228 bool is_enable_account_consistency
= switches::IsEnableAccountConsistency();
230 is_enable_account_consistency
&&
231 (google_util::IsGoogleDomainUrl(
233 google_util::ALLOW_SUBDOMAIN
,
234 google_util::DISALLOW_NON_STANDARD_PORTS
) ||
235 google_util::IsYoutubeDomainUrl(
237 google_util::ALLOW_SUBDOMAIN
,
238 google_util::DISALLOW_NON_STANDARD_PORTS
));
239 if (!is_google_url
&& !IsDriveOrigin(origin
) &&
240 !gaia::IsGaiaSignonRealm(origin
)) {
244 #if !defined(OS_ANDROID) && !defined(OS_IOS)
245 extensions::WebViewRendererState::WebViewInfo webview_info
;
246 bool is_guest
= extensions::WebViewRendererState::GetInstance()->GetInfo(
247 child_id
, route_id
, &webview_info
);
248 // Do not set the x-chrome-connected header on requests from a native signin
249 // webview, as identified by an empty extension id which means the webview is
250 // embedded in a webui page, otherwise user may end up with a blank page as
251 // gaia uses the header to decide whether it returns 204 for certain end
253 if (is_guest
&& webview_info
.owner_extension_id
.empty())
255 #endif // !OS_ANDROID && !OS_IOS
257 int profile_mode_mask
= PROFILE_MODE_DEFAULT
;
258 if (io_data
->incognito_availibility()->GetValue() ==
259 IncognitoModePrefs::DISABLED
||
260 IncognitoModePrefs::ArePlatformParentalControlsEnabled()) {
261 profile_mode_mask
|= PROFILE_MODE_INCOGNITO_DISABLED
;
264 std::string
header_value(base::StringPrintf("%s=%s,%s=%s,%s=%s",
265 kGaiaIdAttrName
, account_id
.c_str(),
266 kProfileModeAttrName
, base::IntToString(profile_mode_mask
).c_str(),
267 kEnableAccountConsistencyAttrName
,
268 is_enable_account_consistency
? "true" : "false"));
269 request
->SetExtraRequestHeaderByName(
270 kChromeConnectedHeader
, header_value
, false);
274 void ProcessMirrorResponseHeaderIfExists(
275 net::URLRequest
* request
,
276 ProfileIOData
* io_data
,
282 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
283 if (!gaia::IsGaiaSignonRealm(request
->url().GetOrigin()))
286 const content::ResourceRequestInfo
* info
=
287 content::ResourceRequestInfo::ForRequest(request
);
288 if (!(info
&& info
->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME
))
291 std::string header_value
;
292 if (!request
->response_headers()->GetNormalizedHeader(
293 kChromeManageAccountsHeader
, &header_value
)) {
297 DCHECK(switches::IsEnableAccountConsistency() && !io_data
->IsOffTheRecord());
298 ManageAccountsParams
params(BuildManageAccountsParams(header_value
));
299 if (params
.service_type
== GAIA_SERVICE_TYPE_NONE
)
302 params
.child_id
= child_id
;
303 params
.route_id
= route_id
;
304 content::BrowserThread::PostTask(
305 content::BrowserThread::UI
, FROM_HERE
,
306 base::Bind(ProcessMirrorHeaderUIThread
, child_id
, route_id
, params
));
307 #endif // defined(OS_IOS)
310 } // namespace signin