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

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

Powered by Google App Engine
This is Rietveld 408576698