cutelyst  5.0.1
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 
12 using 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 + u'=' + 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 }
Validates an input field with libpwquality to check password quality.
ValidatorPwQuality(const QString &field, int threshold=ValidatorPwQuality::defaultThreshold, const QVariant &options=QVariant(), const QString &userName={}, const QString &oldPassword={}, const ValidatorMessages &messages=ValidatorMessages())
Stores custom error messages and the input field label.
bool isEmpty() const const
qsizetype size() const const
static QString errorString(const Context *c, int returnValue, const QString &label={}, int threshold=0)
The Cutelyst Context.
Definition: context.h:42
QString number(double n, char format, int precision)
int toInt(bool *ok) const const
void stash(const QVariantHash &unite)
Definition: context.cpp:562
void validateCb(Context *c, const ParamsMultiMap &params, ValidatorRtFn cb) const override
bool isEmpty() const const
const char * constData() const const
pointer data()
The Cutelyst namespace holds all public Cutelyst API.
QString genericValidationError(Context *c, const QVariant &errorData) const override
QString debugString(const Context *c) const
Base class for all validator rules.
int typeId() const const
QString value(const ParamsMultiMap &params) const
QString label(const Context *c) const
std::function< void(ValidatorReturnType &&result)> ValidatorRtFn
Void callback function for validator rules that processes the ValidatorReturnType.
Definition: validatorrule.h:82
QString validationError(Context *c, const QVariant &errorData={}) const
QMap< QString, QVariant > toMap() const const
QString qtTrId(const char *id, int n=-1) const
Definition: context.h:658
bool isValid() const const
Contains the result of a single input parameter validation.
Definition: validatorrule.h:52
static int validate(const QString &value, const QVariant &options={}, const QString &oldPassword={}, const QString &user={})
Returns the password quality score for value.
QString arg(Args &&... args) const const
QString toString() const const
T value(const Key &key, const T &defaultValue) const const
QByteArray toUtf8() const const