9#include <QLoggingCategory>
10#include <QVariantList>
12#ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP
17using namespace Qt::StringLiterals;
19Q_LOGGING_CATEGORY(C_AUTH_LDAP,
"cutelyst.plugin.authentication.ldap", QtWarningMsg)
22 : m_serverUris({u
"ldap://127.0.0.1:389"_s})
23 , m_userField(u
"id"_s)
24 , m_idAttribute(u
"id"_s)
25 , m_userScope(SearchScope::SubTree)
38 qCWarning(C_AUTH_LDAP) <<
"User field is empty";
42 return findUserByAttribute(c, m_userField, userInfo.
value(m_userField));
61 return findUserByAttribute(c, m_idAttribute, frozenUser.
toString());
76 qCWarning(C_AUTH_LDAP) <<
"LDAP self-check failed, user has no DN value";
80#ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP
82 qCWarning(C_AUTH_LDAP) <<
"No LDAP server URI configured";
89 int rc = ldap_initialize(&ld, uri.
constData());
90 if (rc != LDAP_SUCCESS || !ld) {
91 qCWarning(C_AUTH_LDAP) <<
"Failed to initialize LDAP connection:" << ldap_err2string(rc);
95 int ldapVersion = LDAP_VERSION3;
96 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion);
99 rc = ldap_start_tls_s(ld,
nullptr,
nullptr);
100 if (rc != LDAP_SUCCESS) {
101 qCWarning(C_AUTH_LDAP) <<
"Failed to start TLS:" << ldap_err2string(rc);
102 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
109 bindCred.bv_val =
const_cast<char *
>(passwordUtf8.
constData());
110 bindCred.bv_len =
static_cast<ber_len_t
>(passwordUtf8.
size());
112 rc = ldap_sasl_bind_s(
113 ld, userDn.
toUtf8().
constData(), LDAP_SASL_SIMPLE, &bindCred,
nullptr,
nullptr,
nullptr);
115 if (rc == LDAP_INAPPROPRIATE_AUTH) {
116 qCWarning(C_AUTH_LDAP) <<
"LDAP user bind failed with Inappropriate authentication. "
117 "Server may require StartTLS/LDAPS or stronger auth."
118 <<
"startTls=" << m_startTls <<
"dn=" << userDn;
121 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
123 return rc == LDAP_SUCCESS;
125 qCWarning(C_AUTH_LDAP) <<
"StoreLDAP requested but plugin was built without LDAP support";
152 m_bindPassword = bindPassword;
157 m_userBaseDn = baseDn;
182 return m_idAttribute;
225#ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP
230 case StoreLDAP::SearchScope::Base:
231 return LDAP_SCOPE_BASE;
232 case StoreLDAP::SearchScope::OneLevel:
233 return LDAP_SCOPE_ONELEVEL;
234 case StoreLDAP::SearchScope::SubTree:
236 return LDAP_SCOPE_SUBTREE;
245 for (
const QChar ch : value) {
246 switch (ch.unicode()) {
248 escaped += u
"\\2a"_s;
251 escaped += u
"\\28"_s;
254 escaped += u
"\\29"_s;
257 escaped += u
"\\5c"_s;
260 escaped += u
"\\00"_s;
278 for (
int i = 0; values[i] !=
nullptr; ++i) {
279 const QByteArray value(values[i]->bv_val, values[i]->bv_len);
295 if (value.isEmpty()) {
299#ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP
301 qCWarning(C_AUTH_LDAP) <<
"No LDAP server URI configured";
308 int rc = ldap_initialize(&ld, uri.
constData());
309 if (rc != LDAP_SUCCESS || !ld) {
310 qCWarning(C_AUTH_LDAP) <<
"Failed to initialize LDAP connection:" << ldap_err2string(rc);
314 int ldapVersion = LDAP_VERSION3;
315 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion);
318 rc = ldap_start_tls_s(ld,
nullptr,
nullptr);
319 if (rc != LDAP_SUCCESS) {
320 qCWarning(C_AUTH_LDAP) <<
"Failed to start TLS:" << ldap_err2string(rc);
321 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
330 qCWarning(C_AUTH_LDAP) <<
"LDAP bind password is set but bind DN is empty";
331 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
338 bindCred.bv_val =
const_cast<char *
>(bindPassword.
constData());
339 bindCred.bv_len =
static_cast<ber_len_t
>(bindPassword.
size());
341 rc = ldap_sasl_bind_s(
342 ld, bindDnUtf8.
constData(), LDAP_SASL_SIMPLE, &bindCred,
nullptr,
nullptr,
nullptr);
343 if (rc != LDAP_SUCCESS) {
344 if (rc == LDAP_INAPPROPRIATE_AUTH) {
345 qCWarning(C_AUTH_LDAP) <<
"LDAP bind failed with Inappropriate authentication. "
346 "Server may require StartTLS/LDAPS or stronger auth."
347 <<
"startTls=" << m_startTls <<
"bindDn=" << m_bindDn;
349 qCWarning(C_AUTH_LDAP) <<
"LDAP bind failed:" << ldap_err2string(rc);
351 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
357 const QString escapedValue = escapeFilterValue(value);
359 filter = u
"(%1=%2)"_s.
arg(attribute, escapedValue);
361 filter = filter.
arg(escapedValue);
367 for (
const QString &attr : m_attributes) {
375 LDAPMessage *result =
nullptr;
376 rc = ldap_search_ext_s(ld,
377 m_userBaseDn.
isEmpty() ?
nullptr : m_userBaseDn.toUtf8().constData(),
378 toLdapScope(m_userScope),
379 filter.toUtf8().constData(),
380 m_attributes.isEmpty() ? nullptr : attrs.data(),
388 if (rc != LDAP_SUCCESS) {
389 qCWarning(C_AUTH_LDAP) <<
"LDAP search failed:" << ldap_err2string(rc);
390 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
394 LDAPMessage *entry = ldap_first_entry(ld, result);
396 ldap_msgfree(result);
397 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
401 char *dnRaw = ldap_get_dn(ld, entry);
408 BerElement *ber =
nullptr;
409 for (
char *attr = ldap_first_attribute(ld, entry, &ber); attr !=
nullptr;
410 attr = ldap_next_attribute(ld, entry, ber)) {
413 berval **values = ldap_get_values_len(ld, entry, attr);
414 const QStringList stringValues = valuesToStrings(values);
416 if (stringValues.
size() == 1) {
418 }
else if (!stringValues.
isEmpty()) {
420 list.reserve(stringValues.
size());
421 for (
const QString &item : stringValues) {
422 list.push_back(item);
424 user.
insert(attrName, list);
428 ldap_value_free_len(values);
438 m_idAttribute == u
"dn"_s ? user.
value(u
"dn"_s) : user.
value(m_idAttribute);
445 ldap_msgfree(result);
446 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
449 qCWarning(C_AUTH_LDAP) <<
"StoreLDAP requested but plugin was built without LDAP support";
Container for user data retrieved from an AuthenticationStore.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
void setData(const QVariantMap &data)
void setId(const QVariant &id)
void insert(const QString &key, const QVariant &value)
Authentication store backed by an LDAP directory.
AuthenticationUser fromSession(Context *c, const QVariant &frozenUser) override final
void setBindDn(const QString &bindDn)
void setUserBaseDn(const QString &baseDn)
QString userField() const
QVariant forSession(Context *c, const AuthenticationUser &user) override final
void setBindPassword(const QString &bindPassword)
QString userBaseDn() const
void setUserFilter(const QString &userFilter)
QStringList attributes() const
SearchScope userScope() const
void setIdAttribute(const QString &idAttribute)
void setStartTls(bool startTls)
QString idAttribute() const
void setServerUris(const QStringList &serverUris)
bool validatePassword(Context *c, const AuthenticationUser &user, const QString &password) const
void setUserScope(SearchScope scope)
QString userFilter() const
AuthenticationUser findUser(Context *c, const ParamsMultiMap &userInfo) override final
void setUserField(const QString &userField)
QStringList serverUris() const
void setAttributes(const QStringList &attributes)
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
qsizetype size() const const
const T & constFirst() const const
bool isEmpty() const const
void push_back(QList::parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T value(const Key &key, const T &defaultValue) const const
QString arg(Args &&... args) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
void reserve(qsizetype size)
QByteArray toUtf8() const const
QString join(QChar separator) const const
bool canConvert() const const
bool isNull() const const
QMap< QString, QVariant > toMap() const const
QString toString() const const