diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-11 21:57:27 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-11 21:57:27 +0000 |
commit | 96209ccda702ea92fd257a17d8a70f93bce0ddb5 (patch) | |
tree | 65c94e91dc281bf8ed5f66a7c3f28a270f09ac6f | |
parent | f0b97f1ddf05cdca3cbba263ba663a2cc1ace7b3 (diff) | |
download | chromium_src-96209ccda702ea92fd257a17d8a70f93bce0ddb5.zip chromium_src-96209ccda702ea92fd257a17d8a70f93bce0ddb5.tar.gz chromium_src-96209ccda702ea92fd257a17d8a70f93bce0ddb5.tar.bz2 |
sync: second factor auth support
BUG=58712
TEST=Sign in to sync with two-step verification account. SigninManagerTest.
Review URL: http://codereview.chromium.org/3702002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@62191 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 9 | ||||
-rw-r--r-- | chrome/app/resources/locale_settings.grd | 4 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service.cc | 8 | ||||
-rw-r--r-- | chrome/browser/sync/profile_sync_service.h | 3 | ||||
-rw-r--r-- | chrome/browser/sync/resources/gaia_login.html | 95 | ||||
-rw-r--r-- | chrome/browser/sync/signin_manager.cc | 37 | ||||
-rw-r--r-- | chrome/browser/sync/signin_manager.h | 11 | ||||
-rw-r--r-- | chrome/browser/sync/signin_manager_unittest.cc | 47 | ||||
-rw-r--r-- | chrome/browser/sync/sync_setup_flow.cc | 14 | ||||
-rw-r--r-- | chrome/browser/sync/sync_setup_flow.h | 5 | ||||
-rw-r--r-- | chrome/browser/sync/sync_setup_wizard.cc | 11 | ||||
-rw-r--r-- | chrome/browser/sync/sync_setup_wizard_unittest.cc | 6 |
12 files changed, 220 insertions, 30 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 592c6c3..dd54f07 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -7908,6 +7908,15 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_SYNC_LOGIN_ACCOUNT_MISMATCH" desc="A warning message shown at sync login when the user is signed in to the web store already."> Your web store account is <ph name="EMAIL_ADDRESS">$1<ex>user@gmail.com</ex></ph> - signing in to sync with a different account will result in incompatibilities. </message> + <message name="IDS_SYNC_ENTER_ACCESS_CODE_LABEL" desc="A message asking the user to enter a second factor access code."> + Please enter your access code: + </message> + <message name="IDS_SYNC_ACCESS_CODE_HELP_LABEL" desc="A help link message for users who don't know what an access code is"> + What's this? + </message> + <message name="IDS_SYNC_INVALID_ACCESS_CODE_LABEL" desc="A message telling the user the access code they entered is invalid."> + Invalid access code. + </message> <!-- Sync app menu item strings --> <if expr="not pp_ifdef('use_titlecase')"> diff --git a/chrome/app/resources/locale_settings.grd b/chrome/app/resources/locale_settings.grd index 074b3bc..305f0b5 100644 --- a/chrome/app/resources/locale_settings.grd +++ b/chrome/app/resources/locale_settings.grd @@ -600,6 +600,10 @@ 15 </message> + <message name="IDS_SYNC_GET_ACCESS_CODE_URL" translateable="false"> + https://www.google.com/accounts/IssuedAuthSubTokens?hl=[GRITLANGCODE] + </message> + <!-- The default width/height for confirmation dialogs. --> <message name="IDS_CONFIRM_MESSAGE_BOX_DEFAULT_WIDTH_CHARS" use_name_for_id="true"> 50 diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 01f03bb..18b47cf 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -712,7 +712,7 @@ string16 ProfileSyncService::GetAuthenticatedUsername() const { void ProfileSyncService::OnUserSubmittedAuth( const std::string& username, const std::string& password, - const std::string& captcha) { + const std::string& captcha, const std::string& access_code) { last_attempted_user_email_ = username; is_auth_in_progress_ = true; FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); @@ -726,9 +726,15 @@ void ProfileSyncService::OnUserSubmittedAuth( LOG(WARNING) << "No mechanism on ChromeOS yet. See http://crbug.com/50292"; } + if (!access_code.empty()) { + signin_.ProvideSecondFactorAccessCode(access_code); + return; + } + if (!signin_.GetUsername().empty()) { signin_.SignOut(); } + signin_.StartSignIn(username, password, last_auth_error_.captcha().token, diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index 5a889f1..8041ec0 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -184,7 +184,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // Called when a user enters credentials through UI. virtual void OnUserSubmittedAuth(const std::string& username, const std::string& password, - const std::string& captcha); + const std::string& captcha, + const std::string& access_code); // Update the last auth error and notify observers of error state. void UpdateAuthErrorState(const GoogleServiceAuthError& error); diff --git a/chrome/browser/sync/resources/gaia_login.html b/chrome/browser/sync/resources/gaia_login.html index 7834942..7dcd684 100644 --- a/chrome/browser/sync/resources/gaia_login.html +++ b/chrome/browser/sync/resources/gaia_login.html @@ -106,6 +106,19 @@ #content_table { padding: 2px; } + .access_code_row { + display: none; + } + .access_code_row > td { + text-align: center; + padding-bottom: 1px; + } + #access_code_label_row > td { + padding-top: 6px; + } + .centeredtext { + text-align: center; + } </style> </head> <body bgcolor="#ffffff" vlink="#666666" @@ -173,11 +186,19 @@ var span = document.getElementById('email_readonly'); span.appendChild(document.createTextNode(f.Email.value)); span.style.display = 'inline'; - setElementDisplay("createaccountdiv", "none"); - } - } - if (1 == args.error) { - setElementDisplay("errormsg_1_Password", 'table-row'); + setElementDisplay("createaccountdiv", "none");
+ }
+
+ f.AccessCode.disabled = true; + }
+ if (1 == args.error) {
+ var access_code = document.getElementById('AccessCode');
+ if (access_code.value && access_code.value != "") {
+ setElementDisplay("errormsg_0_AccessCode", 'block');
+ showAccessCodeRequired();
+ } else {
+ setElementDisplay("errormsg_1_Password", 'table-row');
+ }
setBlurbError(); } if (3 == args.error) { @@ -185,7 +206,10 @@ setBlurbError(); } if (4 == args.error) { - showCaptcha(args); + showCaptcha(args);
+ }
+ if (8 == args.error) {
+ showAccessCodeRequired(); } document.getElementById("signIn").disabled = false; document.getElementById("signIn").value = templateData['signin']; @@ -195,8 +219,9 @@ function showCaptcha(args) { g_is_captcha_challenge_active = true; - // The captcha takes up lots of space, so make room. - setElementDisplay("top_blurb", "none"); + // The captcha takes up lots of space, so make room.
+ setElementDisplay("top_blurb", "none");
+ setElementDisplay("top_blurb_error", "none"); setElementDisplay("createaccountdiv", "none"); var gaiaTable = document.getElementById('gaia_table'); gaiaTable.cellPadding = 0; @@ -212,6 +237,18 @@ document.getElementById('CaptchaValue').disabled = false; document.getElementById('captcha_wrapper').style.backgroundImage = url(args.captchaUrl); + }
+
+ function showAccessCodeRequired() {
+ setElementDisplay("password_row", "none");
+ setElementDisplay("email_row", "none");
+ document.getElementById("createaccountcell").style.visibility =
+ "hidden";
+
+ setElementDisplay("access_code_label_row", "table-row");
+ setElementDisplay("access_code_input_row", "table-row");
+ setElementDisplay("access_code_help_row", "table-row");
+ document.getElementById('AccessCode').disabled = false; } function CloseDialog() { @@ -254,15 +291,17 @@ return false; document.getElementById('Email').disabled = true; - document.getElementById('Passwd').disabled = true; - document.getElementById('CaptchaValue').disabled = true; + document.getElementById('Passwd').disabled = true;
+ document.getElementById('CaptchaValue').disabled = true;
+ document.getElementById('AccessCode').disabled = true; var throbber = document.getElementById('throbber_container'); throbber.style.display = "inline"; var f = document.getElementById("gaia_loginform"); var result = JSON.stringify({"user" : f.Email.value, "pass" : f.Passwd.value, - "captcha" : f.CaptchaValue.value}); + "captcha" : f.CaptchaValue.value, + "access_code" : f.AccessCode.value}); document.getElementById("signIn").disabled = true; chrome.send("SubmitAuth", [result]); } @@ -270,7 +309,7 @@ function setElementDisplay(id, display) { var d = document.getElementById(id); if (d) - d.style.display = display; + d.style.display = display;
} function setBlurbError() { @@ -284,8 +323,9 @@ function resetErrorVisibility() { setElementDisplay("errormsg_0_Email", 'none'); setElementDisplay("errormsg_0_Password", 'none'); - setElementDisplay("errormsg_1_Password", 'none'); - setElementDisplay("errormsg_0_Connection", 'none'); + setElementDisplay("errormsg_1_Password", 'none');
+ setElementDisplay("errormsg_0_Connection", 'none');
+ setElementDisplay("errormsg_0_AccessCode", 'none'); } function setErrorVisibility() { @@ -299,6 +339,11 @@ if (null == f.Passwd.value || "" == f.Passwd.value) { setElementDisplay("errormsg_0_Password", 'table-row'); setBlurbError(); + return false;
+ } + if (!f.AccessCode.disabled && (null == f.AccessCode.value ||
+ "" == f.AccessCode.value)) {
+ setElementDisplay("errormsg_0_Password", 'table-row');
return false; } return true; @@ -366,7 +411,7 @@ <tr> <td colspan="2" align="center"> </td> </tr> - <tr> + <tr id="email_row"> <td nowrap="nowrap"> <div class="endaligned"> <span class="gaia le lbl" i18n-content="emaillabel"> @@ -379,6 +424,15 @@ <span id="email_readonly"></span> </td> </tr> + <tr id="access_code_label_row" class="access_code_row"> + <td colspan="2"> + <span class="gaia le lbl" i18n-content="enteraccesscode"></span> + </td> + </tr> + <tr id="access_code_help_row" class="access_code_row"> + <td colspan="2" class="gaia le fpwd"> + <a i18n-values="href:getaccesscodeurl" i18n-content="getaccesscodehelp" target="_blank"></a> + </td> <tr> <td></td> <td> @@ -391,7 +445,7 @@ <td></td> <td></td> </tr> - <tr> + <tr id="password_row"> <td class="endaligned"> <span class="gaia le lbl" i18n-content="passwordlabel"> </span> @@ -401,6 +455,12 @@ class="gaia le val"/> </td> </tr> + <tr id="access_code_input_row" class="access_code_row"> + <td colspan="2"> + <input type="password" name="AccessCode" id="AccessCode" size="18" + class="gaia le val"/> + </td> + </tr> <tr> <td> </td> @@ -412,6 +472,9 @@ <div class="errormsg" id="errormsg_1_Password"> <span i18n-content="invalidcredentials"></span>[<a i18n-values="href:invalidpasswordhelpurl" target="_blank">?</a>] </div> + <div class="errormsg centeredtext" id="errormsg_0_AccessCode" + i18n-content="invalidaccesscode"> + </div> </div> </td> </tr> diff --git a/chrome/browser/sync/signin_manager.cc b/chrome/browser/sync/signin_manager.cc index 109d364..fb83337 100644 --- a/chrome/browser/sync/signin_manager.cc +++ b/chrome/browser/sync/signin_manager.cc @@ -14,6 +14,11 @@ const char kGetInfoEmailKey[] = "email"; +SigninManager::SigninManager() + : profile_(NULL), had_two_factor_error_(false) {} + +SigninManager::~SigninManager() {} + // static void SigninManager::RegisterUserPrefs(PrefService* user_prefs) { user_prefs->RegisterStringPref(prefs::kGoogleServicesUsername, ""); @@ -61,11 +66,28 @@ void SigninManager::StartSignIn(const std::string& username, GaiaAuthenticator2::HostedAccountsNotAllowed); } +void SigninManager::ProvideSecondFactorAccessCode( + const std::string& access_code) { + DCHECK(!username_.empty() && !password_.empty() && + last_result_.data.empty()); + + client_login_.reset(new GaiaAuthenticator2(this, + GaiaConstants::kChromeSource, + profile_->GetRequestContext())); + client_login_->StartClientLogin(username_, + access_code, + "", + std::string(), + std::string(), + GaiaAuthenticator2::HostedAccountsNotAllowed); +} + void SigninManager::SignOut() { client_login_.reset(); last_result_ = ClientLoginResult(); username_.clear(); password_.clear(); + had_two_factor_error_ = false; profile_->GetPrefs()->SetString(prefs::kGoogleServicesUsername, username_); profile_->GetPrefs()->ScheduleSavePersistentPrefs(); profile_->GetTokenService()->ResetCredentialsInMemory(); @@ -113,10 +135,21 @@ void SigninManager::OnGetUserInfoFailure(const GoogleServiceAuthError& error) { } void SigninManager::OnClientLoginFailure(const GoogleServiceAuthError& error) { - GoogleServiceAuthError details(error); NotificationService::current()->Notify( NotificationType::GOOGLE_SIGNIN_FAILED, Source<SigninManager>(this), - Details<const GoogleServiceAuthError>(&details)); + Details<const GoogleServiceAuthError>(&error)); + + // We don't sign-out if the password was valid and we're just dealing with + // a second factor error, and we don't sign out if we're dealing with + // an invalid access code (again, because the password was valid). + bool invalid_gaia = error.state() == + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS; + if (error.state() == GoogleServiceAuthError::TWO_FACTOR || + (had_two_factor_error_ && invalid_gaia)) { + had_two_factor_error_ = true; + return; + } + SignOut(); } diff --git a/chrome/browser/sync/signin_manager.h b/chrome/browser/sync/signin_manager.h index f59da4c..86137fa 100644 --- a/chrome/browser/sync/signin_manager.h +++ b/chrome/browser/sync/signin_manager.h @@ -35,6 +35,9 @@ struct GoogleServiceSigninSuccessDetails { class SigninManager : public GaiaAuthConsumer { public: + SigninManager(); + virtual ~SigninManager(); + // Call to register our prefs. static void RegisterUserPrefs(PrefService* user_prefs); @@ -55,8 +58,13 @@ class SigninManager : public GaiaAuthConsumer { const std::string& password, const std::string& login_token, const std::string& login_captcha); + + // Used when a second factor access code was required to complete a signin + // attempt. + void ProvideSecondFactorAccessCode(const std::string& access_code); + // Sign a user out, removing the preference, erasing all keys - // associated with the user, and cancelling all auth in progress. + // associated with the user, and canceling all auth in progress. void SignOut(); // GaiaAuthConsumer @@ -71,6 +79,7 @@ class SigninManager : public GaiaAuthConsumer { Profile* profile_; std::string username_; std::string password_; // This is kept empty whenever possible. + bool had_two_factor_error_; // Result of the last client login, kept pending the lookup of the // canonical email. diff --git a/chrome/browser/sync/signin_manager_unittest.cc b/chrome/browser/sync/signin_manager_unittest.cc index 7a4114a..5659533 100644 --- a/chrome/browser/sync/signin_manager_unittest.cc +++ b/chrome/browser/sync/signin_manager_unittest.cc @@ -106,6 +106,53 @@ TEST_F(SigninManagerTest, SignInFailure) { EXPECT_TRUE(manager_->GetUsername().empty()); } +TEST_F(SigninManagerTest, ProvideSecondFactorSuccess) { + manager_->Initialize(profile_.get()); + manager_->StartSignIn("username", "password", "", ""); + GoogleServiceAuthError error(GoogleServiceAuthError::TWO_FACTOR); + manager_->OnClientLoginFailure(error); + + EXPECT_EQ(0U, google_login_success_.size()); + EXPECT_EQ(1U, google_login_failure_.size()); + + EXPECT_FALSE(manager_->GetUsername().empty()); + + manager_->ProvideSecondFactorAccessCode("access"); + SimulateValidResponse(); + + EXPECT_EQ(1U, google_login_success_.size()); + EXPECT_EQ(1U, google_login_failure_.size()); +} + +TEST_F(SigninManagerTest, ProvideSecondFactorFailure) { + manager_->Initialize(profile_.get()); + manager_->StartSignIn("username", "password", "", ""); + GoogleServiceAuthError error1(GoogleServiceAuthError::TWO_FACTOR); + manager_->OnClientLoginFailure(error1); + + EXPECT_EQ(0U, google_login_success_.size()); + EXPECT_EQ(1U, google_login_failure_.size()); + + EXPECT_FALSE(manager_->GetUsername().empty()); + + manager_->ProvideSecondFactorAccessCode("badaccess"); + GoogleServiceAuthError error2( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); + manager_->OnClientLoginFailure(error2); + + EXPECT_EQ(0U, google_login_success_.size()); + EXPECT_EQ(2U, google_login_failure_.size()); + EXPECT_FALSE(manager_->GetUsername().empty()); + + manager_->ProvideSecondFactorAccessCode("badaccess"); + GoogleServiceAuthError error3(GoogleServiceAuthError::CONNECTION_FAILED); + manager_->OnClientLoginFailure(error3); + + EXPECT_EQ(0U, google_login_success_.size()); + EXPECT_EQ(3U, google_login_failure_.size()); + EXPECT_TRUE(manager_->GetUsername().empty()); +} + TEST_F(SigninManagerTest, SignOutMidConnect) { manager_->Initialize(profile_.get()); manager_->StartSignIn("username", "password", "", ""); diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc index 0931a3f..594b0e0 100644 --- a/chrome/browser/sync/sync_setup_flow.cc +++ b/chrome/browser/sync/sync_setup_flow.cc @@ -44,7 +44,10 @@ void FlowHandler::RegisterMessages() { } static bool GetAuthData(const std::string& json, - std::string* username, std::string* password, std::string* captcha) { + std::string* username, + std::string* password, + std::string* captcha, + std::string* access_code) { scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) return false; @@ -52,7 +55,8 @@ static bool GetAuthData(const std::string& json, DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); if (!result->GetString("user", username) || !result->GetString("pass", password) || - !result->GetString("captcha", captcha)) { + !result->GetString("captcha", captcha) || + !result->GetString("access_code", access_code)) { return false; } return true; @@ -123,11 +127,11 @@ static bool GetDataTypeChoiceData(const std::string& json, void FlowHandler::HandleSubmitAuth(const ListValue* args) { std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args)); - std::string username, password, captcha; + std::string username, password, captcha, access_code; if (json.empty()) return; - if (!GetAuthData(json, &username, &password, &captcha)) { + if (!GetAuthData(json, &username, &password, &captcha, &access_code)) { // The page sent us something that we didn't understand. // This probably indicates a programming error. NOTREACHED(); @@ -135,7 +139,7 @@ void FlowHandler::HandleSubmitAuth(const ListValue* args) { } if (flow_) - flow_->OnUserSubmittedAuth(username, password, captcha); + flow_->OnUserSubmittedAuth(username, password, captcha, access_code); } void FlowHandler::HandleChooseDataTypes(const ListValue* args) { diff --git a/chrome/browser/sync/sync_setup_flow.h b/chrome/browser/sync/sync_setup_flow.h index 6766963..baf43e6 100644 --- a/chrome/browser/sync/sync_setup_flow.h +++ b/chrome/browser/sync/sync_setup_flow.h @@ -93,8 +93,9 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { void OnUserSubmittedAuth(const std::string& username, const std::string& password, - const std::string& captcha) { - service_->OnUserSubmittedAuth(username, password, captcha); + const std::string& captcha, + const std::string& access_code) { + service_->OnUserSubmittedAuth(username, password, captcha, access_code); } void OnUserChoseDataTypes(bool sync_everything, diff --git a/chrome/browser/sync/sync_setup_wizard.cc b/chrome/browser/sync/sync_setup_wizard.cc index 2eaa00d..5a4ea7b 100644 --- a/chrome/browser/sync/sync_setup_wizard.cc +++ b/chrome/browser/sync/sync_setup_wizard.cc @@ -21,6 +21,7 @@ #include "grit/app_resources.h" #include "grit/browser_resources.h" #include "grit/chromium_strings.h" +#include "grit/locale_settings.h" class SyncResourcesSource : public ChromeURLDataManager::DataSource { public: @@ -112,6 +113,16 @@ void SyncResourcesSource::StartDataRequest(const std::string& path_raw, l10n_util::GetStringUTF16(IDS_SYNC_ERROR_SIGNING_IN)); localized_strings.SetString("captchainstructions", l10n_util::GetStringUTF16(IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS)); + + localized_strings.SetString("invalidaccesscode", + l10n_util::GetStringUTF16(IDS_SYNC_INVALID_ACCESS_CODE_LABEL)); + localized_strings.SetString("enteraccesscode", + l10n_util::GetStringUTF16(IDS_SYNC_ENTER_ACCESS_CODE_LABEL)); + localized_strings.SetString("getaccesscodehelp", + l10n_util::GetStringUTF16(IDS_SYNC_ACCESS_CODE_HELP_LABEL)); + localized_strings.SetString("getaccesscodeurl", + l10n_util::GetStringUTF16(IDS_SYNC_GET_ACCESS_CODE_URL)); + static const base::StringPiece html(ResourceBundle::GetSharedInstance() .GetRawDataResource(IDR_GAIA_LOGIN_HTML)); SetFontAndTextDirection(&localized_strings); diff --git a/chrome/browser/sync/sync_setup_wizard_unittest.cc b/chrome/browser/sync/sync_setup_wizard_unittest.cc index 213ce4d..e4f53f0 100644 --- a/chrome/browser/sync/sync_setup_wizard_unittest.cc +++ b/chrome/browser/sync/sync_setup_wizard_unittest.cc @@ -42,7 +42,8 @@ class ProfileSyncServiceForWizardTest : public ProfileSyncService { virtual void OnUserSubmittedAuth(const std::string& username, const std::string& password, - const std::string& captcha) { + const std::string& captcha, + const std::string& access_code) { username_ = username; password_ = password; captcha_ = captcha; @@ -217,7 +218,8 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { std::string auth = "{\"user\":\""; auth += std::string(kTestUser) + "\",\"pass\":\""; auth += std::string(kTestPassword) + "\",\"captcha\":\""; - auth += std::string(kTestCaptcha) + "\"}"; + auth += std::string(kTestCaptcha) + "\",\"access_code\":\""; + auth += std::string() + "\"}"; credentials.Append(new StringValue(auth)); EXPECT_FALSE(wizard_->IsVisible()); |