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 "base/trace_event/trace_event.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/signin/chrome_signin_client_factory.h"
14 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
15 #include "chrome/browser/signin/signin_manager_factory.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_manager.h"
18 #include "google_apis/gaia/gaia_constants.h"
19 #include "google_apis/gaia/gaia_urls.h"
20 #include "net/base/escape.h"
22 namespace extensions
{
24 GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate
* delegate
,
26 const ExtensionTokenKey
* token_key
,
27 const std::string
& oauth2_client_id
,
28 const std::string
& locale
)
29 : delegate_(delegate
),
31 account_id_(token_key
->account_id
) {
32 TRACE_EVENT_ASYNC_BEGIN2("identity",
36 token_key
->extension_id
,
38 token_key
->account_id
);
40 const char kOAuth2RedirectPathFormat
[] = "/%s#";
41 const char kOAuth2AuthorizeFormat
[] =
42 "?response_type=token&approval_prompt=force&authuser=0&"
45 "origin=chrome-extension://%s/&"
46 "redirect_uri=%s:/%s&"
48 // Additional parameters to pass if device_id is enabled.
49 const char kOAuth2AuthorizeFormatDeviceIdAddendum
[] =
53 std::vector
<std::string
> scopes(token_key
->scopes
.begin(),
54 token_key
->scopes
.end());
55 std::vector
<std::string
> client_id_parts
= base::SplitString(
56 oauth2_client_id
, ".", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
57 std::reverse(client_id_parts
.begin(), client_id_parts
.end());
58 redirect_scheme_
= base::JoinString(client_id_parts
, ".");
59 std::string signin_scoped_device_id
;
60 // profile_ can be nullptr in unittests.
61 SigninClient
* signin_client
=
62 profile_
? ChromeSigninClientFactory::GetForProfile(profile_
) : nullptr;
64 signin_scoped_device_id
= signin_client
->GetSigninScopedDeviceId();
66 redirect_path_prefix_
= base::StringPrintf(kOAuth2RedirectPathFormat
,
67 token_key
->extension_id
.c_str());
69 std::string oauth2_authorize_params
= base::StringPrintf(
70 kOAuth2AuthorizeFormat
,
71 oauth2_client_id
.c_str(),
72 net::EscapeUrlEncodedData(base::JoinString(scopes
, " "), true).c_str(),
73 token_key
->extension_id
.c_str(),
74 redirect_scheme_
.c_str(),
75 token_key
->extension_id
.c_str(),
77 if (!signin_scoped_device_id
.empty()) {
78 oauth2_authorize_params
+= base::StringPrintf(
79 kOAuth2AuthorizeFormatDeviceIdAddendum
,
80 net::EscapeUrlEncodedData(signin_scoped_device_id
, true).c_str());
82 auth_url_
= GaiaUrls::GetInstance()->oauth2_auth_url().Resolve(
83 oauth2_authorize_params
);
86 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
87 TRACE_EVENT_ASYNC_END0("identity", "GaiaWebAuthFlow", this);
90 web_flow_
.release()->DetachDelegateAndDelete();
93 void GaiaWebAuthFlow::Start() {
94 ProfileOAuth2TokenService
* token_service
=
95 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_
);
96 ubertoken_fetcher_
.reset(new UbertokenFetcher(token_service
,
98 GaiaConstants::kChromeSource
,
99 profile_
->GetRequestContext()));
100 ubertoken_fetcher_
->StartFetchingToken(account_id_
);
103 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string
& token
) {
104 TRACE_EVENT_ASYNC_STEP_PAST0(
105 "identity", "GaiaWebAuthFlow", this, "OnUbertokenSuccess");
107 const char kMergeSessionQueryFormat
[] = "?uberauth=%s&"
111 std::string merge_query
= base::StringPrintf(
112 kMergeSessionQueryFormat
,
113 net::EscapeUrlEncodedData(token
, true).c_str(),
114 net::EscapeUrlEncodedData(auth_url_
.spec(), true).c_str());
116 GaiaUrls::GetInstance()->merge_session_url().Resolve(merge_query
));
118 web_flow_
= CreateWebAuthFlow(merge_url
);
122 void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError
& error
) {
123 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
126 "OnUbertokenSuccess",
130 DVLOG(1) << "OnUbertokenFailure: " << error
.error_message();
131 delegate_
->OnGaiaFlowFailure(
132 GaiaWebAuthFlow::SERVICE_AUTH_ERROR
, error
, std::string());
135 void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure
) {
136 GaiaWebAuthFlow::Failure gaia_failure
;
139 case WebAuthFlow::WINDOW_CLOSED
:
140 gaia_failure
= GaiaWebAuthFlow::WINDOW_CLOSED
;
142 case WebAuthFlow::LOAD_FAILED
:
143 DVLOG(1) << "OnAuthFlowFailure LOAD_FAILED";
144 gaia_failure
= GaiaWebAuthFlow::LOAD_FAILED
;
147 NOTREACHED() << "Unexpected error from web auth flow: " << failure
;
148 gaia_failure
= GaiaWebAuthFlow::LOAD_FAILED
;
152 TRACE_EVENT_ASYNC_STEP_PAST1("identity",
159 delegate_
->OnGaiaFlowFailure(
161 GoogleServiceAuthError(GoogleServiceAuthError::NONE
),
165 void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL
& url
) {
166 TRACE_EVENT_ASYNC_STEP_PAST0("identity",
169 "OnAuthFlowURLChange");
171 const char kOAuth2RedirectAccessTokenKey
[] = "access_token";
172 const char kOAuth2RedirectErrorKey
[] = "error";
173 const char kOAuth2ExpiresInKey
[] = "expires_in";
175 // The format of the target URL is:
176 // reversed.oauth.client.id:/extensionid#access_token=TOKEN
178 // Because there is no double slash, everything after the scheme is
179 // interpreted as a path, including the fragment.
181 if (url
.scheme() == redirect_scheme_
&& !url
.has_host() && !url
.has_port() &&
182 base::StartsWith(url
.GetContent(), redirect_path_prefix_
,
183 base::CompareCase::SENSITIVE
)) {
184 web_flow_
.release()->DetachDelegateAndDelete();
186 std::string fragment
= url
.GetContent().substr(
187 redirect_path_prefix_
.length(), std::string::npos
);
188 base::StringPairs pairs
;
189 base::SplitStringIntoKeyValuePairs(fragment
, '=', '&', &pairs
);
190 std::string access_token
;
192 std::string expiration
;
194 for (base::StringPairs::iterator it
= pairs
.begin();
197 if (it
->first
== kOAuth2RedirectAccessTokenKey
)
198 access_token
= it
->second
;
199 else if (it
->first
== kOAuth2RedirectErrorKey
)
201 else if (it
->first
== kOAuth2ExpiresInKey
)
202 expiration
= it
->second
;
205 if (access_token
.empty() && error
.empty()) {
206 delegate_
->OnGaiaFlowFailure(
207 GaiaWebAuthFlow::INVALID_REDIRECT
,
208 GoogleServiceAuthError(GoogleServiceAuthError::NONE
),
210 } else if (!error
.empty()) {
211 delegate_
->OnGaiaFlowFailure(
212 GaiaWebAuthFlow::OAUTH_ERROR
,
213 GoogleServiceAuthError(GoogleServiceAuthError::NONE
),
216 delegate_
->OnGaiaFlowCompleted(access_token
, expiration
);
221 void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string
& title
) {
222 // On the final page the title will be "Loading <redirect-url>".
223 // Treat it as though we'd really been redirected to <redirect-url>.
224 const char kRedirectPrefix
[] = "Loading ";
225 std::string
prefix(kRedirectPrefix
);
227 if (base::StartsWith(title
, prefix
, base::CompareCase::SENSITIVE
)) {
228 GURL
url(title
.substr(prefix
.length(), std::string::npos
));
230 OnAuthFlowURLChange(url
);
234 scoped_ptr
<WebAuthFlow
> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url
) {
235 return scoped_ptr
<WebAuthFlow
>(new WebAuthFlow(this,
238 WebAuthFlow::INTERACTIVE
));
241 } // namespace extensions