1 /*
2 * Copyright (C) 2008-2021 The QXmpp developers
3 *
4 * Author:
5 * Linus Jahn
6 *
7 * Source:
8 * https://github.com/qxmpp-project/qxmpp
9 *
10 * This file is a part of QXmpp library.
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 */
23
24 #include "QXmppAttentionManager.h"
25
26 // Qt
27 #include <QTimer>
28 // QXmpp
29 #include "QXmppClient.h"
30 #include "QXmppConstants_p.h"
31 #include "QXmppMessage.h"
32 #include "QXmppRosterManager.h"
33 #include "QXmppUtils.h"
34
35 ///
36 /// \class QXmppAttentionManager
37 ///
38 /// \brief The QXmppAttentionManager class manages attention requests as defined
39 /// by \xep{0224}: Attention.
40 ///
41 /// The manager also does some checks, including rate limiting and checking
42 /// whether the senders are trusted (aka. in the roster).
43 ///
44 /// Rate limited messages are not emitted on the normal attentionRequested()
45 /// signal and are sent on the attentionRequestRateLimited() signal instead.
46 ///
47 /// To use this manager you still need to instantiate it and register it with
48 /// the QXmppClient:
49 ///
50 /// \code
51 /// auto *attentionManager = new QXmppAttentionManager();
52 /// client->addExtension(attentionManager);
53 /// \endcode
54 ///
55 /// \since QXmpp 1.4
56 ///
57
58 ///
59 /// \fn QXmppAttentionManager::attentionRequested
60 ///
61 /// This signal is emitted when an attention request was received and it passed
62 /// the rate limiter.
63 ///
64 /// \param message The message with the attention request that was received.
65 /// \param isTrusted Whether the sender of the message exists in the user's
66 /// roster.
67 ///
68
69 ///
70 /// \fn QXmppAttentionManager::attentionRequestRateLimited
71 ///
72 /// This signal is emitted when an attention request did not pass the rate
73 /// limiter.
74 ///
75 /// \param message The message with the attention request that did not pass the
76 /// rate limiter.
77 ///
78
79 struct PastRequest {
80 QString bareJid;
81 QDateTime timestamp;
82 };
83
84 class QXmppAttentionManagerPrivate : public QObject
85 {
86 public:
87 QXmppAttentionManagerPrivate(QXmppAttentionManager *parent, quint8 allowedAttempts, QTime timeFrame);
88
89 bool checkRateLimit(const QString &bareJid);
90 void cleanUp();
91
92 quint8 allowedAttempts;
93 QTime allowedAttemptsTimeInterval;
94
95 // map of bare JIDs and time of previous requests
96 QVector<PastRequest> previousRequests;
97 QTimer *cleanUpTimer;
98 };
99
100 ///
101 /// \brief QXmppAttentionManager::QXmppAttentionManager
102 /// \param allowedAttempts
103 /// \param timeFrame
104 ///
QXmppAttentionManager(quint8 allowedAttempts,QTime timeFrame)105 QXmppAttentionManager::QXmppAttentionManager(quint8 allowedAttempts, QTime timeFrame)
106 : d(new QXmppAttentionManagerPrivate(this, allowedAttempts, timeFrame))
107 {
108 }
109
110 ///
111 /// Destructor
112 ///
~QXmppAttentionManager()113 QXmppAttentionManager::~QXmppAttentionManager()
114 {
115 delete d;
116 }
117
118 ///
119 /// Returns the \xep{0224}: Attention feature.
120 ///
discoveryFeatures() const121 QStringList QXmppAttentionManager::discoveryFeatures() const
122 {
123 return {
124 ns_attention
125 };
126 }
127
128 ///
129 /// Returns the number of allowed attempts of attentions from a bare JID in the
130 /// set time frame.
131 ///
132 /// \sa setAllowedAttempts()
133 /// \sa allowedAttemptsTimeInterval()
134 /// \sa setAllowedAttemptsTimeInterval()
135 ///
allowedAttempts() const136 quint8 QXmppAttentionManager::allowedAttempts() const
137 {
138 return d->allowedAttempts;
139 }
140
141 ///
142 /// Sets the number of allowed attempts of attentions from a bare JID in the set
143 /// time frame.
144 ///
145 /// \sa allowedAttempts()
146 /// \sa allowedAttemptsTimeInterval()
147 /// \sa setAllowedAttemptsTimeInterval()
148 ///
setAllowedAttempts(quint8 allowedAttempts)149 void QXmppAttentionManager::setAllowedAttempts(quint8 allowedAttempts)
150 {
151 d->allowedAttempts = allowedAttempts;
152 }
153
154 ///
155 /// Returns the time interval for the allowed attempts for rate limiting.
156 ///
157 /// \sa setAllowedAttemptsTimeInterval()
158 /// \sa allowedAttempts()
159 /// \sa setAllowedAttemptsTimeInterval()
160 ///
allowedAttemptsTimeInterval() const161 QTime QXmppAttentionManager::allowedAttemptsTimeInterval() const
162 {
163 return d->allowedAttemptsTimeInterval;
164 }
165
166 ///
167 /// Returns the time interval for the allowed attempts for rate limiting.
168 ///
169 /// \sa allowedAttemptsTimeInterval()
170 /// \sa allowedAttempts()
171 /// \sa setAllowedAttempts()
172 ///
setAllowedAttemptsTimeInterval(QTime interval)173 void QXmppAttentionManager::setAllowedAttemptsTimeInterval(QTime interval)
174 {
175 d->allowedAttemptsTimeInterval = interval;
176 }
177
178 ///
179 /// Sends a message of type chat with an attention request to the specified JID.
180 ///
181 /// \xep{0224} allows to include other elements with an attention request, but
182 /// the QXmppAttentionManager has no method for this purpose. However, such a
183 /// request can still be made manually.
184 ///
185 /// \param jid The address to which the request should be sent.
186 /// \param message The message body to include in the attention request.
187 ///
188 /// \return The ID of the sent message, if sent successfully, a null string
189 /// otherwise. In case an ID is returned, it also corresponds to the origin ID
190 /// of the message as defined by \xep{0359}: Unique and Stable Stanza IDs.
191 ///
requestAttention(const QString & jid,const QString & message)192 QString QXmppAttentionManager::requestAttention(const QString &jid, const QString &message)
193 {
194 QXmppMessage msg;
195 // The XEP recommends to use type headline, but the message body might still
196 // be of interest later, so we use type chat to allow caching.
197 msg.setType(QXmppMessage::Chat);
198 msg.setId(QXmppUtils::generateStanzaUuid());
199 msg.setOriginId(msg.id());
200 msg.setTo(jid);
201 msg.setBody(message);
202 msg.setAttentionRequested(true);
203
204 if (client()->sendPacket(msg))
205 return msg.id();
206 return {};
207 }
208
setClient(QXmppClient * client)209 void QXmppAttentionManager::setClient(QXmppClient *client)
210 {
211 QXmppClientExtension::setClient(client);
212
213 connect(client, &QXmppClient::messageReceived,
214 this, &QXmppAttentionManager::handleMessageReceived);
215 }
216
217 ///
218 /// Empty reimplementation
219 ///
handleStanza(const QDomElement &)220 bool QXmppAttentionManager::handleStanza(const QDomElement &)
221 {
222 return false;
223 }
224
handleMessageReceived(const QXmppMessage & message)225 void QXmppAttentionManager::handleMessageReceived(const QXmppMessage &message)
226 {
227 if (!message.isAttentionRequested() || !message.stamp().isNull())
228 return;
229
230 const QString bareJid = QXmppUtils::jidToBareJid(message.from());
231
232 // ignore messages from our own bare JID (e.g. carbon or IM-NG message)
233 if (bareJid == client()->configuration().jidBare())
234 return;
235
236 // check rate limit
237 if (!d->checkRateLimit(bareJid)) {
238 emit attentionRequestRateLimited(message);
239 return;
240 }
241
242 bool isTrusted = false;
243 if (auto *rosterManager = client()->findExtension<QXmppRosterManager>()) {
244 isTrusted = rosterManager->getRosterBareJids().contains(bareJid);
245 }
246
247 emit attentionRequested(message, isTrusted);
248 }
249
QXmppAttentionManagerPrivate(QXmppAttentionManager * parent,quint8 allowedAttempts,QTime timeFrame)250 QXmppAttentionManagerPrivate::QXmppAttentionManagerPrivate(QXmppAttentionManager *parent, quint8 allowedAttempts, QTime timeFrame)
251 : allowedAttempts(allowedAttempts),
252 allowedAttemptsTimeInterval(timeFrame),
253 cleanUpTimer(new QTimer(parent))
254 {
255 QObject::connect(cleanUpTimer, &QTimer::timeout, [this]() {
256 cleanUp();
257 });
258 }
259
260 ///
261 /// Returns true if the request passes the rate limit.
262 ///
checkRateLimit(const QString & bareJid)263 bool QXmppAttentionManagerPrivate::checkRateLimit(const QString &bareJid)
264 {
265 // add to request to cache
266 previousRequests << PastRequest { bareJid, QDateTime::currentDateTimeUtc() };
267
268 // start timer to remove request again
269 if (!cleanUpTimer->isActive())
270 cleanUpTimer->start(allowedAttemptsTimeInterval.msecsSinceStartOfDay());
271
272 // check whether there are too many requests
273 int count = std::count_if(previousRequests.cbegin(), previousRequests.cend(), [=](const PastRequest &request) {
274 return request.bareJid == bareJid;
275 });
276 return count <= allowedAttempts;
277 }
278
279 ///
280 /// Removes the first entry and reschedules the timer to remove the next.
281 ///
cleanUp()282 void QXmppAttentionManagerPrivate::cleanUp()
283 {
284 previousRequests.removeFirst();
285
286 if (!previousRequests.isEmpty()) {
287 // reschedule timer for next removal
288 int next = allowedAttemptsTimeInterval.msecsSinceStartOfDay() -
289 previousRequests.first().timestamp.msecsTo(QDateTime::currentDateTimeUtc());
290
291 if (next < 1)
292 cleanUp();
293 else
294 cleanUpTimer->start(next);
295 }
296 }
297