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

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

Issue 1466733002: Google OAuth Device Flow support for FNL (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: updated demo namespaces Created 5 years 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.
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 }
OLDNEW
« services/authentication/auth_data.h ('K') | « services/authentication/dummy_authentication_app.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698