Blob Blame History Raw
From 68c280c3bcf8a67a90eb25c30b2e6c2493d7d4d9 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 30 Sep 2015 12:47:01 -0400
Subject: [PATCH 1/4] authPrompt: use correct service name when answering
 preemptively

If the user hits enter before a question is asked, we try to answer
that question as soon as it comes in.

This doesn't work right now because we only answer the question if
there's a query service, but there won't ever be one until the
question is asked.

This commit corrects the code to it's intention, which is to answer
the first service that asks a question, as as soon as possible.
---
 js/gdm/authPrompt.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 34ad7fb..816c69c 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -167,62 +167,61 @@ const AuthPrompt = new Lang.Class({
                                           can_focus: true,
                                           label: _("Next") });
         this.nextButton.connect('clicked',
                                  Lang.bind(this, function() {
                                      this.emit('next');
                                  }));
         this.nextButton.add_style_pseudo_class('default');
         this._buttonBox.add(this.nextButton,
                             { expand: false,
                               x_fill: false,
                               y_fill: false,
                               x_align: St.Align.END,
                               y_align: St.Align.END });
 
         this._updateNextButtonSensitivity(this._entry.text.length > 0);
 
         this._entry.clutter_text.connect('text-changed',
                                          Lang.bind(this, function() {
                                              if (!this._userVerifier.hasPendingMessages)
                                                  this._fadeOutMessage();
 
                                              this._updateNextButtonSensitivity(this._entry.text.length > 0);
                                          }));
         this._entry.clutter_text.connect('activate', Lang.bind(this, function() {
             this.emit('next');
         }));
     },
 
     _onAskQuestion: function(verifier, serviceName, question, passwordChar) {
         if (this._preemptiveAnswer) {
-            if (this._queryingService)
-                this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
+            this._userVerifier.answerQuery(serviceName, this._preemptiveAnswer);
             this._preemptiveAnswer = null;
             return;
         }
 
         if (this._queryingService)
             this.clear();
 
         this._queryingService = serviceName;
         this.setPasswordChar(passwordChar);
         this.setQuestion(question);
 
         if (passwordChar) {
             if (this._userVerifier.reauthenticating)
                 this.nextButton.label = _("Unlock");
             else
                 this.nextButton.label = C_("button", "Sign In");
         } else {
             this.nextButton.label = _("Next");
         }
 
         this.updateSensitivity(true);
         this.emit('prompted');
     },
 
     _onOVirtUserAuthenticated: function() {
         if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
             this.reset();
     },
 
     _onSmartcardStatusChanged: function() {
-- 
2.5.0


From 1b7fa7ad6874208873eb8ce1e8b03dc07cb01217 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 30 Sep 2015 12:51:24 -0400
Subject: [PATCH 2/4] authPrompt: don't fade out auth messages if user types
 password up front

Right now we fade out any stale auth messages as soon as the user starts
typing. This behavior doesn't really make sense if the user is typing up
front, before a password is asked.
---
 js/gdm/authPrompt.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 816c69c..11b6a23 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -155,61 +155,61 @@ const AuthPrompt = new Lang.Class({
                               x_align: St.Align.START,
                               y_align: St.Align.END });
 
         this._buttonBox.add(this._defaultButtonWell,
                             { expand: true,
                               x_fill: false,
                               y_fill: false,
                               x_align: St.Align.END,
                               y_align: St.Align.MIDDLE });
         this.nextButton = new St.Button({ style_class: 'modal-dialog-button',
                                           button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                           reactive: true,
                                           can_focus: true,
                                           label: _("Next") });
         this.nextButton.connect('clicked',
                                  Lang.bind(this, function() {
                                      this.emit('next');
                                  }));
         this.nextButton.add_style_pseudo_class('default');
         this._buttonBox.add(this.nextButton,
                             { expand: false,
                               x_fill: false,
                               y_fill: false,
                               x_align: St.Align.END,
                               y_align: St.Align.END });
 
         this._updateNextButtonSensitivity(this._entry.text.length > 0);
 
         this._entry.clutter_text.connect('text-changed',
                                          Lang.bind(this, function() {
-                                             if (!this._userVerifier.hasPendingMessages)
+                                             if (!this._userVerifier.hasPendingMessages && this._queryingService && !this._preemptiveAnswer)
                                                  this._fadeOutMessage();
 
                                              this._updateNextButtonSensitivity(this._entry.text.length > 0);
                                          }));
         this._entry.clutter_text.connect('activate', Lang.bind(this, function() {
             this.emit('next');
         }));
     },
 
     _onAskQuestion: function(verifier, serviceName, question, passwordChar) {
         if (this._preemptiveAnswer) {
             this._userVerifier.answerQuery(serviceName, this._preemptiveAnswer);
             this._preemptiveAnswer = null;
             return;
         }
 
         if (this._queryingService)
             this.clear();
 
         this._queryingService = serviceName;
         this.setPasswordChar(passwordChar);
         this.setQuestion(question);
 
         if (passwordChar) {
             if (this._userVerifier.reauthenticating)
                 this.nextButton.label = _("Unlock");
             else
                 this.nextButton.label = C_("button", "Sign In");
         } else {
             this.nextButton.label = _("Next");
-- 
2.5.0


From 3b767c0a55b136b53b7c85aff2095969fb230ce4 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Wed, 30 Sep 2015 14:36:33 -0400
Subject: [PATCH 3/4] authPrompt: don't spin unless answering question

---
 js/gdm/authPrompt.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 11b6a23..875f9c5 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -37,62 +37,62 @@ const BeginRequestType = {
 };
 
 const AuthPrompt = new Lang.Class({
     Name: 'AuthPrompt',
 
     _init: function(gdmClient, mode) {
         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
 
         this._gdmClient = gdmClient;
         this._mode = mode;
 
         let reauthenticationOnly;
         if (this._mode == AuthPromptMode.UNLOCK_ONLY)
             reauthenticationOnly = true;
         else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
             reauthenticationOnly = false;
 
         this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
 
         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));
         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
         this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
         this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged));
         this._userVerifier.connect('ovirt-user-authenticated', Lang.bind(this, this._onOVirtUserAuthenticated));
         this.smartcardDetected = this._userVerifier.smartcardDetected;
 
         this.connect('next', Lang.bind(this, function() {
                          this.updateSensitivity(false);
-                         this.startSpinning();
                          if (this._queryingService) {
+                             this.startSpinning();
                              this._userVerifier.answerQuery(this._queryingService, this._entry.text);
                          } else {
                              this._preemptiveAnswer = this._entry.text;
                          }
                      }));
 
         this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
                                         vertical: true });
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
         this.actor.connect('key-press-event',
                            Lang.bind(this, function(actor, event) {
                                if (event.get_key_symbol() == Clutter.KEY_Escape) {
                                    this.cancel();
                                }
                                return Clutter.EVENT_PROPAGATE;
                            }));
 
         this._userWell = new St.Bin({ x_fill: true,
                                       x_align: St.Align.START });
         this.actor.add(this._userWell,
                        { x_align: St.Align.START,
                          x_fill: true,
                          y_fill: true,
                          expand: true });
         this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });
 
         this.actor.add(this._label,
                        { expand: true,
                          x_fill: false,
                          y_fill: true,
-- 
2.5.0


From 22d618c293906defbac877925491af13f8def9a3 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Mon, 5 Oct 2015 15:26:18 -0400
Subject: [PATCH 4/4] authPrompt: stop accepting preemptive answer if user stops
 typing

We only want to allow the user to type the preemptive password in
one smooth motion.  If they start to type, and then stop typing,
we should discard their preemptive password as expired.

Typing ahead the password is just a convenience for users who don't
want to manually lift the shift before typing their passwords, after
all.
---
 js/gdm/authPrompt.js | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js
index 875f9c5..fe80519 100644
--- a/js/gdm/authPrompt.js
+++ b/js/gdm/authPrompt.js
@@ -1,101 +1,109 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
 const Clutter = imports.gi.Clutter;
 const Lang = imports.lang;
 const Signals = imports.signals;
 const St = imports.gi.St;
 
 const Animation = imports.ui.animation;
 const Batch = imports.gdm.batch;
 const GdmUtil = imports.gdm.util;
+const Meta = imports.gi.Meta;
 const Params = imports.misc.params;
 const ShellEntry = imports.ui.shellEntry;
 const Tweener = imports.ui.tweener;
 const UserWidget = imports.ui.userWidget;
 
 const DEFAULT_BUTTON_WELL_ICON_SIZE = 24;
 const DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0;
 const DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3;
 
 const MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5;
 
 const AuthPromptMode = {
     UNLOCK_ONLY: 0,
     UNLOCK_OR_LOG_IN: 1
 };
 
 const AuthPromptStatus = {
     NOT_VERIFYING: 0,
     VERIFYING: 1,
     VERIFICATION_FAILED: 2,
     VERIFICATION_SUCCEEDED: 3
 };
 
 const BeginRequestType = {
     PROVIDE_USERNAME: 0,
     DONT_PROVIDE_USERNAME: 1
 };
 
 const AuthPrompt = new Lang.Class({
     Name: 'AuthPrompt',
 
     _init: function(gdmClient, mode) {
         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
 
         this._gdmClient = gdmClient;
         this._mode = mode;
 
+        this._idleMonitor = Meta.IdleMonitor.get_core();
+
         let reauthenticationOnly;
         if (this._mode == AuthPromptMode.UNLOCK_ONLY)
             reauthenticationOnly = true;
         else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
             reauthenticationOnly = false;
 
         this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });
 
         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));
         this._userVerifier.connect('verification-complete', Lang.bind(this, this._onVerificationComplete));
         this._userVerifier.connect('reset', Lang.bind(this, this._onReset));
         this._userVerifier.connect('smartcard-status-changed', Lang.bind(this, this._onSmartcardStatusChanged));
         this._userVerifier.connect('ovirt-user-authenticated', Lang.bind(this, this._onOVirtUserAuthenticated));
         this.smartcardDetected = this._userVerifier.smartcardDetected;
 
         this.connect('next', Lang.bind(this, function() {
                          this.updateSensitivity(false);
                          if (this._queryingService) {
                              this.startSpinning();
                              this._userVerifier.answerQuery(this._queryingService, this._entry.text);
                          } else {
                              this._preemptiveAnswer = this._entry.text;
+
+                             if (this._preemptiveAnswerWatchId) {
+                                 this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
+                                 this._preemptiveAnswerWatchId = 0;
+                             }
                          }
                      }));
 
         this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
                                         vertical: true });
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
         this.actor.connect('key-press-event',
                            Lang.bind(this, function(actor, event) {
                                if (event.get_key_symbol() == Clutter.KEY_Escape) {
                                    this.cancel();
                                }
                                return Clutter.EVENT_PROPAGATE;
                            }));
 
         this._userWell = new St.Bin({ x_fill: true,
                                       x_align: St.Align.START });
         this.actor.add(this._userWell,
                        { x_align: St.Align.START,
                          x_fill: true,
                          y_fill: true,
                          expand: true });
         this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });
 
         this.actor.add(this._label,
                        { expand: true,
                          x_fill: false,
                          y_fill: true,
                          x_align: St.Align.START });
         this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
                                      can_focus: true });
