mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-23 19:54:02 -04:00
lock: show the PAM prompt and track the conversation state (#3691)
Pam.qml holds the prompt text PAM asked for and separates "a response is in flight" from "the conversation is open", so the UI stops treating an open conversation as busy. The lock screen shows the prompt instead of a hardcoded "Password", and clears the field when a new prompt arrives.
This commit is contained in:
1 parent
a8bb04a2b2
commit
10eb5f6ec3
2 files changed
+107
-20
No files matched your search
@@ -24,6 +24,15 @@ Scope {
|
||||
property string u2fPendingMode
|
||||
property string buffer
|
||||
|
||||
// the prompt pam asked for, e.g. "PIN: "
|
||||
property string authPromptText: ""
|
||||
property bool responseSubmitted: false
|
||||
property bool awaitingUserInput: false
|
||||
property bool responseInFlight: false
|
||||
// the conversation is held open while waiting, so passwd.active is not "busy"
|
||||
readonly property bool authBusy: passwd.active && root.responseInFlight
|
||||
// stops a conversation that never prompts from being reopened forever
|
||||
property int autoRestartCount: 0
|
||||
property var attemptInfoMessages: []
|
||||
property bool lockoutAnnouncedThisAttempt: false
|
||||
|
||||
@@ -53,6 +62,37 @@ Scope {
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
// open the conversation without answering it, so the prompt is known before typing
|
||||
function beginAuthSession(): void {
|
||||
if (passwd.active || root.unlockInProgress || !root.lockSecured)
|
||||
return;
|
||||
if (root.autoRestartCount >= 3)
|
||||
return;
|
||||
root.autoRestartCount = root.autoRestartCount + 1;
|
||||
root.responseSubmitted = false;
|
||||
root.awaitingUserInput = false;
|
||||
root.responseInFlight = false;
|
||||
eagerStart.restart();
|
||||
}
|
||||
|
||||
function submitPassword(): void {
|
||||
root.responseSubmitted = true;
|
||||
if (!passwd.active) {
|
||||
passwd.start();
|
||||
return;
|
||||
}
|
||||
if (passwd.responseRequired) {
|
||||
root.responseSubmitted = false;
|
||||
root.awaitingUserInput = false;
|
||||
root.responseInFlight = true;
|
||||
passwdActiveTimeout.restart();
|
||||
passwd.respond(root.buffer);
|
||||
} else {
|
||||
// kept and sent when the prompt arrives; a stack that never asks still times out
|
||||
passwdActiveTimeout.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function completeUnlock(): void {
|
||||
if (!root.unlockInProgress) {
|
||||
root.unlockInProgress = true;
|
||||
@@ -187,10 +227,15 @@ Scope {
|
||||
root.attemptInfoMessages = root.attemptInfoMessages.concat([message]);
|
||||
}
|
||||
|
||||
onResponseRequiredChanged: {
|
||||
// not responseRequiredChanged: that only fires on change, so two prompts in
|
||||
// a row leave the second unhandled
|
||||
onPamMessage: {
|
||||
if (!responseRequired)
|
||||
return;
|
||||
|
||||
root.authPromptText = (message || "").replace(/:\s*$/, "").trim();
|
||||
root.autoRestartCount = 0;
|
||||
|
||||
const notice = root.attemptInfoMessages.filter(m => m !== message);
|
||||
if (notice.length > 0) {
|
||||
root.lockMessage = notice.join("\n");
|
||||
@@ -198,7 +243,17 @@ Scope {
|
||||
}
|
||||
root.attemptInfoMessages = [];
|
||||
|
||||
respond(root.buffer);
|
||||
// each prompt needs its own submission, or the second gets the first answer
|
||||
if (root.responseSubmitted) {
|
||||
root.responseSubmitted = false;
|
||||
root.awaitingUserInput = false;
|
||||
root.responseInFlight = true;
|
||||
respond(root.buffer);
|
||||
} else {
|
||||
root.awaitingUserInput = true;
|
||||
root.responseInFlight = false;
|
||||
passwdActiveTimeout.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
onCompleted: res => {
|
||||
@@ -236,6 +291,12 @@ Scope {
|
||||
else if (res === PamResult.Failed)
|
||||
root.state = "fail";
|
||||
|
||||
root.responseSubmitted = false;
|
||||
root.awaitingUserInput = false;
|
||||
root.responseInFlight = false;
|
||||
if (res !== PamResult.MaxTries)
|
||||
root.beginAuthSession();
|
||||
|
||||
root.flashMsg();
|
||||
stateReset.restart();
|
||||
}
|
||||
@@ -248,7 +309,11 @@ Scope {
|
||||
if (passwd.active) {
|
||||
root.attemptInfoMessages = [];
|
||||
root.lockoutAnnouncedThisAttempt = false;
|
||||
passwdActiveTimeout.restart();
|
||||
root.authPromptText = "";
|
||||
if (root.responseSubmitted)
|
||||
passwdActiveTimeout.restart();
|
||||
else
|
||||
passwdActiveTimeout.running = false;
|
||||
} else {
|
||||
passwdActiveTimeout.running = false;
|
||||
}
|
||||
@@ -431,7 +496,7 @@ Scope {
|
||||
}
|
||||
|
||||
function startForAlternativeAuth(): void {
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active)
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam || SettingsData.u2fMode !== "or" || root.unlockInProgress || root.authBusy || active)
|
||||
return;
|
||||
abort();
|
||||
root.u2fPending = true;
|
||||
@@ -548,6 +613,16 @@ Scope {
|
||||
onTriggered: root.cancelU2fPending()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: eagerStart
|
||||
|
||||
interval: 50
|
||||
onTriggered: {
|
||||
if (!passwd.active && root.lockSecured && !root.unlockInProgress)
|
||||
passwd.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: passwdActiveTimeout
|
||||
|
||||
@@ -602,6 +677,10 @@ Scope {
|
||||
root.attemptInfoMessages = [];
|
||||
root.lockoutAnnouncedThisAttempt = false;
|
||||
root.resetAuthFlows();
|
||||
root.responseSubmitted = false;
|
||||
root.awaitingUserInput = false;
|
||||
root.autoRestartCount = 0;
|
||||
root.beginAuthSession();
|
||||
fprint.tries = 0;
|
||||
fprint.errorTries = 0;
|
||||
if (!SettingsData.lockPamExternallyManaged && !dankshellConfigWatcher.loaded && !userPamWatcher.loaded)
|
||||
|
||||
@@ -30,7 +30,7 @@ Item {
|
||||
readonly property bool ring: style === "ring"
|
||||
readonly property bool morph: style === "expressive"
|
||||
readonly property bool minimal: style === "minimal"
|
||||
readonly property bool authenticating: (pam?.passwd.active ?? false) && !unlocking
|
||||
readonly property bool authenticating: (pam?.authBusy ?? false) && !unlocking
|
||||
readonly property bool failed: pamState !== ""
|
||||
readonly property var morphShapes: ["cookie4", "clover4", "sunny", "cookie9", "softBurst", "pentagon", "oval", "cookie6"]
|
||||
readonly property string morphShape: morphShapes[passwordBuffer.length % morphShapes.length]
|
||||
@@ -113,7 +113,7 @@ Item {
|
||||
}
|
||||
|
||||
function canStartSecurityKeyUnlock() {
|
||||
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !unlocking;
|
||||
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.authBusy && !pam.u2f.active && !pam.u2fPending && !unlocking;
|
||||
}
|
||||
|
||||
function triggerSecurityKeyUnlock() {
|
||||
@@ -161,6 +161,12 @@ Item {
|
||||
if (keyboardController.isKeyboardActive)
|
||||
keyboardController.hide();
|
||||
}
|
||||
|
||||
// onStateChanged only fires when the conversation ends, too late between prompts
|
||||
function onAwaitingUserInputChanged() {
|
||||
if (root.pam.awaitingUserInput)
|
||||
passwordField.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -534,7 +540,7 @@ Item {
|
||||
return root.accentColor;
|
||||
return root.contained ? Theme.surfaceVariantText : Theme.withAlpha(root.plainColor, Theme.pendingOpacity);
|
||||
}
|
||||
opacity: root.pam?.passwd.active && !root.morph ? 0 : 1
|
||||
opacity: root.pam?.authBusy && !root.morph ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
@@ -652,8 +658,8 @@ Item {
|
||||
activeFocusOnTab: !root.demoMode
|
||||
onTextChanged: cursorPosition = text.length
|
||||
onAccepted: {
|
||||
if (!root.demoMode && !root.unlocking && !root.pam.passwd.active && !root.pam.u2fPending)
|
||||
root.pam.passwd.start();
|
||||
if (!root.demoMode && !root.unlocking && !root.pam.authBusy && !root.pam.u2fPending)
|
||||
root.pam.submitPassword();
|
||||
}
|
||||
Keys.onPressed: event => handleKey(event)
|
||||
|
||||
@@ -686,7 +692,7 @@ Item {
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.pam.passwd.active) {
|
||||
if (root.pam.authBusy) {
|
||||
root.log.debug("PAM is active, ignoring input");
|
||||
event.accepted = true;
|
||||
return;
|
||||
@@ -809,7 +815,7 @@ Item {
|
||||
return;
|
||||
const committed = text;
|
||||
text = "";
|
||||
if (root.demoMode || root.unlocking || root.pam.passwd.active)
|
||||
if (root.demoMode || root.unlocking || root.pam.authBusy)
|
||||
return;
|
||||
passwordField.insertText(committed);
|
||||
}
|
||||
@@ -848,14 +854,16 @@ Item {
|
||||
return I18n.tr("Insert your security key...");
|
||||
return I18n.tr("Touch your security key...");
|
||||
}
|
||||
if (root.pam.passwd.active)
|
||||
if (root.pam.authBusy)
|
||||
return I18n.tr("Authenticating...", "lock screen status text while the password is checked");
|
||||
if (root.passwordVisibility !== "always")
|
||||
return "";
|
||||
if (root.pam.authPromptText !== "")
|
||||
return root.pam.authPromptText;
|
||||
return I18n.tr("Password", "lock screen password field placeholder") + "…";
|
||||
}
|
||||
color: {
|
||||
if (root.unlocking || (root.pam?.passwd.active ?? false))
|
||||
if (root.unlocking || (root.pam?.authBusy ?? false))
|
||||
return root.accentColor;
|
||||
return root.contained ? Theme.outline : Theme.withAlpha(root.plainColor, Theme.pendingOpacity);
|
||||
}
|
||||
@@ -969,7 +977,7 @@ Item {
|
||||
x: passwordDisplay.x + passwordDisplay.cursorRectangle.x
|
||||
y: passwordDisplay.y + passwordDisplay.cursorRectangle.y
|
||||
height: passwordDisplay.cursorRectangle.height
|
||||
shown: !root.demoMode && root.showPasswordField && !root.ring && passwordField.activeFocus && !(root.pam?.passwd.active ?? false) && !(root.pam?.u2fPending ?? false) && !root.unlocking
|
||||
shown: !root.demoMode && root.showPasswordField && !root.ring && passwordField.activeFocus && !(root.pam?.authBusy ?? false) && !(root.pam?.u2fPending ?? false) && !root.unlocking
|
||||
|
||||
readonly property int fieldCursorPosition: passwordField.cursorPosition
|
||||
readonly property string fieldText: passwordField.text
|
||||
@@ -994,7 +1002,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||
buttonSize: Theme.buttonHeightXS
|
||||
visible: !root.demoMode && !root.ring && !root.minimal && root.passwordBuffer.length > 0 && !(root.pam?.passwd.active ?? false) && !root.unlocking
|
||||
visible: !root.demoMode && !root.ring && !root.minimal && root.passwordBuffer.length > 0 && !(root.pam?.authBusy ?? false) && !root.unlocking
|
||||
enabled: visible
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
@@ -1031,7 +1039,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard"
|
||||
buttonSize: Theme.buttonHeightXS
|
||||
visible: !root.demoMode && !root.ring && !root.minimal && !(root.pam?.passwd.active ?? false) && !root.unlocking && !(root.pam?.u2fPending ?? false)
|
||||
visible: !root.demoMode && !root.ring && !root.minimal && !(root.pam?.authBusy ?? false) && !root.unlocking && !(root.pam?.u2fPending ?? false)
|
||||
enabled: visible && root.showPasswordField
|
||||
onClicked: {
|
||||
if (keyboardController.isKeyboardActive)
|
||||
@@ -1049,7 +1057,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
visible: !root.demoMode && !root.ring && !root.morph && ((root.pam?.passwd.active ?? false) || root.unlocking)
|
||||
visible: !root.demoMode && !root.ring && !root.morph && ((root.pam?.authBusy ?? false) || root.unlocking)
|
||||
|
||||
DIcon {
|
||||
anchors.centerIn: parent
|
||||
@@ -1073,7 +1081,7 @@ Item {
|
||||
size: Theme.iconSize
|
||||
contained: root.style === "expressive"
|
||||
color: root.style === "expressive" ? Theme.onPrimaryContainer : root.accentColor
|
||||
visible: (root.pam?.passwd.active ?? false) && !root.unlocking
|
||||
visible: (root.pam?.authBusy ?? false) && !root.unlocking
|
||||
running: visible
|
||||
}
|
||||
}
|
||||
@@ -1089,11 +1097,11 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard_return"
|
||||
buttonSize: Theme.buttonHeightXS
|
||||
visible: !root.ring && !root.minimal && (root.demoMode || (!(root.pam?.passwd.active ?? false) && !root.unlocking && !(root.pam?.u2fPending ?? false)))
|
||||
visible: !root.ring && !root.minimal && (root.demoMode || (!(root.pam?.authBusy ?? false) && !root.unlocking && !(root.pam?.u2fPending ?? false)))
|
||||
enabled: !root.demoMode && root.showPasswordField
|
||||
onClicked: {
|
||||
if (!root.demoMode && !root.unlocking && !root.pam.u2fPending)
|
||||
root.pam.passwd.start();
|
||||
root.pam.submitPassword();
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
Reference in new issue
Block a user