| Index: google_apis/gaia/gaia_auth_fetcher.cc
 | 
| diff --git a/google_apis/gaia/gaia_auth_fetcher.cc b/google_apis/gaia/gaia_auth_fetcher.cc
 | 
| index 3b3c1902540489725987f862119e7e63f3a98f6a..224cb6c340bb202de347cc3ffdad24a8d0918600 100644
 | 
| --- a/google_apis/gaia/gaia_auth_fetcher.cc
 | 
| +++ b/google_apis/gaia/gaia_auth_fetcher.cc
 | 
| @@ -41,23 +41,39 @@ static bool CookiePartsContains(const std::vector<std::string>& parts,
 | 
|    return false;
 | 
|  }
 | 
|  
 | 
| -bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict,
 | 
| +// From the JSON string |data|, extract the |access_token| and |expires_in_secs|
 | 
| +// both of which must exist. If the |refresh_token| is non-NULL, then it also
 | 
| +// must exist and is extraced; if it's NULL, then no extraction is attempted.
 | 
| +bool ExtractOAuth2TokenPairResponse(const std::string& data,
 | 
|                                      std::string* refresh_token,
 | 
|                                      std::string* access_token,
 | 
|                                      int* expires_in_secs) {
 | 
| -  DCHECK(refresh_token);
 | 
|    DCHECK(access_token);
 | 
|    DCHECK(expires_in_secs);
 | 
|  
 | 
| -  if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) ||
 | 
| -      !dict->GetStringWithoutPathExpansion("access_token", access_token) ||
 | 
| +  scoped_ptr<base::Value> value(base::JSONReader::Read(data));
 | 
| +  if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
 | 
| +    return false;
 | 
| +
 | 
| +  base::DictionaryValue* dict =
 | 
| +        static_cast<base::DictionaryValue*>(value.get());
 | 
| +
 | 
| +  if (!dict->GetStringWithoutPathExpansion("access_token", access_token) ||
 | 
|        !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
 | 
|      return false;
 | 
|    }
 | 
|  
 | 
| +  // Refresh token may not be required.
 | 
| +  if (refresh_token) {
 | 
| +    if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token))
 | 
| +      return false;
 | 
| +  }
 | 
|    return true;
 | 
|  }
 | 
|  
 | 
| +const char kListIdpServiceRequested[] = "list_idp";
 | 
| +const char kGetTokenResponseRequested[] = "get_token";
 | 
| +
 | 
|  }  // namespace
 | 
|  
 | 
|  // TODO(chron): Add sourceless version of this formatter.
 | 
| @@ -192,6 +208,7 @@ GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
 | 
|            GaiaUrls::GetInstance()->ListAccountsURLWithSource(source)),
 | 
|        get_check_connection_info_url_(
 | 
|            GaiaUrls::GetInstance()->GetCheckConnectionInfoURLWithSource(source)),
 | 
| +      oauth2_iframe_url_(GaiaUrls::GetInstance()->oauth2_iframe_url()),
 | 
|        client_login_to_oauth2_gurl_(
 | 
|            GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
 | 
|        fetch_pending_(false) {}
 | 
| @@ -422,6 +439,45 @@ std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
 | 
|  }
 | 
|  
 | 
|  // static
 | 
