File indexing completed on 2024-04-28 03:59:09

0001 // vi: ts=8 sts=4 sw=4
0002 /*
0003     This file is part of the KDE libraries
0004     SPDX-FileCopyrightText: 1998 Pietro Iglio <iglio@fub.it>
0005     SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
0006     SPDX-FileCopyrightText: 2004, 2005 Andrew Coles <andrew_coles@yahoo.co.uk>
0007     SPDX-FileCopyrightText: 2007 Michaƫl Larouche <larouche@kde.org>
0008     SPDX-FileCopyrightText: 2009 Christoph Feck <cfeck@kde.org>
0009     SPDX-FileCopyrightText: 2015 Elvis Angelaccio <elvis.angelaccio@kde.org>
0010 
0011     SPDX-License-Identifier: LGPL-2.0-only
0012 */
0013 
0014 #include "knewpasswordwidget.h"
0015 #include "ui_knewpasswordwidget.h"
0016 
0017 class KNewPasswordWidgetPrivate
0018 {
0019     Q_DECLARE_TR_FUNCTIONS(KNewPasswordWidget)
0020 
0021 public:
0022     KNewPasswordWidgetPrivate(KNewPasswordWidget *parent)
0023         : q(parent)
0024     {
0025     }
0026 
0027     void init();
0028     void passwordChanged();
0029     void toggleEchoMode();
0030     int effectivePasswordLength(QStringView password);
0031     void updatePasswordStatus(KNewPasswordWidget::PasswordStatus status);
0032 
0033     KNewPasswordWidget *const q;
0034 
0035     KNewPasswordWidget::PasswordStatus passwordStatus = KNewPasswordWidget::WeakPassword;
0036     int minimumPasswordLength = 0;
0037     int passwordStrengthWarningLevel = 1;
0038     int reasonablePasswordLength = 8;
0039 
0040     QAction *toggleEchoModeAction = nullptr;
0041     QColor backgroundWarningColor;
0042     QColor defaultBackgroundColor;
0043 
0044     Ui::KNewPasswordWidget ui;
0045 };
0046 
0047 void KNewPasswordWidgetPrivate::init()
0048 {
0049     ui.setupUi(q);
0050 
0051     const QString strengthBarWhatsThis(
0052         tr("The password strength meter gives an indication of the security "
0053            "of the password you have entered. To improve the strength of the password, try:"
0054            "<ul><li>using a longer password;</li>"
0055            "<li>using a mixture of upper- and lower-case letters;</li>"
0056            "<li>using numbers or symbols, such as #, as well as letters.</li></ul>",
0057            "@info:whatsthis"));
0058     ui.labelStrengthMeter->setWhatsThis(strengthBarWhatsThis);
0059     ui.strengthBar->setWhatsThis(strengthBarWhatsThis);
0060 
0061     QObject::connect(ui.linePassword, &KPasswordLineEdit::echoModeChanged, q, [this]() {
0062         toggleEchoMode();
0063     });
0064 
0065     QObject::connect(ui.linePassword, &KPasswordLineEdit::passwordChanged, q, [this]() {
0066         passwordChanged();
0067     });
0068     QObject::connect(ui.lineVerifyPassword, &QLineEdit::textChanged, q, [this]() {
0069         passwordChanged();
0070     });
0071 
0072     defaultBackgroundColor = q->palette().color(QPalette::Active, QPalette::Base);
0073     backgroundWarningColor = defaultBackgroundColor;
0074 
0075     passwordChanged();
0076 }
0077 
0078 void KNewPasswordWidgetPrivate::passwordChanged()
0079 {
0080     const QString password = ui.linePassword->password();
0081     const QString verification = ui.lineVerifyPassword->text();
0082     const bool match = (password == verification);
0083     const bool partialMatch = password.startsWith(verification);
0084     const int minPasswordLength = q->minimumPasswordLength();
0085 
0086     QPalette palette = q->palette();
0087     palette.setColor(QPalette::Active, QPalette::Base, (match || partialMatch) ? defaultBackgroundColor : backgroundWarningColor);
0088     ui.lineVerifyPassword->setPalette(palette);
0089 
0090     // Password strength calculator
0091     int pwstrength =
0092         (20 * ui.linePassword->password().length() + 80 * effectivePasswordLength(ui.linePassword->password())) / qMax(reasonablePasswordLength, 2);
0093     ui.strengthBar->setValue(qBound(0, pwstrength, 100));
0094 
0095     // update the current password status
0096     if (match || ui.lineVerifyPassword->isHidden()) {
0097         if (!q->allowEmptyPasswords() && ui.linePassword->password().isEmpty()) {
0098             updatePasswordStatus(KNewPasswordWidget::EmptyPasswordNotAllowed);
0099         } else if (ui.linePassword->password().length() < minPasswordLength) {
0100             updatePasswordStatus(KNewPasswordWidget::PasswordTooShort);
0101         } else if (ui.strengthBar && ui.strengthBar->value() < passwordStrengthWarningLevel) {
0102             updatePasswordStatus(KNewPasswordWidget::WeakPassword);
0103         } else {
0104             updatePasswordStatus(KNewPasswordWidget::StrongPassword);
0105         }
0106     } else {
0107         updatePasswordStatus(KNewPasswordWidget::PasswordNotVerified);
0108     }
0109 }
0110 
0111 void KNewPasswordWidgetPrivate::toggleEchoMode()
0112 {
0113     if (ui.linePassword->lineEdit()->echoMode() == QLineEdit::Normal) {
0114         ui.lineVerifyPassword->hide();
0115         ui.labelVerifyPassword->hide();
0116     } else if (ui.linePassword->lineEdit()->echoMode() == QLineEdit::Password) {
0117         ui.lineVerifyPassword->show();
0118         ui.labelVerifyPassword->show();
0119     }
0120     passwordChanged();
0121 }
0122 
0123 int KNewPasswordWidgetPrivate::effectivePasswordLength(QStringView password)
0124 {
0125     enum Category {
0126         Digit,
0127         Upper,
0128         Vowel,
0129         Consonant,
0130         Special,
0131     };
0132 
0133     Category previousCategory = Vowel;
0134     static const QLatin1String vowels("aeiou");
0135     int count = 0;
0136 
0137     const int len = password.length();
0138     for (int i = 0; i < len; ++i) {
0139         const QChar currentChar = password.at(i);
0140         if (!password.left(i).contains(currentChar)) {
0141             Category currentCategory;
0142             switch (currentChar.category()) {
0143             case QChar::Letter_Uppercase:
0144                 currentCategory = Upper;
0145                 break;
0146             case QChar::Letter_Lowercase:
0147                 if (vowels.contains(currentChar)) {
0148                     currentCategory = Vowel;
0149                 } else {
0150                     currentCategory = Consonant;
0151                 }
0152                 break;
0153             case QChar::Number_DecimalDigit:
0154                 currentCategory = Digit;
0155                 break;
0156             default:
0157                 currentCategory = Special;
0158                 break;
0159             }
0160             switch (currentCategory) {
0161             case Vowel:
0162                 if (previousCategory != Consonant) {
0163                     ++count;
0164                 }
0165                 break;
0166             case Consonant:
0167                 if (previousCategory != Vowel) {
0168                     ++count;
0169                 }
0170                 break;
0171             default:
0172                 if (previousCategory != currentCategory) {
0173                     ++count;
0174                 }
0175                 break;
0176             }
0177             previousCategory = currentCategory;
0178         }
0179     }
0180     return count;
0181 }
0182 
0183 void KNewPasswordWidgetPrivate::updatePasswordStatus(KNewPasswordWidget::PasswordStatus status)
0184 {
0185     if (passwordStatus == status) {
0186         return;
0187     }
0188 
0189     passwordStatus = status;
0190     Q_EMIT q->passwordStatusChanged();
0191 }
0192 
0193 KNewPasswordWidget::KNewPasswordWidget(QWidget *parent)
0194     : QWidget(parent)
0195     , d(new KNewPasswordWidgetPrivate(this))
0196 {
0197     d->init();
0198 }
0199 
0200 KNewPasswordWidget::~KNewPasswordWidget() = default;
0201 
0202 KNewPasswordWidget::PasswordStatus KNewPasswordWidget::passwordStatus() const
0203 {
0204     return d->passwordStatus;
0205 }
0206 
0207 bool KNewPasswordWidget::allowEmptyPasswords() const
0208 {
0209     return d->minimumPasswordLength == 0;
0210 }
0211 
0212 int KNewPasswordWidget::minimumPasswordLength() const
0213 {
0214     return d->minimumPasswordLength;
0215 }
0216 
0217 int KNewPasswordWidget::maximumPasswordLength() const
0218 {
0219     return d->ui.linePassword->lineEdit()->maxLength();
0220 }
0221 
0222 int KNewPasswordWidget::reasonablePasswordLength() const
0223 {
0224     return d->reasonablePasswordLength;
0225 }
0226 
0227 int KNewPasswordWidget::passwordStrengthWarningLevel() const
0228 {
0229     return d->passwordStrengthWarningLevel;
0230 }
0231 
0232 QColor KNewPasswordWidget::backgroundWarningColor() const
0233 {
0234     return d->backgroundWarningColor;
0235 }
0236 
0237 bool KNewPasswordWidget::isPasswordStrengthMeterVisible() const
0238 {
0239     return d->ui.labelStrengthMeter->isVisible() && d->ui.strengthBar->isVisible();
0240 }
0241 
0242 #if KWIDGETSADDONS_ENABLE_DEPRECATED_SINCE(6, 0)
0243 #pragma GCC diagnostic push
0244 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
0245 bool KNewPasswordWidget::isRevealPasswordAvailable() const
0246 {
0247     return d->ui.linePassword->isRevealPasswordAvailable();
0248 }
0249 #pragma GCC diagnostic pop
0250 #endif
0251 
0252 KPassword::RevealMode KNewPasswordWidget::revealPasswordMode() const
0253 {
0254     return d->ui.linePassword->revealPasswordMode();
0255 }
0256 
0257 QString KNewPasswordWidget::password() const
0258 {
0259     return d->ui.linePassword->password();
0260 }
0261 
0262 void KNewPasswordWidget::setAllowEmptyPasswords(bool allowed)
0263 {
0264     setMinimumPasswordLength(allowed ? 0 : 1);
0265     d->passwordChanged();
0266 }
0267 
0268 void KNewPasswordWidget::setMinimumPasswordLength(int minLength)
0269 {
0270     d->minimumPasswordLength = minLength;
0271     d->passwordChanged();
0272 }
0273 
0274 void KNewPasswordWidget::setMaximumPasswordLength(int maxLength)
0275 {
0276     if (maxLength < minimumPasswordLength()) {
0277         maxLength = minimumPasswordLength();
0278     }
0279 
0280     d->ui.linePassword->lineEdit()->setMaxLength(maxLength);
0281     d->ui.lineVerifyPassword->setMaxLength(maxLength);
0282 }
0283 
0284 // reasonable password length code contributed by Steffen Mthing
0285 
0286 void KNewPasswordWidget::setReasonablePasswordLength(int reasonableLength)
0287 {
0288     d->reasonablePasswordLength = qBound(1, reasonableLength, maximumPasswordLength());
0289 }
0290 
0291 void KNewPasswordWidget::setPasswordStrengthWarningLevel(int warningLevel)
0292 {
0293     d->passwordStrengthWarningLevel = qBound(0, warningLevel, 99);
0294 }
0295 
0296 void KNewPasswordWidget::setBackgroundWarningColor(const QColor &color)
0297 {
0298     d->backgroundWarningColor = color;
0299     update();
0300 }
0301 
0302 void KNewPasswordWidget::setPasswordStrengthMeterVisible(bool visible)
0303 {
0304     d->ui.labelStrengthMeter->setVisible(visible);
0305     d->ui.strengthBar->setVisible(visible);
0306 }
0307 
0308 #if KWIDGETSADDONS_ENABLE_DEPRECATED_SINCE(6, 0)
0309 #pragma GCC diagnostic push
0310 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
0311 void KNewPasswordWidget::setRevealPasswordAvailable(bool reveal)
0312 {
0313     d->ui.linePassword->setRevealPasswordAvailable(reveal);
0314 }
0315 #pragma GCC diagnostic pop
0316 #endif
0317 
0318 void KNewPasswordWidget::setRevealPasswordMode(KPassword::RevealMode revealPasswordMode)
0319 {
0320     d->ui.linePassword->setRevealPasswordMode(revealPasswordMode);
0321 }
0322 
0323 #include "moc_knewpasswordwidget.cpp"