blob: 1b3822cbc5e3de2af70cb02fece91755dd040982 [file] [log] [blame]
// Copyright 2013 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/ui/webui/inline_login_ui.h"
#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/profile_oauth2_token_service.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_global_error.h"
#include "chrome/browser/signin/signin_manager_cookie_helper.h"
#include "chrome/browser/signin/signin_names_io_thread.h"
#include "chrome/browser/signin/signin_oauth_helper.h"
#include "chrome/browser/signin/signin_promo.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/sync/one_click_signin_helper.h"
#include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "grit/browser_resources.h"
#include "net/base/escape.h"
#include "net/base/url_util.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/oauth2_token_fetcher.h"
#endif
namespace {
content::WebUIDataSource* CreateWebUIDataSource() {
content::WebUIDataSource* source =
content::WebUIDataSource::Create(chrome::kChromeUIChromeSigninHost);
source->SetUseJsonJSFormatV2();
source->SetJsonPath("strings.js");
source->SetDefaultResource(IDR_INLINE_LOGIN_HTML);
source->AddResourcePath("inline_login.css", IDR_INLINE_LOGIN_CSS);
source->AddResourcePath("inline_login.js", IDR_INLINE_LOGIN_JS);
return source;
};
#if defined(OS_CHROMEOS)
class InlineLoginUIOAuth2Delegate
: public chromeos::OAuth2TokenFetcher::Delegate {
public:
explicit InlineLoginUIOAuth2Delegate(content::WebUI* web_ui)
: web_ui_(web_ui) {}
virtual ~InlineLoginUIOAuth2Delegate() {}
// OAuth2TokenFetcher::Delegate overrides:
virtual void OnOAuth2TokensAvailable(
const GaiaAuthConsumer::ClientOAuthResult& oauth2_tokens) OVERRIDE {
// Closes sign-in dialog before update token service. Token service update
// might trigger a permission dialog and if this dialog does not close,
// a DCHECK would be triggered because attempting to activate a window
// while there is a modal dialog.
web_ui_->CallJavascriptFunction("inline.login.closeDialog");
Profile* profile = Profile::FromWebUI(web_ui_);
ProfileOAuth2TokenService* token_service =
ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
token_service->UpdateCredentials(token_service->GetPrimaryAccountId(),
oauth2_tokens.refresh_token);
}
virtual void OnOAuth2TokensFetchFailed() OVERRIDE {
LOG(ERROR) << "Failed to fetch oauth2 token with inline login.";
web_ui_->CallJavascriptFunction("inline.login.handleOAuth2TokenFailure");
}
private:
content::WebUI* web_ui_;
};
#else
// Global SequenceNumber used for generating unique webview partition IDs.
base::StaticAtomicSequenceNumber next_partition_id;
#endif // OS_CHROMEOS
class InlineLoginUIHandler : public content::WebUIMessageHandler {
public:
explicit InlineLoginUIHandler(Profile* profile)
: profile_(profile), weak_factory_(this), choose_what_to_sync_(false),
partition_id_("") {}
virtual ~InlineLoginUIHandler() {}
// content::WebUIMessageHandler overrides:
virtual void RegisterMessages() OVERRIDE {
web_ui()->RegisterMessageCallback("initialize",
base::Bind(&InlineLoginUIHandler::HandleInitialize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("completeLogin",
base::Bind(&InlineLoginUIHandler::HandleCompleteLogin,
base::Unretained(this)));
web_ui()->RegisterMessageCallback("switchToFullTab",
base::Bind(&InlineLoginUIHandler::HandleSwitchToFullTab,
base::Unretained(this)));
}
private:
// Enum for gaia auth mode, must match AuthMode defined in
// chrome/browser/resources/gaia_auth_host/gaia_auth_host.js.
enum AuthMode {
kDefaultAuthMode = 0,
kOfflineAuthMode = 1,
kInlineAuthMode = 2
};
void LoadAuthExtension() {
base::DictionaryValue params;
const std::string& app_locale = g_browser_process->GetApplicationLocale();
params.SetString("hl", app_locale);
GaiaUrls* gaiaUrls = GaiaUrls::GetInstance();
params.SetString("gaiaUrl", gaiaUrls->gaia_url().spec());
bool enable_inline = CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableInlineSignin);
params.SetInteger("authMode",
enable_inline ? kInlineAuthMode : kDefaultAuthMode);
// Set parameters specific for inline signin flow.
#if !defined(OS_CHROMEOS)
if (enable_inline) {
// Set continueUrl param for the inline sign in flow. It should point to
// the oauth2 auth code URL so that later we can grab the auth code from
// the cookie jar of the embedded webview.
std::string scope = net::EscapeUrlEncodedData(
gaiaUrls->oauth1_login_scope(), true);
std::string client_id = net::EscapeUrlEncodedData(
gaiaUrls->oauth2_chrome_client_id(), true);
std::string encoded_continue_params = base::StringPrintf(
"?scope=%s&client_id=%s", scope.c_str(), client_id.c_str());
const GURL& current_url = web_ui()->GetWebContents()->GetURL();
signin::Source source = signin::GetSourceForPromoURL(current_url);
DCHECK(source != signin::SOURCE_UNKNOWN);
if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ||
source == signin::SOURCE_AVATAR_BUBBLE_SIGN_IN) {
// Drop the leading slash in the path.
params.SetString("gaiaPath",
gaiaUrls->embedded_signin_url().path().substr(1));
}
params.SetString("service", "chromiumsync");
base::StringAppendF(
&encoded_continue_params, "&%s=%d", "source",
static_cast<int>(source));
params.SetString("continueUrl",
gaiaUrls->client_login_to_oauth2_url().Resolve(
encoded_continue_params).spec());
std::string email;
net::GetValueForKeyInQuery(current_url, "Email", &email);
if (!email.empty())
params.SetString("email", email);
std::string frame_url;
net::GetValueForKeyInQuery(current_url, "frameUrl", &frame_url);
if (!frame_url.empty())
params.SetString("frameUrl", frame_url);
std::string is_constrained;
net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained);
if (!is_constrained.empty())
params.SetString("constrained", is_constrained);
net::GetValueForKeyInQuery(current_url, "partitionId", &partition_id_);
if (partition_id_.empty()) {
partition_id_ =
"gaia-webview-" + base::IntToString(next_partition_id.GetNext());
}
params.SetString("partitionId", partition_id_);
}
#endif // OS_CHROMEOS
web_ui()->CallJavascriptFunction("inline.login.loadAuthExtension", params);
}
// JS callback:
void HandleInitialize(const base::ListValue* args) {
LoadAuthExtension();
}
// JS callback:
void HandleSwitchToFullTab(const base::ListValue* args) {
base::string16 url_str;
CHECK(args->GetString(0, &url_str));
content::WebContents* web_contents = web_ui()->GetWebContents();
GURL main_frame_url(web_contents->GetURL());
main_frame_url = net::AppendOrReplaceQueryParameter(
main_frame_url, "frameUrl", UTF16ToASCII(url_str));
main_frame_url = net::AppendOrReplaceQueryParameter(
main_frame_url, "partitionId", partition_id_);
chrome::NavigateParams params(
profile_,
net::AppendOrReplaceQueryParameter(main_frame_url, "constrained", "0"),
content::PAGE_TRANSITION_AUTO_TOPLEVEL);
chrome::Navigate(&params);
web_ui()->CallJavascriptFunction("inline.login.closeDialog");
}
void HandleCompleteLogin(const base::ListValue* args) {
// TODO(guohui, xiyuan): we should investigate if it is possible to unify
// the signin-with-cookies flow across ChromeOS and Chrome.
#if defined(OS_CHROMEOS)
oauth2_delegate_.reset(new InlineLoginUIOAuth2Delegate(web_ui()));
oauth2_token_fetcher_.reset(new chromeos::OAuth2TokenFetcher(
oauth2_delegate_.get(), profile_->GetRequestContext()));
oauth2_token_fetcher_->StartExchangeFromCookies();
#else
const base::DictionaryValue* dict = NULL;
base::string16 email;
base::string16 password;
if (!args->GetDictionary(0, &dict) || !dict ||
!dict->GetString("email", &email)) {
NOTREACHED();
return;
}
dict->GetString("password", &password);
dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync_);
content::WebContents* contents = web_ui()->GetWebContents();
signin::Source source = signin::GetSourceForPromoURL(contents->GetURL());
OneClickSigninHelper::CanOfferFor can_offer =
source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ?
OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT :
OneClickSigninHelper::CAN_OFFER_FOR_ALL;
std::string error_msg;
OneClickSigninHelper::CanOffer(
contents, can_offer, UTF16ToASCII(email), &error_msg);
if (!error_msg.empty()) {
SyncStarterCallback(
OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
Browser* browser = chrome::FindBrowserWithWebContents(contents);
if (!browser) {
browser = chrome::FindLastActiveWithProfile(
profile_, chrome::GetActiveDesktop());
}
if (browser)
OneClickSigninHelper::ShowSigninErrorBubble(browser, error_msg);
return;
}
content::StoragePartition* partition =
content::BrowserContext::GetStoragePartitionForSite(
contents->GetBrowserContext(),
GURL("chrome-guest://mfffpogegjflfpflabcdkioaeobkgjik/?" +
partition_id_));
scoped_refptr<SigninManagerCookieHelper> cookie_helper(
new SigninManagerCookieHelper(partition->GetURLRequestContext()));
cookie_helper->StartFetchingCookiesOnUIThread(
GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
base::Bind(&InlineLoginUIHandler::OnGaiaCookiesFetched,
weak_factory_.GetWeakPtr(), email, password));
#endif // OS_CHROMEOS
}
void OnGaiaCookiesFetched(
const base::string16 email,
const base::string16 password,
const net::CookieList& cookie_list) {
net::CookieList::const_iterator it;
std::string oauth_code;
for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
if (it->Name() == "oauth_code") {
oauth_code = it->Value();
break;
}
}
DCHECK(!oauth_code.empty());
content::WebContents* contents = web_ui()->GetWebContents();
ProfileSyncService* sync_service =
ProfileSyncServiceFactory::GetForProfile(profile_);
const GURL& current_url = contents->GetURL();
signin::Source source = signin::GetSourceForPromoURL(current_url);
if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) {
// SigninOAuthHelper will delete itself.
SigninOAuthHelper* helper = new SigninOAuthHelper(profile_);
helper->StartAddingAccount(oauth_code);
} else {
OneClickSigninSyncStarter::StartSyncMode start_mode =
source == signin::SOURCE_SETTINGS || choose_what_to_sync_ ?
(SigninGlobalError::GetForProfile(profile_)->HasMenuItem() &&
sync_service && sync_service->HasSyncSetupCompleted()) ?
OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
OneClickSigninSyncStarter::ConfirmationRequired confirmation_required =
source == signin::SOURCE_SETTINGS ||
source == signin::SOURCE_WEBSTORE_INSTALL ||
choose_what_to_sync_?
OneClickSigninSyncStarter::NO_CONFIRMATION :
OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
// Call OneClickSigninSyncStarter to exchange oauth code for tokens.
// OneClickSigninSyncStarter will delete itself once the job is done.
new OneClickSigninSyncStarter(
profile_, NULL, "" /* session_index, not used */,
UTF16ToASCII(email), UTF16ToASCII(password), oauth_code,
start_mode,
contents,
confirmation_required,
base::Bind(&InlineLoginUIHandler::SyncStarterCallback,
weak_factory_.GetWeakPtr()));
}
web_ui()->CallJavascriptFunction("inline.login.closeDialog");
}
void SyncStarterCallback(OneClickSigninSyncStarter::SyncSetupResult result) {
content::WebContents* contents = web_ui()->GetWebContents();
const GURL& current_url = contents->GetURL();
bool auto_close = signin::IsAutoCloseEnabledInURL(current_url);
signin::Source source = signin::GetSourceForPromoURL(current_url);
if (auto_close) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(
&InlineLoginUIHandler::CloseTab, weak_factory_.GetWeakPtr()));
return;
}
OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source);
}
void CloseTab() {
content::WebContents* tab = web_ui()->GetWebContents();
Browser* browser = chrome::FindBrowserWithWebContents(tab);
if (browser) {
TabStripModel* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model) {
int index = tab_strip_model->GetIndexOfWebContents(tab);
if (index != TabStripModel::kNoTab) {
tab_strip_model->ExecuteContextMenuCommand(
index, TabStripModel::CommandCloseTab);
}
}
}
}
Profile* profile_;
base::WeakPtrFactory<InlineLoginUIHandler> weak_factory_;
bool choose_what_to_sync_;
// Partition id for the gaia webview;
std::string partition_id_;
#if defined(OS_CHROMEOS)
scoped_ptr<chromeos::OAuth2TokenFetcher> oauth2_token_fetcher_;
scoped_ptr<InlineLoginUIOAuth2Delegate> oauth2_delegate_;
#endif
DISALLOW_COPY_AND_ASSIGN(InlineLoginUIHandler);
};
} // namespace
InlineLoginUI::InlineLoginUI(content::WebUI* web_ui)
: WebDialogUI(web_ui),
auth_extension_(Profile::FromWebUI(web_ui)) {
Profile* profile = Profile::FromWebUI(web_ui);
content::WebUIDataSource::Add(profile, CreateWebUIDataSource());
web_ui->AddMessageHandler(new InlineLoginUIHandler(profile));
// Required for intercepting extension function calls when the page is loaded
// in a bubble (not a full tab, thus tab helpers are not registered
// automatically).
extensions::TabHelper::CreateForWebContents(web_ui->GetWebContents());
}
InlineLoginUI::~InlineLoginUI() {}