1 /*
2 SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "setupwizard.h"
8
9 #include <KAuthorized>
10 #include <KDAV/DavCollectionsMultiFetchJob>
11 #include <KDesktopFile>
12 #include <KFileUtils>
13 #include <KLocalizedString>
14 #include <KPasswordLineEdit>
15 #include <KService>
16 #include <QIcon>
17 #include <QLineEdit>
18 #include <QTextBrowser>
19
20 #include <QButtonGroup>
21 #include <QCheckBox>
22 #include <QComboBox>
23 #include <QFormLayout>
24 #include <QHBoxLayout>
25 #include <QLabel>
26 #include <QPushButton>
27 #include <QRadioButton>
28 #include <QRegularExpressionValidator>
29 #include <QStandardPaths>
30 #include <QUrl>
31
32 enum GroupwareServers {
33 Citadel,
34 DAVical,
35 eGroupware,
36 OpenGroupware,
37 ScalableOGo,
38 Scalix,
39 Zarafa,
40 Zimbra,
41 };
42
settingsToUrl(const QWizard * wizard,const QString & protocol)43 static QString settingsToUrl(const QWizard *wizard, const QString &protocol)
44 {
45 const QString desktopFilePath = wizard->property("providerDesktopFilePath").toString();
46 if (desktopFilePath.isEmpty()) {
47 return QString();
48 }
49
50 KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
51 if (!service) {
52 return QString();
53 }
54
55 const QStringList supportedProtocols = service->property(QStringLiteral("X-DavGroupware-SupportedProtocols")).toStringList();
56 if (!supportedProtocols.contains(protocol)) {
57 return QString();
58 }
59
60
61 const QString pathPropertyName(QStringLiteral("X-DavGroupware-") + protocol + QStringLiteral("Path"));
62 if (service->property(pathPropertyName).isNull()) {
63 return QString();
64 }
65
66 QString pathPattern;
67 pathPattern.append(service->property(pathPropertyName).toString() + QLatin1Char('/'));
68
69 QString username = wizard->field(QStringLiteral("credentialsUserName")).toString();
70 QString localPart(username);
71 localPart.remove(QRegularExpression(QStringLiteral("@.*$")));
72 pathPattern.replace(QLatin1String("$user$"), username);
73 pathPattern.replace(QLatin1String("$localpart$"), localPart);
74 QString providerName;
75 if (!service->property(QStringLiteral("X-DavGroupware-Provider")).isNull()) {
76 providerName = service->property(QStringLiteral("X-DavGroupware-Provider")).toString();
77 }
78 QString localPath = wizard->field(QStringLiteral("installationPath")).toString();
79 if (!localPath.isEmpty()) {
80 if (providerName == QLatin1String("davical")) {
81 if (!localPath.endsWith(QLatin1Char('/'))) {
82 pathPattern.append(localPath + QLatin1Char('/'));
83 } else {
84 pathPattern.append(localPath);
85 }
86 } else {
87 if (!localPath.startsWith(QLatin1Char('/'))) {
88 pathPattern.prepend(QLatin1Char('/') + localPath);
89 } else {
90 pathPattern.prepend(localPath);
91 }
92 }
93 }
94 QUrl url;
95
96 if (!wizard->property("usePredefinedProvider").isNull()) {
97 if (service->property(QStringLiteral("X-DavGroupware-ProviderUsesSSL")).toBool()) {
98 url.setScheme(QStringLiteral("https"));
99 } else {
100 url.setScheme(QStringLiteral("http"));
101 }
102
103 QString hostPropertyName(QStringLiteral("X-DavGroupware-") + protocol + QStringLiteral("Host"));
104 if (service->property(hostPropertyName).isNull()) {
105 return QString();
106 }
107
108 url.setHost(service->property(hostPropertyName).toString());
109 url.setPath(pathPattern);
110 } else {
111 if (wizard->field(QStringLiteral("connectionUseSecureConnection")).toBool()) {
112 url.setScheme(QStringLiteral("https"));
113 } else {
114 url.setScheme(QStringLiteral("http"));
115 }
116
117 const QString host = wizard->field(QStringLiteral("connectionHost")).toString();
118 if (host.isEmpty()) {
119 return QString();
120 }
121 const QStringList hostParts = host.split(QLatin1Char(':'));
122 url.setHost(hostParts.at(0));
123 url.setPath(pathPattern);
124
125 if (hostParts.size() == 2) {
126 int port = hostParts.at(1).toInt();
127 if (port) {
128 url.setPort(port);
129 }
130 }
131 }
132 return url.toString();
133 }
134
135 /*
136 * SetupWizard
137 */
138
SetupWizard(QWidget * parent)139 SetupWizard::SetupWizard(QWidget *parent)
140 : QWizard(parent)
141 {
142 setWindowTitle(i18nc("@title:window", "DAV groupware configuration wizard"));
143 setWindowIcon(QIcon::fromTheme(QStringLiteral("folder-remote")));
144 setPage(W_CredentialsPage, new CredentialsPage);
145 setPage(W_PredefinedProviderPage, new PredefinedProviderPage);
146 setPage(W_ServerTypePage, new ServerTypePage);
147 setPage(W_ConnectionPage, new ConnectionPage);
148 setPage(W_CheckPage, new CheckPage);
149 }
150
displayName() const151 QString SetupWizard::displayName() const
152 {
153 QString desktopFilePath = property("providerDesktopFilePath").toString();
154 if (desktopFilePath.isEmpty()) {
155 return QString();
156 }
157
158 KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
159 if (!service) {
160 return QString();
161 }
162
163 return service->name();
164 }
165
urls() const166 SetupWizard::Url::List SetupWizard::urls() const
167 {
168 Url::List urls;
169
170 const QString desktopFilePath = property("providerDesktopFilePath").toString();
171 if (desktopFilePath.isEmpty()) {
172 return urls;
173 }
174
175 KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
176 if (!service) {
177 return urls;
178 }
179
180 const QStringList supportedProtocols = service->property(QStringLiteral("X-DavGroupware-SupportedProtocols")).toStringList();
181 for (const QString &protocol : supportedProtocols) {
182 Url url;
183
184 if (protocol == QLatin1String("CalDav")) {
185 url.protocol = KDAV::CalDav;
186 } else if (protocol == QLatin1String("CardDav")) {
187 url.protocol = KDAV::CardDav;
188 } else if (protocol == QLatin1String("GroupDav")) {
189 url.protocol = KDAV::GroupDav;
190 } else {
191 return urls;
192 }
193
194 QString urlStr = settingsToUrl(this, protocol);
195
196 if (!urlStr.isEmpty()) {
197 url.url = urlStr;
198 url.userName = QStringLiteral("$default$");
199 urls << url;
200 }
201 }
202
203 return urls;
204 }
205
206 /*
207 * CredentialsPage
208 */
209
CredentialsPage(QWidget * parent)210 CredentialsPage::CredentialsPage(QWidget *parent)
211 : QWizardPage(parent)
212 {
213 setTitle(i18n("Login Credentials"));
214 setSubTitle(i18n("Enter your credentials to login to the groupware server"));
215
216 auto layout = new QFormLayout(this);
217
218 mUserName = new QLineEdit;
219 layout->addRow(i18n("User:"), mUserName);
220 registerField(QStringLiteral("credentialsUserName*"), mUserName);
221
222 mPassword = new KPasswordLineEdit;
223 mPassword->setRevealPasswordAvailable(KAuthorized::authorize(QStringLiteral("lineedit_reveal_password")));
224 layout->addRow(i18n("Password:"), mPassword);
225 registerField(QStringLiteral("credentialsPassword*"), mPassword, "password", SIGNAL(passwordChanged(QString)));
226 }
227
nextId() const228 int CredentialsPage::nextId() const
229 {
230 QString userName = field(QStringLiteral("credentialsUserName")).toString();
231 if (userName.endsWith(QLatin1String("@yahoo.com"))) {
232 const QString maybeYahooFile =
233 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/akonadi/davgroupware-providers/yahoo.desktop"));
234
235 if (maybeYahooFile.isEmpty()) {
236 return SetupWizard::W_ServerTypePage;
237 }
238
239 const KDesktopFile yahooProvider(maybeYahooFile);
240
241 wizard()->setProperty("usePredefinedProvider", true);
242 wizard()->setProperty("predefinedProviderName", yahooProvider.readName());
243 wizard()->setProperty("providerDesktopFilePath", maybeYahooFile);
244 return SetupWizard::W_PredefinedProviderPage;
245 } else {
246 return SetupWizard::W_ServerTypePage;
247 }
248 }
249
250 /*
251 * PredefinedProviderPage
252 */
253
PredefinedProviderPage(QWidget * parent)254 PredefinedProviderPage::PredefinedProviderPage(QWidget *parent)
255 : QWizardPage(parent)
256 {
257 setTitle(i18n("Predefined provider found"));
258 setSubTitle(i18n("Select if you want to use the auto-detected provider"));
259
260 auto layout = new QVBoxLayout(this);
261
262 mLabel = new QLabel;
263 layout->addWidget(mLabel);
264
265 mProviderGroup = new QButtonGroup(this);
266 mProviderGroup->setExclusive(true);
267
268 mUseProvider = new QRadioButton;
269 mProviderGroup->addButton(mUseProvider);
270 mUseProvider->setChecked(true);
271 layout->addWidget(mUseProvider);
272
273 mDontUseProvider = new QRadioButton(i18n("No, choose another server"));
274 mProviderGroup->addButton(mDontUseProvider);
275 layout->addWidget(mDontUseProvider);
276 }
277
initializePage()278 void PredefinedProviderPage::initializePage()
279 {
280 mLabel->setText(
281 i18n("Based on the email address you used as a login, this wizard\n"
282 "can configure automatically an account for %1 services.\n"
283 "Do you wish to do so?",
284 wizard()->property("predefinedProviderName").toString()));
285
286 mUseProvider->setText(i18n("Yes, use %1 as provider", wizard()->property("predefinedProviderName").toString()));
287 }
288
nextId() const289 int PredefinedProviderPage::nextId() const
290 {
291 if (mUseProvider->isChecked()) {
292 return SetupWizard::W_CheckPage;
293 } else {
294 wizard()->setProperty("usePredefinedProvider", QVariant());
295 wizard()->setProperty("providerDesktopFilePath", QVariant());
296 return SetupWizard::W_ServerTypePage;
297 }
298 }
299
300 /*
301 * ServerTypePage
302 */
303
compareServiceOffers(const QPair<QString,QString> & off1,const QPair<QString,QString> & off2)304 bool compareServiceOffers(const QPair<QString, QString> &off1, const QPair<QString, QString> &off2)
305 {
306 return off1.first.toLower() < off2.first.toLower();
307 }
308
ServerTypePage(QWidget * parent)309 ServerTypePage::ServerTypePage(QWidget *parent)
310 : QWizardPage(parent)
311 {
312 setTitle(i18n("Groupware Server"));
313 setSubTitle(i18n("Select the groupware server the resource shall be configured for"));
314
315 mProvidersCombo = new QComboBox(this);
316 mProvidersCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
317
318 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
319 QStringLiteral("kservices5/akonadi/davgroupware-providers"),
320 QStandardPaths::LocateDirectory);
321 const QStringList providers = KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.desktop")});
322
323 QList<QPair<QString, QString>> offers;
324 offers.reserve(providers.count());
325 for (const QString &fileName : providers) {
326 const KDesktopFile provider(fileName);
327 offers.append(QPair<QString, QString>(provider.readName(), fileName));
328 }
329 std::sort(offers.begin(), offers.end(), compareServiceOffers);
330 QListIterator<QPair<QString, QString>> it(offers);
331 while (it.hasNext()) {
332 QPair<QString, QString> p = it.next();
333 mProvidersCombo->addItem(p.first, p.second);
334 }
335 registerField(QStringLiteral("provider"), mProvidersCombo, "currentText");
336
337 auto layout = new QVBoxLayout(this);
338
339 mServerGroup = new QButtonGroup(this);
340 mServerGroup->setExclusive(true);
341
342 auto hLayout = new QHBoxLayout;
343 auto button = new QRadioButton(i18n("Use one of those servers:"));
344 registerField(QStringLiteral("templateConfiguration"), button);
345 mServerGroup->addButton(button);
346 mServerGroup->setId(button, 0);
347 button->setChecked(true);
348 hLayout->addWidget(button);
349 hLayout->addWidget(mProvidersCombo);
350 layout->addLayout(hLayout);
351
352 button = new QRadioButton(i18n("Configure the resource manually"));
353 connect(button, &QRadioButton::toggled, this, &ServerTypePage::manualConfigToggled);
354 registerField(QStringLiteral("manualConfiguration"), button);
355 mServerGroup->addButton(button);
356 mServerGroup->setId(button, 1);
357 layout->addWidget(button);
358
359 layout->addStretch(1);
360 }
361
manualConfigToggled(bool state)362 void ServerTypePage::manualConfigToggled(bool state)
363 {
364 setFinalPage(state);
365 wizard()->button(QWizard::NextButton)->setEnabled(!state);
366 }
367
validatePage()368 bool ServerTypePage::validatePage()
369 {
370 QVariant desktopFilePath = mProvidersCombo->itemData(mProvidersCombo->currentIndex());
371 if (desktopFilePath.isNull()) {
372 return false;
373 } else {
374 wizard()->setProperty("providerDesktopFilePath", desktopFilePath);
375 return true;
376 }
377 }
378
379 /*
380 * ConnectionPage
381 */
382
ConnectionPage(QWidget * parent)383 ConnectionPage::ConnectionPage(QWidget *parent)
384 : QWizardPage(parent)
385 , mPreviewLayout(nullptr)
386 , mCalDavUrlPreview(nullptr)
387 , mCardDavUrlPreview(nullptr)
388 , mGroupDavUrlPreview(nullptr)
389 {
390 setTitle(i18n("Connection"));
391 setSubTitle(i18n("Enter the connection information for the groupware server"));
392
393 mLayout = new QFormLayout(this);
394 const QRegularExpression hostnameRegexp(QStringLiteral("^[a-z0-9][.a-z0-9-]*[a-z0-9](?::[0-9]+)?$"));
395 mHost = new QLineEdit;
396 registerField(QStringLiteral("connectionHost*"), mHost);
397 mHost->setValidator(new QRegularExpressionValidator(hostnameRegexp, this));
398 mLayout->addRow(i18n("Host"), mHost);
399
400 mPath = new QLineEdit;
401 mLayout->addRow(i18n("Installation path"), mPath);
402 registerField(QStringLiteral("installationPath"), mPath);
403
404 mUseSecureConnection = new QCheckBox(i18n("Use secure connection"));
405 mUseSecureConnection->setChecked(true);
406 registerField(QStringLiteral("connectionUseSecureConnection"), mUseSecureConnection);
407 mLayout->addRow(QString(), mUseSecureConnection);
408
409 connect(mHost, &QLineEdit::textChanged, this, &ConnectionPage::urlElementChanged);
410 connect(mPath, &QLineEdit::textChanged, this, &ConnectionPage::urlElementChanged);
411 connect(mUseSecureConnection, &QCheckBox::toggled, this, &ConnectionPage::urlElementChanged);
412 }
413
initializePage()414 void ConnectionPage::initializePage()
415 {
416 KService::Ptr service = KService::serviceByStorageId(wizard()->property("providerDesktopFilePath").toString());
417 if (!service) {
418 return;
419 }
420
421 QString providerInstallationPath = service->property(QStringLiteral("X-DavGroupware-InstallationPath")).toString();
422 if (!providerInstallationPath.isEmpty()) {
423 mPath->setText(providerInstallationPath);
424 }
425
426 QStringList supportedProtocols = service->property(QStringLiteral("X-DavGroupware-SupportedProtocols")).toStringList();
427
428 mPreviewLayout = new QFormLayout;
429 mLayout->addRow(mPreviewLayout);
430
431 if (supportedProtocols.contains(QLatin1String("CalDav"))) {
432 mCalDavUrlLabel = new QLabel(i18n("Final URL (CalDav)"));
433 mCalDavUrlPreview = new QLabel;
434 mPreviewLayout->addRow(mCalDavUrlLabel, mCalDavUrlPreview);
435 }
436 if (supportedProtocols.contains(QLatin1String("CardDav"))) {
437 mCardDavUrlLabel = new QLabel(i18n("Final URL (CardDav)"));
438 mCardDavUrlPreview = new QLabel;
439 mPreviewLayout->addRow(mCardDavUrlLabel, mCardDavUrlPreview);
440 }
441 if (supportedProtocols.contains(QLatin1String("GroupDav"))) {
442 mGroupDavUrlLabel = new QLabel(i18n("Final URL (GroupDav)"));
443 mGroupDavUrlPreview = new QLabel;
444 mPreviewLayout->addRow(mGroupDavUrlLabel, mGroupDavUrlPreview);
445 }
446 }
447
cleanupPage()448 void ConnectionPage::cleanupPage()
449 {
450 delete mPreviewLayout;
451
452 if (mCalDavUrlPreview) {
453 delete mCalDavUrlLabel;
454 delete mCalDavUrlPreview;
455 mCalDavUrlPreview = nullptr;
456 }
457
458 if (mCardDavUrlPreview) {
459 delete mCardDavUrlLabel;
460 delete mCardDavUrlPreview;
461 mCardDavUrlPreview = nullptr;
462 }
463
464 if (mGroupDavUrlPreview) {
465 delete mGroupDavUrlLabel;
466 delete mGroupDavUrlPreview;
467 mGroupDavUrlPreview = nullptr;
468 }
469
470 QWizardPage::cleanupPage();
471 }
472
urlElementChanged()473 void ConnectionPage::urlElementChanged()
474 {
475 if (mHost->text().isEmpty()) {
476 if (mCalDavUrlPreview) {
477 mCalDavUrlPreview->setText(QStringLiteral("-"));
478 }
479 if (mCardDavUrlPreview) {
480 mCardDavUrlPreview->setText(QStringLiteral("-"));
481 }
482 if (mGroupDavUrlPreview) {
483 mGroupDavUrlPreview->setText(QStringLiteral("-"));
484 }
485 } else {
486 if (mCalDavUrlPreview) {
487 mCalDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("CalDav")));
488 }
489 if (mCardDavUrlPreview) {
490 mCardDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("CardDav")));
491 }
492 if (mGroupDavUrlPreview) {
493 mGroupDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("GroupDav")));
494 }
495 }
496 }
497
498 /*
499 * CheckPage
500 */
501
CheckPage(QWidget * parent)502 CheckPage::CheckPage(QWidget *parent)
503 : QWizardPage(parent)
504 {
505 setTitle(i18n("Test Connection"));
506 setSubTitle(i18n("You can test now whether the groupware server can be accessed with the current configuration"));
507 setFinalPage(true);
508
509 auto layout = new QVBoxLayout(this);
510
511 auto button = new QPushButton(i18n("Test Connection"));
512 layout->addWidget(button);
513
514 mStatusLabel = new QTextBrowser;
515 layout->addWidget(mStatusLabel);
516
517 connect(button, &QRadioButton::clicked, this, &CheckPage::checkConnection);
518 }
519
checkConnection()520 void CheckPage::checkConnection()
521 {
522 mStatusLabel->clear();
523
524 KDAV::DavUrl::List davUrls;
525
526 // convert list of SetupWizard::Url to list of KDAV::DavUrl
527 const SetupWizard::Url::List urls = static_cast<SetupWizard *>(wizard())->urls();
528 for (const SetupWizard::Url &url : urls) {
529 KDAV::DavUrl davUrl;
530 davUrl.setProtocol(url.protocol);
531
532 QUrl serverUrl(url.url);
533 serverUrl.setUserName(wizard()->field(QStringLiteral("credentialsUserName")).toString());
534 serverUrl.setPassword(wizard()->field(QStringLiteral("credentialsPassword")).toString());
535 davUrl.setUrl(serverUrl);
536
537 davUrls << davUrl;
538 }
539
540 // start the dav collections fetch job to test connectivity
541 auto job = new KDAV::DavCollectionsMultiFetchJob(davUrls, this);
542 connect(job, &KDAV::DavCollectionsMultiFetchJob::result, this, &CheckPage::onFetchDone);
543 job->start();
544 }
545
onFetchDone(KJob * job)546 void CheckPage::onFetchDone(KJob *job)
547 {
548 QString msg;
549 QPixmap icon;
550
551 if (job->error()) {
552 msg = i18n("An error occurred: %1", job->errorText());
553 icon = QIcon::fromTheme(QStringLiteral("dialog-close")).pixmap(16, 16);
554 } else {
555 msg = i18n("Connected successfully");
556 icon = QIcon::fromTheme(QStringLiteral("dialog-ok-apply")).pixmap(16, 16);
557 }
558
559 mStatusLabel->setHtml(QStringLiteral("<html><body><img src=\"icon\"> %1</body></html>").arg(msg));
560 mStatusLabel->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("icon")), QVariant(icon));
561 }
562