OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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/message_loop/message_loop.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/strings/stringprintf.h" |
| 15 #include "base/synchronization/waitable_event.h" |
| 16 #include "base/threading/platform_thread.h" |
| 17 #include "base/trace_event/trace_event.h" |
| 18 #include "base/values.h" |
| 19 #include "mojo/common/binding_set.h" |
| 20 #include "mojo/data_pipe_utils/data_pipe_drainer.h" |
| 21 #include "mojo/data_pipe_utils/data_pipe_utils.h" |
| 22 #include "mojo/public/c/system/main.h" |
| 23 #include "mojo/public/cpp/bindings/strong_binding.h" |
| 24 #include "mojo/public/cpp/system/macros.h" |
| 25 #include "mojo/services/network/interfaces/url_loader.mojom.h" |
| 26 #include "services/authentication/credentials_impl_db.mojom.h" |
| 27 |
| 28 namespace authentication { |
| 29 |
| 30 // Mojo Shell OAuth2 Client configuration. |
| 31 // TODO: These should be retrieved from a secure storage or a configuration file |
| 32 // in the future. |
| 33 char kMojoShellOAuth2ClientId[] = |
| 34 "962611923869-3avg0b4vlisgjhin0l98dgp6d8sd634r.apps.googleusercontent.com"; |
| 35 char kMojoShellOAuth2ClientSecret[] = "41IxvPPAt1HyRoYw2hO84dRI"; |
| 36 |
| 37 // Query params used in Google OAuth2 handshake |
| 38 char kOAuth2ClientIdParamName[] = "client_id"; |
| 39 char kOAuth2ClientSecretParamName[] = "client_secret"; |
| 40 char kOAuth2ScopeParamName[] = "scope"; |
| 41 char kOAuth2GrantTypeParamName[] = "grant_type"; |
| 42 char kOAuth2CodeParamName[] = "code"; |
| 43 char kOAuth2RefreshTokenParamName[] = "refresh_token"; |
| 44 char kOAuth2DeviceFlowGrantType[] = "http://oauth.net/grant_type/device/1.0"; |
| 45 char kOAuth2RefreshTokenGrantType[] = "refresh_token"; |
| 46 |
| 47 // TODO(ukode) : Verify the char list |
| 48 char kEscapableUrlParamChars[] = ".$[]/"; |
| 49 |
| 50 std::string EncodeParam(std::string param) { |
| 51 for (size_t i = 0; i < strlen(kEscapableUrlParamChars); ++i) { |
| 52 base::ReplaceSubstringsAfterOffset( |
| 53 ¶m, 0, std::string(1, kEscapableUrlParamChars[i]), |
| 54 base::StringPrintf("%%%x", kEscapableUrlParamChars[i])); |
| 55 } |
| 56 return param; |
| 57 } |
| 58 |
| 59 mojo::String BuildUrlQuery(mojo::Map<mojo::String, mojo::String> params) { |
| 60 std::string message; |
| 61 for (auto it = params.begin(); it != params.end(); ++it) { |
| 62 message += EncodeParam(it.GetKey()) + "=" + EncodeParam(it.GetValue()); |
| 63 message += "&"; |
| 64 } |
| 65 |
| 66 if (!message.empty()) { |
| 67 message = message.substr(0, message.size() - 1); // Trims extra "&". |
| 68 } |
| 69 return message; |
| 70 } |
| 71 |
| 72 static base::DictionaryValue* ParseOAuth2Response(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 return static_cast<base::DictionaryValue*>(root.release()); |
| 84 } |
| 85 |
| 86 GoogleAuthenticationServiceImpl::GoogleAuthenticationServiceImpl( |
| 87 mojo::InterfaceRequest<AuthenticationService> request, |
| 88 const mojo::String app_url, |
| 89 mojo::NetworkServicePtr& network_service, |
| 90 mojo::files::DirectoryPtr& directory) |
| 91 : binding_(this, request.Pass()), |
| 92 app_url_(app_url), |
| 93 network_service_(network_service) { |
| 94 accounts_db_manager_ = new AccountsDbManager(directory.Pass()); |
| 95 } |
| 96 |
| 97 GoogleAuthenticationServiceImpl::~GoogleAuthenticationServiceImpl() { |
| 98 delete accounts_db_manager_; |
| 99 } |
| 100 |
| 101 void GoogleAuthenticationServiceImpl::GetOAuth2Token( |
| 102 const mojo::String& username, |
| 103 mojo::Array<mojo::String> scopes, |
| 104 const GetOAuth2TokenCallback& callback) { |
| 105 if (!accounts_db_manager_->isValid()) { |
| 106 callback.Run(nullptr, "Accounts db validation failed."); |
| 107 return; |
| 108 } |
| 109 |
| 110 authentication::CredentialsPtr creds = |
| 111 accounts_db_manager_->GetCredentials(username); |
| 112 |
| 113 if (!creds->token) { |
| 114 callback.Run(nullptr, "User grant not found"); |
| 115 return; |
| 116 } |
| 117 |
| 118 // TODO: Scopes are not used with the scoped refresh tokens. When we start |
| 119 // supporting full login scoped tokens, then the scopes here gets used for |
| 120 // Sidescoping. |
| 121 mojo::Map<mojo::String, mojo::String> params; |
| 122 params[kOAuth2ClientIdParamName] = kMojoShellOAuth2ClientId; |
| 123 params[kOAuth2ClientSecretParamName] = kMojoShellOAuth2ClientSecret; |
| 124 params[kOAuth2GrantTypeParamName] = kOAuth2RefreshTokenGrantType; |
| 125 params[kOAuth2RefreshTokenParamName] = creds->token; |
| 126 |
| 127 Request("https://www.googleapis.com/oauth2/v3/token", "POST", |
| 128 BuildUrlQuery(params.Pass()), |
| 129 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2Token, |
| 130 base::Unretained(this), callback)); |
| 131 } |
| 132 |
| 133 void GoogleAuthenticationServiceImpl::SelectAccount( |
| 134 bool returnLastSelected, |
| 135 const SelectAccountCallback& callback) { |
| 136 if (!accounts_db_manager_->isValid()) { |
| 137 callback.Run(nullptr, "Accounts db validation failed."); |
| 138 return; |
| 139 } |
| 140 |
| 141 mojo::String username; |
| 142 if (returnLastSelected) { |
| 143 username = accounts_db_manager_->GetAuthorizedUserForApp(app_url_); |
| 144 if (!username.is_null()) { |
| 145 callback.Run(username, nullptr); |
| 146 return; |
| 147 } |
| 148 } |
| 149 |
| 150 // TODO(ukode): Select one among the list of accounts using an AccountPicker |
| 151 // UI instead of the first account always. |
| 152 mojo::Array<mojo::String> users = accounts_db_manager_->GetAllUsers(); |
| 153 if (!users.size()) { |
| 154 callback.Run(nullptr, "No user accounts found."); |
| 155 return; |
| 156 } |
| 157 |
| 158 username = users[0]; |
| 159 accounts_db_manager_->UpdateAuthorization(app_url_, username); |
| 160 callback.Run(username, nullptr); |
| 161 } |
| 162 |
| 163 void GoogleAuthenticationServiceImpl::ClearOAuth2Token( |
| 164 const mojo::String& token) {} |
| 165 |
| 166 void GoogleAuthenticationServiceImpl::GetOAuth2DeviceCode( |
| 167 mojo::Array<mojo::String> scopes, |
| 168 const GetOAuth2DeviceCodeCallback& callback) { |
| 169 std::string scopes_str("email"); |
| 170 for (size_t i = 0; i < scopes.size(); i++) { |
| 171 scopes_str += " "; |
| 172 scopes_str += std::string(scopes[i].data()); |
| 173 } |
| 174 |
| 175 mojo::Map<mojo::String, mojo::String> params; |
| 176 params[kOAuth2ClientIdParamName] = kMojoShellOAuth2ClientId; |
| 177 params[kOAuth2ScopeParamName] = scopes_str; |
| 178 |
| 179 Request("https://accounts.google.com/o/oauth2/device/code", "POST", |
| 180 BuildUrlQuery(params.Pass()), |
| 181 base::Bind(&GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode, |
| 182 base::Unretained(this), callback)); |
| 183 } |
| 184 |
| 185 void GoogleAuthenticationServiceImpl::AddAccount( |
| 186 const mojo::String& device_code, |
| 187 const AddAccountCallback& callback) { |
| 188 // Resets the poll count to "1" |
| 189 AddAccountInternal(device_code, 1, callback); |
| 190 } |
| 191 |
| 192 void GoogleAuthenticationServiceImpl::AddAccountInternal( |
| 193 const mojo::String& device_code, |
| 194 const uint32_t num_poll_attempts, |
| 195 const AddAccountCallback& callback) { |
| 196 mojo::Map<mojo::String, mojo::String> params; |
| 197 params[kOAuth2ClientIdParamName] = kMojoShellOAuth2ClientId; |
| 198 params[kOAuth2ClientSecretParamName] = kMojoShellOAuth2ClientSecret; |
| 199 params[kOAuth2GrantTypeParamName] = kOAuth2DeviceFlowGrantType; |
| 200 params[kOAuth2CodeParamName] = device_code; |
| 201 |
| 202 Request("https://www.googleapis.com/oauth2/v3/token", "POST", |
| 203 BuildUrlQuery(params.Pass()), |
| 204 base::Bind(&GoogleAuthenticationServiceImpl::OnAddAccount, |
| 205 base::Unretained(this), callback, device_code, |
| 206 num_poll_attempts)); |
| 207 } |
| 208 |
| 209 void GoogleAuthenticationServiceImpl::OnGetOAuth2Token( |
| 210 const GetOAuth2TokenCallback& callback, |
| 211 const std::string& response, |
| 212 const std::string& error) { |
| 213 if (response.empty()) { |
| 214 callback.Run(nullptr, "Error from server:" + error); |
| 215 return; |
| 216 } |
| 217 |
| 218 scoped_ptr<base::DictionaryValue> dict(ParseOAuth2Response(response.c_str())); |
| 219 if (!dict.get() || dict->HasKey("error")) { |
| 220 callback.Run(nullptr, "Error in parsing response:" + response); |
| 221 return; |
| 222 } |
| 223 |
| 224 std::string access_token; |
| 225 dict->GetString("access_token", &access_token); |
| 226 |
| 227 callback.Run(access_token, nullptr); |
| 228 } |
| 229 |
| 230 void GoogleAuthenticationServiceImpl::OnGetOAuth2DeviceCode( |
| 231 const GetOAuth2DeviceCodeCallback& callback, |
| 232 const std::string& response, |
| 233 const std::string& error) { |
| 234 if (response.empty()) { |
| 235 callback.Run(nullptr, nullptr, nullptr, "Error from server:" + error); |
| 236 return; |
| 237 } |
| 238 |
| 239 scoped_ptr<base::DictionaryValue> dict(ParseOAuth2Response(response.c_str())); |
| 240 if (!dict.get() || dict->HasKey("error")) { |
| 241 callback.Run(nullptr, nullptr, nullptr, |
| 242 "Error in parsing response:" + response); |
| 243 return; |
| 244 } |
| 245 |
| 246 std::string url; |
| 247 std::string device_code; |
| 248 std::string user_code; |
| 249 dict->GetString("verification_url", &url); |
| 250 dict->GetString("device_code", &device_code); |
| 251 dict->GetString("user_code", &user_code); |
| 252 |
| 253 callback.Run(url, device_code, user_code, nullptr); |
| 254 } |
| 255 |
| 256 void GoogleAuthenticationServiceImpl::GetTokenInfo( |
| 257 const std::string& access_token) { |
| 258 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo"); |
| 259 url += "?access_token=" + EncodeParam(access_token); |
| 260 |
| 261 Request(url, "GET", "", |
| 262 base::Bind(&GoogleAuthenticationServiceImpl::OnGetTokenInfo, |
| 263 base::Unretained(this))); |
| 264 } |
| 265 |
| 266 void GoogleAuthenticationServiceImpl::OnGetTokenInfo( |
| 267 const std::string& response, |
| 268 const std::string& error) { |
| 269 if (response.empty()) { |
| 270 return; |
| 271 } |
| 272 |
| 273 scoped_ptr<base::DictionaryValue> dict(ParseOAuth2Response(response.c_str())); |
| 274 if (!dict.get() || dict->HasKey("error")) { |
| 275 return; |
| 276 } |
| 277 |
| 278 // This field is only present if the profile scope was present in the |
| 279 // request. The value of this field is an immutable identifier for the |
| 280 // logged-in user, and may be used when creating and managing user |
| 281 // sessions in your application. |
| 282 dict->GetString("user_id", &user_id_); |
| 283 dict->GetString("email", &email_); |
| 284 // The space-delimited set of scopes that the user consented to. |
| 285 dict->GetString("scope", &scope_); |
| 286 return; |
| 287 } |
| 288 |
| 289 void GoogleAuthenticationServiceImpl::GetUserInfo(const std::string& id_token) { |
| 290 std::string url("https://www.googleapis.com/oauth2/v1/tokeninfo"); |
| 291 url += "?id_token=" + EncodeParam(id_token); |
| 292 |
| 293 Request(url, "GET", "", |
| 294 base::Bind(&GoogleAuthenticationServiceImpl::OnGetUserInfo, |
| 295 base::Unretained(this))); |
| 296 } |
| 297 |
| 298 void GoogleAuthenticationServiceImpl::OnGetUserInfo(const std::string& response, |
| 299 const std::string& error) { |
| 300 if (response.empty()) { |
| 301 return; |
| 302 } |
| 303 |
| 304 scoped_ptr<base::DictionaryValue> dict(ParseOAuth2Response(response.c_str())); |
| 305 if (!dict.get() || dict->HasKey("error")) { |
| 306 return; |
| 307 } |
| 308 |
| 309 // This field is only present if the email scope was requested |
| 310 dict->GetString("email", &email_); |
| 311 } |
| 312 |
| 313 void GoogleAuthenticationServiceImpl::OnAddAccount( |
| 314 const AddAccountCallback& callback, |
| 315 const mojo::String& device_code, |
| 316 const uint32_t num_poll_attempts, |
| 317 const std::string& response, |
| 318 const std::string& error) { |
| 319 if (response.empty()) { |
| 320 callback.Run(nullptr, "Error from server:" + error); |
| 321 return; |
| 322 } |
| 323 |
| 324 if (!response.empty() && error.empty()) { |
| 325 scoped_ptr<base::Value> root(base::JSONReader::Read(response)); |
| 326 if (!root || !root->IsType(base::Value::TYPE_DICTIONARY)) { |
| 327 callback.Run(response, nullptr); |
| 328 return; |
| 329 } |
| 330 } |
| 331 |
| 332 // Parse response and fetch refresh, access and idtokens |
| 333 scoped_ptr<base::DictionaryValue> dict(ParseOAuth2Response(response.c_str())); |
| 334 std::string error_code; |
| 335 if (!dict.get()) { |
| 336 callback.Run(nullptr, "Error in parsing response:" + response); |
| 337 return; |
| 338 } else if (dict->HasKey("error") && dict->GetString("error", &error_code)) { |
| 339 if (error_code != "authorization_pending") { |
| 340 callback.Run(nullptr, "Server error:" + response); |
| 341 return; |
| 342 } |
| 343 |
| 344 if (num_poll_attempts > 15) { |
| 345 callback.Run(nullptr, "Timed out after max number of polling attempts"); |
| 346 return; |
| 347 } |
| 348 |
| 349 // Rate limit by waiting 7 seconds before polling for a new grant |
| 350 base::MessageLoop::current()->PostDelayedTask( |
| 351 FROM_HERE, |
| 352 base::Bind(&GoogleAuthenticationServiceImpl::AddAccountInternal, |
| 353 base::Unretained(this), device_code, num_poll_attempts + 1, |
| 354 callback), |
| 355 base::TimeDelta::FromMilliseconds(7000)); |
| 356 return; |
| 357 } |
| 358 |
| 359 // Poll success, after detecting user grant. |
| 360 std::string access_token; |
| 361 dict->GetString("access_token", &access_token); |
| 362 GetTokenInfo(access_token); // gets scope, email and user_id |
| 363 |
| 364 if (email_.empty()) { |
| 365 std::string id_token; |
| 366 dict->GetString("id_token", &id_token); |
| 367 GetUserInfo(id_token); // gets user's email |
| 368 } |
| 369 |
| 370 // TODO(ukode): Store access token in cache for the duration set in |
| 371 // response |
| 372 if (!accounts_db_manager_->isValid()) { |
| 373 callback.Run(nullptr, "Accounts db validation failed."); |
| 374 return; |
| 375 } |
| 376 |
| 377 std::string refresh_token; |
| 378 dict->GetString("refresh_token", &refresh_token); |
| 379 authentication::CredentialsPtr creds = authentication::Credentials::New(); |
| 380 creds->token = refresh_token; |
| 381 creds->scopes = scope_; |
| 382 creds->auth_provider = AuthProvider::GOOGLE; |
| 383 creds->credential_type = CredentialType::DOWNSCOPED_OAUTH_REFRESH_TOKEN; |
| 384 std::string username = email_.empty() ? user_id_ : email_; |
| 385 accounts_db_manager_->UpdateCredentials(username, creds.Pass()); |
| 386 |
| 387 callback.Run(username, nullptr); |
| 388 } |
| 389 |
| 390 void GoogleAuthenticationServiceImpl::Request( |
| 391 const std::string& url, |
| 392 const std::string& method, |
| 393 const std::string& message, |
| 394 const mojo::Callback<void(std::string, std::string)>& callback) { |
| 395 Request(url, method, message, callback, nullptr, 0); |
| 396 } |
| 397 |
| 398 void GoogleAuthenticationServiceImpl::Request( |
| 399 const std::string& url, |
| 400 const std::string& method, |
| 401 const std::string& message, |
| 402 const mojo::Callback<void(std::string, std::string)>& callback, |
| 403 const mojo::String& device_code, |
| 404 const uint32_t num_poll_attempts) { |
| 405 mojo::URLRequestPtr request(mojo::URLRequest::New()); |
| 406 request->url = url; |
| 407 request->method = method; |
| 408 request->auto_follow_redirects = true; |
| 409 |
| 410 // Add headers |
| 411 auto content_type_header = mojo::HttpHeader::New(); |
| 412 content_type_header->name = "Content-Type"; |
| 413 content_type_header->value = "application/x-www-form-urlencoded"; |
| 414 request->headers.push_back(content_type_header.Pass()); |
| 415 |
| 416 if (!message.empty()) { |
| 417 request->body.push_back( |
| 418 mojo::common::WriteStringToConsumerHandle(message).Pass()); |
| 419 } |
| 420 |
| 421 mojo::URLLoaderPtr url_loader; |
| 422 network_service_->CreateURLLoader(GetProxy(&url_loader)); |
| 423 |
| 424 url_loader->Start( |
| 425 request.Pass(), |
| 426 base::Bind(&GoogleAuthenticationServiceImpl::HandleServerResponse, |
| 427 base::Unretained(this), callback, device_code, |
| 428 num_poll_attempts)); |
| 429 |
| 430 url_loader.WaitForIncomingResponse(); |
| 431 } |
| 432 |
| 433 void GoogleAuthenticationServiceImpl::HandleServerResponse( |
| 434 const mojo::Callback<void(std::string, std::string)>& callback, |
| 435 const mojo::String& device_code, |
| 436 const uint32_t num_poll_attempts, |
| 437 mojo::URLResponsePtr response) { |
| 438 if (response.is_null()) { |
| 439 LOG(WARNING) << "Something went horribly wrong...exiting!!"; |
| 440 callback.Run("", "Empty response"); |
| 441 return; |
| 442 } |
| 443 |
| 444 if (response->error) { |
| 445 LOG(ERROR) << "Got error (" << response->error->code |
| 446 << "), reason: " << response->error->description.get().c_str(); |
| 447 callback.Run("", response->error->description.get().c_str()); |
| 448 return; |
| 449 } |
| 450 |
| 451 std::string response_body; |
| 452 mojo::common::BlockingCopyToString(response->body.Pass(), &response_body); |
| 453 |
| 454 callback.Run(response_body, ""); |
| 455 } |
| 456 |
| 457 } // authentication namespace |
OLD | NEW |