cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
validatorpwquality.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2018-2025 Matthias Fehring <mf@huessenbergnetz.de>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5
6#include "validatorpwquality_p.h"
7
8#include <pwquality.h>
9
10#include <QLoggingCategory>
11
12using namespace Cutelyst;
13
15 int threshold,
16 const QVariant &options,
17 const QString &userName,
18 const QString &oldPassword,
19 const ValidatorMessages &messages)
20 : ValidatorRule(*new ValidatorPwQualityPrivate(field,
21 threshold,
22 options,
23 userName,
24 oldPassword,
25 messages))
26{
27}
28
30
32 const QVariant &options,
33 const QString &oldPassword,
34 const QString &user)
35{
36 int rv = 0;
37
38 if (!value.isEmpty()) {
39
40 pwquality_settings_t *pwq = pwquality_default_settings();
41 if (pwq) {
42
43 bool optionsSet = false;
44 if (options.isValid()) {
45 if (options.typeId() == QMetaType::QVariantMap) {
46 const QVariantMap map = options.toMap();
47 for (const auto &[key, mapValue] : map.asKeyValueRange()) {
48 const QString opt = key + QLatin1Char('=') + mapValue.toString();
49 const int orv = pwquality_set_option(pwq, opt.toUtf8().constData());
50 if (orv != 0) {
51 QList<char> buf(ValidatorPwQualityPrivate::errStrBufSize);
52 qCWarning(C_VALIDATOR).noquote().nospace()
53 << "ValidatorPwQuality: Failed to set pwquality option " << opt
54 << ": " << pwquality_strerror(buf.data(), buf.size(), orv, nullptr);
55 }
56 optionsSet = true;
57 }
58 } else if (options.typeId() == QMetaType::QString) {
59 const QString configFile = options.toString();
60 if (!configFile.isEmpty()) {
61 if (C_VALIDATOR().isWarningEnabled()) {
62 void *auxerror = nullptr;
63 const int rcrv = pwquality_read_config(
64 pwq, configFile.toUtf8().constData(), &auxerror);
65 if (rcrv != 0) {
66 QList<char> buf(ValidatorPwQualityPrivate::errStrBufSize);
67 qCWarning(C_VALIDATOR).noquote().nospace()
68 << "ValidatorPwQuality: Failed to read configuration file "
69 << configFile << ": "
70 << pwquality_strerror(buf.data(), buf.size(), rcrv, auxerror);
71 }
72 } else {
73 pwquality_read_config(pwq, configFile.toUtf8().constData(), nullptr);
74 }
75 optionsSet = true;
76 }
77 }
78 }
79
80 if (!optionsSet) {
81 if (C_VALIDATOR().isWarningEnabled()) {
82 void *auxerror = nullptr;
83 const int rcrv = pwquality_read_config(pwq, nullptr, &auxerror);
84 if (rcrv != 0) {
85 QList<char> buf(ValidatorPwQualityPrivate::errStrBufSize);
86 qCWarning(C_VALIDATOR).noquote()
87 << "VaidatorPwQuality: Failed to read default configuration file:"
88 << pwquality_strerror(buf.data(), buf.size(), rcrv, auxerror);
89 }
90 } else {
91 pwquality_read_config(pwq, nullptr, nullptr);
92 }
93 }
94
95 const QByteArray pwba = value.toUtf8();
96 const char *pw = pwba.constData();
97 const QByteArray opwba = oldPassword.toUtf8();
98 const char *opw = opwba.isEmpty() ? nullptr : opwba.constData();
99 const QByteArray uba = user.toUtf8();
100 const char *u = uba.isEmpty() ? nullptr : uba.constData();
101
102 rv = pwquality_check(pwq, pw, opw, u, nullptr);
103
104 pwquality_free_settings(pwq);
105
106 } else {
107 rv = PWQ_ERROR_MEM_ALLOC;
108 }
109 } else {
110 rv = PWQ_ERROR_EMPTY_PASSWORD;
111 }
112
113 return rv;
114}
115
117 int returnValue,
118 const QString &label,
119 int threshold)
120{
121 if (label.isEmpty()) {
122 switch (returnValue) {
123 case PWQ_ERROR_MEM_ALLOC:
124 //% "Password quality check failed because of a memory allocation error."
125 return c->qtTrId("cutelyst-valpwq-err-memalloc");
126 case PWQ_ERROR_SAME_PASSWORD:
127 //% "The password is the same as the old one."
128 return c->qtTrId("cutelyst-valpwq-err-samepass");
129 case PWQ_ERROR_PALINDROME:
130 //% "The password is a palindrome."
131 return c->qtTrId("cutelyst-valpwq-err-palindrome");
132 case PWQ_ERROR_CASE_CHANGES_ONLY:
133 //% "The password differs with case changes only from the old one."
134 return c->qtTrId("cutelyst-valpwq-err-casechangesonly");
135 case PWQ_ERROR_TOO_SIMILAR:
136 //% "The password is too similar to the old one."
137 return c->qtTrId("cutelyst-valpwq-err-toosimilar");
138 case PWQ_ERROR_USER_CHECK:
139 //% "The password contains the user name in some form."
140 return c->qtTrId("cutelyst-valpwq-err-usercheck");
141 case PWQ_ERROR_GECOS_CHECK:
142 //% "The password contains words from the real name of the user in some form."
143 return c->qtTrId("cutelyst-valpwq-err-gecoscheck");
144 case PWQ_ERROR_BAD_WORDS:
145 //% "The password contains forbidden words in some form."
146 return c->qtTrId("cutelyst-valpwq-err-badwords");
147 case PWQ_ERROR_MIN_DIGITS:
148 //% "The password does not contain enough digits."
149 return c->qtTrId("cutelyst-valpwq-err-mindigits");
150 case PWQ_ERROR_MIN_UPPERS:
151 //% "The password does not contain enough uppercase letters."
152 return c->qtTrId("cutelyst-valpwq-err-minuppers");
153 case PWQ_ERROR_MIN_LOWERS:
154 //% "The password does not contain enough lowercase letters."
155 return c->qtTrId("cutelyst-valpwq-err-minlowers");
156 case PWQ_ERROR_MIN_OTHERS:
157 //% "The password does not contain enough non-alphanumeric characters."
158 return c->qtTrId("cutelyst-valpwq-err-minothers");
159 case PWQ_ERROR_MIN_LENGTH:
160 //% "The password is too short."
161 return c->qtTrId("cutelyst-valpwq-err-minlength");
162 case PWQ_ERROR_ROTATED:
163 //% "The password is just the rotated old one."
164 return c->qtTrId("cutelyst-valpwq-err-rotated");
165 case PWQ_ERROR_MIN_CLASSES:
166 //% "The password does not contain enough different character types."
167 return c->qtTrId("cutelyst-valpwq-err-minclasses");
168 case PWQ_ERROR_MAX_CONSECUTIVE:
169 //% "The password contains too many same characters consecutively."
170 return c->qtTrId("cutelyst-valpwq-err-maxconsecutive");
171 case PWQ_ERROR_MAX_CLASS_REPEAT:
172 //% "The password contains too many characters of the same type consecutively."
173 return c->qtTrId("cutelyst-valpwq-err-maxclassrepeat");
174 case PWQ_ERROR_MAX_SEQUENCE:
175 //% "The password contains too long a monotonous string."
176 return c->qtTrId("cutelyst-valpwq-err-maxsequence");
177 case PWQ_ERROR_EMPTY_PASSWORD:
178 //% "No password supplied."
179 return c->qtTrId("cutelyst-valpwq-err-emptypw");
180 case PWQ_ERROR_RNG:
181 //% "Password quality check failed because we cannot obtain random "
182 //% "numbers from the RNG device."
183 return c->qtTrId("cutelyst-valpwq-err-rng");
184 case PWQ_ERROR_CRACKLIB_CHECK:
185 //% "The password fails the dictionary check."
186 return c->qtTrId("cutelyst-valpwq-err-cracklibcheck");
187 case PWQ_ERROR_UNKNOWN_SETTING:
188 //% "Password quality check failed because of an unknown setting."
189 return c->qtTrId("cutelyst-valpwq-err-unknownsetting");
190 case PWQ_ERROR_INTEGER:
191 //% "Password quality check failed because of a bad integer value in the settings."
192 return c->qtTrId("cutelyst-valpwq-err-integer");
193 case PWQ_ERROR_NON_INT_SETTING:
194 //% "Password quality check failed because of a settings entry is not "
195 //% "of integer type."
196 return c->qtTrId("cutelyst-valpwq-err-nonintsetting");
197 case PWQ_ERROR_NON_STR_SETTING:
198 //% "Password quality check failed because of a settings entry is not of string type."
199 return c->qtTrId("cutelyst-valpwq-err-nonstrsetting");
200 case PWQ_ERROR_CFGFILE_OPEN:
201 //% "Password quality check failed because opening the configuration file failed."
202 return c->qtTrId("cutelyst-valpwq-err-cfgfileopen");
203 case PWQ_ERROR_CFGFILE_MALFORMED:
204 //% "Password quality check failed because the configuration file is malformed."
205 return c->qtTrId("cutelyst-valpwq-err-cfgfilemalformed");
206 case PWQ_ERROR_FATAL_FAILURE:
207 //% "Password quality check failed because of a fatal failure."
208 return c->qtTrId("cutelyst-valpwq-err-fatalfailure");
209 default:
210 {
211 if (returnValue < 0) {
212 //% "Password quality check failed because of an unknown error."
213 return c->qtTrId("cutelyst-valpwq-err-unknown");
214 } else {
215 if (returnValue < threshold) {
216 //% "The password quality score of %1 is below the threshold of %2."
217 return c->qtTrId("cutelyst-valpwq-err-belowthreshold")
218 .arg(QString::number(returnValue), QString::number(threshold));
219 } else {
220 return {};
221 }
222 }
223 }
224 }
225 } else {
226 switch (returnValue) {
227 case PWQ_ERROR_MEM_ALLOC:
228 //% "Password quality check for the “%1“ field failed because of a "
229 //% "memory allocation error."
230 return c->qtTrId("cutelyst-valpwq-err-memalloc-label").arg(label);
231 case PWQ_ERROR_SAME_PASSWORD:
232 //% "The password in the “%1” field is the same as the old one."
233 return c->qtTrId("cutelyst-valpwq-err-samepass-label").arg(label);
234 case PWQ_ERROR_PALINDROME:
235 //% "The password in the “%1” field is a palindrome."
236 return c->qtTrId("cutelyst-valpwq-err-palindrome-label").arg(label);
237 case PWQ_ERROR_CASE_CHANGES_ONLY:
238 //% "The password in the “%1” field differs with case changes only from the old one."
239 return c->qtTrId("cutelyst-valpwq-err-casechangesonly-label").arg(label);
240 case PWQ_ERROR_TOO_SIMILAR:
241 //% "The password in the “%1” field is too similar to the old one."
242 return c->qtTrId("cutelyst-valpwq-err-toosimilar-label").arg(label);
243 case PWQ_ERROR_USER_CHECK:
244 //% "The password in the “%1” field contains the user name in some form."
245 return c->qtTrId("cutelyst-valpwq-err-usercheck-label").arg(label);
246 case PWQ_ERROR_GECOS_CHECK:
247 //% "The password in the “%1” field contains words from the real name "
248 //% "of the user name in some form."
249 return c->qtTrId("cutelyst-valpwq-err-gecoscheck-label").arg(label);
250 case PWQ_ERROR_BAD_WORDS:
251 //% "The password in the “%1” field contains forbidden words in some form."
252 return c->qtTrId("cutelyst-valpwq-err-badwords-label").arg(label);
253 case PWQ_ERROR_MIN_DIGITS:
254 //% "The password in the “%1” field does not contain enough digits."
255 return c->qtTrId("cutelyst-valpwq-err-mindigits-label").arg(label);
256 case PWQ_ERROR_MIN_UPPERS:
257 //% "The password in the “%1” field does not contain enough uppercase letters."
258 return c->qtTrId("cutelyst-valpwq-err-minuppers-label").arg(label);
259 case PWQ_ERROR_MIN_LOWERS:
260 //% "The password in the “%1” field does not contain enough lowercase letters."
261 return c->qtTrId("cutelyst-valpwq-err-minlowers-label").arg(label);
262 case PWQ_ERROR_MIN_OTHERS:
263 //% "The password in the “%1” field does not contain enough non-alphanumeric "
264 //% "characters."
265 return c->qtTrId("cutelyst-valpwq-err-minothers-label").arg(label);
266 case PWQ_ERROR_MIN_LENGTH:
267 //% "The password in the “%1” field is too short."
268 return c->qtTrId("cutelyst-valpwq-err-minlength-label").arg(label);
269 case PWQ_ERROR_ROTATED:
270 //% "The password in the “%1” field is just the rotated old one."
271 return c->qtTrId("cutelyst-valpwq-err-rotated-label").arg(label);
272 case PWQ_ERROR_MIN_CLASSES:
273 //% "The password in the “%1” field does not contain enough character types."
274 return c->qtTrId("cutelyst-valpwq-err-minclasses-label").arg(label);
275 case PWQ_ERROR_MAX_CONSECUTIVE:
276 //% "The password in the “%1” field contains too many same characters "
277 //% "consecutively."
278 return c->qtTrId("cutelyst-valpwq-err-maxconsecutive-label").arg(label);
279 case PWQ_ERROR_MAX_CLASS_REPEAT:
280 //% "The password in the “%1” field contains too many characters of "
281 //% "the same type consecutively."
282 return c->qtTrId("cutelyst-valpwq-err-maxclassrepeat-label").arg(label);
283 case PWQ_ERROR_MAX_SEQUENCE:
284 //% "The password in the “%1” field contains contains too long a "
285 //% "monotonous string."
286 return c->qtTrId("cutelyst-valpwq-err-maxsequence-label").arg(label);
287 case PWQ_ERROR_EMPTY_PASSWORD:
288 //% "No password supplied in the “%1” field."
289 return c->qtTrId("cutelyst-valpwq-err-emptypw-label").arg(label);
290 case PWQ_ERROR_RNG:
291 //% "Password quality check for the “%1“ field failed because we "
292 //% "cannot obtain random numbers from the RNG device."
293 return c->qtTrId("cutelyst-valpwq-err-rng-label").arg(label);
294 case PWQ_ERROR_CRACKLIB_CHECK:
295 //% "The password in the “%1” field fails the dictionary check."
296 return c->qtTrId("cutelyst-valpwq-err-cracklibcheck-label").arg(label);
297 case PWQ_ERROR_UNKNOWN_SETTING:
298 //% "Password quality check for the “%1“ field failed because of an "
299 //% "unknown setting."
300 return c->qtTrId("cutelyst-valpwq-err-unknownsetting-label").arg(label);
301 case PWQ_ERROR_INTEGER:
302 //% "Password quality check for the “%1“ field failed because of a "
303 //% "bad integer value in the settings."
304 return c->qtTrId("cutelyst-valpwq-err-integer-label").arg(label);
305 case PWQ_ERROR_NON_INT_SETTING:
306 //% "Password quality check for the “%1“ field failed because of a "
307 //% "settings entry is not of integer type."
308 return c->qtTrId("cutelyst-valpwq-err-nonintsetting-label").arg(label);
309 case PWQ_ERROR_NON_STR_SETTING:
310 //% "Password quality check for the “%1“ field failed because of a "
311 //% "settings entry is not of string type."
312 return c->qtTrId("cutelyst-valpwq-err-nonstrsetting-label").arg(label);
313 case PWQ_ERROR_CFGFILE_OPEN:
314 //% "Password quality check for the “%1“ field failed because opening "
315 //% "the configuration file failed."
316 return c->qtTrId("cutelyst-valpwq-err-cfgfileopen-label").arg(label);
317 case PWQ_ERROR_CFGFILE_MALFORMED:
318 //% "Password quality check for the “%1“ field failed because the "
319 //% "configuration file is malformed."
320 return c->qtTrId("cutelyst-valpwq-err-cfgfilemalformed-label").arg(label);
321 case PWQ_ERROR_FATAL_FAILURE:
322 //% "Password quality check for the “%1“ field failed because of a fatal failure."
323 return c->qtTrId("cutelyst-valpwq-err-fatalfailure-label").arg(label);
324 default:
325 {
326 if (returnValue < 0) {
327 //% "Password quality check for the “%1” field failed because of "
328 //% "an unknown error."
329 return c->qtTrId("cutelyst-valpwq-err-unknown-label").arg(label);
330 } else {
331 if (returnValue < threshold) {
332 //% "The quality score of %1 for the password in the “%2” field "
333 //% "is below the threshold of %3."
334 return c->qtTrId("cutelyst-valpwq-err-belowthreshold-label")
335 .arg(QString::number(returnValue), QString::number(threshold));
336 } else {
337 return {};
338 }
339 }
340 }
341 }
342 }
343}
344
346{
347 ValidatorReturnType result;
348
349 const QString v = value(params);
350
351 if (!v.isEmpty()) {
352 Q_D(const ValidatorPwQuality);
353 QVariant opts;
354 if (d->options.isValid()) {
355 if (d->options.typeId() == QMetaType::QVariantMap) {
356 opts = d->options;
357 } else if (d->options.typeId() == QMetaType::QString) {
358 const QString optString = d->options.toString();
359 if (c->stash().contains(optString)) {
360 opts = c->stash(optString);
361 } else {
362 opts = d->options;
363 }
364 }
365 }
366 QString un;
367 if (!d->userName.isEmpty()) {
368 un = params.value(d->userName);
369 if (un.isEmpty()) {
370 un = c->stash(d->userName).toString();
371 }
372 }
373 QString opw;
374 if (!d->oldPassword.isEmpty()) {
375 opw = params.value(d->oldPassword);
376 if (opw.isEmpty()) {
377 opw = c->stash(d->oldPassword).toString();
378 }
379 }
380 int rv = validate(v, opts, opw, un);
381 if (rv < d->threshold) {
382 result.errorMessage = validationError(c, rv);
383 if (C_VALIDATOR().isDebugEnabled()) {
384 if (rv < 0) {
385 QList<char> buf(ValidatorPwQualityPrivate::errStrBufSize);
386 qCDebug(C_VALIDATOR).noquote()
387 << debugString(c)
388 << pwquality_strerror(buf.data(), buf.size(), rv, nullptr);
389 } else {
390 qCDebug(C_VALIDATOR).noquote() << debugString(c) << "The quality score" << rv
391 << "is below the threshold of" << d->threshold;
392 }
393 }
394 } else {
395 qCDebug(C_VALIDATOR).noquote()
396 << "ValidatorPwQuality: \"" << v << "\" got a quality score of" << rv;
397 result.value = v;
398 }
399 }
400
401 return result;
402}
403
405 const ParamsMultiMap &params,
406 ValidatorRtFn cb) const
407{
408 cb(validate(c, params));
409}
410
412{
413 Q_D(const ValidatorPwQuality);
414 return ValidatorPwQuality::errorString(c, errorData.toInt(), label(c), d->threshold);
415}
The Cutelyst Context.
Definition context.h:42
void stash(const QVariantHash &unite)
Definition context.cpp:563
QString qtTrId(const char *id, int n=-1) const
Definition context.h:657
Validates an input field with libpwquality to check password quality.
static QString errorString(const Context *c, int returnValue, const QString &label={}, int threshold=0)
QString genericValidationError(Context *c, const QVariant &errorData) const override
ValidatorPwQuality(const QString &field, int threshold=ValidatorPwQuality::defaultThreshold, const QVariant &options=QVariant(), const QString &userName={}, const QString &oldPassword={}, const ValidatorMessages &messages=ValidatorMessages())
void validateCb(Context *c, const ParamsMultiMap &params, ValidatorRtFn cb) const override
Base class for all validator rules.
QString validationError(Context *c, const QVariant &errorData={}) const
QString label(const Context *c) const
QString debugString(const Context *c) const
std::function< void(ValidatorReturnType &&result)> ValidatorRtFn
Void callback function for validator rules that processes the ValidatorReturnType.
QString value(const ParamsMultiMap &params) const
static int validate(const QString &value, const QVariant &options={}, const QString &oldPassword={}, const QString &user={})
Returns the password quality score for value.
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
bool isEmpty() const const
QList::pointer data()
qsizetype size() const const
T value(const Key &key, const T &defaultValue) const const
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QByteArray toUtf8() const const
bool isValid() const const
int toInt(bool *ok) const const
QMap< QString, QVariant > toMap() const const
QString toString() const const
int typeId() const const
Stores custom error messages and the input field label.
Contains the result of a single input parameter validation.