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