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
GaiaWebAuthFlow(Delegate * delegate,Profile * profile,const ExtensionTokenKey * token_key,const std::string & oauth2_client_id,const std::string & locale)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
~GaiaWebAuthFlow()59 GaiaWebAuthFlow::~GaiaWebAuthFlow() {
60 if (web_flow_)
61 web_flow_.release()->DetachDelegateAndDelete();
62 }
63
Start()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
OnUbertokenSuccess(const std::string & token)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
OnUbertokenFailure(const GoogleServiceAuthError & error)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
OnAuthFlowFailure(WebAuthFlow::Failure failure)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
OnAuthFlowURLChange(const GURL & url)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
OnAuthFlowTitleChange(const std::string & title)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
CreateWebAuthFlow(GURL url)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