• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/debug/trace_event.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.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_urls.h"
19 #include "net/base/escape.h"
20 
21 namespace extensions {
22 
GaiaWebAuthFlow(Delegate * delegate,Profile * profile,const ExtensionTokenKey * token_key,const std::string & oauth2_client_id,const std::string & locale)23 GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
24                                  Profile* profile,
25                                  const ExtensionTokenKey* token_key,
26                                  const std::string& oauth2_client_id,
27                                  const std::string& locale)
28     : delegate_(delegate),
29       profile_(profile),
30       account_id_(token_key->account_id) {
31   TRACE_EVENT_ASYNC_BEGIN2("identity",
32                            "GaiaWebAuthFlow",
33                            this,
34                            "extension_id",
35                            token_key->extension_id,
36                            "account_id",
37                            token_key->account_id);
38 
39   const char kOAuth2RedirectPathFormat[] = "/%s#";
40   const char kOAuth2AuthorizeFormat[] =
41       "?response_type=token&approval_prompt=force&authuser=0&"
42       "client_id=%s&"
43       "scope=%s&"
44       "origin=chrome-extension://%s/&"
45       "redirect_uri=%s:/%s&"
46       "hl=%s";
47   // Additional parameters to pass if device_id is enabled.
48   const char kOAuth2AuthorizeFormatDeviceIdAddendum[] =
49       "&device_id=%s&"
50       "device_type=chrome";
51 
52   std::vector<std::string> scopes(token_key->scopes.begin(),
53                                   token_key->scopes.end());
54   std::vector<std::string> client_id_parts;
55   base::SplitString(oauth2_client_id, '.', &client_id_parts);
56   std::reverse(client_id_parts.begin(), client_id_parts.end());
57   redirect_scheme_ = JoinString(client_id_parts, '.');
58   std::string signin_scoped_device_id;
59   // profile_ can be nullptr in unittests.
60   SigninClient* signin_client =
61       profile_ ? ChromeSigninClientFactory::GetForProfile(profile_) : nullptr;
62   if (signin_client)
63     signin_scoped_device_id = signin_client->GetSigninScopedDeviceId();
64 
65   redirect_path_prefix_ = base::StringPrintf(kOAuth2RedirectPathFormat,
66                                              token_key->extension_id.c_str());
67 
68   std::string oauth2_authorize_params = base::StringPrintf(
69       kOAuth2AuthorizeFormat,
70       oauth2_client_id.c_str(),
71       net::EscapeUrlEncodedData(JoinString(scopes, ' '), true).c_str(),
72       token_key->extension_id.c_str(),
73       redirect_scheme_.c_str(),
74       token_key->extension_id.c_str(),
75       locale.c_str());
76   if (!signin_scoped_device_id.empty()) {
77     oauth2_authorize_params += base::StringPrintf(
78         kOAuth2AuthorizeFormatDeviceIdAddendum,
79         net::EscapeUrlEncodedData(signin_scoped_device_id, true).c_str());
80   }
81   auth_url_ = GaiaUrls::GetInstance()->oauth2_auth_url().Resolve(
82       oauth2_authorize_params);
83 }
84 
~GaiaWebAuthFlow()85 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
86   TRACE_EVENT_ASYNC_END0("identity", "GaiaWebAuthFlow", this);
87 
88   if (web_flow_)
89     web_flow_.release()->DetachDelegateAndDelete();
90 }
91 
Start()92 void GaiaWebAuthFlow::Start() {
93   ProfileOAuth2TokenService* token_service =
94       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
95   ubertoken_fetcher_.reset(new UbertokenFetcher(token_service,
96                                                 this,
97                                                 profile_->GetRequestContext()));
98   ubertoken_fetcher_->StartFetchingToken(account_id_);
99 }
100 
OnUbertokenSuccess(const std::string & token)101 void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
102   TRACE_EVENT_ASYNC_STEP_PAST0(
103       "identity", "GaiaWebAuthFlow", this, "OnUbertokenSuccess");
104 
105   const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
106                                           "continue=%s&"
107                                           "source=appsv2";
108 
109   std::string merge_query = base::StringPrintf(
110       kMergeSessionQueryFormat,
111       net::EscapeUrlEncodedData(token, true).c_str(),
112       net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str());
113   GURL merge_url(
114       GaiaUrls::GetInstance()->merge_session_url().Resolve(merge_query));
115 
116   web_flow_ = CreateWebAuthFlow(merge_url);
117   web_flow_->Start();
118 }
119 
OnUbertokenFailure(const GoogleServiceAuthError & error)120 void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
121   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
122                                "GaiaWebAuthFlow",
123                                this,
124                                "OnUbertokenSuccess",
125                                "error",
126                                error.ToString());
127 
128   DVLOG(1) << "OnUbertokenFailure: " << error.error_message();
129   delegate_->OnGaiaFlowFailure(
130       GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
131 }
132 
OnAuthFlowFailure(WebAuthFlow::Failure failure)133 void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
134   GaiaWebAuthFlow::Failure gaia_failure;
135 
136   switch (failure) {
137     case WebAuthFlow::WINDOW_CLOSED:
138       gaia_failure = GaiaWebAuthFlow::WINDOW_CLOSED;
139       break;
140     case WebAuthFlow::LOAD_FAILED:
141       DVLOG(1) << "OnAuthFlowFailure LOAD_FAILED";
142       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
143       break;
144     default:
145       NOTREACHED() << "Unexpected error from web auth flow: " << failure;
146       gaia_failure = GaiaWebAuthFlow::LOAD_FAILED;
147       break;
148   }
149 
150   TRACE_EVENT_ASYNC_STEP_PAST1("identity",
151                                "GaiaWebAuthFlow",
152                                this,
153                                "OnAuthFlowFailure",
154                                "error",
155                                gaia_failure);
156 
157   delegate_->OnGaiaFlowFailure(
158       gaia_failure,
159       GoogleServiceAuthError(GoogleServiceAuthError::NONE),
160       std::string());
161 }
162 
OnAuthFlowURLChange(const GURL & url)163 void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
164   TRACE_EVENT_ASYNC_STEP_PAST0("identity",
165                                "GaiaWebAuthFlow",
166                                this,
167                                "OnAuthFlowURLChange");
168 
169   const char kOAuth2RedirectAccessTokenKey[] = "access_token";
170   const char kOAuth2RedirectErrorKey[] = "error";
171   const char kOAuth2ExpiresInKey[] = "expires_in";
172 
173   // The format of the target URL is:
174   //     reversed.oauth.client.id:/extensionid#access_token=TOKEN
175   //
176   // Because there is no double slash, everything after the scheme is
177   // interpreted as a path, including the fragment.
178 
179   if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
180       StartsWithASCII(url.GetContent(), redirect_path_prefix_, true)) {
181     web_flow_.release()->DetachDelegateAndDelete();
182 
183     std::string fragment = url.GetContent().substr(
184         redirect_path_prefix_.length(), std::string::npos);
185     base::StringPairs pairs;
186     base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
187     std::string access_token;
188     std::string error;
189     std::string expiration;
190 
191     for (base::StringPairs::iterator it = pairs.begin();
192          it != pairs.end();
193          ++it) {
194       if (it->first == kOAuth2RedirectAccessTokenKey)
195         access_token = it->second;
196       else if (it->first == kOAuth2RedirectErrorKey)
197         error = it->second;
198       else if (it->first == kOAuth2ExpiresInKey)
199         expiration = it->second;
200     }
201 
202     if (access_token.empty() && error.empty()) {
203       delegate_->OnGaiaFlowFailure(
204           GaiaWebAuthFlow::INVALID_REDIRECT,
205           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
206           std::string());
207     } else if (!error.empty()) {
208       delegate_->OnGaiaFlowFailure(
209           GaiaWebAuthFlow::OAUTH_ERROR,
210           GoogleServiceAuthError(GoogleServiceAuthError::NONE),
211           error);
212     } else {
213       delegate_->OnGaiaFlowCompleted(access_token, expiration);
214     }
215   }
216 }
217 
OnAuthFlowTitleChange(const std::string & title)218 void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
219   // On the final page the title will be "Loading <redirect-url>".
220   // Treat it as though we'd really been redirected to <redirect-url>.
221   const char kRedirectPrefix[] = "Loading ";
222   std::string prefix(kRedirectPrefix);
223 
224   if (StartsWithASCII(title, prefix, true)) {
225     GURL url(title.substr(prefix.length(), std::string::npos));
226     if (url.is_valid())
227       OnAuthFlowURLChange(url);
228   }
229 }
230 
CreateWebAuthFlow(GURL url)231 scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
232   return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
233                                                  profile_,
234                                                  url,
235                                                  WebAuthFlow::INTERACTIVE));
236 }
237 
238 }  // namespace extensions
239