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/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/resource_request_info.h"
19 #include "content/public/browser/web_contents.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/url_request/url_request.h"
24 #if defined(OS_ANDROID)
25 #include "chrome/browser/android/signin/account_management_screen_helper.h"
27 #include "chrome/browser/ui/browser_commands.h"
28 #include "chrome/browser/ui/browser_finder.h"
29 #endif // defined(OS_ANDROID)
33 // Dictionary of fields in a mirror response header.
34 typedef std::map
<std::string
, std::string
> MirrorResponseHeaderDictionary
;
36 const char kChromeConnectedHeader
[] = "X-Chrome-Connected";
37 const char kChromeManageAccountsHeader
[] = "X-Chrome-Manage-Accounts";
38 const char kGaiaIdAttrName
[] = "id";
39 const char kProfileModeAttrName
[] = "mode";
40 const char kEnableAccountConsistencyAttrName
[] = "enable_account_consistency";
42 const char kServiceTypeAttrName
[] = "action";
43 const char kEmailAttrName
[] = "email";
44 const char kIsSamlAttrName
[] = "is_saml";
45 const char kContinueUrlAttrName
[] = "continue_url";
46 const char kIsSameTabAttrName
[] = "is_same_tab";
48 // Determines the service type that has been passed from GAIA in the header.
49 signin::GAIAServiceType
GetGAIAServiceTypeFromHeader(
50 const std::string
& header_value
) {
51 if (header_value
== "SIGNOUT")
52 return signin::GAIA_SERVICE_TYPE_SIGNOUT
;
53 else if (header_value
== "INCOGNITO")
54 return signin::GAIA_SERVICE_TYPE_INCOGNITO
;
55 else if (header_value
== "ADDSESSION")
56 return signin::GAIA_SERVICE_TYPE_ADDSESSION
;
57 else if (header_value
== "REAUTH")
58 return signin::GAIA_SERVICE_TYPE_REAUTH
;
59 else if (header_value
== "SIGNUP")
60 return signin::GAIA_SERVICE_TYPE_SIGNUP
;
61 else if (header_value
== "DEFAULT")
62 return signin::GAIA_SERVICE_TYPE_DEFAULT
;
64 return signin::GAIA_SERVICE_TYPE_NONE
;
67 // Parses the mirror response header. Its expected format is
68 // "key1=value1,key2=value2,...".
69 MirrorResponseHeaderDictionary
ParseMirrorResponseHeader(
70 const std::string
& header_value
) {
71 std::vector
<std::string
> fields
;
72 if (!Tokenize(header_value
, std::string(","), &fields
))
73 return MirrorResponseHeaderDictionary();
75 MirrorResponseHeaderDictionary dictionary
;
76 for (std::vector
<std::string
>::iterator i
= fields
.begin();
77 i
!= fields
.end(); ++i
) {
78 std::string
field(*i
);
79 std::vector
<std::string
> tokens
;
80 size_t delim
= field
.find_first_of('=');
81 if (delim
== std::string::npos
) {
82 DLOG(WARNING
) << "Unexpected GAIA header field '" << field
<< "'.";
85 dictionary
[field
.substr(0, delim
)] = net::UnescapeURLComponent(
86 field
.substr(delim
+ 1), net::UnescapeRule::URL_SPECIAL_CHARS
);
91 // Returns the parameters contained in the X-Chrome-Manage-Accounts response
93 signin::ManageAccountsParams
BuildManageAccountsParams(
94 const std::string
& header_value
) {
95 signin::ManageAccountsParams params
;
96 MirrorResponseHeaderDictionary header_dictionary
=
97 ParseMirrorResponseHeader(header_value
);
98 MirrorResponseHeaderDictionary::const_iterator it
= header_dictionary
.begin();
99 for (; it
!= header_dictionary
.end(); ++it
) {
100 const std::string
key_name(it
->first
);
101 if (key_name
== kServiceTypeAttrName
) {
102 params
.service_type
=
103 GetGAIAServiceTypeFromHeader(header_dictionary
[kServiceTypeAttrName
]);
104 } else if (key_name
== kEmailAttrName
) {
105 params
.email
= header_dictionary
[kEmailAttrName
];
106 } else if (key_name
== kIsSamlAttrName
) {
107 params
.is_saml
= header_dictionary
[kIsSamlAttrName
] == "true";
108 } else if (key_name
== kContinueUrlAttrName
) {
109 params
.continue_url
= header_dictionary
[kContinueUrlAttrName
];
110 } else if (key_name
== kIsSameTabAttrName
) {
111 params
.is_same_tab
= header_dictionary
[kIsSameTabAttrName
] == "true";
113 DLOG(WARNING
) << "Unexpected GAIA header attribute '" << key_name
<< "'.";
120 // Processes the mirror response header on the UI thread. Currently depending
121 // on the value of |header_value|, it either shows the profile avatar menu, or
122 // opens an incognito window/tab.
123 void ProcessMirrorHeaderUIThread(
124 int child_id
, int route_id
,
125 signin::ManageAccountsParams manage_accounts_params
) {
126 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
128 signin::GAIAServiceType service_type
= manage_accounts_params
.service_type
;
129 DCHECK_NE(signin::GAIA_SERVICE_TYPE_NONE
, service_type
);
131 content::WebContents
* web_contents
=
132 tab_util::GetWebContentsByID(child_id
, route_id
);
136 #if !defined(OS_ANDROID)
137 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
139 BrowserWindow::AvatarBubbleMode bubble_mode
;
140 switch (service_type
) {
141 case signin::GAIA_SERVICE_TYPE_INCOGNITO
:
142 chrome::NewIncognitoWindow(browser
);
144 case signin::GAIA_SERVICE_TYPE_ADDSESSION
:
145 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_ADD_ACCOUNT
;
147 case signin::GAIA_SERVICE_TYPE_REAUTH
:
148 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH
;
151 bubble_mode
= BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT
;
153 browser
->window()->ShowAvatarBubbleFromAvatarButton(
154 bubble_mode
, manage_accounts_params
);
156 #else // defined(OS_ANDROID)
157 if (service_type
== signin::GAIA_SERVICE_TYPE_INCOGNITO
) {
158 GURL
url(manage_accounts_params
.continue_url
.empty() ?
159 chrome::kChromeUINativeNewTabURL
:
160 manage_accounts_params
.continue_url
);
161 web_contents
->OpenURL(content::OpenURLParams(
162 url
, content::Referrer(), OFF_THE_RECORD
,
163 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, false));
165 AccountManagementScreenHelper::OpenAccountManagementScreen(
166 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
171 #endif // !defined(OS_IOS)
173 bool IsDriveOrigin(const GURL
& url
) {
174 if (!url
.SchemeIsSecure())
177 const GURL
kGoogleDriveURL("https://drive.google.com");
178 const GURL
kGoogleDocsURL("https://docs.google.com");
179 return url
== kGoogleDriveURL
|| url
== kGoogleDocsURL
;
186 ManageAccountsParams::ManageAccountsParams() :
187 service_type(GAIA_SERVICE_TYPE_NONE
),
195 bool AppendMirrorRequestHeaderIfPossible(
196 net::URLRequest
* request
,
197 const GURL
& redirect_url
,
198 ProfileIOData
* io_data
) {
199 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
201 if (io_data
->IsOffTheRecord() ||
202 io_data
->google_services_username()->GetValue().empty()) {
206 // Only set the header for Drive always, and other Google properties if
207 // new-profile-management is enabled.
208 // Vasquette, which is integrated with most Google properties, needs the
209 // header to redirect certain user actions to Chrome native UI. Drive needs
210 // the header to tell if the current user is connected. The drive path is a
211 // temporary workaround until the more generic chrome.principals API is
213 const GURL
& url
= redirect_url
.is_empty() ? request
->url() : redirect_url
;
214 GURL
origin(url
.GetOrigin());
215 bool is_enable_account_consistency
= switches::IsEnableAccountConsistency();
217 !switches::IsEnableWebBasedSignin() &&
218 is_enable_account_consistency
&&
219 (google_util::IsGoogleDomainUrl(
221 google_util::ALLOW_SUBDOMAIN
,
222 google_util::DISALLOW_NON_STANDARD_PORTS
) ||
223 google_util::IsYoutubeDomainUrl(
225 google_util::ALLOW_SUBDOMAIN
,
226 google_util::DISALLOW_NON_STANDARD_PORTS
));
227 if (!is_google_url
&& !IsDriveOrigin(origin
))
230 std::string
account_id(io_data
->google_services_account_id()->GetValue());
232 int profile_mode_mask
= PROFILE_MODE_DEFAULT
;
233 if (io_data
->incognito_availibility()->GetValue() ==
234 IncognitoModePrefs::DISABLED
||
235 IncognitoModePrefs::ArePlatformParentalControlsEnabledCached()) {
236 profile_mode_mask
|= PROFILE_MODE_INCOGNITO_DISABLED
;
239 // TODO(guohui): needs to make a new flag for enabling account consistency.
240 std::string
header_value(base::StringPrintf("%s=%s,%s=%s,%s=%s",
241 kGaiaIdAttrName
, account_id
.c_str(),
242 kProfileModeAttrName
, base::IntToString(profile_mode_mask
).c_str(),
243 kEnableAccountConsistencyAttrName
,
244 is_enable_account_consistency
? "true" : "false"));
245 request
->SetExtraRequestHeaderByName(
246 kChromeConnectedHeader
, header_value
, false);
250 void ProcessMirrorResponseHeaderIfExists(
251 net::URLRequest
* request
,
252 ProfileIOData
* io_data
,
258 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
259 if (!gaia::IsGaiaSignonRealm(request
->url().GetOrigin()))
262 const content::ResourceRequestInfo
* info
=
263 content::ResourceRequestInfo::ForRequest(request
);
264 if (!(info
&& info
->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME
))
267 std::string header_value
;
268 if (!request
->response_headers()->GetNormalizedHeader(
269 kChromeManageAccountsHeader
, &header_value
)) {
273 DCHECK(switches::IsEnableAccountConsistency() && !io_data
->IsOffTheRecord());
274 ManageAccountsParams
params(BuildManageAccountsParams(header_value
));
275 if (params
.service_type
== GAIA_SERVICE_TYPE_NONE
)
278 params
.child_id
= child_id
;
279 params
.route_id
= route_id
;
280 content::BrowserThread::PostTask(
281 content::BrowserThread::UI
, FROM_HERE
,
282 base::Bind(ProcessMirrorHeaderUIThread
, child_id
, route_id
, params
));
283 #endif // defined(OS_IOS)
286 } // namespace signin