source: SMSSender/frontend/gui/ui/importcontacts/pages/google_page_config.cpp @ 405:f056fa7fe8b4

Last change on this file since 405:f056fa7fe8b4 was 405:f056fa7fe8b4, checked in by Sämy Zehnder <saemy.zehnder@…>, 6 years ago
  • Switches to OAuth2 authentication for importing contacts from Google.
File size: 7.0 KB
Line 
1/*
2 smssender - A frontend for fast and easy SMS sending over different gateways.
3 Copyright (C) 2007-2014, gorrión. See http://smssender.gorrion.ch
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18#include "google_page_config.h"
19
20#include <boost/bind.hpp>
21#include <qjson/parser.h>
22
23#include <QDesktopServices>
24
25#include "common/network/snetworkhelper.h"
26#include "common/network/snetworkreply.h"
27#include "frontend/gui/ui/exceptionmessagebox.h"
28
29namespace UI {
30namespace GCI {
31
32namespace {
33static const QSet<QString> kRequiredScopes = QStringList(
34                { OAuth2::Scope::Contacts, OAuth2::Scope::Plus::Me }
35            ).toSet();
36} // namespace
37
38PageConfig::PageConfig(OAuth2::AccessToken *accessToken,
39                       Persistence::IMainStorage *mainStorage)
40    : QWizardPage()
41    , m_accessToken(accessToken)
42
43    , m_credentialStore(new Persistence::OAuth2::CredentialStore(mainStorage))
44    , m_authorizationFlow(new OAuth2::AuthorizationFlow(m_credentialStore.data()))
45{
46        ui.setupUi(this);
47
48    connect(m_authorizationFlow.data(), SIGNAL(accessTokenFetched(const OAuth2::AuthorizationFlow::AccessTokenFetchResult &)),
49            this, SLOT(accessTokenFetched(const OAuth2::AuthorizationFlow::AccessTokenFetchResult &)));
50
51    connect(ui.btn_newConnection, SIGNAL(clicked()),
52            this, SLOT(connectNewAccount()));
53    connect(ui.sel_existing, SIGNAL(currentIndexChanged(int)),
54            this, SLOT(dataChanged()));
55
56    ui.sel_existing->clear();
57    ui.sel_existing->hide();
58}
59
60void PageConfig::initializePage() {
61    // Starts fetching access tokens for the already paired accounts.
62    QStringList pairedAccounts =
63            m_credentialStore->pairedAccounts(kRequiredScopes);
64    foreach(const QString &pairedAccount, pairedAccounts) {
65        m_authorizationFlow->startFetchAccessToken(
66                    pairedAccount, kRequiredScopes,
67                    OAuth2::AuthorizationFlow::UrlToUserCallback());
68    }
69}
70
71void PageConfig::dataChanged() {
72    *m_accessToken =
73            m_pairedAccounts.value(ui.sel_existing->currentIndex()).accessToken;
74
75    emit completeChanged();
76}
77
78bool PageConfig::isComplete() const {
79    return ui.sel_existing->currentIndex() >= 0;
80}
81
82void PageConfig::connectNewAccount() {
83    m_authorizationFlow->startFetchAccessToken(
84                "", kRequiredScopes,
85                boost::bind(&PageConfig::showUrlToUser, this, _1));
86}
87
88void PageConfig::showUrlToUser(const QString &url) {
89    bool ok = QDesktopServices::openUrl(url);
90
91    QMessageBox msg(QMessageBox::Question, tr("Action required"), "",
92                    QMessageBox::Ok, this);
93    if (ok) {
94        msg.setText(tr("Please see your browser to grant smssender access to your Google account."));
95    } else {
96        msg.setText(tr("Please open the following link in your browser to grant smssender access to your Google account:\n<a href='%1'>%1</a>").arg(url));
97    }
98    msg.exec();
99}
100
101// We need access to the https://www.googleapis.com/auth/plus.me scope.
102bool PageConfig::fetchAccountInfo(const OAuth2::AccessToken &accessToken,
103                                  QString *name, QIcon *image) {
104    QScopedPointer<SNetworkHelper> http(new SNetworkHelper);
105    http->addTrustedCA(":/certs/Equifax_Secure_Certificate_Authority.crt");
106    http->defaultHeaders().insert("Authorization",
107                                  QString("%1 %2")
108                                      .arg(accessToken.tokenType)
109                                      .arg(accessToken.accessToken)
110                                      .toUtf8());
111    SNetworkReply reply;
112    try {
113        reply = http->syncGet(QString("https://www.googleapis.com/plus/v1/people/me?"
114                                      "fields=displayName,image/url"));
115    } catch (const EException &e) {
116        qWarning() << "Failed to load Google account information: "
117                   << e.chainedWhat();
118        return false;
119    }
120
121    // Parses the reply.
122    // Can be replaced by QJson in Qt5.
123    QJson::Parser parser;
124    bool ok;
125    QByteArray data = reply->readAll();
126    QVariantMap result = parser.parse(data, &ok).toMap();
127    if (!ok) {
128        return false;
129    }
130
131    // Reads out the account data.
132    *name = result["displayName"].toString();
133    QString imageUrl = result["image"].toMap()["url"].toString();
134    if (name->isEmpty() || imageUrl.isEmpty()) {
135        return false;
136    }
137
138    QByteArray imageData = http->syncGet(imageUrl)->readAll();
139    *image = QPixmap::fromImage(QImage::fromData(imageData));
140
141    return ok;
142}
143
144/**
145 * Gets called if either a connection already existed and we got a refreshed
146 * access token or the user initiated a new connection. In both cases, there
147 * does not yet exist an item in the selection box -> we fetch the required
148 * information from the account and create a new entry.
149 *
150 * @param account
151 * @param fetchResult
152 */
153void PageConfig::accessTokenFetched(
154        const OAuth2::AuthorizationFlow::AccessTokenFetchResult &fetchResult) {
155    try {
156        if (fetchResult.isError) {
157            throw QString("Could not fetch the access token: %2")
158                    .arg(fetchResult.errorMessage);
159        }
160
161        // Checks if the given account is already paired.
162        for (int i = 0; i < m_pairedAccounts.size(); ++i) {
163            Account &account = m_pairedAccounts[i];
164            if (account.id == fetchResult.accountId) {
165                account.accessToken = fetchResult.accessToken;
166                return;
167            }
168        }
169
170        // Creates a new account entry.
171        m_pairedAccounts.append(Account());
172        Account &pairedAccount = m_pairedAccounts.last();
173        pairedAccount.id = fetchResult.accountId;
174        pairedAccount.accessToken = fetchResult.accessToken;
175
176        // Fetches the account email and image.
177        bool ok = fetchAccountInfo(pairedAccount.accessToken, &pairedAccount.name,
178                                   &pairedAccount.image);
179        if (!ok) {
180            throw QString("Errors in the reply from Google.");
181        }
182
183        // Inserts the account into the paired list.
184        ui.sel_existing->addItem(pairedAccount.image, pairedAccount.name);
185        if (ui.sel_existing->currentIndex() < 0) {
186            ui.sel_existing->setCurrentIndex(0);
187        }
188        ui.sel_existing->show();
189    } catch (QString &error) {
190        ExceptionMessageBox msg(
191                    tr("An error occured while connecting to the Google account."),
192                    EException(error), this);
193        msg.exec();
194    }
195}
196
197} // namespace GCI
198} // namespace UI
Note: See TracBrowser for help on using the repository browser.