cutelyst  4.4.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
credentialhttp.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "authenticationrealm.h"
6 #include "credentialhttp_p.h"
7 #include "credentialpassword.h"
8 
9 #include <Cutelyst/Context>
10 #include <Cutelyst/Response>
11 
12 #include <QLoggingCategory>
13 #include <QUrl>
14 
15 using namespace Cutelyst;
16 
17 Q_LOGGING_CATEGORY(C_CREDENTIALHTTP, "cutelyst.plugin.credentialhttp", QtWarningMsg)
18 
20  : AuthenticationCredential(parent)
21  , d_ptr(new CredentialHttpPrivate)
22 {
23 }
24 
26 {
27  delete d_ptr;
28 }
29 
31 {
32  Q_D(CredentialHttp);
33  d->type = type;
34 }
35 
37 {
38  Q_D(CredentialHttp);
39  d->authorizationRequiredMessage = message;
40 }
41 
43 {
44  Q_D(const CredentialHttp);
45  return d->passwordField;
46 }
47 
49 {
50  Q_D(CredentialHttp);
51  d->passwordField = fieldName;
52 }
53 
55 {
56  Q_D(const CredentialHttp);
57  return d->passwordType;
58 }
59 
61 {
62  Q_D(CredentialHttp);
63  d->passwordType = type;
64 }
65 
67 {
68  Q_D(const CredentialHttp);
69  return d->passwordPreSalt;
70 }
71 
72 void CredentialHttp::setPasswordPreSalt(const QString &passwordPreSalt)
73 {
74  Q_D(CredentialHttp);
75  d->passwordPreSalt = passwordPreSalt;
76 }
77 
79 {
80  Q_D(const CredentialHttp);
81  return d->passwordPostSalt;
82 }
83 
84 void CredentialHttp::setPasswordPostSalt(const QString &passwordPostSalt)
85 {
86  Q_D(CredentialHttp);
87  d->passwordPostSalt = passwordPostSalt;
88 }
89 
91 {
92  Q_D(const CredentialHttp);
93  return d->usernameField;
94 }
95 
97 {
98  Q_D(CredentialHttp);
99  d->usernameField = fieldName;
100 }
101 
103 {
104  Q_D(CredentialHttp);
105  d->requireSsl = require;
106 }
107 
109  AuthenticationRealm *realm,
110  const ParamsMultiMap &authinfo)
111 {
112  Q_D(CredentialHttp);
113 
114  AuthenticationUser ret;
115  if (d->requireSsl && !c->request()->secure()) {
116  ret = d->authenticationFailed(c, realm, authinfo);
117  return ret;
118  }
119 
120  if (d->isAuthTypeBasic()) {
121  ret = d->authenticateBasic(c, realm, authinfo);
122  if (!ret.isNull()) {
123  return ret;
124  }
125  }
126 
127  ret = d->authenticationFailed(c, realm, authinfo);
128  return ret;
129 }
130 
131 bool CredentialHttpPrivate::checkPassword(const AuthenticationUser &user,
132  const ParamsMultiMap &authinfo)
133 {
134  const QString password = passwordPreSalt + authinfo.value(passwordField) + passwordPostSalt;
135  const QString storedPassword = user.value(passwordField).toString();
136 
137  if (Q_LIKELY(passwordType == CredentialHttp::Hashed)) {
138  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
139  } else if (passwordType == CredentialHttp::Clear) {
140  return storedPassword == password;
141  } else if (passwordType == CredentialHttp::None) {
142  qCCritical(C_CREDENTIALHTTP) << "CredentialPassword is set to ignore password check";
143  return true;
144  }
145 
146  return false;
147 }
148 
149 AuthenticationUser CredentialHttpPrivate::authenticateBasic(Context *c,
150  AuthenticationRealm *realm,
151  const ParamsMultiMap &authinfo)
152 {
153  Q_UNUSED(authinfo)
154  AuthenticationUser user;
155  qCDebug(C_CREDENTIALHTTP) << "Checking http basic authentication.";
156 
157  const auto userPass = c->req()->headers().authorizationBasicObject();
158  if (userPass.user.isEmpty()) {
159  return user;
160  }
161 
162  ParamsMultiMap auth;
163  auth.insert(usernameField, userPass.user);
164  AuthenticationUser _user = realm->findUser(c, auth);
165  if (!_user.isNull()) {
166  auth.insert(passwordField, userPass.password);
167  if (checkPassword(_user, auth)) {
168  user = _user;
169  } else {
170  qCDebug(C_CREDENTIALHTTP) << "Password didn't match";
171  }
172  } else {
173  qCDebug(C_CREDENTIALHTTP) << "Unable to locate a user matching user info provided in realm";
174  }
175  return user;
176 }
177 
178 AuthenticationUser CredentialHttpPrivate::authenticationFailed(Context *c,
179  AuthenticationRealm *realm,
180  const ParamsMultiMap &authinfo)
181 {
182  Q_UNUSED(authinfo);
183  Response *res = c->response();
184  res->setStatus(Response::Unauthorized); // 401
185  res->setContentType("text/plain; charset=UTF-8"_qba);
186 
187  if (authorizationRequiredMessage.isEmpty()) {
188  res->setBody("Authorization required."_qba);
189  } else {
190  res->setBody(authorizationRequiredMessage);
191  }
192 
193  // Create Basic response
194  if (isAuthTypeBasic()) {
195  createBasicAuthResponse(c, realm);
196  }
197 
198  return AuthenticationUser();
199 }
200 
201 bool CredentialHttpPrivate::isAuthTypeBasic() const
202 {
203  return type == CredentialHttp::Basic || type == CredentialHttp::Any;
204 }
205 
206 void CredentialHttpPrivate::createBasicAuthResponse(Context *c, AuthenticationRealm *realm)
207 {
209  joinAuthHeaderParts("Basic"_qba, buildAuthHeaderCommon(realm)));
210 }
211 
212 QByteArrayList CredentialHttpPrivate::buildAuthHeaderCommon(AuthenticationRealm *realm) const
213 {
214  QByteArrayList ret;
215  // TODO
216  // return realm="realmname"
217  // return domain="realmname"
218  if (!realm->name().isEmpty()) {
219  ret.append("realm=\"" + realm->name().toLatin1() + '"');
220  }
221  return ret;
222 }
223 
224 QByteArray CredentialHttpPrivate::joinAuthHeaderParts(const QByteArray &type,
225  const QByteArrayList &parts) const
226 {
227  QByteArray ret = type;
228  if (!parts.isEmpty()) {
229  ret.append(' ' + parts.join(", "));
230  }
231  return ret;
232 }
233 
234 #include "moc_credentialhttp.cpp"
Abstract class to validate authentication credentials like user name and password.
Combines user store and credential validation into a named realm.
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Container for user data retrieved from an AuthenticationStore.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
QString name() const noexcept
Definition: component.cpp:33
The Cutelyst Context.
Definition: context.h:42
Response * res() const noexcept
Definition: context.cpp:103
Request * request
Definition: context.h:71
Request * req
Definition: context.h:66
Response * response() const noexcept
Definition: context.cpp:97
Use HTTP basic authentication to authenticate a user.
QString usernameField() const
void setPasswordType(PasswordType type)
void setUsernameField(const QString &fieldName)
QString passwordPreSalt() const
QString passwordPostSalt() const
void setPasswordPreSalt(const QString &passwordPreSalt)
QString passwordField() const
PasswordType passwordType() const
void setType(CredentialHttp::AuthType type)
AuthenticationUser authenticate(Context *c, AuthenticationRealm *realm, const ParamsMultiMap &authinfo) final
void setPasswordField(const QString &fieldName)
void setPasswordPostSalt(const QString &passwordPostSalt)
void setRequireSsl(bool require)
void setAuthorizationRequiredMessage(const QString &message)
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
void setWwwAuthenticate(const QByteArray &value)
Definition: headers.cpp:326
Authorization authorizationBasicObject() const
Definition: headers.cpp:358
Headers headers() const noexcept
Definition: request.cpp:312
A Cutelyst response.
Definition: response.h:29
void setContentType(const QByteArray &type)
Definition: response.h:238
void setStatus(quint16 status) noexcept
Definition: response.cpp:72
Headers & headers() noexcept
void setBody(QIODevice *body)
Definition: response.cpp:103
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
QByteArray join(QByteArrayView separator) const const
void append(QList::parameter_type value)
bool isEmpty() const const
QMultiMap::iterator insert(QMultiMap::const_iterator pos, const Key &key, const T &value)
T value(const Key &key, const T &defaultValue) const const
bool isEmpty() const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QString toString() const const