Commit cbbb33cf authored by Maksim Ivanov's avatar Maksim Ivanov Committed by Commit Bot

Implement basic PIN dialog on SAML page

This CL builds the missing integration between the C++ code (starting
from the handler of the PIN requests made by extensions) and the
JavaScript code on the SAML sign-in page:

* dynamically enabling/disabling this UI when the SAML sign-in
  begins/ends;
* sending the PIN request parameters from C++ to JS;
* replying with the entered PIN from JS to C++.

The proper error displaying and other UI improvements will be done in
follow-up CLs.

Bug: 964069
Test: enroll a Chromebook, configure device policies to force-install smart card middleware onto Login Screen, start logging in using a SAML account that uses smart card authentication, check that the PIN dialog appears on top of the SAML page, enter the smart card PIN, check that the login completes
Change-Id: I95c2d5e5110a967ca6b7d8f957bbc8422f62d758
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1742233Reviewed-by: default avatarDenis Kuznetsov <antrim@chromium.org>
Reviewed-by: default avatarAchuith Bhandarkar <achuith@chromium.org>
Reviewed-by: default avatarRoman Sorokin [CET] <rsorokin@chromium.org>
Commit-Queue: Maksim Ivanov <emaxx@chromium.org>
Cr-Commit-Position: refs/heads/master@{#686151}
parent afda9517
...@@ -102,6 +102,7 @@ OobeTypes.OobeConfiguration; ...@@ -102,6 +102,7 @@ OobeTypes.OobeConfiguration;
/** /**
* Specifies the type of the information that is requested by the security token * Specifies the type of the information that is requested by the security token
* PIN dialog. * PIN dialog.
* Must be kept in sync with chromeos/constants/security_token_pin_types.h.
* @enum {number} * @enum {number}
*/ */
OobeTypes.SecurityTokenPinDialogType = { OobeTypes.SecurityTokenPinDialogType = {
...@@ -112,20 +113,23 @@ OobeTypes.SecurityTokenPinDialogType = { ...@@ -112,20 +113,23 @@ OobeTypes.SecurityTokenPinDialogType = {
/** /**
* Specifies the type of the error that is displayed in the security token PIN * Specifies the type of the error that is displayed in the security token PIN
* dialog. * dialog.
* Must be kept in sync with chromeos/constants/security_token_pin_types.h.
* @enum {number} * @enum {number}
*/ */
OobeTypes.SecurityTokenPinDialogErrorType = { OobeTypes.SecurityTokenPinDialogErrorType = {
UNKNOWN_ERROR: 0, NONE: 0,
INVALID_PIN: 1, UNKNOWN: 1,
INVALID_PUK: 2, INVALID_PIN: 2,
MAX_ATTEMPTS_EXCEEDED: 3, INVALID_PUK: 3,
MAX_ATTEMPTS_EXCEEDED: 4,
}; };
/** /**
* Configuration of the security token PIN dialog. * Configuration of the security token PIN dialog.
* @typedef {{ * @typedef {{
* type: OobeTypes.SecurityTokenPinDialogType, * codeType: OobeTypes.SecurityTokenPinDialogType,
* errorType: (OobeTypes.SecurityTokenPinDialogErrorType|undefined), * enableUserInput: boolean,
* errorLabel: OobeTypes.SecurityTokenPinDialogErrorType,
* attemptsLeft: number, * attemptsLeft: number,
* }} * }}
*/ */
......
...@@ -103,6 +103,7 @@ Polymer({ ...@@ -103,6 +103,7 @@ Polymer({
isSaml_: { isSaml_: {
type: Boolean, type: Boolean,
value: false, value: false,
observer: 'onSamlChanged_',
}, },
/** /**
...@@ -114,6 +115,7 @@ Polymer({ ...@@ -114,6 +115,7 @@ Polymer({
pinDialogParameters_: { pinDialogParameters_: {
type: Object, type: Object,
value: null, value: null,
observer: 'onPinDialogParametersChanged_',
}, },
/** /**
...@@ -927,14 +929,28 @@ Polymer({ ...@@ -927,14 +929,28 @@ Polymer({
onAuthFlowChange_: function() { onAuthFlowChange_: function() {
this.isSaml_ = this.isSaml_ =
this.authenticator_.authFlow == cr.login.Authenticator.AuthFlow.SAML; this.authenticator_.authFlow == cr.login.Authenticator.AuthFlow.SAML;
},
/**
* Observer that is called when the |isSaml_| property gets changed.
* @param {number} newValue
* @param {number} oldValue
* @private
*/
onSamlChanged_: function(newValue, oldValue) {
chrome.send('samlStateChanged', [this.isSaml_]);
this.classList.toggle('saml', this.isSaml_); this.classList.toggle('saml', this.isSaml_);
if (Oobe.getInstance().currentScreen.id == 'gaia-signin') { // Skip these updates in the initial observer run, which is happening during
Oobe.getInstance().updateScreenSize(this); // the property initialization.
} if (oldValue !== undefined) {
if (Oobe.getInstance().currentScreen.id == 'gaia-signin') {
Oobe.getInstance().updateScreenSize(this);
}
this.updateGuestButtonVisibility_(); this.updateGuestButtonVisibility_();
}
}, },
/** /**
...@@ -1440,25 +1456,47 @@ Polymer({ ...@@ -1440,25 +1456,47 @@ Polymer({
showPinDialog: function(parameters) { showPinDialog: function(parameters) {
assert(parameters); assert(parameters);
// If currently shown, reset and send the cancellation result if not yet. // Note that this must be done before updating |pinDialogResultReported_|,
this.closePinDialog(); // since the observer will notify the handler about the cancellation of the
this.$.pinDialog.reset(); // previous dialog depending on this flag.
this.pinDialogParameters_ = parameters; this.pinDialogParameters_ = parameters;
this.$.pinDialog.reset();
this.pinDialogResultReported_ = false; this.pinDialogResultReported_ = false;
}, },
/** /**
* Closes the PIN dialog (that was previously opened using showPinDialog()). * Closes the PIN dialog (that was previously opened using showPinDialog()).
* Does nothing if the dialog is not shown.
*/ */
closePinDialog: function() { closePinDialog: function() {
if (this.pinDialogParameters_ && !this.pinDialogResultReported_) { // Note that the update triggers the observer, that notifies the handler
this.pinDialogResultReported_ = true; // about the closing.
// TODO(crbug.com/964069): Send the "canceled" result to the C++ side.
}
this.pinDialogParameters_ = null; this.pinDialogParameters_ = null;
}, },
/**
* Observer that is called when the |pinDialogParameters_| property gets
* changed.
* @param {number} newValue
* @param {number} oldValue
* @private
*/
onPinDialogParametersChanged_: function(newValue, oldValue) {
if (oldValue === undefined) {
// Don't do anything on the initial call, triggered by the property
// initialization.
return;
}
if ((oldValue !== null && newValue === null) ||
(oldValue !== null && newValue !== null &&
!this.pinDialogResultReported_)) {
// Report the cancellation result if the dialog got closed or got reused
// before reporting the result.
chrome.send('securityTokenPinEntered', [/*user_input=*/ '']);
}
},
/** /**
* Invoked when the user cancels the PIN dialog. * Invoked when the user cancels the PIN dialog.
* @param {!CustomEvent} e * @param {!CustomEvent} e
...@@ -1473,7 +1511,7 @@ Polymer({ ...@@ -1473,7 +1511,7 @@ Polymer({
*/ */
onPinDialogCompleted_: function(e) { onPinDialogCompleted_: function(e) {
this.pinDialogResultReported_ = true; this.pinDialogResultReported_ = true;
// TODO(crbug.com/964069): Send the PIN to the C++ side. chrome.send('securityTokenPinEntered', [/*user_input=*/ e.detail]);
}, },
}); });
......
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/authpolicy/authpolicy_helper.h" #include "chrome/browser/chromeos/authpolicy/authpolicy_helper.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/certificate_provider/pin_dialog_manager.h"
#include "chrome/browser/chromeos/language_preferences.h" #include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/login/lock_screen_utils.h" #include "chrome/browser/chromeos/login/lock_screen_utils.h"
#include "chrome/browser/chromeos/login/reauth_stats.h" #include "chrome/browser/chromeos/login/reauth_stats.h"
...@@ -59,6 +62,7 @@ ...@@ -59,6 +62,7 @@
#include "chromeos/constants/chromeos_features.h" #include "chromeos/constants/chromeos_features.h"
#include "chromeos/constants/chromeos_switches.h" #include "chromeos/constants/chromeos_switches.h"
#include "chromeos/constants/devicetype.h" #include "chromeos/constants/devicetype.h"
#include "chromeos/constants/security_token_pin_types.h"
#include "chromeos/dbus/util/version_loader.h" #include "chromeos/dbus/util/version_loader.h"
#include "chromeos/login/auth/challenge_response/cert_utils.h" #include "chromeos/login/auth/challenge_response/cert_utils.h"
#include "chromeos/login/auth/cryptohome_key_constants.h" #include "chromeos/login/auth/cryptohome_key_constants.h"
...@@ -255,6 +259,27 @@ bool GaiaActionButtonsEnabled() { ...@@ -255,6 +259,27 @@ bool GaiaActionButtonsEnabled() {
return base::FeatureList::IsEnabled(chromeos::features::kGaiaActionButtons); return base::FeatureList::IsEnabled(chromeos::features::kGaiaActionButtons);
} }
PinDialogManager* GetLoginScreenPinDialogManager() {
DCHECK(ProfileHelper::IsSigninProfileInitialized());
CertificateProviderService* certificate_provider_service =
CertificateProviderServiceFactory::GetForBrowserContext(
ProfileHelper::GetSigninProfile());
return certificate_provider_service->pin_dialog_manager();
}
base::Value MakeSecurityTokenPinDialogParameters(
SecurityTokenPinCodeType code_type,
bool enable_user_input,
SecurityTokenPinErrorLabel error_label,
int attempts_left) {
base::Value params(base::Value::Type::DICTIONARY);
params.SetIntKey("codeType", static_cast<int>(code_type));
params.SetBoolKey("enableUserInput", enable_user_input);
params.SetIntKey("errorLabel", static_cast<int>(error_label));
params.SetIntKey("attemptsLeft", attempts_left);
return params;
}
} // namespace } // namespace
constexpr StaticOobeScreenId GaiaView::kScreenId; constexpr StaticOobeScreenId GaiaView::kScreenId;
...@@ -299,6 +324,8 @@ GaiaScreenHandler::GaiaScreenHandler( ...@@ -299,6 +324,8 @@ GaiaScreenHandler::GaiaScreenHandler(
GaiaScreenHandler::~GaiaScreenHandler() { GaiaScreenHandler::~GaiaScreenHandler() {
if (network_portal_detector_) if (network_portal_detector_)
network_portal_detector_->RemoveObserver(this); network_portal_detector_->RemoveObserver(this);
if (is_security_token_pin_enabled_)
GetLoginScreenPinDialogManager()->RemovePinDialogHost(this);
} }
void GaiaScreenHandler::MaybePreloadAuthExtension() { void GaiaScreenHandler::MaybePreloadAuthExtension() {
...@@ -633,6 +660,9 @@ void GaiaScreenHandler::RegisterMessages() { ...@@ -633,6 +660,9 @@ void GaiaScreenHandler::RegisterMessages() {
AddCallback("updateSigninUIState", AddCallback("updateSigninUIState",
&GaiaScreenHandler::HandleUpdateSigninUIState); &GaiaScreenHandler::HandleUpdateSigninUIState);
AddCallback("showGuestInOobe", &GaiaScreenHandler::HandleShowGuestInOobe); AddCallback("showGuestInOobe", &GaiaScreenHandler::HandleShowGuestInOobe);
AddCallback("samlStateChanged", &GaiaScreenHandler::HandleSamlStateChanged);
AddCallback("securityTokenPinEntered",
&GaiaScreenHandler::HandleSecurityTokenPinEntered);
// Allow UMA metrics collection from JS. // Allow UMA metrics collection from JS.
web_ui()->AddMessageHandler(std::make_unique<MetricsHandler>()); web_ui()->AddMessageHandler(std::make_unique<MetricsHandler>());
...@@ -950,6 +980,48 @@ void GaiaScreenHandler::HandleShowGuestInOobe(bool show) { ...@@ -950,6 +980,48 @@ void GaiaScreenHandler::HandleShowGuestInOobe(bool show) {
ash::LoginScreen::Get()->ShowGuestButtonInOobe(show); ash::LoginScreen::Get()->ShowGuestButtonInOobe(show);
} }
void GaiaScreenHandler::HandleSamlStateChanged(bool is_saml) {
if (is_saml == is_security_token_pin_enabled_) {
// We're already in the needed |is_security_token_pin_enabled_| state.
return;
}
// Enable ourselves as a security token PIN dialog host during the SAML
// sign-in, so that when the SAML page requires client authentication (e.g.,
// against a smart card), this PIN request is embedded into the SAML login UI.
if (is_saml) {
GetLoginScreenPinDialogManager()->AddPinDialogHost(this);
} else {
security_token_pin_entered_callback_.Reset();
security_token_pin_dialog_closed_callback_.Reset();
GetLoginScreenPinDialogManager()->RemovePinDialogHost(this);
}
is_security_token_pin_enabled_ = is_saml;
}
void GaiaScreenHandler::HandleSecurityTokenPinEntered(
const std::string& user_input) {
// Invariant: when the pin_entered_callback is present, the closed_callback
// must be present as well.
DCHECK(!security_token_pin_entered_callback_ ||
security_token_pin_dialog_closed_callback_);
if (!security_token_pin_dialog_closed_callback_) {
// The PIN request has already been canceled on the handler side.
return;
}
if (user_input.empty()) {
security_token_pin_entered_callback_.Reset();
std::move(security_token_pin_dialog_closed_callback_).Run();
} else {
// The callback must be non-null, since the UI implementation should not
// send multiple non-empty results.
std::move(security_token_pin_entered_callback_).Run(user_input);
// Keep |security_token_pin_dialog_closed_callback_|, in order to be able to
// notify about the dialog closing afterwards.
}
}
void GaiaScreenHandler::OnShowAddUser() { void GaiaScreenHandler::OnShowAddUser() {
signin_screen_handler_->is_account_picker_showing_first_time_ = false; signin_screen_handler_->is_account_picker_showing_first_time_ = false;
lock_screen_utils::EnforcePolicyInputMethods(std::string()); lock_screen_utils::EnforcePolicyInputMethods(std::string());
...@@ -1125,6 +1197,45 @@ void GaiaScreenHandler::ShowSigninScreenForTest(const std::string& username, ...@@ -1125,6 +1197,45 @@ void GaiaScreenHandler::ShowSigninScreenForTest(const std::string& username,
} }
} }
void GaiaScreenHandler::ShowSecurityTokenPinDialog(
const std::string& /*caller_extension_name*/,
SecurityTokenPinCodeType code_type,
bool enable_user_input,
SecurityTokenPinErrorLabel error_label,
int attempts_left,
const base::Optional<AccountId>& /*authenticating_user_account_id*/,
SecurityTokenPinEnteredCallback pin_entered_callback,
SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) {
DCHECK(is_security_token_pin_enabled_);
// There must be either no active PIN dialog, or the active dialog for which
// the PIN has already been entered.
DCHECK(!security_token_pin_entered_callback_);
security_token_pin_entered_callback_ = std::move(pin_entered_callback);
// Note that this overwrites the previous closed_callback in the case where
// the dialog was already shown. This is intended, since the closing callback
// should only be used to notify that the dialog got canceled, which imposes a
// stricter quota on the PIN request caller.
security_token_pin_dialog_closed_callback_ =
std::move(pin_dialog_closed_callback);
CallJS("login.GaiaSigninScreen.showPinDialog",
MakeSecurityTokenPinDialogParameters(code_type, enable_user_input,
error_label, attempts_left));
}
void GaiaScreenHandler::CloseSecurityTokenPinDialog() {
DCHECK(is_security_token_pin_enabled_);
// Invariant: when the pin_entered_callback is present, the closed_callback
// must be present as well.
DCHECK(!security_token_pin_entered_callback_ ||
security_token_pin_dialog_closed_callback_);
security_token_pin_entered_callback_.Reset();
security_token_pin_dialog_closed_callback_.Reset();
CallJS("login.GaiaSigninScreen.closePinDialog");
}
bool GaiaScreenHandler::IsOfflineLoginActive() const { bool GaiaScreenHandler::IsOfflineLoginActive() const {
return (screen_mode_ == GAIA_SCREEN_MODE_OFFLINE) || offline_login_is_active_; return (screen_mode_ == GAIA_SCREEN_MODE_OFFLINE) || offline_login_is_active_;
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "chrome/browser/chromeos/authpolicy/authpolicy_helper.h" #include "chrome/browser/chromeos/authpolicy/authpolicy_helper.h"
#include "chrome/browser/chromeos/certificate_provider/security_token_pin_dialog_host.h"
#include "chrome/browser/chromeos/login/login_client_cert_usage_observer.h" #include "chrome/browser/chromeos/login/login_client_cert_usage_observer.h"
#include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
#include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h" #include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
...@@ -75,7 +76,8 @@ class GaiaView { ...@@ -75,7 +76,8 @@ class GaiaView {
// A class that handles WebUI hooks in Gaia screen. // A class that handles WebUI hooks in Gaia screen.
class GaiaScreenHandler : public BaseScreenHandler, class GaiaScreenHandler : public BaseScreenHandler,
public GaiaView, public GaiaView,
public NetworkPortalDetector::Observer { public NetworkPortalDetector::Observer,
public SecurityTokenPinDialogHost {
public: public:
using TView = GaiaView; using TView = GaiaView;
...@@ -117,6 +119,18 @@ class GaiaScreenHandler : public BaseScreenHandler, ...@@ -117,6 +119,18 @@ class GaiaScreenHandler : public BaseScreenHandler,
const std::string& password, const std::string& password,
const std::string& services) override; const std::string& services) override;
// SecurityTokenPinDialogHost:
void ShowSecurityTokenPinDialog(
const std::string& caller_extension_name,
SecurityTokenPinCodeType code_type,
bool enable_user_input,
SecurityTokenPinErrorLabel error_label,
int attempts_left,
const base::Optional<AccountId>& authenticating_user_account_id,
SecurityTokenPinEnteredCallback pin_entered_callback,
SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) override;
void CloseSecurityTokenPinDialog() override;
// Returns true if offline login mode was either required, or reported by the // Returns true if offline login mode was either required, or reported by the
// WebUI (i.e. WebUI mignt not have completed transition to the new mode). // WebUI (i.e. WebUI mignt not have completed transition to the new mode).
bool IsOfflineLoginActive() const; bool IsOfflineLoginActive() const;
...@@ -222,6 +236,12 @@ class GaiaScreenHandler : public BaseScreenHandler, ...@@ -222,6 +236,12 @@ class GaiaScreenHandler : public BaseScreenHandler,
// OOBE. // OOBE.
void HandleShowGuestInOobe(bool show); void HandleShowGuestInOobe(bool show);
// Called to notify whether the SAML sign-in is currently happening.
void HandleSamlStateChanged(bool is_saml);
// Called to deliver the result of the security token PIN request. Called with
// an empty string when the request is canceled.
void HandleSecurityTokenPinEntered(const std::string& user_input);
void OnShowAddUser(); void OnShowAddUser();
// Really handles the complete login message. // Really handles the complete login message.
...@@ -397,6 +417,22 @@ class GaiaScreenHandler : public BaseScreenHandler, ...@@ -397,6 +417,22 @@ class GaiaScreenHandler : public BaseScreenHandler,
std::unique_ptr<LoginClientCertUsageObserver> std::unique_ptr<LoginClientCertUsageObserver>
extension_provided_client_cert_usage_observer_; extension_provided_client_cert_usage_observer_;
// State of the security token PIN dialogs:
// Whether this instance is currently registered as a host for showing the
// security token PIN dialogs. (See PinDialogManager for the default host.)
bool is_security_token_pin_enabled_ = false;
// The callback to run when the user submits a non-empty input to the security
// token PIN dialog.
// Is non-empty iff the dialog is active and the input wasn't sent yet.
SecurityTokenPinEnteredCallback security_token_pin_entered_callback_;
// The callback to run when the security token PIN dialog gets closed - either
// due to the user canceling the dialog or the whole sign-in attempt being
// aborted.
// Is non-empty iff the dialog is active.
SecurityTokenPinDialogClosedCallback
security_token_pin_dialog_closed_callback_;
base::WeakPtrFactory<GaiaScreenHandler> weak_factory_; base::WeakPtrFactory<GaiaScreenHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(GaiaScreenHandler); DISALLOW_COPY_AND_ASSIGN(GaiaScreenHandler);
......
...@@ -11,18 +11,22 @@ namespace chromeos { ...@@ -11,18 +11,22 @@ namespace chromeos {
// Type of the information asked from the user during a security token PIN // Type of the information asked from the user during a security token PIN
// request. // request.
// Must be kept in sync with
// chrome/browser/resources/chromeos/login/oobe_types.js.
enum class SecurityTokenPinCodeType { enum class SecurityTokenPinCodeType {
kPin, kPin = 0,
kPuk, kPuk = 1,
}; };
// Error to be displayed in the security token PIN request. // Error to be displayed in the security token PIN request.
// Must be kept in sync with
// chrome/browser/resources/chromeos/login/oobe_types.js.
enum class SecurityTokenPinErrorLabel { enum class SecurityTokenPinErrorLabel {
kNone, kNone = 0,
kUnknown, kUnknown = 1,
kInvalidPin, kInvalidPin = 2,
kInvalidPuk, kInvalidPuk = 3,
kMaxAttemptsExceeded, kMaxAttemptsExceeded = 4,
}; };
} // namespace chromeos } // namespace chromeos
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment