OLD | NEW |
---|---|
(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 | |
OLD | NEW |