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 "components/signin/core/browser/signin_header_helper.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_split.h"
9 #include "base/strings/stringprintf.h"
10 #include "components/content_settings/core/browser/cookie_settings.h"
11 #include "components/google/core/browser/google_util.h"
12 #include "components/signin/core/common/profile_management_switches.h"
13 #include "google_apis/gaia/gaia_auth_util.h"
14 #include "google_apis/gaia/gaia_urls.h"
15 #include "net/base/escape.h"
16 #include "net/http/http_response_headers.h"
17 #include "net/url_request/url_request.h"
22 // Dictionary of fields in a mirror response header.
23 typedef std::map
<std::string
, std::string
> MirrorResponseHeaderDictionary
;
25 const char kChromeConnectedHeader
[] = "X-Chrome-Connected";
26 const char kChromeManageAccountsHeader
[] = "X-Chrome-Manage-Accounts";
27 const char kContinueUrlAttrName
[] = "continue_url";
28 const char kEmailAttrName
[] = "email";
29 const char kEnableAccountConsistencyAttrName
[] = "enable_account_consistency";
30 const char kGaiaIdAttrName
[] = "id";
31 const char kProfileModeAttrName
[] = "mode";
32 const char kIsSameTabAttrName
[] = "is_same_tab";
33 const char kIsSamlAttrName
[] = "is_saml";
34 const char kServiceTypeAttrName
[] = "action";
36 bool IsDriveOrigin(const GURL
& url
) {
37 if (!url
.SchemeIsCryptographic())
40 const GURL
kGoogleDriveURL("https://drive.google.com");
41 const GURL
kGoogleDocsURL("https://docs.google.com");
42 return url
== kGoogleDriveURL
|| url
== kGoogleDocsURL
;
45 // Determines the service type that has been passed from GAIA in the header.
46 signin::GAIAServiceType
GetGAIAServiceTypeFromHeader(
47 const std::string
& header_value
) {
48 if (header_value
== "SIGNOUT")
49 return signin::GAIA_SERVICE_TYPE_SIGNOUT
;
50 else if (header_value
== "INCOGNITO")
51 return signin::GAIA_SERVICE_TYPE_INCOGNITO
;
52 else if (header_value
== "ADDSESSION")
53 return signin::GAIA_SERVICE_TYPE_ADDSESSION
;
54 else if (header_value
== "REAUTH")
55 return signin::GAIA_SERVICE_TYPE_REAUTH
;
56 else if (header_value
== "SIGNUP")
57 return signin::GAIA_SERVICE_TYPE_SIGNUP
;
58 else if (header_value
== "DEFAULT")
59 return signin::GAIA_SERVICE_TYPE_DEFAULT
;
61 return signin::GAIA_SERVICE_TYPE_NONE
;
64 // Parses the mirror response header. Its expected format is
65 // "key1=value1,key2=value2,...".
66 MirrorResponseHeaderDictionary
ParseMirrorResponseHeader(
67 const std::string
& header_value
) {
68 MirrorResponseHeaderDictionary dictionary
;
69 for (const base::StringPiece
& field
:
70 base::SplitStringPiece(header_value
, ",", base::KEEP_WHITESPACE
,
71 base::SPLIT_WANT_NONEMPTY
)) {
72 size_t delim
= field
.find_first_of('=');
73 if (delim
== std::string::npos
) {
74 DLOG(WARNING
) << "Unexpected GAIA header field '" << field
<< "'.";
77 dictionary
[field
.substr(0, delim
).as_string()] =
78 net::UnescapeURLComponent(field
.substr(delim
+ 1).as_string(),
79 net::UnescapeRule::URL_SPECIAL_CHARS
);
84 // Returns the parameters contained in the X-Chrome-Manage-Accounts response
86 signin::ManageAccountsParams
BuildManageAccountsParams(
87 const std::string
& header_value
) {
88 signin::ManageAccountsParams params
;
89 MirrorResponseHeaderDictionary header_dictionary
=
90 ParseMirrorResponseHeader(header_value
);
91 MirrorResponseHeaderDictionary::const_iterator it
= header_dictionary
.begin();
92 for (; it
!= header_dictionary
.end(); ++it
) {
93 const std::string
key_name(it
->first
);
94 if (key_name
== kServiceTypeAttrName
) {
96 GetGAIAServiceTypeFromHeader(header_dictionary
[kServiceTypeAttrName
]);
97 } else if (key_name
== kEmailAttrName
) {
98 params
.email
= header_dictionary
[kEmailAttrName
];
99 } else if (key_name
== kIsSamlAttrName
) {
100 params
.is_saml
= header_dictionary
[kIsSamlAttrName
] == "true";
101 } else if (key_name
== kContinueUrlAttrName
) {
102 params
.continue_url
= header_dictionary
[kContinueUrlAttrName
];
103 } else if (key_name
== kIsSameTabAttrName
) {
104 params
.is_same_tab
= header_dictionary
[kIsSameTabAttrName
] == "true";
106 DLOG(WARNING
) << "Unexpected GAIA header attribute '" << key_name
<< "'.";
116 ManageAccountsParams::ManageAccountsParams()
117 : service_type(GAIA_SERVICE_TYPE_NONE
),
125 #endif // !defined(OS_IOS)
128 bool SettingsAllowSigninCookies(
129 content_settings::CookieSettings
* cookie_settings
) {
130 GURL gaia_url
= GaiaUrls::GetInstance()->gaia_url();
131 GURL google_url
= GaiaUrls::GetInstance()->google_url();
132 return cookie_settings
&&
133 cookie_settings
->IsSettingCookieAllowed(gaia_url
, gaia_url
) &&
134 cookie_settings
->IsSettingCookieAllowed(google_url
, google_url
);
137 bool AppendMirrorRequestHeaderIfPossible(
138 net::URLRequest
* request
,
139 const GURL
& redirect_url
,
140 const std::string
& account_id
,
141 content_settings::CookieSettings
* cookie_settings
,
142 int profile_mode_mask
) {
143 if (account_id
.empty())
146 // If signin cookies are not allowed, don't add the header.
147 if (SettingsAllowSigninCookies(cookie_settings
)) {
151 // Only set the header for Drive and Gaia always, and other Google properties
152 // if account consistency is enabled.
153 // Vasquette, which is integrated with most Google properties, needs the
154 // header to redirect certain user actions to Chrome native UI. Drive and Gaia
155 // need the header to tell if the current user is connected. The drive path is
156 // a temporary workaround until the more generic chrome.principals API is
158 const GURL
& url
= redirect_url
.is_empty() ? request
->url() : redirect_url
;
159 GURL
origin(url
.GetOrigin());
160 bool is_enable_account_consistency
= switches::IsEnableAccountConsistency();
161 bool is_google_url
= is_enable_account_consistency
&&
162 (google_util::IsGoogleDomainUrl(
163 url
, google_util::ALLOW_SUBDOMAIN
,
164 google_util::DISALLOW_NON_STANDARD_PORTS
) ||
165 google_util::IsYoutubeDomainUrl(
166 url
, google_util::ALLOW_SUBDOMAIN
,
167 google_util::DISALLOW_NON_STANDARD_PORTS
));
168 if (!is_google_url
&& !IsDriveOrigin(origin
) &&
169 !gaia::IsGaiaSignonRealm(origin
)) {
173 std::string
header_value(base::StringPrintf(
174 "%s=%s,%s=%s,%s=%s", kGaiaIdAttrName
, account_id
.c_str(),
175 kProfileModeAttrName
, base::IntToString(profile_mode_mask
).c_str(),
176 kEnableAccountConsistencyAttrName
,
177 is_enable_account_consistency
? "true" : "false"));
178 request
->SetExtraRequestHeaderByName(kChromeConnectedHeader
, header_value
,
183 ManageAccountsParams
BuildManageAccountsParamsIfExists(net::URLRequest
* request
,
184 bool is_off_the_record
) {
185 ManageAccountsParams empty_params
;
186 empty_params
.service_type
= GAIA_SERVICE_TYPE_NONE
;
187 if (!gaia::IsGaiaSignonRealm(request
->url().GetOrigin()))
190 std::string header_value
;
191 if (!request
->response_headers()->GetNormalizedHeader(
192 kChromeManageAccountsHeader
, &header_value
)) {
196 DCHECK(switches::IsEnableAccountConsistency() && !is_off_the_record
);
197 return BuildManageAccountsParams(header_value
);
200 } // namespace signin