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 "ldapclient_debug.h"
14#include "ldapclientsearchconfig.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 KLDAPWidgets;
34
35class Q_DECL_HIDDEN LdapClientSearch::LdapClientSearchPrivate
36{
37public:
38 LdapClientSearchPrivate(LdapClientSearch *qq)
39 : q(qq)
40 {
41 }
42
43 ~LdapClientSearchPrivate() = default;
44
45 void readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber);
46 void readConfig();
47 void finish();
48 void makeSearchData(QStringList &ret, LdapResult::List &resList);
49
50 void slotLDAPResult(const KLDAPWidgets::LdapClient &client, const KLDAPCore::LdapObject &);
51 void slotLDAPError(const QString &);
52 void slotLDAPDone();
53 void slotDataTimer();
54 void slotFileChanged(const QString &);
55 void init(const QStringList &attributes);
56
57 LdapClientSearch *const q;
58 QList<LdapClient *> mClients;
59 QStringList mAttributes;
60 QString mSearchText;
61 QString mFilter;
62 QTimer mDataTimer;
63 int mActiveClients = 0;
64 bool mNoLDAPLookup = false;
66 QString mConfigFile;
67};
68
69LdapClientSearch::LdapClientSearch(QObject *parent)
70 : QObject(parent)
71 , d(new LdapClientSearchPrivate(this))
72{
73 d->init(LdapClientSearch::defaultAttributes());
74}
75
76LdapClientSearch::LdapClientSearch(const QStringList &attr, QObject *parent)
77 : QObject(parent)
78 , d(new LdapClientSearchPrivate(this))
79{
80 d->init(attr);
81}
82
83LdapClientSearch::~LdapClientSearch() = default;
84
85void LdapClientSearch::LdapClientSearchPrivate::init(const QStringList &attributes)
86{
87 if (!KProtocolInfo::isKnownProtocol(QUrl(QStringLiteral("ldap://localhost")))) {
88 mNoLDAPLookup = true;
89 return;
90 }
91
92 mAttributes = attributes;
93
94 // Set the filter, to make sure old usage (before 4.14) of this object still works.
95 mFilter = QStringLiteral(
96 "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))"
97 "(|(cn=%1*)(mail=%1*)(givenName=%1*)(sn=%1*))");
98
99 readConfig();
100 q->connect(KDirWatch::self(), &KDirWatch::dirty, q, [this](const QString &filename) {
101 slotFileChanged(filename);
102 });
103}
104
105void LdapClientSearch::LdapClientSearchPrivate::readWeighForClient(LdapClient *client, const KConfigGroup &config, int clientNumber)
106{
107 const int completionWeight = config.readEntry(QStringLiteral("SelectedCompletionWeight%1").arg(clientNumber), -1);
108 if (completionWeight != -1) {
109 client->setCompletionWeight(completionWeight);
110 }
111}
112
113void LdapClientSearch::updateCompletionWeights()
114{
115 KConfigGroup config(KLDAPWidgets::LdapClientSearchConfig::config(), QStringLiteral("LDAP"));
116 for (int i = 0, total = d->mClients.size(); i < total; ++i) {
117 d->readWeighForClient(d->mClients[i], config, i);
118 }
119}
120
121QList<LdapClient *> LdapClientSearch::clients() const
122{
123 return d->mClients;
124}
125
126QString LdapClientSearch::filter() const
127{
128 return d->mFilter;
129}
130
131void LdapClientSearch::setFilter(const QString &filter)
132{
133 d->mFilter = filter;
134}
135
136QStringList LdapClientSearch::attributes() const
137{
138 return d->mAttributes;
139}
140
141void LdapClientSearch::setAttributes(const QStringList &attrs)
142{
143 if (attrs != d->mAttributes) {
144 d->mAttributes = attrs;
145 d->readConfig();
146 }
147}
148
149QStringList LdapClientSearch::defaultAttributes()
150{
151 const QStringList attr{QStringLiteral("cn"), QStringLiteral("mail"), QStringLiteral("givenname"), QStringLiteral("sn")};
152 return attr;
153}
154
155void LdapClientSearch::LdapClientSearchPrivate::readConfig()
156{
157 q->cancelSearch();
158 qDeleteAll(mClients);
159 mClients.clear();
160
161 // stolen from KAddressBook
162 KConfigGroup config(KLDAPWidgets::LdapClientSearchConfig::config(), QStringLiteral("LDAP"));
163 const int numHosts = config.readEntry("NumSelectedHosts", 0);
164 if (!numHosts) {
165 mNoLDAPLookup = true;
166 } else {
167 for (int j = 0; j < numHosts; ++j) {
168 auto ldapClient = new LdapClient(j, q);
169 auto job = new LdapSearchClientReadConfigServerJob;
170 job->setCurrentIndex(j);
171 job->setActive(true);
172 job->setConfig(config);
173 job->setLdapClient(ldapClient);
174 job->start();
175
176 mNoLDAPLookup = false;
177 readWeighForClient(ldapClient, config, j);
178
179 ldapClient->setAttributes(mAttributes);
180
181 q->connect(ldapClient, &LdapClient::result, q, [this](const LdapClient &client, const KLDAPCore::LdapObject &obj) {
182 slotLDAPResult(client, obj);
183 });
184 q->connect(ldapClient, &LdapClient::done, q, [this]() {
185 slotLDAPDone();
186 });
187 q->connect(ldapClient, qOverload<const QString &>(&LdapClient::error), q, [this](const QString &str) {
188 slotLDAPError(str);
189 });
190
191 mClients.append(ldapClient);
192 }
193
194 q->connect(&mDataTimer, &QTimer::timeout, q, [this]() {
195 slotDataTimer();
196 });
197 }
198 mConfigFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kabldaprc");
199 KDirWatch::self()->addFile(mConfigFile);
200}
201
202void LdapClientSearch::LdapClientSearchPrivate::slotFileChanged(const QString &file)
203{
204 if (file == mConfigFile) {
205 readConfig();
206 }
207}
208
209void LdapClientSearch::startSearch(const QString &txt)
210{
211 if (d->mNoLDAPLookup) {
212 QMetaObject::invokeMethod(this, &LdapClientSearch::searchDone, Qt::QueuedConnection);
213 return;
214 }
215
216 cancelSearch();
217
218 int pos = txt.indexOf(QLatin1Char('\"'));
219 if (pos >= 0) {
220 ++pos;
221 const int pos2 = txt.indexOf(QLatin1Char('\"'), pos);
222 if (pos2 >= 0) {
223 d->mSearchText = txt.mid(pos, pos2 - pos);
224 } else {
225 d->mSearchText = txt.mid(pos);
226 }
227 } else {
228 d->mSearchText = txt;
229 }
230
231 const QString filter = d->mFilter.arg(d->mSearchText);
232
233 QList<LdapClient *>::Iterator it(d->mClients.begin());
234 const QList<LdapClient *>::Iterator end(d->mClients.end());
235 for (; it != end; ++it) {
236 (*it)->startQuery(filter);
237 qCDebug(LDAPCLIENT_LOG) << "LdapClientSearch::startSearch()" << filter;
238 ++d->mActiveClients;
239 }
240}
241
242void LdapClientSearch::cancelSearch()
243{
244 QList<LdapClient *>::Iterator it(d->mClients.begin());
245 const QList<LdapClient *>::Iterator end(d->mClients.end());
246 for (; it != end; ++it) {
247 (*it)->cancelQuery();
248 }
249
250 d->mActiveClients = 0;
251 d->mResults.clear();
252}
253
254void LdapClientSearch::LdapClientSearchPrivate::slotLDAPResult(const LdapClient &client, const KLDAPCore::LdapObject &obj)
255{
256 LdapResultObject result;
257 result.client = &client;
258 result.object = obj;
259
260 mResults.append(result);
261 if (!mDataTimer.isActive()) {
262 mDataTimer.setSingleShot(true);
263 mDataTimer.start(500);
264 }
265}
266
267void LdapClientSearch::LdapClientSearchPrivate::slotLDAPError(const QString &)
268{
269 slotLDAPDone();
270}
271
272void LdapClientSearch::LdapClientSearchPrivate::slotLDAPDone()
273{
274 if (--mActiveClients > 0) {
275 return;
276 }
277
278 finish();
279}
280
281void LdapClientSearch::LdapClientSearchPrivate::slotDataTimer()
282{
283 QStringList lst;
284 LdapResult::List reslist;
285
286 Q_EMIT q->searchData(mResults);
287
288 makeSearchData(lst, reslist);
289 if (!lst.isEmpty()) {
290 Q_EMIT q->searchData(lst);
291 }
292 if (!reslist.isEmpty()) {
293 Q_EMIT q->searchData(reslist);
294 }
295}
296
297void LdapClientSearch::LdapClientSearchPrivate::finish()
298{
299 mDataTimer.stop();
300
301 slotDataTimer(); // Q_EMIT final bunch of data
302 Q_EMIT q->searchDone();
303}
304
305void LdapClientSearch::LdapClientSearchPrivate::makeSearchData(QStringList &ret, KLDAPWidgets::LdapResult::List &resList)
306{
307 LdapResultObject::List::ConstIterator it1(mResults.constBegin());
308 const LdapResultObject::List::ConstIterator end1(mResults.constEnd());
309 for (; it1 != end1; ++it1) {
312 QString givenname;
313 QString sn;
314 QStringList mails;
315 bool isDistributionList = false;
316 bool wasCN = false;
317 bool wasDC = false;
318
319 // qCDebug(LDAPCLIENT_LOG) <<"\n\nLdapClientSearch::makeSearchData()";
320
322 for (it2 = (*it1).object.attributes().constBegin(); it2 != (*it1).object.attributes().constEnd(); ++it2) {
323 QByteArray val = (*it2).first();
324 int len = val.size();
325 if (len > 0 && '\0' == val[len - 1]) {
326 --len;
327 }
328 const QString tmp = QString::fromUtf8(val.constData(), len);
329 // qCDebug(LDAPCLIENT_LOG) <<" key: \"" << it2.key() <<"\" value: \"" << tmp <<"\"";
330 if (it2.key() == QLatin1StringView("cn")) {
331 name = tmp;
332 if (mail.isEmpty()) {
333 mail = tmp;
334 } else {
335 if (wasCN) {
336 mail.prepend(QLatin1Char('.'));
337 } else {
338 mail.prepend(QLatin1Char('@'));
339 }
340 mail.prepend(tmp);
341 }
342 wasCN = true;
343 } else if (it2.key() == QLatin1StringView("dc")) {
344 if (mail.isEmpty()) {
345 mail = tmp;
346 } else {
347 if (wasDC) {
348 mail.append(QLatin1Char('.'));
349 } else {
350 mail.append(QLatin1Char('@'));
351 }
352 mail.append(tmp);
353 }
354 wasDC = true;
355 } else if (it2.key() == QLatin1StringView("mail")) {
356 mail = tmp;
357 KLDAPCore::LdapAttrValue::ConstIterator it3 = it2.value().constBegin();
358 for (; it3 != it2.value().constEnd(); ++it3) {
359 mails.append(QString::fromUtf8((*it3).data(), (*it3).size()));
360 }
361 } else if (it2.key() == QLatin1StringView("givenName")) {
362 givenname = tmp;
363 } else if (it2.key() == QLatin1StringView("sn")) {
364 sn = tmp;
365 } else if (it2.key() == QLatin1StringView("objectClass")
366 && (tmp == QLatin1StringView("groupOfNames") || tmp == QLatin1StringView("kolabGroupOfNames"))) {
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
413bool LdapClientSearch::isAvailable() const
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)
This class represents an LDAP Object.
Definition ldapobject.h:31
An object that represents a configured LDAP server.
Definition ldapclient.h:34
void setCompletionWeight(int weight)
Sets the completion weight of this client.
QAction * mail(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
QCA_EXPORT void init()
const char * constData() const const
QByteArray first(qsizetype n) const const
qsizetype size() const const
typedef ConstIterator
void append(QList< T > &&value)
bool isEmpty() const const
typedef ConstIterator
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QString writableLocation(StandardLocation type)
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
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
void timeout()
Describes the result returned by an LdapClientSearch query.
Describes the result returned by an LdapClientSearch query.
int completionWeight
The weight of the contact (used for sorting in a completion list).
QString name
The full name of the contact.
int clientNumber
The client the contact comes from (used for sorting in a ldap-only lookup).
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 Tue Mar 26 2024 11:18:34 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.