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
35 class BrowserSigninResourcesSource : public ChromeURLDataManager::DataSource {
36 public:
BrowserSigninResourcesSource()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
GetMimeType(const std::string & path) const45 virtual std::string GetMimeType(const std::string& path) const {
46 return "text/html";
47 }
48
49 private:
~BrowserSigninResourcesSource()50 virtual ~BrowserSigninResourcesSource() {}
51
52 DISALLOW_COPY_AND_ASSIGN(BrowserSigninResourcesSource);
53 };
54
StartDataRequest(const std::string & path,bool is_incognito,int request_id)55 void 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
76 class BrowserSigninHtml : public HtmlDialogUIDelegate,
77 public WebUIMessageHandler {
78 public:
79 BrowserSigninHtml(BrowserSignin* signin,
80 const string16& suggested_email,
81 const string16& login_message);
~BrowserSigninHtml()82 virtual ~BrowserSigninHtml() {}
83
84 // HtmlDialogUIDelegate implementation
IsDialogModal() const85 virtual bool IsDialogModal() const {
86 return false;
87 };
GetDialogTitle() const88 virtual std::wstring GetDialogTitle() const {
89 return L"";
90 }
GetDialogContentURL() const91 virtual GURL GetDialogContentURL() const {
92 return GURL("chrome://dialog/signin");
93 }
GetWebUIMessageHandlers(std::vector<WebUIMessageHandler * > * handlers) const94 virtual void GetWebUIMessageHandlers(
95 std::vector<WebUIMessageHandler*>* handlers) const {
96 const WebUIMessageHandler* handler = this;
97 handlers->push_back(const_cast<WebUIMessageHandler*>(handler));
98 }
GetDialogSize(gfx::Size * size) const99 virtual void GetDialogSize(gfx::Size* size) const {
100 size->set_width(600);
101 size->set_height(300);
102 }
GetDialogArgs() const103 virtual std::string GetDialogArgs() const {
104 return UTF16ToASCII(login_message_);
105 }
OnDialogClosed(const std::string & json_retval)106 virtual void OnDialogClosed(const std::string& json_retval) {
107 closed_ = true;
108 signin_->Cancel();
109 }
OnCloseContents(TabContents * source,bool * out_close_dialog)110 virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) {
111 }
ShouldShowDialogTitle() const112 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
BrowserSigninHtml(BrowserSignin * signin,const string16 & suggested_email,const string16 & login_message)137 BrowserSigninHtml::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
RegisterMessages()146 void BrowserSigninHtml::RegisterMessages() {
147 web_ui_->RegisterMessageCallback(
148 "SubmitAuth", NewCallback(this, &BrowserSigninHtml::HandleSubmitAuth));
149 web_ui_->RegisterMessageCallback(
150 "SigninInit", NewCallback(this, &BrowserSigninHtml::HandleSigninInit));
151 }
152
ReloadUI()153 void BrowserSigninHtml::ReloadUI() {
154 HandleSigninInit(NULL);
155 }
156
ForceDialogClose()157 void 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
HandleSigninInit(const ListValue * args)166 void 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
HandleSubmitAuth(const ListValue * args)195 void 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
BrowserSignin(Profile * profile)218 BrowserSignin::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
~BrowserSignin()229 BrowserSignin::~BrowserSignin() {
230 delegate_ = NULL;
231 }
232
RequestSignin(TabContents * tab_contents,const string16 & suggested_email,const string16 & login_message,SigninDelegate * delegate)233 void 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
GetSignedInUsername() const249 std::string BrowserSignin::GetSignedInUsername() const {
250 std::string username =
251 profile_->GetPrefs()->GetString(prefs::kGoogleServicesUsername);
252 VLOG(1) << "GetSignedInUsername: " << username;
253 return username;
254 }
255
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)256 void 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
Cancel()279 void BrowserSignin::Cancel() {
280 if (delegate_) {
281 delegate_->OnLoginFailure(GoogleServiceAuthError(
282 GoogleServiceAuthError::REQUEST_CANCELED));
283 GetProfileSyncService()->OnUserCancelledDialog();
284 }
285 OnLoginFinished();
286 }
287
OnLoginFinished()288 void 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
GetProfileSyncService() const301 ProfileSyncService* BrowserSignin::GetProfileSyncService() const {
302 return profile_->GetProfileSyncService();
303 }
304
CreateHtmlDialogUI()305 BrowserSigninHtml* BrowserSignin::CreateHtmlDialogUI() {
306 return new BrowserSigninHtml(this, suggested_email_, login_message_);
307 }
308
RegisterAuthNotifications()309 void 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
UnregisterAuthNotifications()318 void 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
ShowSigninTabModal(TabContents * tab_contents)327 void 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