gdm: Integrate username asking into the ShellUserVerifier flow

Currently, prompting for the username on the login screen is done
"out-of-band". This leads to subtle bugs where we're trying to
integrate two different state machines, or interpret results from
one state machine as part of another.

It also complicates the logic, as the ability to know whether we
need or want a username is currently in the UI layer rather than
the logic layer.

Move this into the verifier proper.
This commit is contained in:
Jasper St. Pierre 2014-03-07 19:09:48 -05:00
parent 056375cbcf
commit fb824131ae
4 changed files with 83 additions and 64 deletions

View File

@ -24,11 +24,6 @@ const AuthPromptMode = {
UNLOCK_OR_LOG_IN: 1
};
const BeginRequestType = {
PROVIDE_USERNAME: 0,
DONT_PROVIDE_USERNAME: 1
};
const AuthPrompt = new Lang.Class({
Name: 'AuthPrompt',
@ -44,6 +39,7 @@ const AuthPrompt = new Lang.Class({
this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
this._userVerifier.connect('needs-username', Lang.bind(this, this._onNeedsUserName));
this._userVerifier.connect('ask-question', Lang.bind(this, this._onAskQuestion));
this._userVerifier.connect('show-message', Lang.bind(this, this._onShowMessage));
this._userVerifier.connect('verification-failed', Lang.bind(this, this._onVerificationFailed));
@ -186,6 +182,14 @@ const AuthPrompt = new Lang.Class({
}));
},
_onNeedsUserName: function() {
this.emit('needs-username');
},
gotUserName: function(userName) {
this._userVerifier.gotUserName(userName);
},
_onAskQuestion: function(verifier, serviceName, question, passwordChar) {
if (this._preemptiveAnswer) {
if (this._queryingService)
@ -403,24 +407,7 @@ const AuthPrompt = new Lang.Class({
this.setUser(null);
this.stopSpinning();
let beginRequestType;
if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
// The user is constant at the unlock screen, so it will immediately
// respond to the request with the username
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
} else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) ||
(this.smartcardDetected &&
this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) {
// We don't need to know the username if the user preempted the login screen
// with a smartcard or with preauthenticated oVirt credentials
beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
} else {
// In all other cases, we should get the username up front.
beginRequestType = BeginRequestType.PROVIDE_USERNAME;
}
this.emit('reset', beginRequestType);
this.emit('reset');
},
addCharacter: function(unichar) {
@ -431,9 +418,12 @@ const AuthPrompt = new Lang.Class({
this._entry.clutter_text.insert_unichar(unichar);
},
begin: function(userName) {
begin: function() {
this.updateSensitivity(false);
this._userVerifier.begin();
},
needsUsername: function() {
this._userVerifier.begin(userName);
},

View File

@ -421,6 +421,7 @@ const LoginDialog = new Lang.Class({
this._authPrompt = new AuthPrompt.AuthPrompt(gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN);
this._authPrompt.connect('prompted', Lang.bind(this, this._onPrompted));
this._authPrompt.connect('reset', Lang.bind(this, this._onReset));
this._authPrompt.connect('needs-username', Lang.bind(this, this._onNeedsUserName));
this._authPrompt.hide();
this.actor.add_child(this._authPrompt.actor);
@ -469,14 +470,13 @@ const LoginDialog = new Lang.Class({
this._sessionMenuButton.actor.show();
this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor);
this._disableUserList = undefined;
this._updateDisableUserList();
this._userListLoaded = false;
// If the user list is enabled, it should take key focus; make sure the
// screen shield is initialized first to prevent it from stealing the
// focus later
Main.layoutManager.connect('startup-complete',
Lang.bind(this, this._updateDisableUserList));
Main.layoutManager.connect('startup-complete', Lang.bind(this, this._reset));
},
_ensureUserListLoaded: function() {
@ -493,15 +493,20 @@ const LoginDialog = new Lang.Class({
GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, this._loadUserList));
},
_reset: function() {
this._authPrompt.reset();
this._authPrompt.begin();
},
_updateDisableUserList: function() {
let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY);
if (disableUserList == this._disableUserList)
return;
if (disableUserList != this._disableUserList) {
this._disableUserList = disableUserList;
if (this._authPrompt.verificationStatus == GdmUtil.VerificationStatus.NOT_VERIFYING)
this._authPrompt.reset();
}
if (this._authPrompt.verificationStatus == GdmUtil.VerificationStatus.ASKING_FOR_USERNAME)
this._reset();
},
_updateCancelButton: function() {
@ -509,7 +514,7 @@ const LoginDialog = new Lang.Class({
// Hide the cancel button if the user list is disabled and we're asking for
// a username
if (this._authPrompt.verificationStatus == GdmUtil.VerificationStatus.NOT_VERIFYING && this._disableUserList)
if (this._authPrompt.verificationStatus == GdmUtil.VerificationStatus.ASKING_FOR_USERNAME && this._disableUserList)
cancelVisible = false;
else
cancelVisible = true;
@ -554,19 +559,18 @@ const LoginDialog = new Lang.Class({
this._showPrompt();
},
_onReset: function(authPrompt, beginRequest) {
_onReset: function() {
this._sessionMenuButton.updateSensitivity(true);
this._user = null;
if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) {
this._reset();
},
_onNeedsUserName: function() {
if (!this._disableUserList)
this._showUserList();
else
this._hideUserListAskForUsernameAndBeginVerification();
} else {
this._hideUserListAndBeginVerification();
}
},
_onDefaultSessionChanged: function(client, sessionId) {
@ -625,7 +629,7 @@ const LoginDialog = new Lang.Class({
this._user = this._userManager.get_user(answer);
this._authPrompt.clear();
this._authPrompt.startSpinning();
this._authPrompt.begin(answer);
this._authPrompt.gotUserName(answer);
this._updateCancelButton();
realmManager.disconnect(realmSignalId)
@ -805,11 +809,6 @@ const LoginDialog = new Lang.Class({
this._askForUsernameAndBeginVerification();
},
_hideUserListAndBeginVerification: function() {
this._hideUserList();
this._authPrompt.begin();
},
_showUserList: function() {
this._ensureUserListLoaded();
this._authPrompt.hide();
@ -823,7 +822,7 @@ const LoginDialog = new Lang.Class({
this._authPrompt.setUser(item.user);
let userName = item.user.get_user_name();
this._authPrompt.begin(userName);
this._authPrompt.gotUserName(userName);
},
_onUserListActivated: function(activatedItem) {

View File

@ -121,9 +121,10 @@ function cloneAndFadeOutActor(actor) {
const VerificationStatus = {
NOT_VERIFYING: 0,
VERIFYING: 1,
VERIFICATION_FAILED: 2,
VERIFICATION_SUCCEEDED: 3
ASKING_FOR_USERNAME: 1,
VERIFYING: 2,
VERIFICATION_FAILED: 3,
VERIFICATION_SUCCEEDED: 4,
};
const ShellUserVerifier = new Lang.Class({
@ -169,24 +170,52 @@ const ShellUserVerifier = new Lang.Class({
_reset: function() {
// Clear previous attempts to authenticate
this.verificationStatus = VerificationStatus.NOT_VERIFYING;
this._userName = null;
this._failCounter = 0;
this._updateDefaultService();
this.emit('reset');
},
begin: function(userName) {
this.verificationStatus = VerificationStatus.VERIFYING;
begin: function() {
if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
// The user is constant at the unlock screen, so it will immediately
// respond to the request with the username
needsUsername = true;
} else if (this.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) ||
(this.smartcardDetected &&
this.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME))) {
// We don't need to know the username if the user preempted the login screen
// with a smartcard or with preauthenticated oVirt credentials
needsUsername = false;
} else {
// In all other cases, we should get the username up front.
needsUsername = true;
}
this._cancellable = new Gio.Cancellable();
if (needsUsername) {
this.verificationStatus = VerificationStatus.ASKING_FOR_USERNAME;
this.emit('needs-username');
} else {
this._beginAuthentication();
}
},
gotUserName: function(userName) {
this._userName = userName;
this._beginAuthentication();
},
_beginAuthentication: function() {
this.verificationStatus = VerificationStatus.VERIFYING;
this._cancellable = new Gio.Cancellable();
this.reauthenticating = false;
this._checkForFingerprintReader();
if (userName) {
if (this._userName) {
// If possible, reauthenticate an already running session,
// so any session specific credentials get updated appropriately
this._client.open_reauthentication_channel(userName, this._cancellable,
this._client.open_reauthentication_channel(this._userName, this._cancellable,
Lang.bind(this, this._reauthenticationChannelOpened));
} else {
this._client.get_user_verifier(this._cancellable, Lang.bind(this, this._userVerifierGot));
@ -501,10 +530,6 @@ const ShellUserVerifier = new Lang.Class({
this.verificationStatus = VerificationStatus.VERIFICATION_SUCCEEDED;
},
_retry: function() {
this.begin(this._userName);
},
_verificationFailed: function(retry) {
// For Not Listed / enterprise logins, immediately reset
// the dialog
@ -520,7 +545,7 @@ const ShellUserVerifier = new Lang.Class({
this._doAfterPendingMessages(Lang.bind(this, function() {
if (canRetry)
this._retry();
this._beginAuthentication();
else
this.clear();
}));

View File

@ -54,6 +54,7 @@ const UnlockDialog = new Lang.Class({
this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY);
this._authPrompt.connect('failed', Lang.bind(this, this._fail));
this._authPrompt.connect('reset', Lang.bind(this, this._onReset));
this._authPrompt.connect('needs-username', Lang.bind(this, this._onNeedsUserName));
this._authPrompt.setPasswordChar('\u25cf');
this._authPrompt.nextButton.label = _("Unlock");
@ -100,6 +101,10 @@ const UnlockDialog = new Lang.Class({
},
_onReset: function(authPrompt, beginRequest) {
this._authPrompt.begin();
},
_onNeedsUserName: function() {
let userName;
if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) {
this._authPrompt.setUser(this._user);
@ -108,7 +113,7 @@ const UnlockDialog = new Lang.Class({
userName = null;
}
this._authPrompt.begin(userName);
this._authPrompt.gotUserName(userName);
},
_escape: function() {