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 std::string
BuildMirrorRequestIfPossible(
87 const std::string
& account_id
,
88 const content_settings::CookieSettings
* cookie_settings
,
89 int profile_mode_mask
) {
90 if (account_id
.empty())
93 // If signin cookies are not allowed, don't add the header.
94 if (!signin::SettingsAllowSigninCookies(cookie_settings
)) {
98 // Only set the header for Drive and Gaia always, and other Google properties
99 // if account consistency is enabled.
100 // Vasquette, which is integrated with most Google properties, needs the
101 // header to redirect certain user actions to Chrome native UI. Drive and Gaia
102 // need the header to tell if the current user is connected. The drive path is
103 // a temporary workaround until the more generic chrome.principals API is
105 GURL
origin(url
.GetOrigin());
106 bool is_enable_account_consistency
= switches::IsEnableAccountConsistency();
107 bool is_google_url
= is_enable_account_consistency
&&
108 (google_util::IsGoogleDomainUrl(
109 url
, google_util::ALLOW_SUBDOMAIN
,
110 google_util::DISALLOW_NON_STANDARD_PORTS
) ||
111 google_util::IsYoutubeDomainUrl(
112 url
, google_util::ALLOW_SUBDOMAIN
,
113 google_util::DISALLOW_NON_STANDARD_PORTS
));
114 if (!is_google_url
&& !IsDriveOrigin(origin
) &&
115 !gaia::IsGaiaSignonRealm(origin
)) {
116 return std::string();
119 return base::StringPrintf(pattern
, kGaiaIdAttrName
, account_id
.c_str(),
120 kProfileModeAttrName
,
121 base::IntToString(profile_mode_mask
).c_str(),
122 kEnableAccountConsistencyAttrName
,
123 is_enable_account_consistency
? "true" : "false");
130 ManageAccountsParams::ManageAccountsParams()
131 : service_type(GAIA_SERVICE_TYPE_NONE
),
139 #endif // !defined(OS_IOS)
142 bool SettingsAllowSigninCookies(
143 const content_settings::CookieSettings
* cookie_settings
) {
144 GURL gaia_url
= GaiaUrls::GetInstance()->gaia_url();
145 GURL google_url
= GaiaUrls::GetInstance()->google_url();
146 return cookie_settings
&&
147 cookie_settings
->IsSettingCookieAllowed(gaia_url
, gaia_url
) &&
148 cookie_settings
->IsSettingCookieAllowed(google_url
, google_url
);
151 std::string
BuildMirrorRequestCookieIfPossible(
153 const std::string
& account_id
,
154 const content_settings::CookieSettings
* cookie_settings
,
155 int profile_mode_mask
) {
156 return BuildMirrorRequestIfPossible("%s=%s:%s=%s:%s=%s", url
, account_id
,
157 cookie_settings
, profile_mode_mask
);
160 bool AppendMirrorRequestHeaderIfPossible(
161 net::URLRequest
* request
,
162 const GURL
& redirect_url
,
163 const std::string
& account_id
,
164 const content_settings::CookieSettings
* cookie_settings
,
165 int profile_mode_mask
) {
166 const GURL
& url
= redirect_url
.is_empty() ? request
->url() : redirect_url
;
167 std::string header_value
= BuildMirrorRequestIfPossible(
168 "%s=%s,%s=%s,%s=%s", url
, account_id
, cookie_settings
, profile_mode_mask
);
169 if (header_value
.empty())
171 request
->SetExtraRequestHeaderByName(kChromeConnectedHeader
, header_value
,
176 ManageAccountsParams
BuildManageAccountsParams(
177 const std::string
& header_value
) {
178 signin::ManageAccountsParams params
;
179 MirrorResponseHeaderDictionary header_dictionary
=
180 ParseMirrorResponseHeader(header_value
);
181 MirrorResponseHeaderDictionary::const_iterator it
= header_dictionary
.begin();
182 for (; it
!= header_dictionary
.end(); ++it
) {
183 const std::string
key_name(it
->first
);
184 if (key_name
== kServiceTypeAttrName
) {
185 params
.service_type
=
186 GetGAIAServiceTypeFromHeader(header_dictionary
[kServiceTypeAttrName
]);
187 } else if (key_name
== kEmailAttrName
) {
188 params
.email
= header_dictionary
[kEmailAttrName
];
189 } else if (key_name
== kIsSamlAttrName
) {
190 params
.is_saml
= header_dictionary
[kIsSamlAttrName
] == "true";
191 } else if (key_name
== kContinueUrlAttrName
) {
192 params
.continue_url
= header_dictionary
[kContinueUrlAttrName
];
193 } else if (key_name
== kIsSameTabAttrName
) {
194 params
.is_same_tab
= header_dictionary
[kIsSameTabAttrName
] == "true";
196 DLOG(WARNING
) << "Unexpected GAIA header attribute '" << key_name
<< "'.";
202 ManageAccountsParams
BuildManageAccountsParamsIfExists(net::URLRequest
* request
,
203 bool is_off_the_record
) {
204 ManageAccountsParams empty_params
;
205 empty_params
.service_type
= GAIA_SERVICE_TYPE_NONE
;
206 if (!gaia::IsGaiaSignonRealm(request
->url().GetOrigin()))
209 std::string header_value
;
210 if (!request
->response_headers()->GetNormalizedHeader(
211 kChromeManageAccountsHeader
, &header_value
)) {
215 DCHECK(switches::IsEnableAccountConsistency() && !is_off_the_record
);
216 return BuildManageAccountsParams(header_value
);
219 } // namespace signin