LeechCraft 0.6.70-19424-g027e980fd1
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
vkauthmanager.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * LeechCraft - modular cross-platform feature rich internet client.
3 * Copyright (C) 2006-2014 Georg Rudoy
4 *
5 * Distributed under the Boost Software License, Version 1.0.
6 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
7 **********************************************************************/
8
9#include "vkauthmanager.h"
10#include <QNetworkRequest>
11#include <QNetworkReply>
12#include <QNetworkCookie>
13#include <QtDebug>
14#include <QTimer>
15#include <QEvent>
16#include <QFuture>
17#include <QFutureInterface>
21#include <util/sll/either.h>
22#include <util/sll/qtutil.h>
23#include <util/xpc/util.h>
28#include <xmlsettingsdialog/basesettingsmanager.h>
29
31{
32 namespace
33 {
34 QUrl URLFromClientID (const QString& id, const QStringList& scope)
35 {
36 auto url = QUrl::fromEncoded ("https://oauth.vk.com/authorize?redirect_uri=http%3A%2F%2Foauth.vk.com%2Fblank.html&response_type=token&state=");
37 UrlOperator { url }
38 (QStringLiteral ("client_id"), id)
39 (QStringLiteral ("scope"), scope.join (','));
40 return url;
41 }
42 }
43
44 VkAuthManager::VkAuthManager (const QString& accName,
45 const QString& id, const QStringList& scope,
46 const QByteArray& cookies, const ICoreProxy_ptr& proxy,
47 QueueManager *queueMgr, QObject *parent)
48 : QObject (parent)
49 , Proxy_ (proxy)
50 , AccountHR_ (accName)
51 , AuthNAM_ (new QNetworkAccessManager (this))
52 , Cookies_ (new Util::CustomCookieJar (this))
53 , Queue_ (queueMgr)
54 , ID_ (id)
55 , URL_ (URLFromClientID (ID_, scope))
56 , ScheduleTimer_ (new QTimer (this))
57 {
58 AuthNAM_->setCookieJar (Cookies_);
59 Cookies_->Load (cookies);
60
61 ScheduleTimer_->setSingleShot (true);
62 connect (ScheduleTimer_,
63 &QTimer::timeout,
64 this,
65 [this]
66 {
67 IsRequestScheduled_ = false;
68 RequestAuthKey ();
69 });
70 }
71
73
75 {
76 return !Token_.isEmpty () &&
77 (!ValidFor_ || ReceivedAt_.secsTo (QDateTime::currentDateTime ()) < ValidFor_);
78 }
79
81 {
82 return !Token_.isEmpty () || !Cookies_->allCookies ().isEmpty ();
83 }
84
85 void VkAuthManager::UpdateScope (const QStringList& scope)
86 {
87 const auto& newUrl = URLFromClientID (ID_, scope);
88 if (URL_ == newUrl)
89 return;
90
91 URL_ = newUrl;
92 Token_.clear ();
93 ReceivedAt_ = QDateTime ();
94 ValidFor_ = 0;
95 }
96
98 {
99 if (!IsAuthenticated ())
100 {
101 if (!SilentMode_)
102 RequestAuthKey ();
103 else
104 {
105 for (const auto& queue : PrioManagedQueues_)
106 queue->clear ();
107 for (const auto& queue : ManagedQueues_)
108 queue->clear ();
109 }
110
111 return;
112 }
113
114 InvokeQueues (Token_);
115 emit gotAuthKey (Token_);
116 }
117
119 {
120 QFutureInterface<AuthKeyResult_t> iface;
121 iface.reportStarted ();
122
123 if (SilentMode_ && !IsAuthenticated ())
124 ReportFutureResult (iface, AuthKeyError_t { SilentMode {} });
125 else
126 {
127 connect (this,
129 this,
130 [this, iface] () mutable { ReportFutureResult (iface, Token_); });
131 GetAuthKey ();
132 }
133
134 return iface.future ();
135 }
136
138 {
139 if (!Queue_)
140 {
141 qWarning () << Q_FUNC_INFO
142 << "cannot manage request queue if queue manager wasn't set";
143 return {};
144 }
145
146 ManagedQueues_ << queue;
147
148 return Util::MakeScopeGuard ([this, queue] { ManagedQueues_.removeAll (queue); });
149 }
150
152 {
153 if (!Queue_)
154 {
155 qWarning () << Q_FUNC_INFO
156 << "cannot manage request queue if queue manager wasn't set";
157 return {};
158 }
159
160 PrioManagedQueues_ << queue;
161
162 return Util::MakeScopeGuard ([this, queue] { PrioManagedQueues_.removeAll (queue); });
163 }
164
166 {
167 SilentMode_ = silent;
168 }
169
170 void VkAuthManager::InvokeQueues (const QString& token)
171 {
172 ScheduleTrack (token);
173
174 for (auto queue : PrioManagedQueues_)
175 while (!queue->isEmpty ())
176 {
177 const auto& pair = queue->takeFirst ();
178 const auto& f = pair.first;
179 Queue_->Schedule ([f, token] { f (token); }, nullptr, pair.second);
180 }
181
182 for (auto queue : ManagedQueues_)
183 while (!queue->isEmpty ())
184 {
185 const auto& f = queue->takeFirst ();
186 Queue_->Schedule ([f, token] { f (token); });
187 }
188 }
189
190 void VkAuthManager::RequestURL (const QUrl& url)
191 {
192 qDebug () << Q_FUNC_INFO << url;
193 auto reply = AuthNAM_->get (QNetworkRequest (url));
194 connect (reply,
195 &QNetworkReply::finished,
196 this,
197 [this, reply] { HandleGotForm (reply); });
198 }
199
200 void VkAuthManager::RequestAuthKey ()
201 {
202 if (IsRequestScheduled_ && ScheduleTimer_->isActive ())
203 ScheduleTimer_->stop ();
204
205 if (IsRequesting_)
206 return;
207
208 RequestURL (URL_);
209 IsRequesting_ = true;
210 }
211
212 bool VkAuthManager::CheckReply (QUrl location)
213 {
214 if (location.path () != "/blank.html"_ql)
215 return CheckError (location);
216
217 location = QUrl::fromEncoded (location.toEncoded ().replace ('#', '?'));
218 const QUrlQuery query { location };
219 Token_ = query.queryItemValue (QStringLiteral ("access_token"));
220 ValidFor_ = query.queryItemValue (QStringLiteral ("expires_in")).toInt ();
221 ReceivedAt_ = QDateTime::currentDateTime ();
222 qDebug () << Q_FUNC_INFO << Token_ << ValidFor_;
223 IsRequesting_ = false;
224
225 InvokeQueues (Token_);
226 emit gotAuthKey (Token_);
227 emit justAuthenticated ();
228
229 return true;
230 }
231
232 bool VkAuthManager::CheckError (const QUrl& url)
233 {
234 if (url.path () != "/error"_ql)
235 return false;
236
237 const auto errNum = QUrlQuery { url }.queryItemValue (QStringLiteral ("err")).toInt ();
238
239 IsRequesting_ = false;
240
241 qWarning () << Q_FUNC_INFO
242 << "got error"
243 << errNum;
244 if (errNum == 2)
245 {
246 ClearAuthData ();
247
248 RequestAuthKey ();
249 return true;
250 }
251
252 const auto& e = Util::MakeNotification (QStringLiteral ("VK.com"),
253 tr ("VK.com authentication for %1 failed because of error %2. "
254 "Report upstream please.")
255 .arg (AccountHR_)
256 .arg (errNum),
258 Proxy_->GetEntityManager ()->HandleEntity (e);
259
260 return true;
261 }
262
263 void VkAuthManager::ScheduleTrack (const QString& key)
264 {
265 if (HasTracked_)
266 return;
267
268 if (!Proxy_->GetSettingsManager ()->property ("TrackVK").toBool ())
269 return;
270
271 HasTracked_ = true;
272
273 QUrl url { QStringLiteral ("https://api.vk.com/method/stats.trackVisitor") };
274 Util::UrlOperator { url }
275 (QStringLiteral ("access_token"), key);
276
277 auto reply = AuthNAM_->get (QNetworkRequest { url });
278 connect (reply,
279 &QNetworkReply::finished,
280 reply,
281 &QNetworkReply::deleteLater);
282 }
283
285 {
286 Cookies_->Load ({});
287 Token_.clear ();
288 ReceivedAt_ = QDateTime ();
289 ValidFor_ = 0;
290 }
291
292 namespace
293 {
294 template<typename F>
295 class CloseEventFilter : public QObject
296 {
297 const F Handler_;
298 public:
299 CloseEventFilter (const F& handler, QObject *handlee)
300 : QObject { handlee }
301 , Handler_ { handler }
302 {
303 handlee->installEventFilter (this);
304 }
305
306 bool eventFilter (QObject*, QEvent *event) override
307 {
308 if (event->type () == QEvent::Close)
309 Handler_ ();
310 return false;
311 }
312 };
313 }
314
316 {
317 const auto& browsers = Proxy_->GetPluginsManager ()->GetAllCastableTo<IWebBrowser*> ();
318 if (browsers.isEmpty ())
319 {
320 const auto& e = Util::MakeNotification (tr ("VK.com authentication"),
321 tr ("Could not authenticate %1 since authentication requires a browser plugin. "
322 "Consider installing one like Poshuku.")
323 .arg (AccountHR_),
325 Proxy_->GetEntityManager ()->HandleEntity (e);
326 emit authCanceled ();
327 return;
328 }
329
330 if (!Browser_)
331 Browser_ = browsers.value (0)->CreateWidget ();
332 auto viewWidget = Browser_->GetQWidget ();
333 viewWidget->setWindowTitle (tr ("VK.com authentication for %1")
334 .arg (AccountHR_));
335 viewWidget->setWindowFlags (Qt::Window);
336 viewWidget->resize (800, 600);
337 viewWidget->show ();
338
339 Browser_->Load (URL_);
340 connect (viewWidget,
341 SIGNAL (urlChanged (const QUrl&)),
342 this,
343 SLOT (handleUrlChanged (const QUrl&)));
344
345 new CloseEventFilter { [this] { emit authCanceled (); }, viewWidget };
346 }
347
348 void VkAuthManager::HandleGotForm (QNetworkReply *reply)
349 {
350 reply->deleteLater ();
351
352 if (reply->error () != QNetworkReply::NoError &&
353 reply->error () != QNetworkReply::AuthenticationRequiredError)
354 {
355 qWarning () << Q_FUNC_INFO
356 << reply->error ()
357 << reply->errorString ();
358
359 IsRequesting_ = false;
360
361 if (!IsRequestScheduled_)
362 {
363 IsRequestScheduled_ = true;
364 ScheduleTimer_->start (30000);
365 }
366
367 return;
368 }
369
370 const auto& location = reply->header (QNetworkRequest::LocationHeader).toUrl ();
371 if (location.isEmpty ())
372 {
373 Reauth ();
374 return;
375 }
376
377 if (CheckReply (location))
378 return;
379
380 RequestURL (location);
381 }
382
383 void VkAuthManager::handleUrlChanged (const QUrl& url)
384 {
385 if (!CheckReply (url))
386 return;
387
388 emit cookiesChanged (Cookies_->Save ());
389 Browser_.reset ();
390 }
391}
Base class for plugins that provide a web browser.
Definition iwebbrowser.h:84
virtual std::unique_ptr< IWebWidget > CreateWidget() const =0
Returns the IWebWidget for use in another modules of LeechCraft.
virtual QWidget * GetQWidget()=0
Returns the IWebWidget as a QWidget.
A simple scheduling manager for a queue of functors.
void Schedule(std::function< void()> functor, QObject *dependent=nullptr, QueuePriority prio=QueuePriority::Normal)
Adds the given functor.
Util::DefaultScopeGuard ScheduleGuard_t
ScheduleGuard_t ManageQueue(RequestQueue_ptr)
PrioRequestQueue_t * PrioRequestQueue_ptr
QFuture< AuthKeyResult_t > GetAuthKeyFuture()
void UpdateScope(const QStringList &)
std::variant< SilentMode > AuthKeyError_t
void cookiesChanged(const QByteArray &)
void gotAuthKey(const QString &)
VkAuthManager(const QString &accountName, const QString &clientId, const QStringList &scope, const QByteArray &cookies, const ICoreProxy_ptr &, QueueManager *=nullptr, QObject *=nullptr)
Manipulates query part of an QUrl object.
Definition urloperator.h:45
std::shared_ptr< ICoreProxy > ICoreProxy_ptr
Definition icoreproxy.h:177
detail::ScopeGuard< F > MakeScopeGuard(const F &f)
Returns an object performing passed function on scope exit.
Definition util.h:155
Entity MakeNotification(const QString &header, const QString &text, Priority priority)
An utility function to make a Entity with notification.
Definition util.cpp:95