1// Copyright (c) 2011 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 "chrome/browser/browser_signin.h" 6 7#include <string> 8#include <vector> 9 10#include "base/json/json_reader.h" 11#include "base/json/json_writer.h" 12#include "base/memory/singleton.h" 13#include "base/message_loop.h" 14#include "base/string_util.h" 15#include "base/values.h" 16#include "chrome/browser/prefs/pref_service.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/sync/profile_sync_service.h" 19#include "chrome/browser/sync/sync_setup_flow.h" 20#include "chrome/browser/ui/webui/chrome_url_data_manager.h" 21#include "chrome/browser/ui/webui/constrained_html_ui.h" 22#include "chrome/browser/ui/webui/html_dialog_ui.h" 23#include "chrome/common/jstemplate_builder.h" 24#include "chrome/common/pref_names.h" 25#include "chrome/common/url_constants.h" 26#include "content/browser/browser_thread.h" 27#include "content/browser/renderer_host/render_view_host.h" 28#include "content/browser/tab_contents/tab_contents.h" 29#include "content/common/notification_details.h" 30#include "content/common/notification_source.h" 31#include "content/common/notification_type.h" 32#include "grit/browser_resources.h" 33#include "ui/base/resource/resource_bundle.h" 34 35class BrowserSigninResourcesSource : public ChromeURLDataManager::DataSource { 36 public: 37 BrowserSigninResourcesSource() 38 : DataSource(chrome::kChromeUIDialogHost, MessageLoop::current()) { 39 } 40 41 virtual void StartDataRequest(const std::string& path, 42 bool is_incognito, 43 int request_id); 44 45 virtual std::string GetMimeType(const std::string& path) const { 46 return "text/html"; 47 } 48 49 private: 50 virtual ~BrowserSigninResourcesSource() {} 51 52 DISALLOW_COPY_AND_ASSIGN(BrowserSigninResourcesSource); 53}; 54 55void BrowserSigninResourcesSource::StartDataRequest(const std::string& path, 56 bool is_incognito, 57 int request_id) { 58 const char kSigninPath[] = "signin"; 59 60 std::string response; 61 if (path == kSigninPath) { 62 const base::StringPiece html( 63 ResourceBundle::GetSharedInstance().GetRawDataResource( 64 IDR_SIGNIN_HTML)); 65 DictionaryValue dict; 66 SetFontAndTextDirection(&dict); 67 response = jstemplate_builder::GetI18nTemplateHtml(html, &dict); 68 } 69 70 scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); 71 html_bytes->data.resize(response.size()); 72 std::copy(response.begin(), response.end(), html_bytes->data.begin()); 73 SendResponse(request_id, html_bytes); 74} 75 76class BrowserSigninHtml : public HtmlDialogUIDelegate, 77 public WebUIMessageHandler { 78 public: 79 BrowserSigninHtml(BrowserSignin* signin, 80 const string16& suggested_email, 81 const string16& login_message); 82 virtual ~BrowserSigninHtml() {} 83 84 // HtmlDialogUIDelegate implementation 85 virtual bool IsDialogModal() const { 86 return false; 87 }; 88 virtual std::wstring GetDialogTitle() const { 89 return L""; 90 } 91 virtual GURL GetDialogContentURL() const { 92 return GURL("chrome://dialog/signin"); 93 } 94 virtual void GetWebUIMessageHandlers( 95 std::vector<WebUIMessageHandler*>* handlers) const { 96 const WebUIMessageHandler* handler = this; 97 handlers->push_back(const_cast<WebUIMessageHandler*>(handler)); 98 } 99 virtual void GetDialogSize(gfx::Size* size) const { 100 size->set_width(600); 101 size->set_height(300); 102 } 103 virtual std::string GetDialogArgs() const { 104 return UTF16ToASCII(login_message_); 105 } 106 virtual void OnDialogClosed(const std::string& json_retval) { 107 closed_ = true; 108 signin_->Cancel(); 109 } 110 virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { 111 } 112 virtual bool ShouldShowDialogTitle() const { return true; } 113 114 // WebUIMessageHandler implementation. 115 virtual void RegisterMessages(); 116 117 // Refreshes the UI, such as after an authentication error. 118 void ReloadUI(); 119 120 // Method which calls into javascript to force the dialog to close. 121 void ForceDialogClose(); 122 123 private: 124 // JS callback handlers. 125 void HandleSigninInit(const ListValue* args); 126 void HandleSubmitAuth(const ListValue* args); 127 128 // Nonowned pointer; |signin_| owns this object. 129 BrowserSignin* signin_; 130 131 string16 suggested_email_; 132 string16 login_message_; 133 134 bool closed_; 135}; 136 137BrowserSigninHtml::BrowserSigninHtml(BrowserSignin* signin, 138 const string16& suggested_email, 139 const string16& login_message) 140 : signin_(signin), 141 suggested_email_(suggested_email), 142 login_message_(login_message), 143 closed_(false) { 144} 145 146void BrowserSigninHtml::RegisterMessages() { 147 web_ui_->RegisterMessageCallback( 148 "SubmitAuth", NewCallback(this, &BrowserSigninHtml::HandleSubmitAuth)); 149 web_ui_->RegisterMessageCallback( 150 "SigninInit", NewCallback(this, &BrowserSigninHtml::HandleSigninInit)); 151} 152 153void BrowserSigninHtml::ReloadUI() { 154 HandleSigninInit(NULL); 155} 156 157void BrowserSigninHtml::ForceDialogClose() { 158 if (!closed_ && web_ui_) { 159 StringValue value("DialogClose"); 160 ListValue close_args; 161 close_args.Append(new StringValue("")); 162 web_ui_->CallJavascriptFunction("chrome.send", value, close_args); 163 } 164} 165 166void BrowserSigninHtml::HandleSigninInit(const ListValue* args) { 167 if (!web_ui_) 168 return; 169 170 RenderViewHost* rvh = web_ui_->tab_contents()->render_view_host(); 171 rvh->ExecuteJavascriptInWebFrame(ASCIIToUTF16("//iframe[@id='login']"), 172 ASCIIToUTF16("hideBlurb();")); 173 174 DictionaryValue json_args; 175 std::string json; 176 std::wstring javascript(L""); 177 SyncSetupFlow::GetArgsForGaiaLogin(signin_->GetProfileSyncService(), 178 &json_args); 179 180 // Replace the suggested email, unless sync has already required a 181 // particular value. 182 bool is_editable; 183 std::string user; 184 json_args.GetBoolean("editable_user", &is_editable); 185 json_args.GetString("user", &user); 186 if (is_editable && user.empty() && !suggested_email_.empty()) 187 json_args.SetString("user", suggested_email_); 188 189 base::JSONWriter::Write(&json_args, false, &json); 190 javascript += L"showGaiaLogin(" + UTF8ToWide(json) + L");"; 191 rvh->ExecuteJavascriptInWebFrame(ASCIIToUTF16("//iframe[@id='login']"), 192 WideToUTF16Hack(javascript)); 193} 194 195void BrowserSigninHtml::HandleSubmitAuth(const ListValue* args) { 196 std::string json; 197 if (!args->GetString(0, &json)) 198 NOTREACHED() << "Could not read JSON argument"; 199 200 scoped_ptr<DictionaryValue> result(static_cast<DictionaryValue*>( 201 base::JSONReader::Read(json, false))); 202 std::string username; 203 std::string password; 204 std::string captcha; 205 std::string access_code; 206 if (!result.get() || 207 !result->GetString("user", &username) || 208 !result->GetString("pass", &password) || 209 !result->GetString("captcha", &captcha) || 210 !result->GetString("access_code", &access_code)) { 211 LOG(ERROR) << "Unintelligble format for authentication data from page."; 212 signin_->Cancel(); 213 } 214 signin_->GetProfileSyncService()->OnUserSubmittedAuth( 215 username, password, captcha, access_code); 216} 217 218BrowserSignin::BrowserSignin(Profile* profile) 219 : profile_(profile), 220 delegate_(NULL), 221 html_dialog_ui_delegate_(NULL) { 222 // profile is NULL during testing. 223 if (profile) { 224 BrowserSigninResourcesSource* source = new BrowserSigninResourcesSource(); 225 profile->GetChromeURLDataManager()->AddDataSource(source); 226 } 227} 228 229BrowserSignin::~BrowserSignin() { 230 delegate_ = NULL; 231} 232 233void BrowserSignin::RequestSignin(TabContents* tab_contents, 234 const string16& suggested_email, 235 const string16& login_message, 236 SigninDelegate* delegate) { 237 CHECK(tab_contents); 238 CHECK(delegate); 239 // Cancel existing request. 240 if (delegate_) 241 Cancel(); 242 delegate_ = delegate; 243 suggested_email_ = suggested_email; 244 login_message_ = login_message; 245 RegisterAuthNotifications(); 246 ShowSigninTabModal(tab_contents); 247} 248 249std::string BrowserSignin::GetSignedInUsername() const { 250 std::string username = 251 profile_->GetPrefs()->GetString(prefs::kGoogleServicesUsername); 252 VLOG(1) << "GetSignedInUsername: " << username; 253 return username; 254} 255 256void BrowserSignin::Observe(NotificationType type, 257 const NotificationSource& source, 258 const NotificationDetails& details) { 259 switch (type.value) { 260 case NotificationType::GOOGLE_SIGNIN_SUCCESSFUL: { 261 VLOG(1) << "GOOGLE_SIGNIN_SUCCESSFUL"; 262 if (delegate_) 263 delegate_->OnLoginSuccess(); 264 // Close the dialog. 265 OnLoginFinished(); 266 break; 267 } 268 case NotificationType::GOOGLE_SIGNIN_FAILED: { 269 VLOG(1) << "GOOGLE_SIGNIN_FAILED"; 270 // The signin failed, refresh the UI with error information. 271 html_dialog_ui_delegate_->ReloadUI(); 272 break; 273 } 274 default: 275 NOTREACHED(); 276 } 277} 278 279void BrowserSignin::Cancel() { 280 if (delegate_) { 281 delegate_->OnLoginFailure(GoogleServiceAuthError( 282 GoogleServiceAuthError::REQUEST_CANCELED)); 283 GetProfileSyncService()->OnUserCancelledDialog(); 284 } 285 OnLoginFinished(); 286} 287 288void BrowserSignin::OnLoginFinished() { 289 if (html_dialog_ui_delegate_) 290 html_dialog_ui_delegate_->ForceDialogClose(); 291 // The dialog will be deleted by WebUI due to the dialog close, 292 // don't hold a reference. 293 html_dialog_ui_delegate_ = NULL; 294 295 if (delegate_) { 296 UnregisterAuthNotifications(); 297 delegate_ = NULL; 298 } 299} 300 301ProfileSyncService* BrowserSignin::GetProfileSyncService() const { 302 return profile_->GetProfileSyncService(); 303} 304 305BrowserSigninHtml* BrowserSignin::CreateHtmlDialogUI() { 306 return new BrowserSigninHtml(this, suggested_email_, login_message_); 307} 308 309void BrowserSignin::RegisterAuthNotifications() { 310 registrar_.Add(this, 311 NotificationType::GOOGLE_SIGNIN_SUCCESSFUL, 312 Source<Profile>(profile_)); 313 registrar_.Add(this, 314 NotificationType::GOOGLE_SIGNIN_FAILED, 315 Source<Profile>(profile_)); 316} 317 318void BrowserSignin::UnregisterAuthNotifications() { 319 registrar_.Remove(this, 320 NotificationType::GOOGLE_SIGNIN_SUCCESSFUL, 321 Source<Profile>(profile_)); 322 registrar_.Remove(this, 323 NotificationType::GOOGLE_SIGNIN_FAILED, 324 Source<Profile>(profile_)); 325} 326 327void BrowserSignin::ShowSigninTabModal(TabContents* tab_contents) { 328// TODO(johnnyg): Need a linux views implementation for ConstrainedHtmlDialog. 329#if defined(OS_WIN) || defined(OS_CHROMEOS) || !defined(TOOLKIT_VIEWS) 330 html_dialog_ui_delegate_ = CreateHtmlDialogUI(); 331 ConstrainedHtmlUI::CreateConstrainedHtmlDialog(profile_, 332 html_dialog_ui_delegate_, 333 tab_contents); 334#endif 335} 336