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