Home | History | Annotate | Download | only in identity
      1 // Copyright (c) 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/extensions/api/identity/gaia_web_auth_flow.h"
      6 
      7 #include "base/strings/string_number_conversions.h"
      8 #include "base/strings/string_split.h"
      9 #include "base/strings/string_util.h"
     10 #include "base/strings/stringprintf.h"
     11 #include "chrome/browser/profiles/profile.h"
     12 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     13 #include "chrome/browser/signin/signin_manager_factory.h"
     14 #include "components/signin/core/browser/profile_oauth2_token_service.h"
     15 #include "components/signin/core/browser/signin_manager.h"
     16 #include "google_apis/gaia/gaia_urls.h"
     17 #include "net/base/escape.h"
     18 
     19 namespace extensions {
     20 
     21 GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
     22                                  Profile* profile,
     23                                  const ExtensionTokenKey* token_key,
     24                                  const std::string& oauth2_client_id,
     25                                  const std::string& locale)
     26     : delegate_(delegate),
     27       profile_(profile),
     28       account_id_(token_key->account_id) {
     29   const char kOAuth2RedirectPathFormat[] = "/%s#";
     30   const char kOAuth2AuthorizeFormat[] =
     31       "?response_type=token&approval_prompt=force&authuser=0&"
     32       "client_id=%s&"
     33       "scope=%s&"
     34       "origin=chrome-extension://%s/&"
     35       "redirect_uri=%s:/%s&"
     36       "hl=%s";
     37 
     38   std::vector<std::string> scopes(token_key->scopes.begin(),
     39                                   token_key->scopes.end());
     40   std::vector<std::string> client_id_parts;
     41   base::SplitString(oauth2_client_id, '.', &client_id_parts);
     42   std::reverse(client_id_parts.begin(), client_id_parts.end());
     43   redirect_scheme_ = JoinString(client_id_parts, '.');
     44 
     45   redirect_path_prefix_ = base::StringPrintf(kOAuth2RedirectPathFormat,
     46                                              token_key->extension_id.c_str());
     47 
     48   auth_url_ =
     49       GaiaUrls::GetInstance()->oauth2_auth_url().Resolve(base::StringPrintf(
     50           kOAuth2AuthorizeFormat,
     51           oauth2_client_id.c_str(),
     52           net::EscapeUrlEncodedData(JoinString(scopes, ' '), true).c_str(),
     53           token_key->extension_id.c_str(),
     54           redirect_scheme_.c_str(),
     55           token_key->extension_id.c_str(),
     56           locale.c_str()));
     57 }
     58 
     59 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
     60   if (web_flow_)
     61     web_flow_.release()->DetachDelegateAndDelete();
     62 }
     63 
     64 void GaiaWebAuthFlow::Start() {
     65   ProfileOAuth2TokenService* token_service =
     66       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
     67   ubertoken_fetcher_.reset(new UbertokenFetcher(token_service,
     68                                                 this,
     69                                                 profile_->GetRequestContext()));
     70   ubertoken_fetcher_->StartFetchingToken(account_id_);
     71 }
     72 
     73 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
     74   const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
     75                                           "continue=%s&"
     76                                           "source=appsv2";
     77 
     78   std::string merge_query = base::StringPrintf(
     79       kMergeSessionQueryFormat,
     80       net::EscapeUrlEncodedData(token, true).c_str(),
     81       net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str());
     82   GURL merge_url(
     83       GaiaUrls::GetInstance()->merge_session_url().Resolve(merge_query));
     84 
     85   web_flow_ = CreateWebAuthFlow(merge_url);
     86   web_flow_->Start();
     87 }
     88 
     89 void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
     90   DVLOG(1) << "OnUbertokenFailure: " << error.error_message();
     91   delegate_->OnGaiaFlowFailure(
     92       GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
     93 }
     94 
     95 void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
     96   GaiaWebAuthFlow::Failure gaia_failure;
     97 
     98   switch (failure) {
     99     case WebAuthFlow::WINDOW_CLOSED:
    100       gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED;
    101       break;
    102     case WebAuthFlow::LOAD_FAILED:
    103       DVLOG(1) << "OnAuthFlowFailure LOAD_FAILED";
    104       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
    105       break;
    106     default:
    107       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
    108       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
    109       break;
    110   }
    111 
    112   delegate_->OnGaiaFlowFailure(
    113       gaia_failure,
    114       GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    115       std::string());
    116 }
    117 
    118 void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
    119   const char kOAuth2RedirectAccessTokenKey[] = "access_token";
    120   const char kOAuth2RedirectErrorKey[] = "error";
    121   const char kOAuth2ExpiresInKey[] = "expires_in";
    122 
    123   // The format of the target URL is:
    124   //     reversed.oauth.client.id:/extensionid#access_token=TOKEN
    125   //
    126   // Because there is no double slash, everything after the scheme is
    127   // interpreted as a path, including the fragment.
    128 
    129   if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
    130       StartsWithASCII(url.GetContent(), redirect_path_prefix_, true)) {
    131     web_flow_.release()->DetachDelegateAndDelete();
    132 
    133     std::string fragment = url.GetContent().substr(
    134         redirect_path_prefix_.length(), std::string::npos);
    135     std::vector<std::pair<std::string, std::string> > pairs;
    136     base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
    137     std::string access_token;
    138     std::string error;
    139     std::string expiration;
    140 
    141     for (std::vector<std::pair<std::string, std::string> >::iterator
    142              it = pairs.begin();
    143          it != pairs.end();
    144          ++it) {
    145       if (it->first == kOAuth2RedirectAccessTokenKey)
    146         access_token = it->second;
    147       else if (it->first == kOAuth2RedirectErrorKey)
    148         error = it->second;
    149       else if (it->first == kOAuth2ExpiresInKey)
    150         expiration = it->second;
    151     }
    152 
    153     if (access_token.empty() && error.empty()) {
    154       delegate_->OnGaiaFlowFailure(
    155           GaiaWebAuthFlow::INVALID_REDIRECT,
    156           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    157           std::string());
    158     } else if (!error.empty()) {
    159       delegate_->OnGaiaFlowFailure(
    160           GaiaWebAuthFlow::OAUTH_ERROR,
    161           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
    162           error);
    163     } else {
    164       delegate_->OnGaiaFlowCompleted(access_token, expiration);
    165     }
    166   }
    167 }
    168 
    169 void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
    170   // On the final page the title will be "Loading <redirect-url>".
    171   // Treat it as though we'd really been redirected to <redirect-url>.
    172   const char kRedirectPrefix[] = "Loading ";
    173   std::string prefix(kRedirectPrefix);
    174 
    175   if (StartsWithASCII(title, prefix, true)) {
    176     GURL url(title.substr(prefix.length(), std::string::npos));
    177     if (url.is_valid())
    178       OnAuthFlowURLChange(url);
    179   }
    180 }
    181 
    182 scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
    183   return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
    184                                                  profile_,
    185                                                  url,
    186                                                  WebAuthFlow::INTERACTIVE));
    187 }
    188 
    189 }  // namespace extensions
    190