Index: leahi.qrc =================================================================== diff -u -r934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- leahi.qrc (.../leahi.qrc) (revision 934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f) +++ leahi.qrc (.../leahi.qrc) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -102,6 +102,8 @@ resources/images/backspace.png resources/images/Vitals.png resources/images/Vitals_Red.png + resources/images/check_green.png + resources/images/eye_closed.png sources/gui/qml/components/MainMenu.qml @@ -162,6 +164,7 @@ sources/gui/qml/components/AlarmButtonRow.qml sources/gui/qml/components/BaseComboBox.qml sources/gui/qml/components/VitalsButton.qml + sources/gui/qml/components/PasswordRequirements.qml sources/gui/qml/compounds/PressureRangeSlider.qml Index: sources/gui/qml/components/PasswordRequirements.qml =================================================================== diff -u --- sources/gui/qml/components/PasswordRequirements.qml (revision 0) +++ sources/gui/qml/components/PasswordRequirements.qml (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -0,0 +1,62 @@ +import QtQuick 2.12 + +import "qrc:/globals" +import "qrc:/components" + +Item { id: _root + property string password : "" + + readonly property string checkGreen : "qrc:/images/iCheckGreen" + readonly property int titlePixelSize : 25 + readonly property int delegatePixelSize : 22 + + Text { id: passwordRequirementsLabel + anchors.top : parent.top + font { + pixelSize : _root.titlePixelSize + weight : Font.Medium + } + width : parent.width + height : Variables.contentHeight + horizontalAlignment : Text.AlignLeft + verticalAlignment : Text.AlignVCenter + wrapMode : Text.Wrap + color : Colors.textMain + text : qsTr("The password must contain at least the following:") + } + + Column { id: contentColumn + anchors.top : passwordRequirementsLabel.bottom + anchors.topMargin : Variables.defaultMargin + spacing : 5 + width : _root.width + height : _root.height + + Repeater { + model: [ + { name: qsTr("Upper Case"), source: vSettings.passwordContainsUpperCase (_root.password) ? _root.checkGreen : "" }, + { name: qsTr("Lower Case"), source: vSettings.passwordContainsLowerCase (_root.password) ? _root.checkGreen : "" }, + { name: qsTr("Digits"), source: vSettings.passwordContainsDigit (_root.password) ? _root.checkGreen : "" }, + { name: qsTr("Symbols"), source: vSettings.passwordContainsSymbol (_root.password) ? _root.checkGreen : "" }, + { name: qsTr("Character Limit"), source: vSettings.passwordContainsCharacterLimit(_root.password) ? _root.checkGreen : "" } + ] + + delegate: Text { id: _delegate + font.pixelSize : _root.delegatePixelSize + color : Colors.textMain + text : modelData.name + width : parent.width / 2 + + Image { id: _indicator + anchors { + right : parent.right + verticalCenter : parent.verticalCenter + } + source : modelData.source + sourceSize.width : parent.height - 5 + sourceSize.height : parent.height - 5 + } + } + } + } +} Index: sources/gui/qml/pages/UserConfirmation.qml =================================================================== diff -u -r934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/gui/qml/pages/UserConfirmation.qml (.../UserConfirmation.qml) (revision 934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f) +++ sources/gui/qml/pages/UserConfirmation.qml (.../UserConfirmation.qml) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -14,7 +14,7 @@ */ // Qt -import QtQuick 2.12 +import QtQuick 2.15 // Project @@ -27,8 +27,12 @@ * \brief Contains the message for user information or password entry for user to confirm. */ SettingsBase { id: _root - objectName : "UserConfirmation" // SquishQt testability + objectName : "UserConfirmation" // SquishQt testability + confirmEnabled : ! passwordChangeMode || + (vSettings.isPasswordHighStrength(_root.passwordUpdated) && + _root.passwordUpdated === _root.passwordConfirm) + property bool isPassword : false property bool passwordChangeMode : false readonly property string passwordCurrent : _passwordCurrent.textInput.text @@ -37,53 +41,77 @@ property int passwordLengthMax : 30 property alias message : _message.text - function clearPasswordCurrnet() { _passwordCurrent.textInput.text = "" } + readonly property string hidePassword : "qrc:/images/iEyeClosed" + readonly property string showPassword : "qrc:/images/iEye" + + function clearPasswordCurrent() { _passwordCurrent.textInput.text = "" } function clearPasswordUpdated() { _passwordUpdated.textInput.text = "" } function clearPasswordReEntry() { _passwordConfirm.textInput.text = "" } function clearPasswords () { - clearPasswordCurrnet() + clearPasswordCurrent() clearPasswordUpdated() clearPasswordReEntry() } firstFocusInput : isPassword ? _passwordCurrent : undefined - contentItem: Column { + contentItem: Item { visible: isPassword - PasswordEntry { id: _passwordCurrent - label.text : ! passwordChangeMode ? "" : qsTr("Current") - label.width : ! passwordChangeMode ? 0 : labelWidth - lengthMax : passwordLengthMax - nextInput : _passwordUpdated - anchors { - top : parent.top - topMargin : Variables.defaultMargin * 5 - horizontalCenter : parent.horizontalCenter + Column { id: _passwordEntries + anchors.horizontalCenter : parent.horizontalCenter + anchors.horizontalCenterOffset : passwordChangeMode ? Variables.defaultMargin * -4 : 0 + width : parent.width / 2.5 + + PasswordEntry { id: _passwordCurrent + visible : ! passwordChangeMode + label.text : "" + label.width : 0 + lengthMax : passwordLengthMax + nextInput : _passwordUpdated + anchors { + top : parent.top + topMargin : passwordChangeMode ? Variables.defaultMargin * 2 : Variables.defaultMargin * 5 + horizontalCenter : parent.horizontalCenter + } } - } - PasswordEntry { id: _passwordUpdated - visible : passwordChangeMode - label.text : qsTr("New") - nextInput : _passwordConfirm - lengthMax : passwordLengthMax - anchors { - top : _passwordCurrent.bottom - horizontalCenter : parent.horizontalCenter + PasswordEntry { id: _passwordUpdated + visible : passwordChangeMode + label.text : qsTr("New") + nextInput : _passwordConfirm + lengthMax : passwordLengthMax + anchors { + top : _passwordCurrent.bottom + horizontalCenter : parent.horizontalCenter + } } + + PasswordEntry { id: _passwordConfirm + visible : passwordChangeMode + label.text : qsTr("Confirm") + lengthMax : passwordLengthMax + anchors { + top : _passwordUpdated.bottom + horizontalCenter : parent.horizontalCenter + } + } } - PasswordEntry { id: _passwordConfirm - visible : passwordChangeMode - label.text : qsTr("Confirm") - lengthMax : passwordLengthMax + PasswordRequirements {id: _passwordRequirements anchors { - top : _passwordUpdated.bottom - horizontalCenter : parent.horizontalCenter + top : parent.top + topMargin : Variables.defaultMargin * 2 + left : _passwordEntries.right + leftMargin : Variables.defaultMargin * 2 } + visible : passwordChangeMode + height : parent.height / 2 + width : parent.width / 4 + password : passwordUpdated } } + component PasswordEntry : Item { property alias textInput : _passwordEntry.textInput property alias nextInput : _passwordEntry.nextInput @@ -95,23 +123,31 @@ label.width : labelWidth TextEntry { id: _passwordEntry - clip : true - textInput .width : 450 + clip : true + textInput .width : 450 textInput.inputMethodHints : Qt.ImhNone textInput.echoMode : TextInput.Password anchors.horizontalCenter : parent.horizontalCenter + + onTextChanged: { + // Replace non-ASCII characters as they are typed in + var filtered = text.replace(/[^\x00-\x7F]/g, "") + if (text !== filtered) { text = filtered } // revert invalid characters + } } Image { visible : _passwordEntry.visible anchors.left : _passwordEntry.right anchors.leftMargin : Variables.minVGap anchors.verticalCenter : _passwordEntry.verticalCenter - width : Variables.iconsDiameter - height : Variables.iconsDiameter - source : "qrc:/images/iEye" + width : 35 + height : 35 - MouseArea { + source : _mouseArea.pressed ? _root.showPassword : _root.hidePassword + fillMode : Image.PreserveAspectFit + + MouseArea { id: _mouseArea anchors.fill: parent anchors.margins: -20 onPressed : _passwordEntry.textInput.echoMode = TextInput.Normal Index: sources/gui/qml/pages/settings/SettingsDecommission.qml =================================================================== diff -u -r934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/gui/qml/pages/settings/SettingsDecommission.qml (.../SettingsDecommission.qml) (revision 934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f) +++ sources/gui/qml/pages/settings/SettingsDecommission.qml (.../SettingsDecommission.qml) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -38,12 +38,12 @@ backEnabled : vDevice.decommissionEnabled onConfirmClicked : { - _confirmDialog.titleText = _root.title + _confirmDialog.titleText = _headerBar.titleText _confirmDialog.open() } Connections { target: _confirmDialog function onAccepted() { - if ( _confirmDialog.titleText == _root.title ) { // use the title as the indication of what has been confirmed and if that is related to this function. + if ( _confirmDialog.titleText === _headerBar.titleText ) { // use the title as the indication of what has been confirmed and if that is related to this function. vDevice.decommission = "" // Need to set to something (ie: emtpy string) to trigger CPP code } } Index: sources/gui/qml/pages/settings/SettingsFactoryReset.qml =================================================================== diff -u -r934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/gui/qml/pages/settings/SettingsFactoryReset.qml (.../SettingsFactoryReset.qml) (revision 934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f) +++ sources/gui/qml/pages/settings/SettingsFactoryReset.qml (.../SettingsFactoryReset.qml) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -38,12 +38,12 @@ backEnabled : vDevice.factoryResetEnabled onConfirmClicked : { - _confirmDialog.titleText = _root.title + _confirmDialog.titleText = _headerBar.titleText _confirmDialog.open() } Connections { target: _confirmDialog function onAccepted() { - if ( _confirmDialog.titleText == _root.title ) { // use the title as the indication of what has been confirmed and if that is related to this function. + if ( _confirmDialog.titleText === _headerBar.titleText ) { // use the title as the indication of what has been confirmed and if that is related to this function. vDevice.factoryReset = "" // Need to set to something (ie: emtpy string) to trigger CPP code } } Index: sources/gui/qml/pages/settings/SettingsLocalization.qml =================================================================== diff -u -rbeadb6f68cff8bc50f255f83203ec201a5468dc1 -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/gui/qml/pages/settings/SettingsLocalization.qml (.../SettingsLocalization.qml) (revision beadb6f68cff8bc50f255f83203ec201a5468dc1) +++ sources/gui/qml/pages/settings/SettingsLocalization.qml (.../SettingsLocalization.qml) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -44,20 +44,19 @@ } onConfirmClicked : { - _confirmDialog.titleText = _root.title + _confirmDialog.titleText = _headerBar.titleText _confirmDialog.open() } Connections { target: _confirmDialog function onAccepted() { - if ( _confirmDialog.titleText == _root.title ) { // use the title as the indication of what has been confirmed and if that is related to this function. + if ( _confirmDialog.titleText === _headerBar.titleText ) { // use the title as the indication of what has been confirmed and if that is related to this function. vLocalization.doAdjustment(_settingsLanguageCombo.currentIndex) } } } Connections { target: _settingsLanguageCombo - function onCurrentIndexChanged() { vLocalization.notification = "" } Index: sources/gui/qml/pages/settings/SettingsServicePassword.qml =================================================================== diff -u -r934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/gui/qml/pages/settings/SettingsServicePassword.qml (.../SettingsServicePassword.qml) (revision 934354462a353ff5e7fc2ddfe6f3a8f0121a8f3f) +++ sources/gui/qml/pages/settings/SettingsServicePassword.qml (.../SettingsServicePassword.qml) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -47,12 +47,6 @@ // if not accepted return if ( ! isPassword_Accepted ) return - // if accepted save the password - if ( passwordChangeMode ) vSettings.updateServicePassword(passwordUpdated) - - // ask HD to go to service mode - vAdjustmentServiceMode.doAdjustment() - // clear the screen _root.notificationText = "" clearPasswords() @@ -64,15 +58,27 @@ if ( vTDOpMode.service ) gotoServiceMode(true) } - // TODO: Add logic here to check for which user is trying to log in function checkPassword() { - if ( ! vSettings.isServicePasswordMatch( passwordCurrent ) ) { _root.notificationText = qsTr("Incorrect password" ); return false } + let servicePasswordCheck = false + let user1PasswordCheck = false // does nothing right now - if ( ! passwordChangeMode ) { _root.notificationText = "" ; return true } + if ( vSettings.isServicePasswordMatch( passwordCurrent ) || passwordChangeMode ){ handleServicePassword(); servicePasswordCheck = true } - if ( passwordConfirm != passwordUpdated ) { _root.notificationText = qsTr("Mismatch Passwords" ); return false } - if ( ! vSettings.isPasswordValid(passwordUpdated ) ) { _root.notificationText = qsTr("Invalid Password" ); return false } + // TODO: Add other password match checks for other users +// if ( vSettings.isUser1PasswordMatch( passwordCurrent ) || passwordChangeMode ){ handleUser1Password(); user1PasswordCheck = true } - _root.notificationText = "" ; return true + // check all password check if any are successful + let allPasswordCheck = servicePasswordCheck || user1PasswordCheck + _root.notificationText = allPasswordCheck ? "" : qsTr("Incorrect password") + + return allPasswordCheck } + + function handleServicePassword() { + // if accepted save the password + if ( passwordChangeMode ) vSettings.updateServicePassword(passwordUpdated) + + // ask TD to go to service mode + vAdjustmentServiceMode.doAdjustment() + } } Index: sources/view/settings/VSettings.cpp =================================================================== diff -u -ra5760947d3ed0d2748ba023a1c25e3c6aa0b1de1 -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/view/settings/VSettings.cpp (.../VSettings.cpp) (revision a5760947d3ed0d2748ba023a1c25e3c6aa0b1de1) +++ sources/view/settings/VSettings.cpp (.../VSettings.cpp) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -225,47 +225,69 @@ } /*! - * \brief VSettings::isPasswordValid - * \details Validates the passed string for conforming to the - * - at least one capital letter, - * - at least one digit, - * - at least one symbol - * rule - * \param vPassword - the string to check - * \returns true if string pass conforms, false otherwise + * \brief Return whether the password has the minimum character limit + * \param[in] password password to check + * \return true if the password contains requirement */ -bool View::VSettings::isPasswordValid(const QString &vPassword) { - int minLen = 10 ; // in the SRS the max is required to be 12 which seems incorrect and I didn't set it here. - QString pla = "(?=.*[%1])" ; // positive look ahead - QString regSntnc = "^%1$" ; // regular expression sentence with has a start/end - QString regUpper = "A-Z" ; - QString regLower = "a-z" ; - QString regDigit = "0-9" ; - QString regSymbl = "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{\\|}~"; - QString rln = "[%1]{%2,}" ; +bool View::VSettings::passwordContainsCharacterLimit(const QString &vPassword) const +{ + return vPassword.size() >= 10; +} - // "^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{\|}~])[A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{\|}~]{10,}$" - // !"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{\\|}~ - QString regStr = regSntnc.arg( - pla.arg(regUpper) + - pla.arg(regLower) + - pla.arg(regDigit) + - pla.arg(regSymbl) + - rln.arg(regUpper + - regLower + - regDigit + - regSymbl) - .arg(minLen ) - ); - QRegularExpression passwordRegex(regStr); - bool ok; +/*! + * \brief Return whether the password has a upper case letter + * \param[in] password password to check + * \return true if the password contains requirement + */ +bool View::VSettings::passwordContainsUpperCase(const QString &vPassword) const +{ + static const QRegularExpression upperCaseLetter("[A-Z]"); + return vPassword.contains(upperCaseLetter); +} - //DEBUG: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - //DEBUG: vPassword = "Ab0!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" - ok = passwordRegex.match(vPassword).hasMatch(); - return ok; +/*! + * \brief Return whether the password has a lower case letter + * \param[in] password password to check + * \return true if the password contains requirement + */ +bool View::VSettings::passwordContainsLowerCase(const QString &vPassword) const +{ + static const QRegularExpression lowerCaseLetter("[a-z]"); + return vPassword.contains(lowerCaseLetter); } +/*! + * \brief Return whether the password has a digit + * \param[in] password password to check + * \return true if the password contains requirement + */ +bool View::VSettings::passwordContainsDigit(const QString &vPassword) const +{ + static const QRegularExpression number("[0-9]"); + return vPassword.contains(number); +} + +bool View::VSettings::passwordContainsSymbol(const QString &vPassword) const +{ + static const QRegularExpression symbol( + QStringLiteral("[%1]").arg(QRegularExpression::escape("{}[],.<>;:'\"?/|\\`~!@#$%^&*()_-+="))); + return vPassword.contains(symbol); +} + +/*! + * \brief Return whether the password is high strength. + * \param[in] password password to check + * \return true if the password is high strength and false otherwise. + */ +bool View::VSettings::isPasswordHighStrength(const QString &vPassword) const +{ + return passwordContainsUpperCase(vPassword) && + passwordContainsLowerCase(vPassword) && + passwordContainsDigit(vPassword) && + passwordContainsSymbol(vPassword) && + passwordContainsCharacterLimit(vPassword); +} + QString View::VSettings::hashedPassword(const QString &vPassword, bool vIsService) { bool ok; Index: sources/view/settings/VSettings.h =================================================================== diff -u -ra5760947d3ed0d2748ba023a1c25e3c6aa0b1de1 -r88a09dc4b26cfdd5fd111d20adfb9cb60697186c --- sources/view/settings/VSettings.h (.../VSettings.h) (revision a5760947d3ed0d2748ba023a1c25e3c6aa0b1de1) +++ sources/view/settings/VSettings.h (.../VSettings.h) (revision 88a09dc4b26cfdd5fd111d20adfb9cb60697186c) @@ -85,7 +85,12 @@ QString hashedPassword (const QString &vPassword, bool vIsService); public slots: - bool isPasswordValid (const QString &vPassword); + bool isPasswordHighStrength (const QString &vPassword) const; + bool passwordContainsCharacterLimit (const QString &vPassword) const; + bool passwordContainsUpperCase (const QString &vPassword) const; + bool passwordContainsLowerCase (const QString &vPassword) const; + bool passwordContainsDigit (const QString &vPassword) const; + bool passwordContainsSymbol (const QString &vPassword) const; bool isServicePasswordMatch (const QString &vPassword); void updateServicePassword (const QString &vPassword);