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/web_contents.h" 19 #include "google_apis/gaia/gaia_auth_util.h" 20 #include "net/http/http_response_headers.h" 21 #include "net/url_request/url_request.h" 22 23 #if defined(OS_ANDROID) 24 #include "chrome/browser/android/signin/account_management_screen_helper.h" 25 #else 26 #include "chrome/browser/ui/browser_commands.h" 27 #include "chrome/browser/ui/browser_finder.h" 28 #endif // defined(OS_ANDROID) 29 30 namespace { 31 32 // Dictionary of fields in a mirror response header. 33 typedef std::map<std::string, std::string> MirrorResponseHeaderDictionary; 34 35 const char kChromeConnectedHeader[] = "X-Chrome-Connected"; 36 const char kChromeManageAccountsHeader[] = "X-Chrome-Manage-Accounts"; 37 const char kGaiaIdAttrName[] = "id"; 38 const char kProfileModeAttrName[] = "mode"; 39 const char kEnableAccountConsistencyAttrName[] = "enable_account_consistency"; 40 41 const char kServiceTypeAttrName[] = "action"; 42 const char kEmailAttrName[] = "email"; 43 const char kIsSamlAttrName[] = "is_saml"; 44 const char kContinueUrlAttrName[] = "continue_url"; 45 const char kIsSameTabAttrName[] = "is_same_tab"; 46 47 // Determines the service type that has been passed from GAIA in the header. 48 signin::GAIAServiceType GetGAIAServiceTypeFromHeader( 49 const std::string& header_value) { 50 if (header_value == "SIGNOUT") 51 return signin::GAIA_SERVICE_TYPE_SIGNOUT; 52 else if (header_value == "INCOGNITO") 53 return signin::GAIA_SERVICE_TYPE_INCOGNITO; 54 else if (header_value == "ADDSESSION") 55 return signin::GAIA_SERVICE_TYPE_ADDSESSION; 56 else if (header_value == "REAUTH") 57 return signin::GAIA_SERVICE_TYPE_REAUTH; 58 else if (header_value == "DEFAULT") 59 return signin::GAIA_SERVICE_TYPE_DEFAULT; 60 else 61 return signin::GAIA_SERVICE_TYPE_NONE; 62 } 63 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 std::vector<std::string> fields; 69 if (!Tokenize(header_value, std::string(","), &fields)) 70 return MirrorResponseHeaderDictionary(); 71 72 MirrorResponseHeaderDictionary dictionary; 73 for (std::vector<std::string>::iterator i = fields.begin(); 74 i != fields.end(); ++i) { 75 std::string field(*i); 76 std::vector<std::string> tokens; 77 if (Tokenize(field, "=", &tokens) != 2) { 78 DLOG(WARNING) << "Unexpected GAIA header field '" << field << "'."; 79 continue; 80 } 81 dictionary[tokens[0]] = tokens[1]; 82 } 83 return dictionary; 84 } 85 86 // Returns the parameters contained in the X-Chrome-Manage-Accounts response 87 // header. 88 signin::ManageAccountsParams BuildManageAccountsParams( 89 const std::string& header_value) { 90 signin::ManageAccountsParams params; 91 MirrorResponseHeaderDictionary header_dictionary = 92 ParseMirrorResponseHeader(header_value); 93 MirrorResponseHeaderDictionary::const_iterator it = header_dictionary.begin(); 94 for (; it != header_dictionary.end(); ++it) { 95 const std::string key_name(it->first); 96 if (key_name == kServiceTypeAttrName) { 97 params.service_type = 98 GetGAIAServiceTypeFromHeader(header_dictionary[kServiceTypeAttrName]); 99 } else if (key_name == kEmailAttrName) { 100 params.email = header_dictionary[kEmailAttrName]; 101 } else if (key_name == kIsSamlAttrName) { 102 params.is_saml = header_dictionary[kIsSamlAttrName] == "true"; 103 } else if (key_name == kContinueUrlAttrName) { 104 params.continue_url = header_dictionary[kContinueUrlAttrName]; 105 } else if (key_name == kIsSameTabAttrName) { 106 params.is_same_tab = header_dictionary[kIsSameTabAttrName] == "true"; 107 } else { 108 DLOG(WARNING) << "Unexpected GAIA header attribute '" << key_name << "'."; 109 } 110 } 111 return params; 112 } 113 114 #if !defined(OS_IOS) 115 // Processes the mirror response header on the UI thread. Currently depending 116 // on the value of |header_value|, it either shows the profile avatar menu, or 117 // opens an incognito window/tab. 118 void ProcessMirrorHeaderUIThread( 119 int child_id, int route_id, 120 signin::ManageAccountsParams manage_accounts_params) { 121 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 122 123 signin::GAIAServiceType service_type = manage_accounts_params.service_type; 124 DCHECK_NE(signin::GAIA_SERVICE_TYPE_NONE, service_type); 125 126 content::WebContents* web_contents = 127 tab_util::GetWebContentsByID(child_id, route_id); 128 if (!web_contents) 129 return; 130 131 #if !defined(OS_ANDROID) 132 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 133 if (browser) { 134 BrowserWindow::AvatarBubbleMode bubble_mode; 135 switch (service_type) { 136 case signin::GAIA_SERVICE_TYPE_INCOGNITO: 137 chrome::NewIncognitoWindow(browser); 138 return; 139 case signin::GAIA_SERVICE_TYPE_ADDSESSION: 140 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN; 141 break; 142 case signin::GAIA_SERVICE_TYPE_REAUTH: 143 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH; 144 break; 145 default: 146 bubble_mode = BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT; 147 } 148 browser->window()->ShowAvatarBubbleFromAvatarButton( 149 bubble_mode, manage_accounts_params); 150 } 151 #else // defined(OS_ANDROID) 152 if (service_type == signin::GAIA_SERVICE_TYPE_INCOGNITO) { 153 web_contents->OpenURL(content::OpenURLParams( 154 GURL(chrome::kChromeUINativeNewTabURL), content::Referrer(), 155 OFF_THE_RECORD, content::PAGE_TRANSITION_AUTO_TOPLEVEL, false)); 156 } else { 157 AccountManagementScreenHelper::OpenAccountManagementScreen( 158 Profile::FromBrowserContext(web_contents->GetBrowserContext()), 159 service_type); 160 } 161 #endif // OS_ANDROID 162 } 163 #endif // !defined(OS_IOS) 164 165 bool IsDriveOrigin(const GURL& url) { 166 if (!url.SchemeIsSecure()) 167 return false; 168 169 const GURL kGoogleDriveURL("https://drive.google.com"); 170 const GURL kGoogleDocsURL("https://docs.google.com"); 171 return url == kGoogleDriveURL || url == kGoogleDocsURL; 172 } 173 174 } // empty namespace 175 176 namespace signin { 177 178 ManageAccountsParams::ManageAccountsParams() : 179 service_type(GAIA_SERVICE_TYPE_NONE), 180 email(""), 181 is_saml(false), 182 continue_url(""), 183 is_same_tab(false), 184 child_id(0), 185 route_id(0) {} 186 187 bool AppendMirrorRequestHeaderIfPossible( 188 net::URLRequest* request, 189 const GURL& redirect_url, 190 ProfileIOData* io_data, 191 int child_id, 192 int route_id) { 193 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 194 195 if (io_data->IsOffTheRecord() || 196 io_data->google_services_username()->GetValue().empty()) { 197 return false; 198 } 199 200 // Only set the header for Drive always, and other Google properties if 201 // new-profile-management is enabled. 202 // Vasquette, which is integrated with most Google properties, needs the 203 // header to redirect certain user actions to Chrome native UI. Drive needs 204 // the header to tell if the current user is connected. The drive path is a 205 // temporary workaround until the more generic chrome.principals API is 206 // available. 207 const GURL& url = redirect_url.is_empty() ? request->url() : redirect_url; 208 GURL origin(url.GetOrigin()); 209 bool is_new_profile_management = switches::IsNewProfileManagement(); 210 bool is_google_url = 211 !switches::IsEnableWebBasedSignin() && 212 is_new_profile_management && 213 (google_util::IsGoogleDomainUrl( 214 url, 215 google_util::ALLOW_SUBDOMAIN, 216 google_util::DISALLOW_NON_STANDARD_PORTS) || 217 google_util::IsYoutubeDomainUrl( 218 url, 219 google_util::ALLOW_SUBDOMAIN, 220 google_util::DISALLOW_NON_STANDARD_PORTS)); 221 if (!is_google_url && !IsDriveOrigin(origin)) 222 return false; 223 224 std::string account_id(io_data->google_services_account_id()->GetValue()); 225 226 int profile_mode_mask = PROFILE_MODE_DEFAULT; 227 if (io_data->incognito_availibility()->GetValue() == 228 IncognitoModePrefs::DISABLED || 229 IncognitoModePrefs::ArePlatformParentalControlsEnabledCached()) { 230 profile_mode_mask |= PROFILE_MODE_INCOGNITO_DISABLED; 231 } 232 233 // TODO(guohui): needs to make a new flag for enabling account consistency. 234 std::string header_value(base::StringPrintf("%s=%s,%s=%s,%s=%s", 235 kGaiaIdAttrName, account_id.c_str(), 236 kProfileModeAttrName, base::IntToString(profile_mode_mask).c_str(), 237 kEnableAccountConsistencyAttrName, 238 is_new_profile_management ? "true" : "false")); 239 request->SetExtraRequestHeaderByName( 240 kChromeConnectedHeader, header_value, false); 241 return true; 242 } 243 244 void ProcessMirrorResponseHeaderIfExists( 245 net::URLRequest* request, 246 ProfileIOData* io_data, 247 int child_id, 248 int route_id) { 249 #if defined(OS_IOS) 250 NOTREACHED(); 251 #else 252 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 253 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin())) 254 return; 255 256 std::string header_value; 257 if (!request->response_headers()->GetNormalizedHeader( 258 kChromeManageAccountsHeader, &header_value)) { 259 return; 260 } 261 262 DCHECK(switches::IsNewProfileManagement() && !io_data->IsOffTheRecord()); 263 ManageAccountsParams params(BuildManageAccountsParams(header_value)); 264 if (params.service_type == GAIA_SERVICE_TYPE_NONE) 265 return; 266 267 params.child_id = child_id; 268 params.route_id = route_id; 269 content::BrowserThread::PostTask( 270 content::BrowserThread::UI, FROM_HERE, 271 base::Bind(ProcessMirrorHeaderUIThread, child_id, route_id, params)); 272 #endif // defined(OS_IOS) 273 } 274 275 } // namespace signin 276