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 "?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_ = GaiaUrls::GetInstance()->oauth2_auth_url().Resolve( 41 base::StringPrintf(kOAuth2AuthorizeFormat, 42 oauth2_info.client_id.c_str(), 43 net::EscapeUrlEncodedData( 44 JoinString(oauth2_info.scopes, ' '), true).c_str(), 45 extension_id.c_str(), 46 redirect_scheme_.c_str(), 47 extension_id.c_str(), 48 locale.c_str())); 49 } 50 51 GaiaWebAuthFlow::~GaiaWebAuthFlow() { 52 if (web_flow_) 53 web_flow_.release()->DetachDelegateAndDelete(); 54 } 55 56 void GaiaWebAuthFlow::Start() { 57 ubertoken_fetcher_.reset(new UbertokenFetcher(profile_, this)); 58 ubertoken_fetcher_->StartFetchingToken(); 59 } 60 61 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) { 62 const char kMergeSessionQueryFormat[] = "?uberauth=%s&" 63 "continue=%s&" 64 "source=appsv2"; 65 66 std::string merge_query = base::StringPrintf( 67 kMergeSessionQueryFormat, 68 net::EscapeUrlEncodedData(token, true).c_str(), 69 net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str()); 70 GURL merge_url( 71 GaiaUrls::GetInstance()->merge_session_url().Resolve(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 } // namespace extensions 176