Chromium Code Reviews| 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 |