Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(578)

Side by Side Diff: services/authentication/google_authentication_impl.cc

Issue 1466733002: Google OAuth Device Flow support for FNL (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 "services/authentication/google_authentication_impl.h"
6
7 #include "base/json/json_reader.h"
8 #include "base/json/json_writer.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/strings/string_piece.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_tokenizer.h"
13 #include "base/strings/string_util.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/threading/platform_thread.h"
16 #include "base/trace_event/trace_event.h"
17 #include "base/values.h"
18 #include "mojo/common/binding_set.h"
19 #include "mojo/data_pipe_utils/data_pipe_drainer.h"
20 #include "mojo/data_pipe_utils/data_pipe_utils.h"
21 #include "mojo/public/c/system/main.h"
22 #include "mojo/public/cpp/bindings/strong_binding.h"
23 #include "mojo/public/cpp/system/macros.h"
24 #include "mojo/services/network/interfaces/url_loader.mojom.h"
25 #include "services/authentication/auth_data.h"
26
27 namespace authentication {
28
29 // Mojo Shell OAuth2 Client configuration.
30 // TODO: These should be retrieved from a secure storage or a configuration file
31 // in the future.
32 char kMojoShellOAuth2ClientId[] =
33 "962611923869-3avg0b4vlisgjhin0l98dgp6d8sd634r.apps.googleusercontent.com";
34 char kMojoShellOAuth2ClientSecret[] = "41IxvPPAt1HyRoYw2hO84dRI";
35
36 // Query params used in Google OAuth2 handshake
37 const std::string kKeyValSeparator("=");
38 char kUrlQueryParamSeparator[] = "&";
39
40 char kOAuth2ClientIdParamName[] = "client_id";
41 char kOAuth2ClientSecretParamName[] = "client_secret";
42 char kOAuth2ScopeParamName[] = "scope";
43 char kOAuth2GrantTypeParamName[] = "grant_type";
44 char kOAuth2CodeParamName[] = "code";
45 char kOAuth2RefreshTokenParamName[] = "refresh_token";
46 char kOAuth2DeviceFlowGrantType[] = "http://oauth.net/grant_type/device/1.0";
47 char kOAuth2RefreshTokenGrantType[] = "refresh_token";
48
49 mojo::String ValueToString(const base::Value& value) {
50 if (value.IsType(base::Value::TYPE_STRING)) {
51 std::string value_string;
52 value.GetAsString(&value_string);
53 return value_string;
54 }
55 if (value.IsType(base::Value::TYPE_INTEGER)) {
56 int value_int;
57 value.GetAsInteger(&value_int);
58 return std::to_string(value_int);
59 }
60 if (value.IsType(base::Value::TYPE_BOOLEAN)) {
61 bool value_bool;
62 value.GetAsBoolean(&value_bool);
63 return value_bool ? "true" : "false";
64 }
65 if (!value.IsType(base::Value::TYPE_NULL)) {
66 LOG(ERROR) << "Unexpected JSON value (requires string or null): " << value;
67 }
68
69 return nullptr;
70 }
71
72 scoped_ptr<base::DictionaryValue> ParseOAuth2Response(
73 const std::string& response) {
74 if (response.empty()) {
75 return nullptr;
76 }
77
78 scoped_ptr<base::Value> root(base::JSONReader::Read(response));
79 if (!root || !root->IsType(base::Value::TYPE_DICTIONARY)) {
80 LOG(ERROR) << "Unexpected json response:" << std::endl << response;
81 return nullptr;
82 }
83
84 base::DictionaryValue::Iterator it(
85 *static_cast<base::DictionaryValue*>(root.get()));
qsr 2016/02/16 14:17:06 What is this for? It seems you already have a dict
ukode 2016/02/26 21:35:50 Some of the response values are ints, doubles and
86 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
87 std::string val;
88 while (!it.IsAtEnd()) {
89 val = ValueToString(it.value());
90 base::ReplaceChars(val, "\"", "", &val);
qsr 2016/02/16 14:17:06 Why isn't this done in ValueToString, for the Stri
ukode 2016/02/26 21:35:50 ValueToString is a very basic method that returns
91 dict->SetString(it.key(), val);
92 it.Advance();
93 }
94
95 return dict.Pass();
96 }
97
98 GoogleAuthenticationServiceImpl::GoogleAuthenticationServiceImpl(
99 mojo::InterfaceRequest<AuthenticationService> request,
100 const mojo::String app_url,
101 mojo::NetworkServicePtr& network_service,
102 mojo::files::DirectoryPtr& directory)
103 : binding_(this, request.Pass()),
104 app_url_(app_url),
105 network_service_(network_service),
106 weak_ptr_factory_(this) {
107 accounts_db_manager_ = new AccountsDbManager(directory.Pass());
qsr 2016/02/16 14:17:06 That looks like a leak.
ukode 2016/02/26 21:35:50 Fixed it.
108 }
109
110 GoogleAuthenticationServiceImpl::~GoogleAuthenticationServiceImpl() {}
111
112 void GoogleAuthenticationServiceImpl::GetOAuth2Token(
113 const mojo::String& username,
114 mojo::Array<mojo::String> scopes,
115 const GetOAuth2TokenCallback& callback) {
116 mojo::String user_data =
117 accounts_db_manager_->GetAccountDataForUser(username);
118 AuthData* auth_data = authentication::GetAuthDataFromString(user_data.get());
119
120 if ((auth_data == nullptr) || auth_data->persistent_credential.empty()) {
121 callback.Run(nullptr, "User grant not found");
122 return;
123 }
124
125 // TODO: Scopes are not used with the scoped refresh tokens. When we start
126 // supporting full login scoped tokens, then the scopes here gets used for
127 // Sidescoping.
128 std::string message;
129 message +=
130 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
131
132 message += kUrlQueryParamSeparator;
qsr 2016/02/16 14:17:06 You should have some helper method to generate que
ukode 2016/02/26 21:35:50 Acknowledged. Added one above.
133 message += kOAuth2ClientSecretParamName + kKeyValSeparator +
134 kMojoShellOAuth2ClientSecret;
135
136 message += kUrlQueryParamSeparator;
137 message += kOAuth2GrantTypeParamName + kKeyValSeparator +
138 kOAuth2RefreshTokenGrantType;
139
140 message += kUrlQueryParamSeparator;
141 message += kOAuth2RefreshTokenParamName + kKeyValSeparator;
142 message += auth_data->persistent_credential;
143
144 Request("https://www.googleapis.com/oauth2/v3/token", "POST", message,
145 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2Token,
146 base::Unretained(this), callback));
147 }
148
149 void GoogleAuthenticationServiceImpl::SelectAccount(
150 bool returnLastSelected,
151 const SelectAccountCallback& callback) {
152 mojo::String username;
153 if (returnLastSelected) {
154 username = accounts_db_manager_->GetAuthorizedUserForApp(app_url_);
155 if (!username.is_null()) {
156 callback.Run(username, nullptr);
157 return;
158 }
159 }
160
161 // TODO(ukode): Select one among the list of accounts using an AccountPicker
162 // UI instead of the first account always.
163 mojo::String user_list = accounts_db_manager_->GetAllUserAccounts();
164 if (user_list.is_null()) {
165 callback.Run(nullptr, "No user accounts found.");
166 return;
167 }
168
169 base::StringTokenizer lines(user_list, "\n");
170 std::string entry;
171 if (!lines.GetNext()) {
172 callback.Run(nullptr, "No valid user accounts found.");
173 return;
174 }
175 entry = lines.token();
176 AuthData* auth_data = authentication::GetAuthDataFromString(entry);
177 username = auth_data->username;
178
179 accounts_db_manager_->UpdateAuthorization(app_url_, username);
180 callback.Run(username, nullptr);
181 }
182
183 void GoogleAuthenticationServiceImpl::ClearOAuth2Token(
184 const mojo::String& token) {}
185
186 void GoogleAuthenticationServiceImpl::GetOAuth2DeviceCode(
187 mojo::Array<mojo::String> scopes,
188 const GetOAuth2DeviceCodeCallback& callback) {
189 std::string message;
190 message +=
191 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
192
193 std::string scopes_str("email");
194 for (size_t i = 0; i < scopes.size(); i++) {
195 scopes_str += " ";
196 scopes_str += std::string(scopes[i].data());
197 }
198
199 message += kUrlQueryParamSeparator;
200 message += kOAuth2ScopeParamName + kKeyValSeparator;
201 message += scopes_str;
202
203 Request("https://accounts.google.com/o/oauth2/device/code", "POST", message,
204 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode,
205 base::Unretained(this), callback));
206 }
207
208 void GoogleAuthenticationServiceImpl::AddAccount(
209 const mojo::String& device_code,
210 const AddAccountCallback& callback) {
211 // Resets the poll count to "1"
212 AddAccount(device_code, 1, callback);
213 }
214
215 void GoogleAuthenticationServiceImpl::AddAccount(
216 const mojo::String& device_code,
217 const uint32_t num_poll_attempts,
218 const AddAccountCallback& callback) {
219 std::string message;
220 message +=
221 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId;
222
223 message += kUrlQueryParamSeparator;
224 message += kOAuth2ClientSecretParamName + kKeyValSeparator +
225 kMojoShellOAuth2ClientSecret;
226
227 message += kUrlQueryParamSeparator;
228 message +=
229 kOAuth2GrantTypeParamName + kKeyValSeparator + kOAuth2DeviceFlowGrantType;
230
231 message += kUrlQueryParamSeparator;
232 message += kOAuth2CodeParamName + kKeyValSeparator;
233 message += device_code;
234
235 Request("https://www.googleapis.com/oauth2/v3/token", "POST", message,
236 base::Bind(&GoogleAuthenticationServiceImpl::OnAddAccount,
237 base::Unretained(this), callback, device_code,
238 num_poll_attempts));
239 }
240
241 void GoogleAuthenticationServiceImpl::OnGetOAuth2Token(
242 const GetOAuth2TokenCallback& callback,
243 const std::string& response,
244 const std::string& error) {
245 if (response.empty()) {
246 callback.Run(nullptr, "Error from server:" + error);
247 return;
248 }
249
250 scoped_ptr<base::DictionaryValue> dict =
251 ParseOAuth2Response(response.c_str());
252 if (!dict || dict->HasKey("error")) {
253 callback.Run(nullptr, "Error in parsing response:" + response);
254 return;
255 }
256
257 std::string access_token;
258 dict->GetString("access_token", &access_token);
259
260 callback.Run(access_token, nullptr);
261 }
262
263 void GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode(
264 const GetOAuth2DeviceCodeCallback& callback,
265 const std::string& response,
266 const std::string& error) {
267 if (response.empty()) {
268 callback.Run(nullptr, nullptr, nullptr, "Error from server:" + error);
269 return;
270 }
271
272 scoped_ptr<base::DictionaryValue> dict =
273 ParseOAuth2Response(response.c_str());
274 if (!dict || dict->HasKey("error")) {
275 callback.Run(nullptr, nullptr, nullptr,
276 "Error in parsing response:" + response);
277 return;
278 }
279
280 std::string url;
281 std::string device_code;
282 std::string user_code;
283 dict->GetString("verification_url", &url);
284 dict->GetString("device_code", &device_code);
285 dict->GetString("user_code", &user_code);
286
287 callback.Run(url, device_code, user_code, nullptr);
288 }
289
290 void GoogleAuthenticationServiceImpl::GetTokenInfo(
291 const std::string& access_token) {
292 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo");
293 url += "?access_token" + kKeyValSeparator;
294 url += access_token;
295 Request(url, "GET", "",
296 base::Bind(&GoogleAuthenticationServiceImpl::OnGetTokenInfo,
297 base::Unretained(this)));
298 }
299
300 void GoogleAuthenticationServiceImpl::OnGetTokenInfo(
301 const std::string& response,
302 const std::string& error) {
303 if (response.empty()) {
304 return;
305 }
306
307 scoped_ptr<base::DictionaryValue> dict =
308 ParseOAuth2Response(response.c_str());
309 if (!dict || dict->HasKey("error")) {
310 return;
311 }
312
313 // This field is only present if the profile scope was present in the
314 // request. The value of this field is an immutable identifier for the
315 // logged-in user, and may be used when creating and managing user
316 // sessions in your application.
317 dict->GetString("user_id", &user_id_);
318 dict->GetString("email", &email_);
319 // The space-delimited set of scopes that the user consented to.
320 dict->GetString("scope", &scope_);
321 return;
322 }
323
324 void GoogleAuthenticationServiceImpl::GetUserInfo(const std::string& id_token) {
325 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo");
326 url += "?id_token" + kKeyValSeparator;
327 url += id_token;
328 Request(url, "GET", "",
329 base::Bind(&GoogleAuthenticationServiceImpl::OnGetUserInfo,
330 base::Unretained(this)));
331 }
332
333 void GoogleAuthenticationServiceImpl::OnGetUserInfo(const std::string& response,
334 const std::string& error) {
335 if (response.empty()) {
336 return;
337 }
338
339 scoped_ptr<base::DictionaryValue> dict =
340 ParseOAuth2Response(response.c_str());
341 if (!dict || dict->HasKey("error")) {
342 return;
343 }
344
345 // This field is only present if the email scope was requested
346 dict->GetString("email", &email_);
347
348 return;
349 }
350
351 void GoogleAuthenticationServiceImpl::OnAddAccount(
352 const AddAccountCallback& callback,
353 const mojo::String& device_code,
354 const uint32_t num_poll_attempts,
355 const std::string& response,
356 const std::string& error) {
357 if (response.empty()) {
358 callback.Run(nullptr, "Error from server:" + error);
359 return;
360 }
361
362 if (!response.empty() && error.empty()) {
363 scoped_ptr<base::Value> root(base::JSONReader::Read(response));
364 if (!root || !root->IsType(base::Value::TYPE_DICTIONARY)) {
365 callback.Run(response, nullptr);
366 return;
367 }
368 }
369
370 // Parse response and fetch refresh, access and idtokens
371 scoped_ptr<base::DictionaryValue> dict =
372 ParseOAuth2Response(response.c_str());
373 std::string error_code;
374 if (!dict) {
375 callback.Run(nullptr, "Error in parsing response:" + response);
376 return;
377 } else if (dict->HasKey("error") && dict->GetString("error", &error_code)) {
378 if (error_code != "authorization_pending") {
379 callback.Run(nullptr, "Server error:" + response);
380 return;
381 }
382
383 if (num_poll_attempts > 15) {
384 callback.Run(nullptr, "Timed out after max number of polling attempts");
385 return;
386 }
387
388 // Rate limit by waiting 7 seconds before polling for a new grant
389 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(7000));
390 AddAccount(device_code, num_poll_attempts + 1,
391 base::Bind(&GoogleAuthenticationServiceImpl::OnAddAccount,
392 base::Unretained(this), callback, device_code,
393 num_poll_attempts + 1));
394 return;
395 }
396
397 // Poll success after detecting user grant.
398 AuthData* auth_data = new AuthData();
399 std::string access_token;
400 dict->GetString("access_token", &access_token);
401 GetTokenInfo(access_token); // gets scope, email and user_id
402
403 if (email_.empty()) {
404 std::string id_token;
405 dict->GetString("id_token", &id_token);
406 GetUserInfo(id_token); // gets user's email
407 }
408
409 auth_data->username = email_.empty() ? user_id_ : email_;
410 auth_data->scopes = scope_;
411 auth_data->auth_provider = "Google";
412 auth_data->persistent_credential_type = "RT";
413 dict->GetString("refresh_token", &auth_data->persistent_credential);
414
415 // TODO(ukode): Store access token in cache for the duration set in
416 // response
417 accounts_db_manager_->UpdateAccount(
418 auth_data->username, authentication::GetAuthDataAsString(*auth_data));
419
420 callback.Run(auth_data->username, nullptr);
421 }
422
423 void GoogleAuthenticationServiceImpl::Request(
424 const std::string& url,
425 const std::string& method,
426 const std::string& message,
427 const GetOAuth2TokenCallback& callback) {
428 Request(url, method, message, callback, nullptr, 0);
429 }
430
431 void GoogleAuthenticationServiceImpl::Request(
432 const std::string& url,
433 const std::string& method,
434 const std::string& message,
435 const GetOAuth2TokenCallback& callback,
436 const mojo::String& device_code,
437 const uint32_t num_poll_attempts) {
438 mojo::URLRequestPtr request(mojo::URLRequest::New());
439 request->url = url;
440 request->method = method;
441 request->auto_follow_redirects = true;
442
443 // Add headers
444 auto content_type_header = mojo::HttpHeader::New();
445 content_type_header->name = "Content-Type";
446 content_type_header->value = "application/x-www-form-urlencoded";
447 request->headers.push_back(content_type_header.Pass());
448
449 if (!message.empty()) {
450 request->body.push_back(
451 mojo::common::WriteStringToConsumerHandle(message).Pass());
452 }
453
454 mojo::URLLoaderPtr url_loader;
455 network_service_->CreateURLLoader(GetProxy(&url_loader));
456
457 url_loader->Start(
458 request.Pass(),
459 base::Bind(&GoogleAuthenticationServiceImpl::HandleServerResponse,
460 base::Unretained(this), callback, device_code,
461 num_poll_attempts));
462
463 url_loader.WaitForIncomingResponse();
464 }
465
466 void GoogleAuthenticationServiceImpl::HandleServerResponse(
467 const GetOAuth2TokenCallback& callback,
468 const mojo::String& device_code,
469 const uint32_t num_poll_attempts,
470 mojo::URLResponsePtr response) {
471 if (response.is_null()) {
472 LOG(WARNING) << "Something went horribly wrong...exiting!!";
473 callback.Run("", "Empty response");
474 return;
475 }
476
477 if (response->error) {
478 LOG(ERROR) << "Got error (" << response->error->code
479 << "), reason: " << response->error->description.get().c_str();
480 callback.Run("", response->error->description.get().c_str());
481 return;
482 }
483
484 std::string response_body;
485 mojo::common::BlockingCopyToString(response->body.Pass(), &response_body);
486
487 callback.Run(response_body, "");
488 }
489
490 } // authentication namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698