| +std::string GaiaAuthFetcher::MakeListIDPSessionsBody(
 | 
| +    const std::string& scopes,
 | 
| +    const std::string& domain) {
 | 
| +  static const char getTokenResponseBodyFormat[] =
 | 
| +    "action=listSessions&"
 | 
| +    "client_id=%s&"
 | 
| +    "e=3100087&"  // temporarily enable the experiment.
 | 
| +    "origin=%s&"
 | 
| +    "scope=%s";
 | 
| +  std::string encoded_client_id = net::EscapeUrlEncodedData(
 | 
| +      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
 | 
| +  return base::StringPrintf(getTokenResponseBodyFormat,
 | 
| +                            encoded_client_id.c_str(),
 | 
| +                            domain.c_str(),
 | 
| +                            scopes.c_str());
 | 
| +}
 | 
| +
 | 
| +std::string GaiaAuthFetcher::MakeGetTokenResponseBody(
 | 
| +    const std::string& scopes,
 | 
| +    const std::string& domain,
 | 
| +    const std::string& login_hint) {
 | 
| +  static const char getTokenResponseBodyFormat[] =
 | 
| +    "action=issueToken&"
 | 
| +    "client_id=%s&"
 | 
| +    "login_hint=%s&"
 | 
| +    "origin=%s&"
 | 
| +    "e=3100087&"  // temporarily enable the experiment.
 | 
| +    "response_type=token&"
 | 
| +    "scope=%s";
 | 
| +  std::string encoded_client_id = net::EscapeUrlEncodedData(
 | 
| +      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
 | 
| +  return base::StringPrintf(getTokenResponseBodyFormat,
 | 
| +                            encoded_client_id.c_str(),
 | 
| +                            login_hint.c_str(),
 | 
| +                            domain.c_str(),
 | 
| +                            scopes.c_str());
 | 
| +}
 | 
| +
 | 
| +// static
 | 
|  void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
 | 
|                                                std::string* error,
 | 
|                                                std::string* error_url,
 | 
| @@ -484,6 +540,39 @@ bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
 | 
|    return false;
 | 
|  }
 | 
|  
 | 
| +// static
 | 
| +bool GaiaAuthFetcher::ParseListIdpSessionsResponse(const std::string& data,
 | 
| +                                                   std::string* login_hint) {
 | 
| +  DCHECK(login_hint);
 | 
| +
 | 
| +  scoped_ptr<base::Value> value(base::JSONReader::Read(data));
 | 
| +  if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
 | 
| +    return false;
 | 
| +
 | 
| +  base::DictionaryValue* dict =
 | 
| +        static_cast<base::DictionaryValue*>(value.get());
 | 
| +
 | 
| +  base::ListValue* sessionsList;
 | 
| +  if (!dict->GetList("sessions", &sessionsList))
 | 
| +    return false;
 | 
| +
 | 
| +  // Find the first login_hint present in any session.
 | 
| +  for (base::ListValue::iterator iter = sessionsList->begin();
 | 
| +       iter != sessionsList->end();
 | 
| +       iter++) {
 | 
| +    base::DictionaryValue* sessionDictionary;
 | 
| +    if (!(*iter)->GetAsDictionary(&sessionDictionary))
 | 
| +      continue;
 | 
| +
 | 
| +    if (sessionDictionary->GetString("login_hint", login_hint))
 | 
| +      break;
 | 
| +  }
 | 
| +
 | 
| +  if (login_hint->empty())
 | 
| +    return false;
 | 
| +  return true;
 | 
| +}
 | 
| +
 | 
|  void GaiaAuthFetcher::StartClientLogin(
 | 
|      const std::string& username,
 | 
|      const std::string& password,
 | 
| @@ -724,6 +813,40 @@ void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
 | 
|    fetcher_->Start();
 | 
|  }
 | 
|  
 | 
| +void GaiaAuthFetcher::StartListIDPSessions(const std::string& scopes,
 | 
| +                                           const std::string& domain) {
 | 
| +  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
 | 
| +
 | 
| +  request_body_ = MakeListIDPSessionsBody(scopes, domain);
 | 
| +  fetcher_.reset(CreateGaiaFetcher(getter_,
 | 
| +                                   request_body_,
 | 
| +                                   std::string(),
 | 
| +                                   oauth2_iframe_url_,
 | 
| +                                   net::LOAD_NORMAL,
 | 
| +                                   this));
 | 
| +  requested_service_ = kListIdpServiceRequested;
 | 
| +  fetch_pending_ = true;
 | 
| +  fetcher_->Start();
 | 
| +}
 | 
| +
 | 
| +void GaiaAuthFetcher::StartGetTokenResponse(const std::string& scopes,
 | 
| +                                            const std::string& domain,
 | 
| +                                            const std::string& login_hint) {
 | 
| +  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
 | 
| +
 | 
| +  request_body_ = MakeGetTokenResponseBody(scopes, domain, login_hint);
 | 
| +  fetcher_.reset(CreateGaiaFetcher(getter_,
 | 
| +                                   request_body_,
 | 
| +                                   std::string(),
 | 
| +                                   oauth2_iframe_url_,
 | 
| +                                   net::LOAD_NORMAL,
 | 
| +                                   this));
 | 
| +
 | 
| +  requested_service_ = kGetTokenResponseRequested;
 | 
| +  fetch_pending_ = true;
 | 
| +  fetcher_->Start();
 | 
| +}
 | 
| +
 | 
|  // static
 | 
|  GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
 | 
|      const std::string& data,
 | 
| @@ -834,13 +957,8 @@ void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
 | 
|  
 | 
|    bool success = false;
 | 
|    if (status.is_success() && response_code == net::HTTP_OK) {
 | 
| -    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
 | 
| -    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
 | 
| -      base::DictionaryValue* dict =
 | 
| -          static_cast<base::DictionaryValue*>(value.get());
 | 
| -      success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
 | 
| +      success = ExtractOAuth2TokenPairResponse(data, &refresh_token,
 | 
|                                                 &access_token, &expires_in_secs);
 | 
| -    }
 | 
|    }
 | 
