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