KLdap

ldapclientsearch.cpp
1/* kldapclient.cpp - LDAP access
2 * SPDX-FileCopyrightText: 2002 Klarälvdalens Datakonsult AB
3 * SPDX-FileContributor: Steffen Hansen <hansen@kde.org>
4 *
5 * Ported to KABC by Daniel Molkentin <molkentin@kde.org>
6 *
7 * SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
8 *
9 * SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11
12#include "ldapclientsearch.h"
13#include "kldapcore/ldapclientsearchconfig.h"
14#include "ldapclient_core_debug.h"
15#include "ldapsearchclientreadconfigserverjob.h"
16
17#include "ldapclient.h"
18
19#include "kldapcore/ldapserver.h"
20#include "kldapcore/ldapurl.h"
21#include "kldapcore/ldif.h"
22
23#include <KConfig>
24#include <KConfigGroup>
25#include <KDirWatch>
26#include <KProtocolInfo>
27
28#include <KIO/Job>
29
30#include <QStandardPaths>
31#include <QTimer>
32
33using namespace KLDAPCore;
34using namespace Qt::Literals::StringLiterals;
35
36class Q_DECL_HIDDEN LdapClientSearch::LdapClientSearchPrivate
37{
38public:
39 LdapClientSearchPrivate(LdapClientSearch *qq)
40 : q(qq)
41 {
42 }
43
44 ~LdapClientSearchPrivate() = default;
45
46 void readWeighForClient(KLDAPCore::LdapClient *client, const KConfigGroup &config, int clientNumber);
47 void readConfig();
48 void finish();
49 void makeSearchData(QStringList &ret, LdapResult::List &resList);
50
51 void slotLDAPResult(const KLDAPCore::LdapClient &client, const KLDAPCore::LdapObject &);
52 void slotLDAPError(const QString &);
53 void slotLDAPDone();
54 void slotDataTimer();
55 void slotFileChanged(const QString &);
56 void init(const QStringList &attributes);
57
58 LdapClientSearch *const q;
60 QStringList mAttributes;
61 QString mSearchText;
62 QString mFilter;
63 QTimer mDataTimer;
64 int mActiveClients = 0;
65 bool mNoLDAPLookup = false;
67 QString mConfigFile;
68};
69
71 : QObject(parent)
72 , d(new LdapClientSearchPrivate(this))
73{
74 d->init(LdapClientSearch::defaultAttributes());
75}
76
78 : QObject(parent)
79 , d(new LdapClientSearchPrivate(this))
80{
81 d->init(attr);
82}
83
85
86void LdapClientSearch::LdapClientSearchPrivate::init(const QStringList &attributes)
87{
88 if (!KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("ldap://localhost")))) {
89 mNoLDAPLookup = true;
90 return;
91 }
92
93 mAttributes = attributes;
94
95 // Set the filter, to make sure old usage (before 4.14) of this object still works.
96 mFilter = QStringLiteral(
97 "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))"
98 "(|(cn=%1*)(mail=%1*)(givenName=%1*)(sn=%1*))");
99
100 readConfig();
101 q->connect(KDirWatch::self(), &KDirWatch::dirty, q, [this](const QString &filename) {
102 slotFileChanged(filename);
103 });
104}
105
106void LdapClientSearch::LdapClientSearchPrivate::readWeighForClient(KLDAPCore::LdapClient *client, const KConfigGroup &config, int clientNumber)
107{
108 const int completionWeight = config.readEntry(QStringLiteral("SelectedCompletionWeight%1").arg(clientNumber), -1);
109 if (completionWeight != -1) {
110 client->setCompletionWeight(completionWeight);
111 }
112}
113
115{
116 KConfigGroup config(KLDAPCore::LdapClientSearchConfig::config(), QStringLiteral("LDAP"));
117 for (int i = 0, total = d->mClients.size(); i < total; ++i) {
118 d->readWeighForClient(d->mClients[i], config, i);
119 }
120}
121
123{
124 return d->mClients;
125}
126
128{
129 return d->mFilter;
130}
131
133{
134 d->mFilter = filter;
135}
136
138{
139 return d->mAttributes;
140}
141
143{
144 if (attrs != d->mAttributes) {
145 d->mAttributes = attrs;
146 d->readConfig();
147 }
148}
149
150QStringList LdapClientSearch::defaultAttributes()
151{
152 const QStringList attr{QStringLiteral("cn"), QStringLiteral("mail"), QStringLiteral("givenname"), QStringLiteral("sn")};
153 return attr;
154}
155
156void LdapClientSearch::LdapClientSearchPrivate::readConfig()
157{
158 q->cancelSearch();
159 qDeleteAll(mClients);
160 mClients.clear();
161
162 // stolen from KAddressBook
163 KConfigGroup config(KLDAPCore::LdapClientSearchConfig::config(), QStringLiteral("LDAP"));
164 const int numHosts = config.readEntry("NumSelectedHosts", 0);
165 if (!numHosts) {
166 mNoLDAPLookup = true;
167 } else {
168 for (int j = 0; j < numHosts; ++j) {
169 auto ldapClient = new KLDAPCore::LdapClient(j, q);
170 auto job = new KLDAPCore::LdapSearchClientReadConfigServerJob;
171 job->setCurrentIndex(j);
172 job->setActive(true);
173 job->setConfig(config);
174 job->setLdapClient(ldapClient);
175 job->start();
176
177 mNoLDAPLookup = false;
178 readWeighForClient(ldapClient, config, j);
179
180 ldapClient->setAttributes(mAttributes);
181
182 q->connect(ldapClient, &KLDAPCore::LdapClient::result, q, [this](const KLDAPCore::LdapClient &client, const KLDAPCore::LdapObject &obj) {
183 slotLDAPResult(client, obj);
184 });
185 q->connect(ldapClient, &KLDAPCore::LdapClient::done, q, [this]() {
186 slotLDAPDone();
187 });
188 q->connect(ldapClient, qOverload<const QString &>(&KLDAPCore::LdapClient::error), q, [this](const QString &str) {
189 slotLDAPError(str);
190 });
191
192 mClients.append(ldapClient);
193 }
194
195 q->connect(&mDataTimer, &QTimer::timeout, q, [this]() {
196 slotDataTimer();
197 });
198 }
199 mConfigFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kabldaprc");
200 KDirWatch::self()->addFile(mConfigFile);
201}
202
203void LdapClientSearch::LdapClientSearchPrivate::slotFileChanged(const QString &file)
204{
205 if (file == mConfigFile) {
206 readConfig();
207 }
208}
209
211{
212 if (d->mNoLDAPLookup) {
214 return;
215 }
216
217 cancelSearch();
218
219 int pos = txt.indexOf(QLatin1Char('\"'));
220 if (pos >= 0) {
221 ++pos;
222 const int pos2 = txt.indexOf(QLatin1Char('\"'), pos);
223 if (pos2 >= 0) {
224 d->mSearchText = txt.mid(pos, pos2 - pos);
225 } else {
226 d->mSearchText = txt.mid(pos);
227 }
228 } else {
229 d->mSearchText = txt;
230 }
231
232 const QString filter = d->mFilter.arg(d->mSearchText);
233
234 QList<KLDAPCore::LdapClient *>::Iterator it(d->mClients.begin());
235 const QList<KLDAPCore::LdapClient *>::Iterator end(d->mClients.end());
236 for (; it != end; ++it) {
237 (*it)->startQuery(filter);
238 qCDebug(LDAPCLIENT_CORE_LOG) << "LdapClientSearch::startSearch()" << filter;
239 ++d->mActiveClients;
240 }
241}
242
244{
245 QList<KLDAPCore::LdapClient *>::Iterator it(d->mClients.begin());
246 const QList<KLDAPCore::LdapClient *>::Iterator end(d->mClients.end());
247 for (; it != end; ++it) {
248 (*it)->cancelQuery();
249 }
250
251 d->mActiveClients = 0;
252 d->mResults.clear();
253}
254
255void LdapClientSearch::LdapClientSearchPrivate::slotLDAPResult(const KLDAPCore::LdapClient &client, const KLDAPCore::LdapObject &obj)
256{
257 LdapResultObject result;
258 result.client = &client;
259 result.object = obj;
260
261 mResults.append(result);
262 if (!mDataTimer.isActive()) {
263 mDataTimer.setSingleShot(true);
264 mDataTimer.start(500);
265 }
266}
267
268void LdapClientSearch::LdapClientSearchPrivate::slotLDAPError(const QString &)
269{
270 slotLDAPDone();
271}
272
273void LdapClientSearch::LdapClientSearchPrivate::slotLDAPDone()
274{
275 if (--mActiveClients > 0) {
276 return;
277 }
278
279 finish();
280}
281
282void LdapClientSearch::LdapClientSearchPrivate::slotDataTimer()
283{
284 QStringList lst;
285 LdapResult::List reslist;
286
287 Q_EMIT q->searchData(mResults);
288
289 makeSearchData(lst, reslist);
290 if (!lst.isEmpty()) {
291 Q_EMIT q->searchData(lst);
292 }
293 if (!reslist.isEmpty()) {
294 Q_EMIT q->searchData(reslist);
295 }
296}
297
298void LdapClientSearch::LdapClientSearchPrivate::finish()
299{
300 mDataTimer.stop();
301
302 slotDataTimer(); // Q_EMIT final bunch of data
303 Q_EMIT q->searchDone();
304}
305
306void LdapClientSearch::LdapClientSearchPrivate::makeSearchData(QStringList &ret, KLDAPCore::LdapResult::List &resList)
307{
308 LdapResultObject::List::ConstIterator it1(mResults.constBegin());
309 const LdapResultObject::List::ConstIterator end1(mResults.constEnd());
310 for (; it1 != end1; ++it1) {
313 QString givenname;
314 QString sn;
315 QStringList mails;
316 bool isDistributionList = false;
317 bool wasCN = false;
318 bool wasDC = false;
319
320 // qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData()";
321
323 for (it2 = (*it1).object.attributes().constBegin(); it2 != (*it1).object.attributes().constEnd(); ++it2) {
324 QByteArray val = (*it2).first();
325 int len = val.size();
326 if (len > 0 && '\0' == val[len - 1]) {
327 --len;
328 }
329 const QString tmp = QString::fromUtf8(val.constData(), len);
330 // qCDebug(LDAPCLIENT_LOG) <<" key: \"" << it2.key() <<"\" value: \"" << tmp <<"\"";
331 if (it2.key() == "cn"_L1) {
332 name = tmp;
333 if (mail.isEmpty()) {
334 mail = tmp;
335 } else {
336 if (wasCN) {
337 mail.prepend(QLatin1Char('.'));
338 } else {
339 mail.prepend(QLatin1Char('@'));
340 }
341 mail.prepend(tmp);
342 }
343 wasCN = true;
344 } else if (it2.key() == "dc"_L1) {
345 if (mail.isEmpty()) {
346 mail = tmp;
347 } else {
348 if (wasDC) {
349 mail.append(QLatin1Char('.'));
350 } else {
351 mail.append(QLatin1Char('@'));
352 }
353 mail.append(tmp);
354 }
355 wasDC = true;
356 } else if (it2.key() == "mail"_L1) {
357 mail = tmp;
359 for (; it3 != it2.value().constEnd(); ++it3) {
360 mails.append(QString::fromUtf8((*it3).data(), (*it3).size()));
361 }
362 } else if (it2.key() == "givenName"_L1) {
363 givenname = tmp;
364 } else if (it2.key() == "sn"_L1) {
365 sn = tmp;
366 } else if (it2.key() == "objectClass"_L1 && (tmp == "groupOfNames"_L1 || tmp == "kolabGroupOfNames"_L1)) {
367 isDistributionList = true;
368 }
369 }
370
371 if (mails.isEmpty()) {
372 if (!mail.isEmpty()) {
373 mails.append(mail);
374 }
375 if (isDistributionList) {
376 // qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData() found a list:" << name;
377 ret.append(name);
378 // following lines commented out for bugfixing kolab issue #177:
379 //
380 // Unlike we thought previously we may NOT append the server name here.
381 //
382 // The right server is found by the SMTP server instead: Kolab users
383 // must use the correct SMTP server, by definition.
384 //
385 // mail = (*it1).client->base().simplified();
386 // mail.replace( ",dc=", ".", false );
387 // if( mail.startsWith("dc=", false) )
388 // mail.remove(0, 3);
389 // mail.prepend( '@' );
390 // mail.prepend( name );
391 // mail = name;
392 } else {
393 continue; // nothing, bad entry
394 }
395 } else if (name.isEmpty()) {
396 ret.append(mail);
397 } else {
398 ret.append(QStringLiteral("%1 <%2>").arg(name, mail));
399 }
400
401 LdapResult sr;
402 sr.dn = (*it1).object.dn();
403 sr.clientNumber = (*it1).client->clientNumber();
404 sr.completionWeight = (*it1).client->completionWeight();
405 sr.name = name;
406 sr.email = mails;
407 resList.append(sr);
408 }
409
410 mResults.clear();
411}
412
414{
415 return !d->mNoLDAPLookup;
416}
417
418#include "moc_ldapclientsearch.cpp"
QString readEntry(const char *key, const char *aDefault=nullptr) const
void addFile(const QString &file)
static KDirWatch * self()
void dirty(const QString &path)
LdapClientSearch(QObject *parent=nullptr)
Creates a new ldap client search object.
QString filter() const
Returns the filter for the Query.
void updateCompletionWeights()
Updates the completion weights for the configured LDAP clients from the configuration file.
void setAttributes(const QStringList &)
Sets the attributes, that are queried the LDAP Server.
QList< KLDAPCore::LdapClient * > clients() const
Returns the list of configured LDAP clients.
void cancelSearch()
Cancels the currently running search query.
bool isAvailable() const
Returns whether LDAP search is possible at all.
~LdapClientSearch() override
Destroys the ldap client search object.
void searchDone()
This signal is emitted whenever the lookup is complete or the user has canceled the query.
QStringList attributes() const
Returns the attributes, that are queried the LDAP Server.
void setFilter(const QString &)
Sets the filter for the Query.
void startSearch(const QString &query)
Starts the LDAP search on all configured LDAP clients with the given search query.
An object that represents a configured LDAP server.
Definition ldapclient.h:29
void result(const KLDAPCore::LdapClient &client, const KLDAPCore::LdapObject &)
This signal is emitted once for each object that is returned from the query.
void error(const QString &message)
This signal is emitted in case of an error.
void setCompletionWeight(int weight)
Sets the completion weight of this client.
void done()
This signal is emitted when the query has finished.
This class represents an LDAP Object.
Definition ldapobject.h:31
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
QString name(StandardAction id)
QAction * mail(const QObject *recvr, const char *slot, QObject *parent)
const char * constData() const const
QByteArray first(qsizetype n) const const
qsizetype size() const const
void append(QList< T > &&value)
bool isEmpty() const const
ConstIterator
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
const_iterator constBegin() const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QueuedConnection
void timeout()
Describes the result returned by an LdapClientSearch query.
Describes the result returned by an LdapClientSearch query.
int clientNumber
The client the contact comes from (used for sorting in a ldap-only lookup).
QString name
The full name of the contact.
int completionWeight
The weight of the contact (used for sorting in a completion list).
QStringList email
The list of emails of the contact.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:14:23 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.