LeechCraft  0.6.70-15082-g543737046d
Modular cross-platform feature rich live environment.
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>
19 #include <util/sll/queuemanager.h>
20 #include <util/sll/urloperator.h>
21 #include <util/sll/either.h>
22 #include <util/sll/qtutil.h>
23 #include <util/xpc/util.h>
24 #include <util/threads/futures.h>
27 #include <interfaces/iwebbrowser.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 
72  VkAuthManager::~VkAuthManager () = default;
73 
74  bool VkAuthManager::IsAuthenticated () const
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  {
121  iface.reportStarted ();
122 
123  if (SilentMode_ && !IsAuthenticated ())
124  ReportFutureResult (iface, AuthKeyError_t { SilentMode {} });
125  else
126  {
127  connect (this,
129  [this, iface] () mutable { ReportFutureResult (iface, Token_); });
130  GetAuthKey ();
131  }
132 
133  return iface.future ();
134  }
135 
136  auto VkAuthManager::ManageQueue (VkAuthManager::RequestQueue_ptr queue) -> ScheduleGuard_t
137  {
138  if (!Queue_)
139  {
140  qWarning () << Q_FUNC_INFO
141  << "cannot manage request queue if queue manager wasn't set";
142  return {};
143  }
144 
145  ManagedQueues_ << queue;
146 
147  return Util::MakeScopeGuard ([this, queue] { ManagedQueues_.removeAll (queue); });
148  }
149 
151  {
152  if (!Queue_)
153  {
154  qWarning () << Q_FUNC_INFO
155  << "cannot manage request queue if queue manager wasn't set";
156  return {};
157  }
158 
159  PrioManagedQueues_ << queue;
160 
161  return Util::MakeScopeGuard ([this, queue] { PrioManagedQueues_.removeAll (queue); });
162  }
163 
164  void VkAuthManager::SetSilentMode (bool silent)
165  {
166  SilentMode_ = silent;
167  }
168 
169  void VkAuthManager::InvokeQueues (const QString& token)
170  {
171  ScheduleTrack (token);
172 
173  for (auto queue : PrioManagedQueues_)
174  while (!queue->isEmpty ())
175  {
176  const auto& pair = queue->takeFirst ();
177  const auto& f = pair.first;
178  Queue_->Schedule ([f, token] { f (token); }, nullptr, pair.second);
179  }
180 
181  for (auto queue : ManagedQueues_)
182  while (!queue->isEmpty ())
183  {
184  const auto& f = queue->takeFirst ();
185  Queue_->Schedule ([f, token] { f (token); });
186  }
187  }
188 
189  void VkAuthManager::RequestURL (const QUrl& url)
190  {
191  qDebug () << Q_FUNC_INFO << url;
192  auto reply = AuthNAM_->get (QNetworkRequest (url));
193  connect (reply,
194  &QNetworkReply::finished,
195  this,
196  [this, reply] { HandleGotForm (reply); });
197  }
198 
199  void VkAuthManager::RequestAuthKey ()
200  {
201  if (IsRequestScheduled_ && ScheduleTimer_->isActive ())
202  ScheduleTimer_->stop ();
203 
204  if (IsRequesting_)
205  return;
206 
207  RequestURL (URL_);
208  IsRequesting_ = true;
209  }
210 
211  bool VkAuthManager::CheckReply (QUrl location)
212  {
213  if (location.path () != "/blank.html"_ql)
214  return CheckError (location);
215 
216  location = QUrl::fromEncoded (location.toEncoded ().replace ('#', '?'));
217  const QUrlQuery query { location };
218  Token_ = query.queryItemValue (QStringLiteral ("access_token"));
219  ValidFor_ = query.queryItemValue (QStringLiteral ("expires_in")).toInt ();
220  ReceivedAt_ = QDateTime::currentDateTime ();
221  qDebug () << Q_FUNC_INFO << Token_ << ValidFor_;
222  IsRequesting_ = false;
223 
224  InvokeQueues (Token_);
225  emit gotAuthKey (Token_);
226  emit justAuthenticated ();
227 
228  return true;
229  }
230 
231  bool VkAuthManager::CheckError (const QUrl& url)
232  {
233  if (url.path () != "/error"_ql)
234  return false;
235 
236  const auto errNum = QUrlQuery { url }.queryItemValue (QStringLiteral ("err")).toInt ();
237 
238  IsRequesting_ = false;
239 
240  qWarning () << Q_FUNC_INFO
241  << "got error"
242  << errNum;
243  if (errNum == 2)
244  {
245  ClearAuthData ();
246 
247  RequestAuthKey ();
248  return true;
249  }
250 
251  const auto& e = Util::MakeNotification (QStringLiteral ("VK.com"),
252  tr ("VK.com authentication for %1 failed because of error %2. "
253  "Report upstream please.")
254  .arg (AccountHR_)
255  .arg (errNum),
257  Proxy_->GetEntityManager ()->HandleEntity (e);
258 
259  return true;
260  }
261 
262  void VkAuthManager::ScheduleTrack (const QString& key)
263  {
264  if (HasTracked_)
265  return;
266 
267  if (!Proxy_->GetSettingsManager ()->property ("TrackVK").toBool ())
268  return;
269 
270  HasTracked_ = true;
271 
272  QUrl url { QStringLiteral ("https://api.vk.com/method/stats.trackVisitor") };
273  Util::UrlOperator { url }
274  (QStringLiteral ("access_token"), key);
275 
276  auto reply = AuthNAM_->get (QNetworkRequest { url });
277  connect (reply,
278  &QNetworkReply::finished,
279  reply,
280  &QNetworkReply::deleteLater);
281  }
282 
284  {
285  Cookies_->Load ({});
286  Token_.clear ();
287  ReceivedAt_ = QDateTime ();
288  ValidFor_ = 0;
289  }
290 
291  namespace
292  {
293  template<typename F>
294  class CloseEventFilter : public QObject
295  {
296  const F Handler_;
297  public:
298  CloseEventFilter (const F& handler, QObject *handlee)
299  : QObject { handlee }
300  , Handler_ { handler }
301  {
302  handlee->installEventFilter (this);
303  }
304 
305  bool eventFilter (QObject*, QEvent *event) override
306  {
307  if (event->type () == QEvent::Close)
308  Handler_ ();
309  return false;
310  }
311  };
312  }
313 
314  void VkAuthManager::Reauth ()
315  {
316  const auto& browsers = Proxy_->GetPluginsManager ()->GetAllCastableTo<IWebBrowser*> ();
317  if (browsers.isEmpty ())
318  {
319  const auto& e = Util::MakeNotification (tr ("VK.com authentication"),
320  tr ("Could not authenticate %1 since authentication requires a browser plugin. "
321  "Consider installing one like Poshuku.")
322  .arg (AccountHR_),
324  Proxy_->GetEntityManager ()->HandleEntity (e);
325  emit authCanceled ();
326  return;
327  }
328 
329  if (!Browser_)
330  Browser_ = browsers.value (0)->CreateWidget ();
331  auto viewWidget = Browser_->GetQWidget ();
332  viewWidget->setWindowTitle (tr ("VK.com authentication for %1")
333  .arg (AccountHR_));
334  viewWidget->setWindowFlags (Qt::Window);
335  viewWidget->resize (800, 600);
336  viewWidget->show ();
337 
338  Browser_->Load (URL_);
339  connect (viewWidget,
340  SIGNAL (urlChanged (const QUrl&)),
341  this,
342  SLOT (handleUrlChanged (const QUrl&)));
343 
344  new CloseEventFilter { [this] { emit authCanceled (); }, viewWidget };
345  }
346 
347  void VkAuthManager::HandleGotForm (QNetworkReply *reply)
348  {
349  reply->deleteLater ();
350 
351  if (reply->error () != QNetworkReply::NoError &&
352  reply->error () != QNetworkReply::AuthenticationRequiredError)
353  {
354  qWarning () << Q_FUNC_INFO
355  << reply->error ()
356  << reply->errorString ();
357 
358  IsRequesting_ = false;
359 
360  if (!IsRequestScheduled_)
361  {
362  IsRequestScheduled_ = true;
363  ScheduleTimer_->start (30000);
364  }
365 
366  return;
367  }
368 
369  const auto& location = reply->header (QNetworkRequest::LocationHeader).toUrl ();
370  if (location.isEmpty ())
371  {
372  Reauth ();
373  return;
374  }
375 
376  if (CheckReply (location))
377  return;
378 
379  RequestURL (location);
380  }
381 
382  void VkAuthManager::handleUrlChanged (const QUrl& url)
383  {
384  if (!CheckReply (url))
385  return;
386 
387  emit cookiesChanged (Cookies_->Save ());
388  Browser_.reset ();
389  }
390 }
ipluginsmanager.h
LC::Util::QueueManager::Schedule
void Schedule(std::function< void()> functor, QObject *dependent=nullptr, QueuePriority prio=QueuePriority::Normal)
Adds the given functor.
Definition: queuemanager.cpp:41
LC::Util::SvcAuth::VkAuthManager::AuthKeyError_t
std::variant< SilentMode > AuthKeyError_t
Definition: vkauthmanager.h:92
urloperator.h
LC::Util::SvcAuth::VkAuthManager::RequestQueue_ptr
RequestQueue_t * RequestQueue_ptr
Definition: vkauthmanager.h:69
queuemanager.h
LC::Util::SvcAuth::VkAuthManager::ManageQueue
ScheduleGuard_t ManageQueue(RequestQueue_ptr)
Definition: vkauthmanager.cpp:142
LC::Util::SvcAuth::VkAuthManager::IsAuthenticated
bool IsAuthenticated() const
Definition: vkauthmanager.cpp:80
LC::Util::SvcAuth::VkAuthManager::cookiesChanged
void cookiesChanged(const QByteArray &)
LC::Util::SvcAuth::VkAuthManager::GetAuthKey
void GetAuthKey()
Definition: vkauthmanager.cpp:103
futures.h
either.h
LC::Util::SvcAuth::VkAuthManager::~VkAuthManager
~VkAuthManager() override
vkauthmanager.h
LC::Util::SvcAuth::VkAuthManager::PrioRequestQueue_ptr
PrioRequestQueue_t * PrioRequestQueue_ptr
Definition: vkauthmanager.h:72
LC::Util::QueueManager
A simple scheduling manager for a queue of functors.
Definition: queuemanager.h:43
LC::Util::SvcAuth::VkAuthManager::ClearAuthData
void ClearAuthData()
Definition: vkauthmanager.cpp:289
LC::Util::SvcAuth::VkAuthManager::authCanceled
void authCanceled()
util.h
LC::Util::SvcAuth::VkAuthManager::Reauth
void Reauth()
Definition: vkauthmanager.cpp:320
iwebbrowser.h
ICoreProxy_ptr
std::shared_ptr< ICoreProxy > ICoreProxy_ptr
Definition: icoreproxy.h:181
qtutil.h
customcookiejar.h
LC::Util::SvcAuth::VkAuthManager::SetSilentMode
void SetSilentMode(bool)
Definition: vkauthmanager.cpp:170
LC::Util::MakeScopeGuard
detail::ScopeGuard< F > MakeScopeGuard(const F &f)
Returns an object performing passed function on scope exit.
Definition: util.h:148
LC::Util::SvcAuth::VkAuthManager::justAuthenticated
void justAuthenticated()
LC::Util::SvcAuth::VkAuthManager::UpdateScope
void UpdateScope(const QStringList &)
Definition: vkauthmanager.cpp:91
LC::Util::SvcAuth::VkAuthManager::VkAuthManager
VkAuthManager(const QString &accountName, const QString &clientId, const QStringList &scope, const QByteArray &cookies, const ICoreProxy_ptr &, QueueManager *=nullptr, QObject *=nullptr)
Definition: vkauthmanager.cpp:50
LC::Util::SvcAuth::VkAuthManager::gotAuthKey
void gotAuthKey(const QString &)
LC::Util::SvcAuth::VkAuthManager::HadAuthentication
bool HadAuthentication() const
Definition: vkauthmanager.cpp:86
LC::Util::UrlOperator
Manipulates query part of an QUrl object.
Definition: urloperator.h:60
QFuture
Definition: idownload.h:17
LC::Util::oral::sph::f
constexpr detail::ExprTree< detail::ExprType::LeafStaticPlaceholder, detail::MemberPtrs< Ptr > > f
Definition: oral.h:952
Window
unsigned long Window
Definition: xwrapper.h:26
QFutureInterface
Definition: consistencychecker.h:20
LC::Util::SvcAuth::VkAuthManager::GetAuthKeyFuture
QFuture< AuthKeyResult_t > GetAuthKeyFuture()
Definition: vkauthmanager.cpp:124
ientitymanager.h
LC::Util::SvcAuth
Definition: vkauthmanager.cpp:30
LC::Util::SvcAuth::VkAuthManager::SilentMode
Definition: vkauthmanager.h:91
IWebBrowser
Base class for plugins that provide a web browser.
Definition: iwebbrowser.h:83
LC::Util::MakeNotification
Entity MakeNotification(const QString &header, const QString &text, Priority priority)
An utility function to make a Entity with notification.
Definition: util.cpp:95
LC::Util::Close
@ Close
Definition: winflags.h:52
LC::Priority::Critical
@ Critical