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