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