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