1 /*
2 * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
3 * Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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 <QDir>
17 #include <QFileDialog>
18 #include <QUrl>
19 #include <QTimer>
20 #include <QPushButton>
21 #include <QMessageBox>
22 #include <QSsl>
23 #include <QSslCertificate>
24 #include <QNetworkAccessManager>
25 #include <QPropertyAnimation>
26 #include <QGraphicsPixmapItem>
27 #include <QBuffer>
28
29 #include "QProgressIndicator.h"
30
31 #include "wizard/owncloudwizardcommon.h"
32 #include "wizard/owncloudsetuppage.h"
33 #include "wizard/owncloudconnectionmethoddialog.h"
34 #include "wizard/slideshow.h"
35 #include "theme.h"
36 #include "account.h"
37 #include "config.h"
38
39 namespace OCC {
40
OwncloudSetupPage(QWidget * parent)41 OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
42 : QWizardPage()
43 , _progressIndi(new QProgressIndicator(this))
44 , _ocWizard(qobject_cast<OwncloudWizard *>(parent))
45 {
46 _ui.setupUi(this);
47
48 setupServerAddressDescriptionLabel();
49
50 Theme *theme = Theme::instance();
51 if (theme->overrideServerUrl().isEmpty()) {
52 _ui.leUrl->setPostfix(theme->wizardUrlPostfix());
53 _ui.leUrl->setPlaceholderText(theme->wizardUrlHint());
54 } else if (Theme::instance()->forceOverrideServerUrl()) {
55 _ui.leUrl->setEnabled(false);
56 }
57
58
59 registerField(QLatin1String("OCUrl*"), _ui.leUrl);
60
61 auto sizePolicy = _progressIndi->sizePolicy();
62 sizePolicy.setRetainSizeWhenHidden(true);
63 _progressIndi->setSizePolicy(sizePolicy);
64
65 _ui.progressLayout->addWidget(_progressIndi);
66 stopSpinner();
67
68 setupCustomization();
69
70 slotUrlChanged(QLatin1String("")); // don't jitter UI
71 connect(_ui.leUrl, &QLineEdit::textChanged, this, &OwncloudSetupPage::slotUrlChanged);
72 connect(_ui.leUrl, &QLineEdit::editingFinished, this, &OwncloudSetupPage::slotUrlEditFinished);
73
74 addCertDial = new AddCertificateDialog(this);
75 connect(addCertDial, &QDialog::accepted, this, &OwncloudSetupPage::slotCertificateAccepted);
76 }
77
setLogo()78 void OwncloudSetupPage::setLogo()
79 {
80 _ui.logoLabel->setPixmap(Theme::instance()->wizardApplicationLogo());
81 }
82
setupServerAddressDescriptionLabel()83 void OwncloudSetupPage::setupServerAddressDescriptionLabel()
84 {
85 const auto appName = Theme::instance()->appNameGUI();
86 _ui.serverAddressDescriptionLabel->setText(tr("The link to your %1 web interface when you open it in the browser.", "%1 will be replaced with the application name").arg(appName));
87 }
88
setServerUrl(const QString & newUrl)89 void OwncloudSetupPage::setServerUrl(const QString &newUrl)
90 {
91 _ocWizard->setRegistration(false);
92 _oCUrl = newUrl;
93 if (_oCUrl.isEmpty()) {
94 _ui.leUrl->clear();
95 return;
96 }
97
98 _ui.leUrl->setText(_oCUrl);
99 }
100
setupCustomization()101 void OwncloudSetupPage::setupCustomization()
102 {
103 // set defaults for the customize labels.
104 _ui.topLabel->hide();
105 _ui.bottomLabel->hide();
106
107 Theme *theme = Theme::instance();
108 QVariant variant = theme->customMedia(Theme::oCSetupTop);
109 if (!variant.isNull()) {
110 WizardCommon::setupCustomMedia(variant, _ui.topLabel);
111 }
112
113 variant = theme->customMedia(Theme::oCSetupBottom);
114 WizardCommon::setupCustomMedia(variant, _ui.bottomLabel);
115
116 auto leUrlPalette = _ui.leUrl->palette();
117 leUrlPalette.setColor(QPalette::Text, Qt::black);
118 leUrlPalette.setColor(QPalette::Base, Qt::white);
119 _ui.leUrl->setPalette(leUrlPalette);
120 }
121
122 // slot hit from textChanged of the url entry field.
slotUrlChanged(const QString & url)123 void OwncloudSetupPage::slotUrlChanged(const QString &url)
124 {
125 // Need to set next button as default button here because
126 // otherwise the on OSX the next button does not stay the default
127 // button
128 auto nextButton = qobject_cast<QPushButton *>(_ocWizard->button(QWizard::NextButton));
129 if (nextButton) {
130 nextButton->setDefault(true);
131 }
132
133 _authTypeKnown = false;
134
135 QString newUrl = url;
136 if (url.endsWith("index.php")) {
137 newUrl.chop(9);
138 }
139 if (_ocWizard && _ocWizard->account()) {
140 QString webDavPath = _ocWizard->account()->davPath();
141 if (url.endsWith(webDavPath)) {
142 newUrl.chop(webDavPath.length());
143 }
144 if (webDavPath.endsWith(QLatin1Char('/'))) {
145 webDavPath.chop(1); // cut off the slash
146 if (url.endsWith(webDavPath)) {
147 newUrl.chop(webDavPath.length());
148 }
149 }
150 }
151 if (newUrl != url) {
152 _ui.leUrl->setText(newUrl);
153 }
154 }
155
slotUrlEditFinished()156 void OwncloudSetupPage::slotUrlEditFinished()
157 {
158 QString url = _ui.leUrl->fullText();
159 if (QUrl(url).isRelative() && !url.isEmpty()) {
160 // no scheme defined, set one
161 url.prepend("https://");
162 _ui.leUrl->setFullText(url);
163 }
164 }
165
isComplete() const166 bool OwncloudSetupPage::isComplete() const
167 {
168 return !_ui.leUrl->text().isEmpty() && !_checking;
169 }
170
initializePage()171 void OwncloudSetupPage::initializePage()
172 {
173 customizeStyle();
174
175 WizardCommon::initErrorLabel(_ui.errorLabel);
176
177 _authTypeKnown = false;
178 _checking = false;
179
180 QAbstractButton *nextButton = wizard()->button(QWizard::NextButton);
181 auto *pushButton = qobject_cast<QPushButton *>(nextButton);
182 if (pushButton) {
183 pushButton->setDefault(true);
184 }
185
186 _ui.leUrl->setFocus();
187
188 const auto isServerUrlOverridden = !Theme::instance()->overrideServerUrl().isEmpty();
189 if (isServerUrlOverridden && !Theme::instance()->forceOverrideServerUrl()) {
190 // If the url is overwritten but we don't force to use that url
191 // Just focus the next button to let the user navigate quicker
192 if (nextButton) {
193 nextButton->setFocus();
194 }
195 } else if (isServerUrlOverridden) {
196 // If the overwritten url is not empty and we force this overwritten url
197 // we just check the server type and switch to next page
198 // immediately.
199 setCommitPage(true);
200 // Hack: setCommitPage() changes caption, but after an error this page could still be visible
201 setButtonText(QWizard::CommitButton, tr("&Next >"));
202 validatePage();
203 setVisible(false);
204 }
205 }
206
nextId() const207 int OwncloudSetupPage::nextId() const
208 {
209 switch (_authType) {
210 case DetermineAuthTypeJob::Basic:
211 return WizardCommon::Page_HttpCreds;
212 case DetermineAuthTypeJob::OAuth:
213 return WizardCommon::Page_OAuthCreds;
214 case DetermineAuthTypeJob::LoginFlowV2:
215 return WizardCommon::Page_Flow2AuthCreds;
216 #ifdef WITH_WEBENGINE
217 case DetermineAuthTypeJob::WebViewFlow:
218 return WizardCommon::Page_WebView;
219 #endif // WITH_WEBENGINE
220 case DetermineAuthTypeJob::NoAuthType:
221 return WizardCommon::Page_HttpCreds;
222 }
223 Q_UNREACHABLE();
224 }
225
url() const226 QString OwncloudSetupPage::url() const
227 {
228 QString url = _ui.leUrl->fullText().simplified();
229 return url;
230 }
231
validatePage()232 bool OwncloudSetupPage::validatePage()
233 {
234 if (!_authTypeKnown) {
235 slotUrlEditFinished();
236 QString u = url();
237 QUrl qurl(u);
238 if (!qurl.isValid() || qurl.host().isEmpty()) {
239 setErrorString(tr("Server address does not seem to be valid"), false);
240 return false;
241 }
242
243 setErrorString(QString(), false);
244 _checking = true;
245 startSpinner();
246 emit completeChanged();
247
248 emit determineAuthType(u);
249 return false;
250 } else {
251 // connecting is running
252 stopSpinner();
253 _checking = false;
254 emit completeChanged();
255 return true;
256 }
257 }
258
setAuthType(DetermineAuthTypeJob::AuthType type)259 void OwncloudSetupPage::setAuthType(DetermineAuthTypeJob::AuthType type)
260 {
261 _authTypeKnown = true;
262 _authType = type;
263 stopSpinner();
264 }
265
setErrorString(const QString & err,bool retryHTTPonly)266 void OwncloudSetupPage::setErrorString(const QString &err, bool retryHTTPonly)
267 {
268 if (err.isEmpty()) {
269 _ui.errorLabel->setVisible(false);
270 } else {
271 if (retryHTTPonly) {
272 QUrl url(_ui.leUrl->fullText());
273 if (url.scheme() == "https") {
274 // Ask the user how to proceed when connecting to a https:// URL fails.
275 // It is possible that the server is secured with client-side TLS certificates,
276 // but that it has no way of informing the owncloud client that this is the case.
277
278 OwncloudConnectionMethodDialog dialog;
279 dialog.setUrl(url);
280 // FIXME: Synchronous dialogs are not so nice because of event loop recursion
281 int retVal = dialog.exec();
282
283 switch (retVal) {
284 case OwncloudConnectionMethodDialog::No_TLS: {
285 url.setScheme("http");
286 _ui.leUrl->setFullText(url.toString());
287 // skip ahead to next page, since the user would expect us to retry automatically
288 wizard()->next();
289 } break;
290 case OwncloudConnectionMethodDialog::Client_Side_TLS:
291 addCertDial->show();
292 break;
293 case OwncloudConnectionMethodDialog::Closed:
294 case OwncloudConnectionMethodDialog::Back:
295 default:
296 // No-op.
297 break;
298 }
299 }
300 }
301
302 _ui.errorLabel->setVisible(true);
303 _ui.errorLabel->setText(err);
304 }
305 _checking = false;
306 emit completeChanged();
307 stopSpinner();
308 }
309
startSpinner()310 void OwncloudSetupPage::startSpinner()
311 {
312 _ui.progressLayout->setEnabled(true);
313 _progressIndi->setVisible(true);
314 _progressIndi->startAnimation();
315 }
316
stopSpinner()317 void OwncloudSetupPage::stopSpinner()
318 {
319 _ui.progressLayout->setEnabled(false);
320 _progressIndi->setVisible(false);
321 _progressIndi->stopAnimation();
322 }
323
subjectInfoHelper(const QSslCertificate & cert,const QByteArray & qa)324 QString subjectInfoHelper(const QSslCertificate &cert, const QByteArray &qa)
325 {
326 return cert.subjectInfo(qa).join(QLatin1Char('/'));
327 }
328
329 //called during the validation of the client certificate.
slotCertificateAccepted()330 void OwncloudSetupPage::slotCertificateAccepted()
331 {
332 QFile certFile(addCertDial->getCertificatePath());
333 certFile.open(QFile::ReadOnly);
334 QByteArray certData = certFile.readAll();
335 QByteArray certPassword = addCertDial->getCertificatePasswd().toLocal8Bit();
336
337 QBuffer certDataBuffer(&certData);
338 certDataBuffer.open(QIODevice::ReadOnly);
339 if (QSslCertificate::importPkcs12(&certDataBuffer,
340 &_ocWizard->_clientSslKey, &_ocWizard->_clientSslCertificate,
341 &_ocWizard->_clientSslCaCertificates, certPassword)) {
342 _ocWizard->_clientCertBundle = certData;
343 _ocWizard->_clientCertPassword = certPassword;
344
345 addCertDial->reinit(); // FIXME: Why not just have this only created on use?
346
347 // The extracted SSL key and cert gets added to the QSslConfiguration in checkServer()
348 validatePage();
349 } else {
350 addCertDial->showErrorMessage(tr("Could not load certificate. Maybe wrong password?"));
351 addCertDial->show();
352 }
353 }
354
355 OwncloudSetupPage::~OwncloudSetupPage() = default;
356
slotStyleChanged()357 void OwncloudSetupPage::slotStyleChanged()
358 {
359 customizeStyle();
360 }
361
customizeStyle()362 void OwncloudSetupPage::customizeStyle()
363 {
364 setLogo();
365
366 if (_progressIndi) {
367 const auto isDarkBackground = Theme::isDarkColor(palette().window().color());
368 if (isDarkBackground) {
369 _progressIndi->setColor(Qt::white);
370 } else {
371 _progressIndi->setColor(Qt::black);
372 }
373 }
374
375
376 WizardCommon::customizeHintLabel(_ui.serverAddressDescriptionLabel);
377 }
378
379 } // namespace OCC
380