@@ -107,60 +115,65 @@ const AuthPrompt = new Lang.Class({
                          y_fill: false,
                          x_align: St.Align.START });
 
         this._entry.grab_key_focus();
 
         this._message = new St.Label({ opacity: 0,
                                        styleClass: 'login-dialog-message' });
         this._message.clutter_text.line_wrap = true;
         this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START });
 
         this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
                                              vertical: false });
         this.actor.add(this._buttonBox,
                        { expand:  true,
                          x_align: St.Align.MIDDLE,
                          y_align: St.Align.END });
 
         this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
         this._defaultButtonWellActor = null;
 
         this._initButtons();
 
         let spinnerIcon = global.datadir + '/theme/process-working.svg';
         this._spinner = new Animation.AnimatedIcon(spinnerIcon, DEFAULT_BUTTON_WELL_ICON_SIZE);
         this._spinner.actor.opacity = 0;
         this._spinner.actor.show();
         this._defaultButtonWell.add_child(this._spinner.actor);
     },
 
     _onDestroy: function() {
+        if (this._preemptiveAnswerWatchId) {
+            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
+            this._preemptiveAnswerWatchId = 0;
+        }
+
         this._userVerifier.destroy();
         this._userVerifier = null;
     },
 
     _initButtons: function() {
         this.cancelButton = new St.Button({ style_class: 'modal-dialog-button',
                                             button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                             reactive: true,
                                             can_focus: true,
                                             label: _("Cancel") });
         this.cancelButton.connect('clicked',
                                    Lang.bind(this, function() {
                                        this.cancel();
                                    }));
         this._buttonBox.add(this.cancelButton,
                             { expand: false,
                               x_fill: false,
                               y_fill: false,
                               x_align: St.Align.START,
                               y_align: St.Align.END });
 
         this._buttonBox.add(this._defaultButtonWell,
                             { expand: true,
                               x_fill: false,
                               y_fill: false,
                               x_align: St.Align.END,
                               y_align: St.Align.MIDDLE });
         this.nextButton = new St.Button({ style_class: 'modal-dialog-button',
                                           button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                           reactive: true,
@@ -316,60 +329,65 @@ const AuthPrompt = new Lang.Class({
             else
                 Tweener.addTween(actor,
                                  { opacity: 255,
                                    time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                                    transition: 'linear' });
         }
 
         this._defaultButtonWellActor = actor;
     },
 
     startSpinning: function() {
         this.setActorInDefaultButtonWell(this._spinner.actor, true);
     },
 
     stopSpinning: function() {
         this.setActorInDefaultButtonWell(null, false);
     },
 
     clear: function() {
         this._entry.text = '';
         this.stopSpinning();
     },
 
     setPasswordChar: function(passwordChar) {
         this._entry.clutter_text.set_password_char(passwordChar);
         this._entry.menu.isPassword = passwordChar != '';
     },
 
     setQuestion: function(question) {
+        if (this._preemptiveAnswerWatchId) {
+            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
+            this._preemptiveAnswerWatchId = 0;
+        }
+
         this._label.set_text(question);
 
         this._label.show();
         this._entry.show();
 
         this._entry.grab_key_focus();
     },
 
     getAnswer: function() {
         let text;
 
         if (this._preemptiveAnswer) {
             text = this._preemptiveAnswer;
             this._preemptiveAnswer = null;
         } else {
             text = this._entry.get_text();
         }
 
         return text;
     },
 
     _fadeOutMessage: function() {
         if (this._message.opacity == 0)
             return;
         Tweener.removeTweens(this._message);
         Tweener.addTween(this._message,
                          { opacity: 0,
                            time: MESSAGE_FADE_OUT_ANIMATION_TIME,
                            transition: 'easeOutQuad'
                          });
@@ -401,66 +419,86 @@ const AuthPrompt = new Lang.Class({
     },
 
     updateSensitivity: function(sensitive) {
         this._updateNextButtonSensitivity(sensitive && this._entry.text.length > 0);
         this._entry.reactive = sensitive;
         this._entry.clutter_text.editable = sensitive;
     },
 
     hide: function() {
         this.setActorInDefaultButtonWell(null, true);
         this.actor.hide();
         this._message.opacity = 0;
 
         this.setUser(null);
 
         this.updateSensitivity(true);
         this._entry.set_text('');
     },
 
     setUser: function(user) {
         let oldChild = this._userWell.get_child();
         if (oldChild)
             oldChild.destroy();
 
         if (user) {
             let userWidget = new UserWidget.UserWidget(user);
             this._userWell.set_child(userWidget.actor);
         }
     },
 
+    _onUserStoppedTypePreemptiveAnswer: function() {
+        if (!this._preemptiveAnswerWatchId ||
+            this._preemptiveAnswer ||
+            this._queryingService)
+            return;
+
+        this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
+        this._preemptiveAnswerWatchId = 0;
+
+        this._entry.text = '';
+        this.updateSensitivity(false);
+    },
+
     reset: function() {
         let oldStatus = this.verificationStatus;
         this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
         this.cancelButton.reactive = true;
         this.nextButton.label = _("Next");
 
+        if (this._preemptiveAnswerWatchId) {
+            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
+        }
+        this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch (500,
+                                                                          Lang.bind(this,
+                                                                                    this._onUserStoppedTypePreemptiveAnswer));
+
         if (this._userVerifier)
             this._userVerifier.cancel();
 
         this._queryingService = null;
         this.clear();
         this._message.opacity = 0;
         this.setUser(null);
         this.stopSpinning();
 
         if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
             this.emit('failed');
 
         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._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);
     },
-- 
2.5.0