OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
jln (very slow on Chromium)
2015/12/08 22:27:29
Did not look yet.
ukode
2015/12/16 19:24:13
Acknowledged.
| |
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/accounts_db_manager.h" | |
6 #include "services/authentication/auth_data.h" | |
7 | |
8 #include "base/json/json_reader.h" | |
9 #include "base/json/json_writer.h" | |
10 #include "base/memory/weak_ptr.h" | |
11 #include "base/strings/string_piece.h" | |
12 #include "base/strings/string_split.h" | |
13 #include "base/strings/string_tokenizer.h" | |
14 #include "base/strings/string_util.h" | |
15 #include "base/trace_event/trace_event.h" | |
16 #include "base/values.h" | |
17 #include "mojo/common/binding_set.h" | |
18 #include "mojo/data_pipe_utils/data_pipe_drainer.h" | |
19 #include "mojo/data_pipe_utils/data_pipe_utils.h" | |
20 #include "mojo/public/c/system/main.h" | |
21 #include "mojo/public/cpp/application/application_connection.h" | |
22 #include "mojo/public/cpp/application/application_delegate.h" | |
23 #include "mojo/public/cpp/application/application_impl.h" | |
24 #include "mojo/public/cpp/application/application_runner.h" | |
25 #include "mojo/public/cpp/application/interface_factory.h" | |
26 #include "mojo/public/cpp/bindings/strong_binding.h" | |
27 #include "mojo/public/cpp/system/macros.h" | |
28 #include "mojo/services/authentication/interfaces/authentication.mojom.h" | |
29 #include "mojo/services/network/interfaces/network_service.mojom.h" | |
30 #include "mojo/services/network/interfaces/url_loader.mojom.h" | |
31 | |
32 namespace authentication { | |
33 | |
34 // Mojo Shell OAuth2 Client configuration. | |
35 // TODO: These should be retrieved from a secure storage or a configuration file | |
36 // in the future. | |
37 const char* kMojoShellOAuth2ClientId = | |
38 "962611923869-3avg0b4vlisgjhin0l98dgp6d8sd634r.apps.googleusercontent.com"; | |
39 const char* kMojoShellOAuth2ClientSecret = "41IxvPPAt1HyRoYw2hO84dRI"; | |
40 | |
41 // Query params used in Google OAuth2 handshake | |
42 const std::string kKeyValSeparator("="); | |
43 const char* kUrlQueryParamSeparator = "&"; | |
44 | |
45 const char* kOAuth2ClientIdParamName = "client_id"; | |
46 const char* kOAuth2ClientSecretParamName = "client_secret"; | |
47 const char* kOAuth2ScopeParamName = "scope"; | |
48 const char* kOAuth2GrantTypeParamName = "grant_type"; | |
49 const char* kOAuth2CodeParamName = "code"; | |
50 const char* kOAuth2RefreshTokenParamName = "refresh_token"; | |
51 const char* kOAuth2DeviceFlowGrantType = | |
52 "http://oauth.net/grant_type/device/1.0"; | |
53 const char* kOAuth2RefreshTokenGrantType = "refresh_token"; | |
54 | |
55 mojo::String ValueToString(const base::Value& value) { | |
56 if (value.IsType(base::Value::TYPE_STRING)) { | |
57 std::string value_string; | |
58 value.GetAsString(&value_string); | |
59 return value_string; | |
60 } | |
61 if (value.IsType(base::Value::TYPE_INTEGER)) { | |
62 int value_int; | |
63 value.GetAsInteger(&value_int); | |
64 return std::to_string(value_int); | |
65 } | |
66 if (value.IsType(base::Value::TYPE_BOOLEAN)) { | |
67 bool value_bool; | |
68 value.GetAsBoolean(&value_bool); | |
69 return value_bool ? "true" : "false"; | |
70 } | |
71 if (!value.IsType(base::Value::TYPE_NULL)) { | |
72 LOG(ERROR) << "Unexpected JSON value (requires string or null): " << value; | |
73 } | |
74 | |
75 return nullptr; | |
76 } | |
77 | |
78 scoped_ptr<base::DictionaryValue> ParseOAuth2Response( | |
79 const std::string& response) { | |
80 if (response.empty()) { | |
81 return nullptr; | |
82 } | |
83 | |
84 scoped_ptr<base::Value> root(base::JSONReader::Read(response)); | |
85 if (!root || !root->IsType(base::Value::TYPE_DICTIONARY)) { | |
86 LOG(ERROR) << "Unexpected json response:" << std::endl << response; | |
87 return nullptr; | |
88 } | |
89 | |
90 base::DictionaryValue::Iterator it( | |
91 *static_cast<base::DictionaryValue*>(root.get())); | |
92 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
93 std::string val; | |
94 while (!it.IsAtEnd()) { | |
95 val = ValueToString(it.value()); | |
96 base::ReplaceChars(val, "\"", "", &val); | |
97 dict->SetString(it.key(), val); | |
98 it.Advance(); | |
99 } | |
100 | |
101 return dict.Pass(); | |
102 } | |
103 | |
104 /** | |
105 * Implementation of AuthenticationService from | |
106 * services/authentication/authentication.mojom for Google users. | |
107 */ | |
108 class GoogleAuthenticationServiceImpl | |
109 : public authentication::AuthenticationService { | |
110 public: | |
111 GoogleAuthenticationServiceImpl( | |
112 mojo::InterfaceRequest<AuthenticationService> request, | |
113 mojo::NetworkServicePtr& network_service, | |
114 mojo::files::FilesPtr& files) | |
115 : binding_(this, request.Pass()), | |
116 network_service_(network_service), | |
117 weak_ptr_factory_(this) { | |
118 accounts_db_manager_ = new AccountsDbManager(files.Pass()); | |
119 } | |
120 | |
121 ~GoogleAuthenticationServiceImpl() override {} | |
122 | |
123 void onConnectionError() {} | |
124 | |
125 void GetOAuth2Token(const mojo::String& username, | |
126 mojo::Array<mojo::String> scopes, | |
127 const GetOAuth2TokenCallback& callback) override { | |
128 mojo::String user_data; | |
129 accounts_db_manager_->GetAccountDataForUser(username, user_data); | |
130 AuthData* auth_data = | |
131 authentication::GetAuthDataFromString(user_data.get()); | |
132 | |
133 if ((auth_data == nullptr) || auth_data->persistent_credential.empty()) { | |
134 callback.Run(nullptr, "User grant not found"); | |
135 } | |
136 | |
137 // TODO: Scopes are not used with the scoped refresh tokens. When we start | |
138 // supporting full login scoped tokens, then the scopes here gets used for | |
139 // Sidescoping. | |
140 std::string message; | |
141 message += | |
142 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId; | |
143 | |
144 message += kUrlQueryParamSeparator; | |
145 message += kOAuth2ClientSecretParamName + kKeyValSeparator + | |
146 kMojoShellOAuth2ClientSecret; | |
147 | |
148 message += kUrlQueryParamSeparator; | |
149 message += kOAuth2GrantTypeParamName + kKeyValSeparator + | |
150 kOAuth2RefreshTokenGrantType; | |
151 | |
152 message += kUrlQueryParamSeparator; | |
153 message += kOAuth2RefreshTokenParamName + kKeyValSeparator; | |
154 message += auth_data->persistent_credential; | |
155 | |
156 Request("https://www.googleapis.com/oauth2/v3/token", "POST", message, | |
157 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2Token, | |
158 base::Unretained(this), callback)); | |
159 } | |
160 | |
161 void SelectAccount(bool returnLastSelected, | |
162 const SelectAccountCallback& callback) override { | |
163 // Select account will be implemented once we have downscoping enabled. For | |
164 // now, we are issuing tokens on behalf of Mojo Shell, not the actual app. | |
165 callback.Run(nullptr, "Not implemented"); | |
166 } | |
167 | |
168 void ClearOAuth2Token(const mojo::String& token) override {} | |
169 | |
170 void GetOAuth2DeviceCode( | |
171 mojo::Array<mojo::String> scopes, | |
172 const GetOAuth2DeviceCodeCallback& callback) override { | |
173 std::string message; | |
174 message += | |
175 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId; | |
176 | |
177 std::string scopes_str("email"); | |
178 for (size_t i = 0; i < scopes.size(); i++) { | |
179 scopes_str += " "; | |
180 scopes_str += std::string(scopes[i].data()); | |
181 } | |
182 | |
183 message += kUrlQueryParamSeparator; | |
184 message += kOAuth2ScopeParamName + kKeyValSeparator; | |
185 message += scopes_str; | |
186 | |
187 Request("https://accounts.google.com/o/oauth2/device/code", "POST", message, | |
188 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode, | |
189 base::Unretained(this), callback)); | |
190 } | |
191 | |
192 void AddAccount(const mojo::String& device_code, | |
193 const AddAccountCallback& callback) override { | |
194 std::string message; | |
195 message += | |
196 kOAuth2ClientIdParamName + kKeyValSeparator + kMojoShellOAuth2ClientId; | |
197 | |
198 message += kUrlQueryParamSeparator; | |
199 message += kOAuth2ClientSecretParamName + kKeyValSeparator + | |
200 kMojoShellOAuth2ClientSecret; | |
201 | |
202 message += kUrlQueryParamSeparator; | |
203 message += kOAuth2GrantTypeParamName + kKeyValSeparator + | |
204 kOAuth2DeviceFlowGrantType; | |
205 | |
206 message += kUrlQueryParamSeparator; | |
207 message += kOAuth2CodeParamName + kKeyValSeparator; | |
208 message += device_code; | |
209 | |
210 Request("https://www.googleapis.com/oauth2/v3/token", "POST", message, | |
211 base::Bind(&GoogleAuthenticationServiceImpl::OnAddAccount, | |
212 base::Unretained(this), callback)); | |
213 } | |
214 | |
215 private: | |
216 void OnGetOAuth2Token(const GetOAuth2TokenCallback& callback, | |
217 const std::string& response, | |
218 const std::string& error) { | |
219 if (response.empty()) { | |
220 callback.Run(nullptr, "Error from server:" + error); | |
221 return; | |
222 } | |
223 | |
224 scoped_ptr<base::DictionaryValue> dict = | |
225 ParseOAuth2Response(response.c_str()); | |
226 if (!dict || dict->HasKey("error")) { | |
227 callback.Run(nullptr, "Error in parsing response:" + response); | |
228 return; | |
229 } | |
230 | |
231 std::string access_token; | |
232 dict->GetString("access_token", &access_token); | |
233 | |
234 callback.Run(access_token, nullptr); | |
235 } | |
236 | |
237 void OnGetOAuth2DeviceCode(const GetOAuth2DeviceCodeCallback& callback, | |
238 const std::string& response, | |
239 const std::string& error) { | |
240 if (response.empty()) { | |
241 callback.Run(nullptr, nullptr, nullptr, "Error from server:" + error); | |
242 return; | |
243 } | |
244 | |
245 scoped_ptr<base::DictionaryValue> dict = | |
246 ParseOAuth2Response(response.c_str()); | |
247 if (!dict || dict->HasKey("error")) { | |
248 callback.Run(nullptr, nullptr, nullptr, | |
249 "Error in parsing response:" + response); | |
250 return; | |
251 } | |
252 | |
253 std::string url; | |
254 std::string device_code; | |
255 std::string user_code; | |
256 dict->GetString("verification_url", &url); | |
257 dict->GetString("device_code", &device_code); | |
258 dict->GetString("user_code", &user_code); | |
259 | |
260 callback.Run(url, device_code, user_code, nullptr); | |
261 } | |
262 | |
263 void GetTokenInfo(const std::string& access_token) { | |
264 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo"); | |
265 url += "?access_token" + kKeyValSeparator; | |
266 url += access_token; | |
267 Request(url, "GET", "", | |
268 base::Bind(&GoogleAuthenticationServiceImpl::OnGetTokenInfo, | |
269 base::Unretained(this))); | |
270 } | |
271 | |
272 void OnGetTokenInfo(const std::string& response, const std::string& error) { | |
273 if (response.empty()) { | |
274 return; | |
275 } | |
276 | |
277 scoped_ptr<base::DictionaryValue> dict = | |
278 ParseOAuth2Response(response.c_str()); | |
279 if (!dict || dict->HasKey("error")) { | |
280 return; | |
281 } | |
282 | |
283 // This field is only present if the profile scope was present in the | |
284 // request. The value of this field is an immutable identifier for the | |
285 // logged-in user, and may be used when creating and managing user | |
286 // sessions in your application. | |
287 dict->GetString("user_id", &user_id_); | |
288 dict->GetString("email", &email_); | |
289 // The space-delimited set of scopes that the user consented to. | |
290 dict->GetString("scope", &scope_); | |
291 return; | |
292 } | |
293 | |
294 void GetUserInfo(const std::string& id_token) { | |
295 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo"); | |
296 url += "?id_token" + kKeyValSeparator; | |
297 url += id_token; | |
298 Request(url, "GET", "", | |
299 base::Bind(&GoogleAuthenticationServiceImpl::OnGetUserInfo, | |
300 base::Unretained(this))); | |
301 } | |
302 | |
303 void OnGetUserInfo(const std::string& response, const std::string& error) { | |
304 if (response.empty()) { | |
305 return; | |
306 } | |
307 | |
308 scoped_ptr<base::DictionaryValue> dict = | |
309 ParseOAuth2Response(response.c_str()); | |
310 if (!dict || dict->HasKey("error")) { | |
311 return; | |
312 } | |
313 | |
314 // This field is only present if the email scope was requested | |
315 dict->GetString("email", &email_); | |
316 | |
317 return; | |
318 } | |
319 | |
320 void OnAddAccount(const AddAccountCallback& callback, | |
321 const std::string& response, | |
322 const std::string& error) { | |
323 if (response.empty()) { | |
324 callback.Run(nullptr, "Error from server:" + error); | |
325 return; | |
326 } | |
327 | |
328 // Parse response and fetch refresh, access and idtokens | |
329 scoped_ptr<base::DictionaryValue> dict = | |
330 ParseOAuth2Response(response.c_str()); | |
331 if (!dict || dict->HasKey("error")) { | |
332 callback.Run(nullptr, "Error in parsing response:" + response); | |
333 return; | |
334 } | |
335 | |
336 AuthData* auth_data = new AuthData(); | |
337 | |
338 std::string access_token; | |
339 dict->GetString("access_token", &access_token); | |
340 GetTokenInfo(access_token); // gets scope, email and user_id | |
341 | |
342 if (email_.empty()) { | |
343 std::string id_token; | |
344 dict->GetString("id_token", &id_token); | |
345 GetUserInfo(id_token); // gets user's email | |
346 } | |
347 | |
348 auth_data->username = email_.empty() ? user_id_ : email_; | |
349 auth_data->scopes = scope_; | |
350 auth_data->auth_provider = "Google"; | |
351 auth_data->persistent_credential_type = "RT"; | |
352 dict->GetString("refresh_token", &auth_data->persistent_credential); | |
353 | |
354 // TODO(ukode): Store access token in cache for the duration set in | |
355 // response | |
356 if (!accounts_db_manager_->UpdateAccount( | |
357 auth_data->username, | |
358 authentication::GetAuthDataAsString(*auth_data))) { | |
359 callback.Run(nullptr, "Unable to save refresh grant"); | |
360 return; | |
361 } | |
362 | |
363 callback.Run(auth_data->username, nullptr); | |
364 } | |
365 | |
366 void Request(const std::string& url, | |
367 const std::string& method, | |
368 const std::string& message, | |
369 const GetOAuth2TokenCallback& callback) { | |
370 mojo::URLRequestPtr request(mojo::URLRequest::New()); | |
371 request->url = url; | |
372 request->method = method; | |
373 request->auto_follow_redirects = true; | |
374 | |
375 // Add headers | |
376 auto content_type_header = mojo::HttpHeader::New(); | |
377 content_type_header->name = "Content-Type"; | |
378 content_type_header->value = "application/x-www-form-urlencoded"; | |
379 request->headers.push_back(content_type_header.Pass()); | |
380 | |
381 if (!message.empty()) { | |
382 request->body.push_back( | |
383 mojo::common::WriteStringToConsumerHandle(message).Pass()); | |
384 } | |
385 | |
386 mojo::URLLoaderPtr url_loader; | |
387 network_service_->CreateURLLoader(GetProxy(&url_loader)); | |
388 | |
389 url_loader->Start( | |
390 request.Pass(), | |
391 base::Bind(&GoogleAuthenticationServiceImpl::HandleServerResponse, | |
392 base::Unretained(this), callback)); | |
393 | |
394 url_loader.WaitForIncomingResponse(); | |
395 } | |
396 | |
397 void HandleServerResponse(const GetOAuth2TokenCallback& callback, | |
398 mojo::URLResponsePtr response) { | |
399 if (response.is_null()) { | |
400 LOG(WARNING) << "Something went horribly wrong...exiting!!"; | |
401 callback.Run("", "Empty response"); | |
402 return; | |
403 } | |
404 | |
405 if (response->error) { | |
406 LOG(ERROR) << "Got error (" << response->error->code | |
407 << "), reason: " << response->error->description.get().c_str(); | |
408 callback.Run("", response->error->description.get().c_str()); | |
409 return; | |
410 } | |
411 | |
412 std::string response_body; | |
413 mojo::common::BlockingCopyToString(response->body.Pass(), &response_body); | |
414 | |
415 callback.Run(response_body, ""); | |
416 } | |
417 | |
418 std::string user_id_; | |
419 std::string email_; | |
420 std::string scope_; | |
421 mojo::StrongBinding<AuthenticationService> binding_; | |
422 mojo::NetworkServicePtr& network_service_; | |
423 AccountsDbManager* accounts_db_manager_; | |
424 base::WeakPtrFactory<GoogleAuthenticationServiceImpl> weak_ptr_factory_; | |
425 | |
426 DISALLOW_COPY_AND_ASSIGN(GoogleAuthenticationServiceImpl); | |
427 }; | |
428 | |
429 class GooglerAccountManagerApp | |
430 : public mojo::ApplicationDelegate, | |
431 public mojo::InterfaceFactory<AuthenticationService> { | |
432 public: | |
433 GooglerAccountManagerApp() {} | |
434 ~GooglerAccountManagerApp() override {} | |
435 | |
436 void Initialize(mojo::ApplicationImpl* app) override { | |
437 app->ConnectToService("mojo:network_service", &network_service_); | |
438 app->ConnectToService("mojo:files", &files_); | |
439 } | |
440 | |
441 bool ConfigureIncomingConnection( | |
442 mojo::ApplicationConnection* connection) override { | |
443 connection->AddService<AuthenticationService>(this); | |
444 return true; | |
445 } | |
446 | |
447 void Create(mojo::ApplicationConnection* connection, | |
448 mojo::InterfaceRequest<AuthenticationService> request) override { | |
449 new authentication::GoogleAuthenticationServiceImpl( | |
450 request.Pass(), network_service_, files_); | |
451 } | |
452 | |
453 private: | |
454 mojo::NetworkServicePtr network_service_; | |
455 mojo::files::FilesPtr files_; | |
456 | |
457 DISALLOW_COPY_AND_ASSIGN(GooglerAccountManagerApp); | |
458 }; | |
459 | |
460 } // namespace authentication | |
461 | |
462 MojoResult MojoMain(MojoHandle application_request) { | |
463 mojo::ApplicationRunner runner( | |
464 new authentication::GooglerAccountManagerApp()); | |
465 return runner.Run(application_request); | |
466 } | |
OLD | NEW |