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/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" 23 24#if defined(OS_ANDROID) 25#include "chrome/browser/android/signin/account_management_screen_helper.h" 26#else 27#include "chrome/browser/ui/browser_commands.h" 28#include "chrome/browser/ui/browser_finder.h" 29#endif // defined(OS_ANDROID) 30 31namespace { 32 33// Dictionary of fields in a mirror response header. 34typedef std::map<std::string, std::string> MirrorResponseHeaderDictionary; 35 36const char kChromeConnectedHeader[] = "X-Chrome-Connected"; 37const char kChromeManageAccountsHeader[] = "X-Chrome-Manage-Accounts"; 38const char kGaiaIdAttrName[] = "id"; 39const char kProfileModeAttrName[] = "mode"; 40const char kEnableAccountConsistencyAttrName[] = "enable_account_consistency"; 41 42const char kServiceTypeAttrName[] = "action"; 43const char kEmailAttrName[] = "email"; 44const char kIsSamlAttrName[] = "is_saml"; 45const char kContinueUrlAttrName[] = "continue_url"; 46const char kIsSameTabAttrName[] = "is_same_tab"; 47 48// Determines the service type that has been passed from GAIA in the header. 49signin::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; 63 else 64 return signin::GAIA_SERVICE_TYPE_NONE; 65} 66 67// Parses the mirror response header. Its expected format is 68// "key1=value1,key2=value2,...". 69MirrorResponseHeaderDictionary ParseMirrorResponseHeader( 70 const std::string& header_value) { 71 std::vector<std::string> fields; 72 if (!Tokenize(header_value, std::string(","), &fields)) 73 return MirrorResponseHeaderDictionary(); 74 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 << "'."; 83 continue; 84 } 85 dictionary[field.substr(0, delim)] = net::UnescapeURLComponent( 86 field.substr(delim + 1), net::UnescapeRule::URL_SPECIAL_CHARS); 87 } 88 return dictionary; 89} 90 91// Returns the parameters contained in the X-Chrome-Manage-Accounts response 92// header. 93signin::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"; 112 } else { 113 DLOG(WARNING) << "Unexpected GAIA header attribute '" << key_name << "'."; 114 } 115 } 116 return params; 117} 118 119#if !defined(OS_IOS) 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. 123void ProcessMirrorHeaderUIThread( 124 int child_id, int route_id, 125 signin::ManageAccountsParams manage_accounts_params) { 126 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 127 128 signin::GAIAServiceType service_type = manage_accounts_params.service_type; 129 DCHECK_NE(signin::GAIA_SERVICE_TYPE_NONE, service_type); 130 131 content::WebContents* web_contents = 132 tab_util::GetWebContentsByID(child_id, route_id); 133 if (!web_contents) 134 return; 135 136#if !defined(OS_ANDROID) 137 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 138 if (browser) { 139 BrowserWindow::AvatarBubbleMode bubble_mode; 140 switch (service_type) { 141 case signin::GAIA_SERVICE_TYPE_INCOGNITO: 142 chrome::NewIncognitoWindow(browser); 143 return; 144 case signin::GAIA_SERVICE_TYPE_ADDSESSION: 145 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_ADD_ACCOUNT; 146 break; 147 case signin::GAIA_SERVICE_TYPE_REAUTH: 148 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH; 149 break; 150 default: 151 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT; 152 } 153 browser->window()->ShowAvatarBubbleFromAvatarButton( 154 bubble_mode, manage_accounts_params); 155 } 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)); 164 } else { 165 AccountManagementScreenHelper::OpenAccountManagementScreen( 166 Profile::FromBrowserContext(web_contents->GetBrowserContext()), 167 service_type); 168 } 169#endif // OS_ANDROID 170} 171#endif // !defined(OS_IOS) 172 173bool IsDriveOrigin(const GURL& url) { 174 if (!url.SchemeIsSecure()) 175 return false; 176 177 const GURL kGoogleDriveURL("https://drive.google.com"); 178 const GURL kGoogleDocsURL("https://docs.google.com"); 179 return url == kGoogleDriveURL || url == kGoogleDocsURL; 180} 181 182} // empty namespace 183 184namespace signin { 185 186ManageAccountsParams::ManageAccountsParams() : 187 service_type(GAIA_SERVICE_TYPE_NONE), 188 email(""), 189 is_saml(false), 190 continue_url(""), 191 is_same_tab(false), 192 child_id(0), 193 route_id(0) {} 194 195bool AppendMirrorRequestHeaderIfPossible( 196 net::URLRequest* request, 197 const GURL& redirect_url, 198 ProfileIOData* io_data) { 199 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 200 201 if (io_data->IsOffTheRecord() || 202 io_data->google_services_username()->GetValue().empty()) { 203 return false; 204 } 205 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 212 // available. 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(); 216 bool is_google_url = 217 !switches::IsEnableWebBasedSignin() && 218 is_enable_account_consistency && 219 (google_util::IsGoogleDomainUrl( 220 url, 221 google_util::ALLOW_SUBDOMAIN, 222 google_util::DISALLOW_NON_STANDARD_PORTS) || 223 google_util::IsYoutubeDomainUrl( 224 url, 225 google_util::ALLOW_SUBDOMAIN, 226 google_util::DISALLOW_NON_STANDARD_PORTS)); 227 if (!is_google_url && !IsDriveOrigin(origin)) 228 return false; 229 230 std::string account_id(io_data->google_services_account_id()->GetValue()); 231 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; 237 } 238 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); 247 return true; 248} 249 250void ProcessMirrorResponseHeaderIfExists( 251 net::URLRequest* request, 252 ProfileIOData* io_data, 253 int child_id, 254 int route_id) { 255#if defined(OS_IOS) 256 NOTREACHED(); 257#else 258 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 259 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin())) 260 return; 261 262 const content::ResourceRequestInfo* info = 263 content::ResourceRequestInfo::ForRequest(request); 264 if (!(info && info->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME)) 265 return; 266 267 std::string header_value; 268 if (!request->response_headers()->GetNormalizedHeader( 269 kChromeManageAccountsHeader, &header_value)) { 270 return; 271 } 272 273 DCHECK(switches::IsEnableAccountConsistency() && !io_data->IsOffTheRecord()); 274 ManageAccountsParams params(BuildManageAccountsParams(header_value)); 275 if (params.service_type == GAIA_SERVICE_TYPE_NONE) 276 return; 277 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) 284} 285 286} // namespace signin 287