Home | History | Annotate | Download | only in signin
      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