9 #include <QLoggingCategory> 10 #include <QVariantList> 12 #ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP 19 Q_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)
37 if (m_userField.isEmpty()) {
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 81 if (m_serverUris.isEmpty()) {
82 qCWarning(C_AUTH_LDAP) <<
"No LDAP server URI configured";
86 const QByteArray uri = m_serverUris.join(u
" "_s).toUtf8();
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";
132 m_serverUris = serverUris;
152 m_bindPassword = bindPassword;
157 m_userBaseDn = baseDn;
167 m_userField = userField;
177 m_idAttribute = idAttribute;
182 return m_idAttribute;
187 m_userFilter = userFilter;
207 m_attributes = attributes;
217 m_startTls = startTls;
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;
271 QStringList valuesToStrings(
struct berval **values)
278 for (
int i = 0; values[i] !=
nullptr; ++i) {
279 const QByteArray value(values[i]->bv_val, values[i]->bv_len);
299 #ifdef CUTELYST_PLUGIN_AUTHENTICATION_HAS_LDAP 300 if (m_serverUris.isEmpty()) {
301 qCWarning(C_AUTH_LDAP) <<
"No LDAP server URI configured";
305 const QByteArray uri = m_serverUris.join(u
" "_s).toUtf8();
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);
328 if (!m_bindDn.isEmpty() || !m_bindPassword.isEmpty()) {
329 if (m_bindDn.isEmpty()) {
330 qCWarning(C_AUTH_LDAP) <<
"LDAP bind password is set but bind DN is empty";
331 ldap_unbind_ext_s(ld,
nullptr,
nullptr);
335 const QByteArray bindPassword = m_bindPassword.toUtf8();
336 const QByteArray bindDnUtf8 = m_bindDn.toUtf8();
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);
366 attrs.
reserve(m_attributes.size() + 1);
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),
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";
void insert(const QString &key, const QVariant &value)
void setId(const QVariant &id)
void setUserField(const QString &userField)
void setServerUris(const QStringList &serverUris)
void push_back(parameter_type value)
SearchScope userScope() const
void reserve(qsizetype size)
qsizetype size() const const
const T & constFirst() const const
void setBindPassword(const QString &bindPassword)
QString fromUtf8(QByteArrayView str)
void setAttributes(const QStringList &attributes)
AuthenticationUser findUser(Context *c, const ParamsMultiMap &userInfo) override final
Authentication store backed by an LDAP directory.
qsizetype size() const const
QString userField() const
void setData(const QVariantMap &data)
bool canConvert() const const
bool isNull() const const
QString userFilter() const
bool isEmpty() const const
bool isEmpty() const const
const char * constData() const const
The Cutelyst namespace holds all public Cutelyst API.
void setUserBaseDn(const QString &baseDn)
void setUserScope(SearchScope scope)
void setUserFilter(const QString &userFilter)
AuthenticationUser fromSession(Context *c, const QVariant &frozenUser) override final
Container for user data retrieved from an AuthenticationStore.
QStringList serverUris() const
QMap< QString, QVariant > toMap() const const
QStringList attributes() const
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
QString idAttribute() const
void reserve(qsizetype size)
void setIdAttribute(const QString &idAttribute)
bool validatePassword(Context *c, const AuthenticationUser &user, const QString &password) const override
qsizetype size() const const
void setStartTls(bool startTls)
void setBindDn(const QString &bindDn)
QString arg(Args &&... args) const const
QString toString() const const
QVariant forSession(Context *c, const AuthenticationUser &user) override final
QString userBaseDn() const
T value(const Key &key, const T &defaultValue) const const
QByteArray toUtf8() const const