1 // vi: ts=8 sts=4 sw=4
2 /*
3     This file is part of the KDE libraries
4     SPDX-FileCopyrightText: 1998 Pietro Iglio <iglio@fub.it>
5     SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
6     SPDX-FileCopyrightText: 2004, 2005 Andrew Coles <andrew_coles@yahoo.co.uk>
7     SPDX-FileCopyrightText: 2007 Michaël Larouche <larouche@kde.org>
8     SPDX-FileCopyrightText: 2009 Christoph Feck <cfeck@kde.org>
9     SPDX-FileCopyrightText: 2015 Elvis Angelaccio <elvis.angelaccio@kde.org>
10 
11     SPDX-License-Identifier: LGPL-2.0-only
12 */
13 
14 #include "knewpasswordwidget.h"
15 #include "ui_knewpasswordwidget.h"
16 
17 class KNewPasswordWidgetPrivate
18 {
19     Q_DECLARE_TR_FUNCTIONS(KNewPasswordWidget)
20 
21 public:
KNewPasswordWidgetPrivate(KNewPasswordWidget * parent)22     KNewPasswordWidgetPrivate(KNewPasswordWidget *parent)
23         : q(parent)
24     {
25     }
26 
27     void init();
28     void passwordChanged();
29     void toggleEchoMode();
30     int effectivePasswordLength(QStringView password);
31     void updatePasswordStatus(KNewPasswordWidget::PasswordStatus status);
32 
33     KNewPasswordWidget *const q;
34 
35     KNewPasswordWidget::PasswordStatus passwordStatus = KNewPasswordWidget::WeakPassword;
36     int minimumPasswordLength = 0;
37     int passwordStrengthWarningLevel = 1;
38     int reasonablePasswordLength = 8;
39 
40     QAction *toggleEchoModeAction = nullptr;
41     QColor backgroundWarningColor;
42     QColor defaultBackgroundColor;
43 
44     Ui::KNewPasswordWidget ui;
45 };
46 
init()47 void KNewPasswordWidgetPrivate::init()
48 {
49     ui.setupUi(q);
50 
51     const QString strengthBarWhatsThis(
52         tr("The password strength meter gives an indication of the security "
53            "of the password you have entered. To improve the strength of the password, try:"
54            "<ul><li>using a longer password;</li>"
55            "<li>using a mixture of upper- and lower-case letters;</li>"
56            "<li>using numbers or symbols, such as #, as well as letters.</li></ul>",
57            "@info:whatsthis"));
58     ui.labelStrengthMeter->setWhatsThis(strengthBarWhatsThis);
59     ui.strengthBar->setWhatsThis(strengthBarWhatsThis);
60 
61     QObject::connect(ui.linePassword, &KPasswordLineEdit::echoModeChanged, q, [this]() {
62         toggleEchoMode();
63     });
64 
65     QObject::connect(ui.linePassword, &KPasswordLineEdit::passwordChanged, q, [this]() {
66         passwordChanged();
67     });
68     QObject::connect(ui.lineVerifyPassword, &QLineEdit::textChanged, q, [this]() {
69         passwordChanged();
70     });
71 
72     defaultBackgroundColor = q->palette().color(QPalette::Active, QPalette::Base);
73     backgroundWarningColor = defaultBackgroundColor;
74 
75     passwordChanged();
76 }
77 
passwordChanged()78 void KNewPasswordWidgetPrivate::passwordChanged()
79 {
80     const QString password = ui.linePassword->password();
81     const QString verification = ui.lineVerifyPassword->text();
82     const bool match = (password == verification);
83     const bool partialMatch = password.startsWith(verification);
84     const int minPasswordLength = q->minimumPasswordLength();
85 
86     QPalette palette = q->palette();
87     palette.setColor(QPalette::Active, QPalette::Base, (match || partialMatch) ? defaultBackgroundColor : backgroundWarningColor);
88     ui.lineVerifyPassword->setPalette(palette);
89 
90     // Password strength calculator
91     int pwstrength =
92         (20 * ui.linePassword->password().length() + 80 * effectivePasswordLength(ui.linePassword->password())) / qMax(reasonablePasswordLength, 2);
93     ui.strengthBar->setValue(qBound(0, pwstrength, 100));
94 
95     // update the current password status
96     if (match || ui.lineVerifyPassword->isHidden()) {
97         if (!q->allowEmptyPasswords() && ui.linePassword->password().isEmpty()) {
98             updatePasswordStatus(KNewPasswordWidget::EmptyPasswordNotAllowed);
99         } else if (ui.linePassword->password().length() < minPasswordLength) {
100             updatePasswordStatus(KNewPasswordWidget::PasswordTooShort);
101         } else if (ui.strengthBar && ui.strengthBar->value() < passwordStrengthWarningLevel) {
102             updatePasswordStatus(KNewPasswordWidget::WeakPassword);
103         } else {
104             updatePasswordStatus(KNewPasswordWidget::StrongPassword);
105         }
106     } else {
107         updatePasswordStatus(KNewPasswordWidget::PasswordNotVerified);
108     }
109 }
110 
toggleEchoMode()111 void KNewPasswordWidgetPrivate::toggleEchoMode()
112 {
113     if (ui.linePassword->lineEdit()->echoMode() == QLineEdit::Normal) {
114         ui.lineVerifyPassword->hide();
115         ui.labelVerifyPassword->hide();
116     } else if (ui.linePassword->lineEdit()->echoMode() == QLineEdit::Password) {
117         ui.lineVerifyPassword->show();
118         ui.labelVerifyPassword->show();
119     }
120     passwordChanged();
121 }
122 
effectivePasswordLength(QStringView password)123 int KNewPasswordWidgetPrivate::effectivePasswordLength(QStringView password)
124 {
125     enum Category {
126         Digit,
127         Upper,
128         Vowel,
129         Consonant,
130         Special,
131     };
132 
133     Category previousCategory = Vowel;
134     static const QLatin1String vowels("aeiou");
135     int count = 0;
136 
137     const int len = password.length();
138     for (int i = 0; i < len; ++i) {
139         const QChar currentChar = password.at(i);
140         if (!password.left(i).contains(currentChar)) {
141             Category currentCategory;
142             switch (currentChar.category()) {
143             case QChar::Letter_Uppercase:
144                 currentCategory = Upper;
145                 break;
146             case QChar::Letter_Lowercase:
147                 if (vowels.contains(currentChar)) {
148                     currentCategory = Vowel;
149                 } else {
150                     currentCategory = Consonant;
151                 }
152                 break;
153             case QChar::Number_DecimalDigit:
154                 currentCategory = Digit;
155                 break;
156             default:
157                 currentCategory = Special;
158                 break;
159             }
160             switch (currentCategory) {
161             case Vowel:
162                 if (previousCategory != Consonant) {
163                     ++count;
164                 }
165                 break;
166             case Consonant:
167                 if (previousCategory != Vowel) {
168                     ++count;
169                 }
170                 break;
171             default:
172                 if (previousCategory != currentCategory) {
173                     ++count;
174                 }
175                 break;
176             }
177             previousCategory = currentCategory;
178         }
179     }
180     return count;
181 }
182 
updatePasswordStatus(KNewPasswordWidget::PasswordStatus status)183 void KNewPasswordWidgetPrivate::updatePasswordStatus(KNewPasswordWidget::PasswordStatus status)
184 {
185     if (passwordStatus == status) {
186         return;
187     }
188 
189     passwordStatus = status;
190     Q_EMIT q->passwordStatusChanged();
191 }
192 
KNewPasswordWidget(QWidget * parent)193 KNewPasswordWidget::KNewPasswordWidget(QWidget *parent)
194     : QWidget(parent)
195     , d(new KNewPasswordWidgetPrivate(this))
196 {
197     d->init();
198 }
199 
200 KNewPasswordWidget::~KNewPasswordWidget() = default;
201 
passwordStatus() const202 KNewPasswordWidget::PasswordStatus KNewPasswordWidget::passwordStatus() const
203 {
204     return d->passwordStatus;
205 }
206 
allowEmptyPasswords() const207 bool KNewPasswordWidget::allowEmptyPasswords() const
208 {
209     return d->minimumPasswordLength == 0;
210 }
211 
minimumPasswordLength() const212 int KNewPasswordWidget::minimumPasswordLength() const
213 {
214     return d->minimumPasswordLength;
215 }
216 
maximumPasswordLength() const217 int KNewPasswordWidget::maximumPasswordLength() const
218 {
219     return d->ui.linePassword->lineEdit()->maxLength();
220 }
221 
reasonablePasswordLength() const222 int KNewPasswordWidget::reasonablePasswordLength() const
223 {
224     return d->reasonablePasswordLength;
225 }
226 
passwordStrengthWarningLevel() const227 int KNewPasswordWidget::passwordStrengthWarningLevel() const
228 {
229     return d->passwordStrengthWarningLevel;
230 }
231 
backgroundWarningColor() const232 QColor KNewPasswordWidget::backgroundWarningColor() const
233 {
234     return d->backgroundWarningColor;
235 }
236 
isPasswordStrengthMeterVisible() const237 bool KNewPasswordWidget::isPasswordStrengthMeterVisible() const
238 {
239     return d->ui.labelStrengthMeter->isVisible() && d->ui.strengthBar->isVisible();
240 }
241 
isRevealPasswordAvailable() const242 bool KNewPasswordWidget::isRevealPasswordAvailable() const
243 {
244     return d->ui.linePassword->isRevealPasswordAvailable();
245 }
246 
password() const247 QString KNewPasswordWidget::password() const
248 {
249     return d->ui.linePassword->password();
250 }
251 
setAllowEmptyPasswords(bool allowed)252 void KNewPasswordWidget::setAllowEmptyPasswords(bool allowed)
253 {
254     setMinimumPasswordLength(allowed ? 0 : 1);
255     d->passwordChanged();
256 }
257 
setMinimumPasswordLength(int minLength)258 void KNewPasswordWidget::setMinimumPasswordLength(int minLength)
259 {
260     d->minimumPasswordLength = minLength;
261     d->passwordChanged();
262 }
263 
setMaximumPasswordLength(int maxLength)264 void KNewPasswordWidget::setMaximumPasswordLength(int maxLength)
265 {
266     if (maxLength < minimumPasswordLength()) {
267         maxLength = minimumPasswordLength();
268     }
269 
270     d->ui.linePassword->lineEdit()->setMaxLength(maxLength);
271     d->ui.lineVerifyPassword->setMaxLength(maxLength);
272 }
273 
274 // reasonable password length code contributed by Steffen Mthing
275 
setReasonablePasswordLength(int reasonableLength)276 void KNewPasswordWidget::setReasonablePasswordLength(int reasonableLength)
277 {
278     d->reasonablePasswordLength = qBound(1, reasonableLength, maximumPasswordLength());
279 }
280 
setPasswordStrengthWarningLevel(int warningLevel)281 void KNewPasswordWidget::setPasswordStrengthWarningLevel(int warningLevel)
282 {
283     d->passwordStrengthWarningLevel = qBound(0, warningLevel, 99);
284 }
285 
setBackgroundWarningColor(const QColor & color)286 void KNewPasswordWidget::setBackgroundWarningColor(const QColor &color)
287 {
288     d->backgroundWarningColor = color;
289     update();
290 }
291 
setPasswordStrengthMeterVisible(bool visible)292 void KNewPasswordWidget::setPasswordStrengthMeterVisible(bool visible)
293 {
294     d->ui.labelStrengthMeter->setVisible(visible);
295     d->ui.strengthBar->setVisible(visible);
296 }
297 
setRevealPasswordAvailable(bool reveal)298 void KNewPasswordWidget::setRevealPasswordAvailable(bool reveal)
299 {
300     d->ui.linePassword->setRevealPasswordAvailable(reveal);
301 }
302 
303 #include "moc_knewpasswordwidget.cpp"
304