1/*
2 * Copyright (C) by Klaas Freitag <freitag@kde.org>
3 * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
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 2 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, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16#include <QInputDialog>
17#include <QLabel>
18#include <QDesktopServices>
19#include <QNetworkReply>
20#include <QTimer>
21#include <QBuffer>
22#include "creds/httpcredentialsgui.h"
23#include "theme.h"
24#include "account.h"
25#include "networkjobs.h"
26#include <QMessageBox>
27#include "common/asserts.h"
28
29using namespace QKeychain;
30
31namespace OCC {
32
33Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "sync.credentials.http.gui", QtInfoMsg)
34
35void HttpCredentialsGui::askFromUser()
36{
37 // This function can be called from AccountState::slotInvalidCredentials,
38 // which (indirectly, through HttpCredentials::invalidateToken) schedules
39 // a cache wipe of the qnam. We can only execute a network job again once
40 // the cache has been cleared, otherwise we'd interfere with the job.
41 QTimer::singleShot(100, this, &HttpCredentialsGui::askFromUserAsync);
42}
43
44void HttpCredentialsGui::askFromUserAsync()
45{
46 // First, we will check what kind of auth we need.
47 auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
48 QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
49 if (type == DetermineAuthTypeJob::OAuth) {
50 _asyncAuth.reset(new OAuth(_account, this));
51 _asyncAuth->_expectedUser = _user;
52 connect(_asyncAuth.data(), &OAuth::result,
53 this, &HttpCredentialsGui::asyncAuthResult);
54 connect(_asyncAuth.data(), &OAuth::destroyed,
55 this, &HttpCredentialsGui::authorisationLinkChanged);
56 _asyncAuth->start();
57 emit authorisationLinkChanged();
58 } else if (type == DetermineAuthTypeJob::Basic) {
59 // Show the dialog
60 // We will re-enter the event loop, so better wait the next iteration
61 QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
62 } else {
63 // Shibboleth?
64 qCWarning(lcHttpCredentialsGui) << "Bad http auth type:" << type;
65 emit asked();
66 }
67 });
68 job->start();
69}
70
71void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user,
72 const QString &token, const QString &refreshToken)
73{
74 switch (r) {
75 case OAuth::NotSupported:
76 // We will re-enter the event loop, so better wait the next iteration
77 QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
78 _asyncAuth.reset(0);
79 return;
80 case OAuth::Error:
81 _asyncAuth.reset(0);
82 emit asked();
83 return;
84 case OAuth::LoggedIn:
85 break;
86 }
87
88 ASSERT(_user == user); // ensured by _asyncAuth
89
90 _password = token;
91 _refreshToken = refreshToken;
92 _ready = true;
93 persist();
94 _asyncAuth.reset(0);
95 emit asked();
96}
97
98void HttpCredentialsGui::showDialog()
99{
100 QString msg = tr("Please enter %1 password:<br>"
101 "<br>"
102 "User: %2<br>"
103 "Account: %3<br>")
104 .arg(Utility::escape(Theme::instance()->appNameGUI()),
105 Utility::escape(_user),
106 Utility::escape(_account->displayName()));
107
108 QString reqTxt = requestAppPasswordText(_account);
109 if (!reqTxt.isEmpty()) {
110 msg += QLatin1String("<br>") + reqTxt + QLatin1String("<br>");
111 }
112 if (!_fetchErrorString.isEmpty()) {
113 msg += QLatin1String("<br>")
114 + tr("Reading from keychain failed with error: '%1'")
115 .arg(Utility::escape(_fetchErrorString))
116 + QLatin1String("<br>");
117 }
118
119 QInputDialog dialog;
120 dialog.setWindowTitle(tr("Enter Password"));
121 dialog.setLabelText(msg);
122 dialog.setTextValue(_previousPassword);
123 dialog.setTextEchoMode(QLineEdit::Password);
124 if (QLabel *dialogLabel = dialog.findChild<QLabel *>()) {
125 dialogLabel->setOpenExternalLinks(true);
126 dialogLabel->setTextFormat(Qt::RichText);
127 }
128
129 bool ok = dialog.exec();
130 if (ok) {
131 _password = dialog.textValue();
132 _refreshToken.clear();
133 _ready = true;
134 persist();
135 }
136 emit asked();
137}
138
139QString HttpCredentialsGui::requestAppPasswordText(const Account *account)
140{
141 int version = account->serverVersionInt();
142 QString path;
143
144 // Version may not be available before login on new servers!
145 if (!version || version >= Account::makeServerVersion(10, 0, 0)) {
146 path = QLatin1String("/index.php/settings/personal?sectionid=security#apppasswords");
147 } else if (version >= Account::makeServerVersion(9, 1, 0)) {
148 path = QLatin1String("/index.php/settings/personal?section=apppasswords");
149 } else {
150 // Older server than 9.1 does not have the feature to request App Password
151 return QString();
152 }
153
154 auto baseUrl = account->url().toString();
155 if (baseUrl.endsWith('/'))
156 baseUrl.chop(1);
157 return tr("<a href=\"%1\">Click here</a> to request an app password from the web interface.")
158 .arg(baseUrl + path);
159}
160} // namespace OCC
161