Cutelyst  2.3.0
validatorpwquality.cpp
1 /*
2  * Copyright (C) 2018 Matthias Fehring <kontakt@buschmann23.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include "validatorpwquality_p.h"
20 #include <pwquality.h>
21 #include <QLoggingCategory>
22 
23 using namespace Cutelyst;
24 
25 ValidatorPwQuality::ValidatorPwQuality(const QString &field, int threshold, const QVariant &options, const QString &userName, const QString &oldPassword, const ValidatorMessages &messages) :
26  ValidatorRule(*new ValidatorPwQualityPrivate(field, threshold, options, userName, oldPassword, messages))
27 {
28 
29 }
30 
32 {
33 
34 }
35 
36 int ValidatorPwQuality::validate(const QString &value, const QVariant &options, const QString &oldPassword, const QString &user)
37 {
38  int rv = 0;
39 
40  if (!value.isEmpty()) {
41 
42  pwquality_settings_t *pwq = pwquality_default_settings();
43  if (pwq) {
44 
45  bool optionsSet = false;
46  if (options.isValid()) {
47  if (options.type() == QVariant::Map) {
48  const QVariantMap map = options.toMap();
49  if (!map.empty()) {
50  auto i = map.constBegin();
51  while (i != map.constEnd()) {
52  const QString opt = i.key() + QLatin1Char('=') + i.value().toString();
53  const int orv = pwquality_set_option(pwq, opt.toUtf8().constData());
54  if (orv != 0) {
55  char buf[1024];
56  qCWarning(C_VALIDATOR, "ValidatorPwQuality: Failed to set pwquality option %s: %s", qUtf8Printable(opt), pwquality_strerror(buf, sizeof(buf), orv, nullptr));
57  }
58  ++i;
59  }
60  optionsSet = true;
61  }
62  } else if (options.type() == QVariant::String) {
63  const QString configFile = options.toString();
64  if (!configFile.isEmpty()) {
65  if (C_VALIDATOR().isWarningEnabled()) {
66  void *auxerror;
67  const int rcrv = pwquality_read_config(pwq, configFile.toUtf8().constData(), &auxerror);
68  if (rcrv != 0) {
69  char buf[1024];
70  qCWarning(C_VALIDATOR, "ValidatorPwQuality: Failed to read configuration file %s: %s", qUtf8Printable(configFile), pwquality_strerror(buf, sizeof(buf), 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;
83  const int rcrv = pwquality_read_config(pwq, nullptr, &auxerror);
84  if (rcrv != 0) {
85  char buf[1024];
86  qCWarning(C_VALIDATOR, "ValidatorPwQuality: Failed to read default configuration file: %s", pwquality_strerror(buf, sizeof(buf), rcrv, auxerror));
87  }
88  } else {
89  pwquality_read_config(pwq, nullptr, nullptr);
90  }
91  }
92 
93  const QByteArray pwba = value.toUtf8();
94  const char *pw = pwba.constData();
95  const QByteArray opwba = oldPassword.toUtf8();
96  const char *opw = opwba.isEmpty() ? nullptr : opwba.constData();
97  const QByteArray uba = user.toUtf8();
98  const char *u = uba.isEmpty() ? nullptr : uba.constData();
99 
100  rv = pwquality_check(pwq, pw, opw, u, nullptr);
101 
102  pwquality_free_settings(pwq);
103 
104  } else {
105  rv = PWQ_ERROR_MEM_ALLOC;
106  }
107  } else {
108  rv = PWQ_ERROR_EMPTY_PASSWORD;
109  }
110 
111  return rv;
112 }
113 
114 QString ValidatorPwQuality::errorString(Context *c, int returnValue, const QString &label, int threshold)
115 {
116  QString error;
117 
118  if (label.isEmpty()) {
119  switch (returnValue) {
120  case PWQ_ERROR_MEM_ALLOC:
121  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of a memory allocation error.");
122  break;
123  case PWQ_ERROR_SAME_PASSWORD:
124  error = c->translate("Cutelyst::ValidatorPwQuality", "The password is the same as the old one.");
125  break;
126  case PWQ_ERROR_PALINDROME:
127  error = c->translate("Cutelyst::ValidatorPwQuality", "The password is a palindrome.");
128  break;
129  case PWQ_ERROR_CASE_CHANGES_ONLY:
130  error = c->translate("Cutelyst::ValidatorPwQuality", "The password differs with case changes only.");
131  break;
132  case PWQ_ERROR_TOO_SIMILAR:
133  error = c->translate("Cutelyst::ValidatorPwQuality", "The password is too similar to the old one.");
134  break;
135  case PWQ_ERROR_USER_CHECK:
136  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains the user name in some form.");
137  break;
138  case PWQ_ERROR_GECOS_CHECK:
139  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains words from the real name of the user in some form.");
140  break;
141  case PWQ_ERROR_BAD_WORDS:
142  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains forbidden words in some form.");
143  break;
144  case PWQ_ERROR_MIN_DIGITS:
145  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too few digits.");
146  break;
147  case PWQ_ERROR_MIN_UPPERS:
148  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too few uppercase letters.");
149  break;
150  case PWQ_ERROR_MIN_LOWERS:
151  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too few lowercase letters.");
152  break;
153  case PWQ_ERROR_MIN_OTHERS:
154  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains less than %ld non-alphanumeric characters.");
155  break;
156  case PWQ_ERROR_MIN_LENGTH:
157  error = c->translate("Cutelyst::ValidatorPwQuality", "The password is too short.");
158  break;
159  case PWQ_ERROR_ROTATED:
160  error = c->translate("Cutelyst::ValidatorPwQuality", "The password is just rotated old one.");
161  break;
162  case PWQ_ERROR_MIN_CLASSES:
163  error = c->translate("Cutelyst::ValidatorPwQuality", "The password does not contain enough character classes.");
164  break;
165  case PWQ_ERROR_MAX_CONSECUTIVE:
166  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too many same characters consecutively.");
167  break;
168  case PWQ_ERROR_MAX_CLASS_REPEAT:
169  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too many characters of the same class consecutively.");
170  break;
171  case PWQ_ERROR_MAX_SEQUENCE:
172  error = c->translate("Cutelyst::ValidatorPwQuality", "The password contains too long of a monotonic character sequence.");
173  break;
174  case PWQ_ERROR_EMPTY_PASSWORD:
175  error = c->translate("Cutelyst::ValidatorPwQuality", "No password supplied.");
176  break;
177  case PWQ_ERROR_RNG:
178  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because we cannot obtain random numbers from the RNG device");
179  break;
180  case PWQ_ERROR_CRACKLIB_CHECK:
181  error = c->translate("Cutelyst::ValidatorPwQuality", "The password fails the dictionary check.");
182  break;
183  case PWQ_ERROR_UNKNOWN_SETTING:
184  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of an unknown setting.");
185  break;
186  case PWQ_ERROR_INTEGER:
187  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of a bad integer value in the settings.");
188  break;
189  case PWQ_ERROR_NON_INT_SETTING:
190  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of a settings entry is not of integer type.");
191  break;
192  case PWQ_ERROR_NON_STR_SETTING:
193  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of a settings entry is not of string type.");
194  break;
195  case PWQ_ERROR_CFGFILE_OPEN:
196  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because opening the configuration file failed.");
197  break;
198  case PWQ_ERROR_CFGFILE_MALFORMED:
199  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because the configuration file is malformed.");
200  break;
201  case PWQ_ERROR_FATAL_FAILURE:
202  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of a fatal failure.");
203  break;
204  default:
205  {
206  if (returnValue < 0) {
207  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check failed because of an unknown error.");
208  } else {
209  if (returnValue < threshold) {
210  error = c->translate("Cutelyst::ValidatorPwQuality", "The password quality score of %1 is below the threshold of %2.").arg(QString::number(returnValue), QString::number(threshold));
211  }
212  }
213  }
214  break;
215  }
216  } else {
217  switch (returnValue) {
218  case PWQ_ERROR_MEM_ALLOC:
219  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of a memory allocation error.").arg(label);
220  break;
221  case PWQ_ERROR_SAME_PASSWORD:
222  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field is the same as the old one.").arg(label);
223  break;
224  case PWQ_ERROR_PALINDROME:
225  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field is a palindrome.").arg(label);
226  break;
227  case PWQ_ERROR_CASE_CHANGES_ONLY:
228  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field differs with case changes only.").arg(label);
229  break;
230  case PWQ_ERROR_TOO_SIMILAR:
231  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field is too similar to the old one.").arg(label);
232  break;
233  case PWQ_ERROR_USER_CHECK:
234  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains the user name in some form.").arg(label);
235  break;
236  case PWQ_ERROR_GECOS_CHECK:
237  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains words from the real name of the user in some form.").arg(label);
238  break;
239  case PWQ_ERROR_BAD_WORDS:
240  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains forbidden words in some form.").arg(label);
241  break;
242  case PWQ_ERROR_MIN_DIGITS:
243  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too few digits.").arg(label);
244  break;
245  case PWQ_ERROR_MIN_UPPERS:
246  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too few uppercase letters.").arg(label);
247  break;
248  case PWQ_ERROR_MIN_LOWERS:
249  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too few lowercase letters.").arg(label);
250  break;
251  case PWQ_ERROR_MIN_OTHERS:
252  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains less than %ld non-alphanumeric characters.").arg(label);
253  break;
254  case PWQ_ERROR_MIN_LENGTH:
255  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field is too short.").arg(label);
256  break;
257  case PWQ_ERROR_ROTATED:
258  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field is just rotated old one.").arg(label);
259  break;
260  case PWQ_ERROR_MIN_CLASSES:
261  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field does not contain enough character classes.").arg(label);
262  break;
263  case PWQ_ERROR_MAX_CONSECUTIVE:
264  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too many same characters consecutively.").arg(label);
265  break;
266  case PWQ_ERROR_MAX_CLASS_REPEAT:
267  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too many characters of the same class consecutively.").arg(label);
268  break;
269  case PWQ_ERROR_MAX_SEQUENCE:
270  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field contains too long of a monotonic character sequence.").arg(label);
271  break;
272  case PWQ_ERROR_EMPTY_PASSWORD:
273  error = c->translate("Cutelyst::ValidatorPwQuality", "No password supplied in the “%1” field.").arg(label);
274  break;
275  case PWQ_ERROR_RNG:
276  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because we cannot obtain random numbers from the RNG device").arg(label);
277  break;
278  case PWQ_ERROR_CRACKLIB_CHECK:
279  error = c->translate("Cutelyst::ValidatorPwQuality", "The password in the “%1” field fails the dictionary check.").arg(label);
280  break;
281  case PWQ_ERROR_UNKNOWN_SETTING:
282  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of an unknown setting.").arg(label);
283  break;
284  case PWQ_ERROR_INTEGER:
285  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of a bad integer value in the settings.").arg(label);
286  break;
287  case PWQ_ERROR_NON_INT_SETTING:
288  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of a settings entry is not of integer type.").arg(label);
289  break;
290  case PWQ_ERROR_NON_STR_SETTING:
291  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of a settings entry is not of string type.").arg(label);
292  break;
293  case PWQ_ERROR_CFGFILE_OPEN:
294  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because opening the configuration file failed.").arg(label);
295  break;
296  case PWQ_ERROR_CFGFILE_MALFORMED:
297  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because the configuration file is malformed.").arg(label);
298  break;
299  case PWQ_ERROR_FATAL_FAILURE:
300  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1“ field failed because of a fatal failure.").arg(label);
301  break;
302  default:
303  {
304  if (returnValue < 0) {
305  error = c->translate("Cutelyst::ValidatorPwQuality", "Password quality check for the “%1” field failed because of an unknown error.").arg(label);
306  } else {
307  if (returnValue < threshold) {
308  error = c->translate("Cutelyst::ValidatorPwQuality", "The quality score of %1 for the password in the “%2” field is below the threshold of %3.").arg(QString::number(returnValue), label, QString::number(threshold));
309  }
310  }
311  }
312  break;
313  }
314  }
315 
316  return error;
317 }
318 
320 {
321  ValidatorReturnType result;
322 
323  const QString v = value(params);
324 
325  if (!v.isEmpty()) {
326  Q_D(const ValidatorPwQuality);
327  QVariant opts;
328  if (d->options.isValid()) {
329  if (d->options.type() == QVariant::Map) {
330  opts = d->options;
331  } else if (d->options.type() == QVariant::String) {
332  const QString optString = d->options.toString();
333  if (c->stash().contains(optString)) {
334  opts = c->stash(optString);
335  } else {
336  opts = d->options;
337  }
338  }
339  }
340  QString un;
341  if (!d->userName.isEmpty()) {
342  un = params.value(d->userName);
343  if (un.isEmpty()) {
344  un = c->stash(d->userName).toString();
345  }
346  }
347  QString opw;
348  if (!d->oldPassword.isEmpty()) {
349  opw = params.value(d->oldPassword);
350  if (opw.isEmpty()) {
351  opw = c->stash(d->oldPassword).toString();
352  }
353  }
354  int rv = validate(v, opts, opw, un);
355  if (rv < d->threshold) {
356  result.errorMessage = validationError(c, rv);
357  if (C_VALIDATOR().isDebugEnabled()) {
358  if (rv < 0) {
359  char buf[1024];
360  qCDebug(C_VALIDATOR, "ValidatorPwQuality: Validation failed for field %s at %s::%s: %s", qPrintable(field()), qPrintable(c->controllerName()), qPrintable(c->actionName()), pwquality_strerror(buf, sizeof(buf), rv, nullptr));
361  } else {
362  qCDebug(C_VALIDATOR, "ValidatorPwQuality: Validation failed for field %s at %s::%s because the quality score %i is below the threshold of %i.", qPrintable(field()), qPrintable(c->controllerName()), qPrintable(c->actionName()), rv, d->threshold);
363  }
364  }
365  } else {
366  qCDebug(C_VALIDATOR, "ValidatorPwQuality: \"%s\" got a quality score of %i", qPrintable(v), rv);
367  result.value.setValue<QString>(v);
368  }
369  }
370 
371  return result;
372 }
373 
374 QString ValidatorPwQuality::genericValidationError(Context *c, const QVariant &errorData) const
375 {
376  QString error;
377 
378  Q_D(const ValidatorPwQuality);
379  const int returnValue = errorData.toInt();
380  const QString _label = label(c);
381  error = ValidatorPwQuality::errorString(c, returnValue, _label, d->threshold);
382 
383  return error;
384 }
QMap< QString, QString > ParamsMultiMap
Validates an input field with libpwquality to check password quality.
QString validationError(Context *c, const QVariant &errorData=QVariant()) const
Returns a descriptive error message if validation failed.
Stores custom error messages and the input field label.
static int validate(const QString &value, const QVariant &options=QVariant(), const QString &oldPassword=QString(), const QString &user=QString())
Returns the password quality score for value.
The Cutelyst Context.
Definition: context.h:50
ValidatorPwQuality(const QString &field, int threshold=30, const QVariant &options=QVariant(), const QString &userName=QString(), const QString &oldPassword=QString(), const ValidatorMessages &messages=ValidatorMessages())
Constructs a new ValidatorPwQuality with the given parameters.
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:414
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
QString genericValidationError(Context *c, const QVariant &errorData) const override
Returns a generic error message if validation failed.
~ValidatorPwQuality()
Deconstructs the ValidatorPwQuality.
Base class for all validator rules.
QString label(Context *c) const
Returns the human readable field label used for generic error messages.
static QString errorString(Context *c, int returnValue, const QString &label=QString(), int threshold=0)
Returns a human readable string for the return value of ValidatorPwQuality::validate() ...
QString value(const ParamsMultiMap &params) const
Returns the value of the field from the input params.
QString field() const
Returns the name of the field to validate.
Contains the result of a single input parameter validation.
Definition: validatorrule.h:62
void stash(const QVariantHash &unite)
Definition: context.h:515