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.
5 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
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.h"
13 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
14 #include "chrome/browser/signin/signin_manager.h"
15 #include "chrome/browser/signin/signin_manager_factory.h"
16 #include "google_apis/gaia/gaia_urls.h"
17 #include "net/base/escape.h"
19 namespace extensions {
21 GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
23 const std::string& extension_id,
24 const OAuth2Info& oauth2_info,
25 const std::string& locale)
26 : delegate_(delegate),
28 const char kOAuth2RedirectPathFormat[] = "/%s#";
29 const char kOAuth2AuthorizeFormat[] =
30 "?response_type=token&approval_prompt=force&authuser=0&"
33 "origin=chrome-extension://%s/&"
34 "redirect_uri=%s:/%s&"
37 std::vector<std::string> client_id_parts;
38 base::SplitString(oauth2_info.client_id, '.', &client_id_parts);
39 std::reverse(client_id_parts.begin(), client_id_parts.end());
40 redirect_scheme_ = JoinString(client_id_parts, '.');
42 redirect_path_prefix_ =
43 base::StringPrintf(kOAuth2RedirectPathFormat, extension_id.c_str());
45 auth_url_ = GaiaUrls::GetInstance()->oauth2_auth_url().Resolve(
46 base::StringPrintf(kOAuth2AuthorizeFormat,
47 oauth2_info.client_id.c_str(),
48 net::EscapeUrlEncodedData(
49 JoinString(oauth2_info.scopes, ' '), true).c_str(),
51 redirect_scheme_.c_str(),
56 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
58 web_flow_.release()->DetachDelegateAndDelete();
61 void GaiaWebAuthFlow::Start() {
62 ProfileOAuth2TokenService* token_service =
63 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
64 ubertoken_fetcher_.reset(new UbertokenFetcher(token_service,
66 profile_->GetRequestContext()));
67 SigninManagerBase* signin_manager =
68 SigninManagerFactory::GetForProfile(profile_);
69 ubertoken_fetcher_->StartFetchingToken(
70 signin_manager->GetAuthenticatedAccountId());
73 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
74 const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
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());
83 GaiaUrls::GetInstance()->merge_session_url().Resolve(merge_query));
85 web_flow_ = CreateWebAuthFlow(merge_url);
89 void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
90 delegate_->OnGaiaFlowFailure(
91 GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
94 void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
95 GaiaWebAuthFlow::Failure gaia_failure;
98 case WebAuthFlow::WINDOW_CLOSED:
99 gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED;
101 case WebAuthFlow::LOAD_FAILED:
102 gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
105 NOTREACHED() << "Unexpected error from web auth flow: " << failure;
106 gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
110 delegate_->OnGaiaFlowFailure(
112 GoogleServiceAuthError(GoogleServiceAuthError::NONE),
116 void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
117 const char kOAuth2RedirectAccessTokenKey[] = "access_token";
118 const char kOAuth2RedirectErrorKey[] = "error";
119 const char kOAuth2ExpiresInKey[] = "expires_in";
121 // The format of the target URL is:
122 // reversed.oauth.client.id:/extensionid#access_token=TOKEN
124 // Because there is no double slash, everything after the scheme is
125 // interpreted as a path, including the fragment.
127 if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
128 StartsWithASCII(url.GetContent(), redirect_path_prefix_, true)) {
129 web_flow_.release()->DetachDelegateAndDelete();
131 std::string fragment = url.GetContent().substr(
132 redirect_path_prefix_.length(), std::string::npos);
133 std::vector<std::pair<std::string, std::string> > pairs;
134 base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
135 std::string access_token;
137 std::string expiration;
139 for (std::vector<std::pair<std::string, std::string> >::iterator
143 if (it->first == kOAuth2RedirectAccessTokenKey)
144 access_token = it->second;
145 else if (it->first == kOAuth2RedirectErrorKey)
147 else if (it->first == kOAuth2ExpiresInKey)
148 expiration = it->second;
151 if (access_token.empty() && error.empty()) {
152 delegate_->OnGaiaFlowFailure(
153 GaiaWebAuthFlow::INVALID_REDIRECT,
154 GoogleServiceAuthError(GoogleServiceAuthError::NONE),
156 } else if (!error.empty()) {
157 delegate_->OnGaiaFlowFailure(
158 GaiaWebAuthFlow::OAUTH_ERROR,
159 GoogleServiceAuthError(GoogleServiceAuthError::NONE),
162 delegate_->OnGaiaFlowCompleted(access_token, expiration);
167 void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
168 // On the final page the title will be "Loading <redirect-url>".
169 // Treat it as though we'd really been redirected to <redirect-url>.
170 const char kRedirectPrefix[] = "Loading ";
171 std::string prefix(kRedirectPrefix);
173 if (StartsWithASCII(title, prefix, true)) {
174 GURL url(title.substr(prefix.length(), std::string::npos));
176 OnAuthFlowURLChange(url);
180 scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
181 return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
184 WebAuthFlow::INTERACTIVE));
187 } // namespace extensions