// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/browser_signin.h" #include <string> #include <vector> #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/memory/singleton.h" #include "base/message_loop.h" #include "base/string_util.h" #include "base/values.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/sync_setup_flow.h" #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/browser/ui/webui/constrained_html_ui.h" #include "chrome/browser/ui/webui/html_dialog_ui.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/browser/browser_thread.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_details.h" #include "content/common/notification_source.h" #include "content/common/notification_type.h" #include "grit/browser_resources.h" #include "ui/base/resource/resource_bundle.h" class BrowserSigninResourcesSource : public ChromeURLDataManager::DataSource { public: BrowserSigninResourcesSource() : DataSource(chrome::kChromeUIDialogHost, MessageLoop::current()) { } virtual void StartDataRequest(const std::string& path, bool is_incognito, int request_id); virtual std::string GetMimeType(const std::string& path) const { return "text/html"; } private: virtual ~BrowserSigninResourcesSource() {} DISALLOW_COPY_AND_ASSIGN(BrowserSigninResourcesSource); }; void BrowserSigninResourcesSource::StartDataRequest(const std::string& path, bool is_incognito, int request_id) { const char kSigninPath[] = "signin"; std::string response; if (path == kSigninPath) { const base::StringPiece html( ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_SIGNIN_HTML)); DictionaryValue dict; SetFontAndTextDirection(&dict); response = jstemplate_builder::GetI18nTemplateHtml(html, &dict); } scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); html_bytes->data.resize(response.size()); std::copy(response.begin(), response.end(), html_bytes->data.begin()); SendResponse(request_id, html_bytes); } class BrowserSigninHtml : public HtmlDialogUIDelegate, public WebUIMessageHandler { public: BrowserSigninHtml(BrowserSignin* signin, const string16& suggested_email, const string16& login_message); virtual ~BrowserSigninHtml() {} // HtmlDialogUIDelegate implementation virtual bool IsDialogModal() const { return false; }; virtual std::wstring GetDialogTitle() const { return L""; } virtual GURL GetDialogContentURL() const { return GURL("chrome://dialog/signin"); } virtual void GetWebUIMessageHandlers( std::vector<WebUIMessageHandler*>* handlers) const { const WebUIMessageHandler* handler = this; handlers->push_back(const_cast<WebUIMessageHandler*>(handler)); } virtual void GetDialogSize(gfx::Size* size) const { size->set_width(600); size->set_height(300); } virtual std::string GetDialogArgs() const { return UTF16ToASCII(login_message_); } virtual void OnDialogClosed(const std::string& json_retval) { closed_ = true; signin_->Cancel(); } virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } virtual bool ShouldShowDialogTitle() const { return true; } // WebUIMessageHandler implementation. virtual void RegisterMessages(); // Refreshes the UI, such as after an authentication error. void ReloadUI(); // Method which calls into javascript to force the dialog to close. void ForceDialogClose(); private: // JS callback handlers. void HandleSigninInit(const ListValue* args); void HandleSubmitAuth(const ListValue* args); // Nonowned pointer; |signin_| owns this object. BrowserSignin* signin_; string16 suggested_email_; string16 login_message_; bool closed_; }; BrowserSigninHtml::BrowserSigninHtml(BrowserSignin* signin, const string16& suggested_email, const string16& login_message) : signin_(signin), suggested_email_(suggested_email), login_message_(login_message), closed_(false) { } void BrowserSigninHtml::RegisterMessages() { web_ui_->RegisterMessageCallback( "SubmitAuth", NewCallback(this, &BrowserSigninHtml::HandleSubmitAuth)); web_ui_->RegisterMessageCallback( "SigninInit", NewCallback(this, &BrowserSigninHtml::HandleSigninInit)); } void BrowserSigninHtml::ReloadUI() { HandleSigninInit(NULL); } void BrowserSigninHtml::ForceDialogClose() { if (!closed_ && web_ui_) { StringValue value("DialogClose"); ListValue close_args; close_args.Append(new StringValue("")); web_ui_->CallJavascriptFunction("chrome.send", value, close_args); } } void BrowserSigninHtml::HandleSigninInit(const ListValue* args) { if (!web_ui_) return; RenderViewHost* rvh = web_ui_->tab_contents()->render_view_host(); rvh->ExecuteJavascriptInWebFrame(ASCIIToUTF16("//iframe[@id='login']"), ASCIIToUTF16("hideBlurb();")); DictionaryValue json_args; std::string json; std::wstring javascript(L""); SyncSetupFlow::GetArgsForGaiaLogin(signin_->GetProfileSyncService(), &json_args); // Replace the suggested email, unless sync has already required a // particular value. bool is_editable; std::string user; json_args.GetBoolean("editable_user", &is_editable); json_args.GetString("user", &user); if (is_editable && user.empty() && !suggested_email_.empty()) json_args.SetString("user", suggested_email_); base::JSONWriter::Write(&json_args, false, &json); javascript += L"showGaiaLogin(" + UTF8ToWide(json) + L");"; rvh->ExecuteJavascriptInWebFrame(ASCIIToUTF16("//iframe[@id='login']"), WideToUTF16Hack(javascript)); } void BrowserSigninHtml::HandleSubmitAuth(const ListValue* args) { std::string json; if (!args->GetString(0, &json)) NOTREACHED() << "Could not read JSON argument"; scoped_ptr<DictionaryValue> result(static_cast<DictionaryValue*>( base::JSONReader::Read(json, false))); std::string username; std::string password; std::string captcha; std::string access_code; if (!result.get() || !result->GetString("user", &username) || !result->GetString("pass", &password) || !result->GetString("captcha", &captcha) || !result->GetString("access_code", &access_code)) { LOG(ERROR) << "Unintelligble format for authentication data from page."; signin_->Cancel(); } signin_->GetProfileSyncService()->OnUserSubmittedAuth( username, password, captcha, access_code); } BrowserSignin::BrowserSignin(Profile* profile) : profile_(profile), delegate_(NULL), html_dialog_ui_delegate_(NULL) { // profile is NULL during testing. if (profile) { BrowserSigninResourcesSource* source = new BrowserSigninResourcesSource(); profile->GetChromeURLDataManager()->AddDataSource(source); } } BrowserSignin::~BrowserSignin() { delegate_ = NULL; } void BrowserSignin::RequestSignin(TabContents* tab_contents, const string16& suggested_email, const string16& login_message, SigninDelegate* delegate) { CHECK(tab_contents); CHECK(delegate); // Cancel existing request. if (delegate_) Cancel(); delegate_ = delegate; suggested_email_ = suggested_email; login_message_ = login_message; RegisterAuthNotifications(); ShowSigninTabModal(tab_contents); } std::string BrowserSignin::GetSignedInUsername() const { std::string username = profile_->GetPrefs()->GetString(prefs::kGoogleServicesUsername); VLOG(1) << "GetSignedInUsername: " << username; return username; } void BrowserSignin::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::GOOGLE_SIGNIN_SUCCESSFUL: { VLOG(1) << "GOOGLE_SIGNIN_SUCCESSFUL"; if (delegate_) delegate_->OnLoginSuccess(); // Close the dialog. OnLoginFinished(); break; } case NotificationType::GOOGLE_SIGNIN_FAILED: { VLOG(1) << "GOOGLE_SIGNIN_FAILED"; // The signin failed, refresh the UI with error information. html_dialog_ui_delegate_->ReloadUI(); break; } default: NOTREACHED(); } } void BrowserSignin::Cancel() { if (delegate_) { delegate_->OnLoginFailure(GoogleServiceAuthError( GoogleServiceAuthError::REQUEST_CANCELED)); GetProfileSyncService()->OnUserCancelledDialog(); } OnLoginFinished(); } void BrowserSignin::OnLoginFinished() { if (html_dialog_ui_delegate_) html_dialog_ui_delegate_->ForceDialogClose(); // The dialog will be deleted by WebUI due to the dialog close, // don't hold a reference. html_dialog_ui_delegate_ = NULL; if (delegate_) { UnregisterAuthNotifications(); delegate_ = NULL; } } ProfileSyncService* BrowserSignin::GetProfileSyncService() const { return profile_->GetProfileSyncService(); } BrowserSigninHtml* BrowserSignin::CreateHtmlDialogUI() { return new BrowserSigninHtml(this, suggested_email_, login_message_); } void BrowserSignin::RegisterAuthNotifications() { registrar_.Add(this, NotificationType::GOOGLE_SIGNIN_SUCCESSFUL, Source<Profile>(profile_)); registrar_.Add(this, NotificationType::GOOGLE_SIGNIN_FAILED, Source<Profile>(profile_)); } void BrowserSignin::UnregisterAuthNotifications() { registrar_.Remove(this, NotificationType::GOOGLE_SIGNIN_SUCCESSFUL, Source<Profile>(profile_)); registrar_.Remove(this, NotificationType::GOOGLE_SIGNIN_FAILED, Source<Profile>(profile_)); } void BrowserSignin::ShowSigninTabModal(TabContents* tab_contents) { // TODO(johnnyg): Need a linux views implementation for ConstrainedHtmlDialog. #if defined(OS_WIN) || defined(OS_CHROMEOS) || !defined(TOOLKIT_VIEWS) html_dialog_ui_delegate_ = CreateHtmlDialogUI(); ConstrainedHtmlUI::CreateConstrainedHtmlDialog(profile_, html_dialog_ui_delegate_, tab_contents); #endif }