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::FilesPtr& files) |
| 101 : binding_(this, request.Pass()), |
| 102 network_service_(network_service), |
| 103 weak_ptr_factory_(this) { |
| 104 accounts_db_manager_ = new AccountsDbManager(files.Pass()); |
| 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( |
| 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 |