// 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
}