|  
 | 
|    if (success) {
 | 
| @@ -934,6 +1052,48 @@ void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched(
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +void GaiaAuthFetcher::OnListIdpSessionsFetched(
 | 
| +    const std::string& data,
 | 
| +    const net::URLRequestStatus& status,
 | 
| +    int response_code) {
 | 
| +  if (status.is_success() && response_code == net::HTTP_OK) {
 | 
| +    DVLOG(1) << "ListIdpSessions successful!";
 | 
| +    std::string login_hint;
 | 
| +    if (ParseListIdpSessionsResponse(data, &login_hint)) {
 | 
| +      consumer_->OnListIdpSessionsSuccess(login_hint);
 | 
| +    } else {
 | 
| +      GoogleServiceAuthError auth_error(
 | 
| +          GoogleServiceAuthError::FromUnexpectedServiceResponse(
 | 
| +              "List Sessions response didn't contain a login_hint."));
 | 
| +      consumer_->OnListIdpSessionsError(auth_error);
 | 
| +    }
 | 
| +  } else {
 | 
| +    consumer_->OnListIdpSessionsError(GenerateAuthError(data, status));
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +void GaiaAuthFetcher::OnGetTokenResponseFetched(
 | 
| +    const std::string& data,
 | 
| +    const net::URLRequestStatus& status,
 | 
| +    int response_code) {
 | 
| +  std::string access_token;
 | 
| +  int expires_in_secs = 0;
 | 
| +  bool success = false;
 | 
| +  if (status.is_success() && response_code == net::HTTP_OK) {
 | 
| +    DVLOG(1) << "GetTokenResponse successful!";
 | 
| +    success = ExtractOAuth2TokenPairResponse(data, NULL,
 | 
| +                                             &access_token, &expires_in_secs);
 | 
| +  }
 | 
| +
 | 
| +  if (success) {
 | 
| +    consumer_->OnGetTokenResponseSuccess(
 | 
| +        GaiaAuthConsumer::ClientOAuthResult(std::string(), access_token,
 | 
| +                                            expires_in_secs));
 | 
| +  } else {
 | 
| +    consumer_->OnGetTokenResponseError(GenerateAuthError(data, status));
 | 
| +  }
 | 
| +}
 | 
| +
 | 
|  void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
 | 
|    fetch_pending_ = false;
 | 
|    // Some of the GAIA requests perform redirects, which results in the final
 | 
| @@ -977,6 +1137,13 @@ void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
 | 
|      OnListAccountsFetched(data, status, response_code);
 | 
|    } else if (url == get_check_connection_info_url_) {
 | 
|      OnGetCheckConnectionInfoFetched(data, status, response_code);
 | 
| +  } else if (url == oauth2_iframe_url_) {
 | 
| +    if (requested_service_ == kListIdpServiceRequested)
 | 
| +      OnListIdpSessionsFetched(data, status, response_code);
 | 
| +    else if (requested_service_ == kGetTokenResponseRequested)
 | 
| +      OnGetTokenResponseFetched(data, status, response_code);
 | 
| +    else
 | 
| +      NOTREACHED();
 | 
|    } else {
 | 
|      NOTREACHED();
 | 
|    }
 | 
| 
 |