File indexing completed on 2024-12-22 04:56:57

0001 /*
0002     SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "setupwizard.h"
0008 
0009 #include <KAuthorized>
0010 #include <KDAV/DavCollectionsMultiFetchJob>
0011 #include <KDesktopFile>
0012 #include <KFileUtils>
0013 #include <KLocalizedString>
0014 #include <KPasswordLineEdit>
0015 #include <KService>
0016 #include <QIcon>
0017 #include <QLineEdit>
0018 #include <QTextBrowser>
0019 
0020 #include <QButtonGroup>
0021 #include <QCheckBox>
0022 #include <QComboBox>
0023 #include <QFormLayout>
0024 #include <QHBoxLayout>
0025 #include <QLabel>
0026 #include <QPushButton>
0027 #include <QRadioButton>
0028 #include <QRegularExpressionValidator>
0029 #include <QStandardPaths>
0030 #include <QUrl>
0031 
0032 enum GroupwareServers {
0033     Citadel,
0034     DAVical,
0035     eGroupware,
0036     OpenGroupware,
0037     ScalableOGo,
0038     Scalix,
0039     Zarafa,
0040     Zimbra,
0041 };
0042 
0043 static QString settingsToUrl(const QWizard *wizard, const QString &protocol)
0044 {
0045     const QString desktopFilePath = wizard->property("providerDesktopFilePath").toString();
0046     if (desktopFilePath.isEmpty()) {
0047         return {};
0048     }
0049 
0050     KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
0051     if (!service) {
0052         return {};
0053     }
0054 
0055     const QStringList supportedProtocols = service->property<QStringList>(QStringLiteral("X-DavGroupware-SupportedProtocols"));
0056     if (!supportedProtocols.contains(protocol)) {
0057         return {};
0058     }
0059 
0060     const QString pathPropertyName(QStringLiteral("X-DavGroupware-") + protocol + QStringLiteral("Path"));
0061     if (service->property<QString>(pathPropertyName).isEmpty()) {
0062         return {};
0063     }
0064 
0065     QString pathPattern = service->property<QString>(pathPropertyName) + QLatin1Char('/');
0066 
0067     const QString username = wizard->field(QStringLiteral("credentialsUserName")).toString();
0068     QString localPart(username);
0069     localPart.remove(QRegularExpression(QStringLiteral("@.*$")));
0070     pathPattern.replace(QLatin1StringView("$user$"), username);
0071     pathPattern.replace(QLatin1StringView("$localpart$"), localPart);
0072     QString providerName = service->property<QString>(QStringLiteral("X-DavGroupware-Provider"));
0073     const QString localPath = wizard->field(QStringLiteral("installationPath")).toString();
0074     if (!localPath.isEmpty()) {
0075         if (providerName == QLatin1StringView("davical")) {
0076             if (!localPath.endsWith(QLatin1Char('/'))) {
0077                 pathPattern.append(localPath + QLatin1Char('/'));
0078             } else {
0079                 pathPattern.append(localPath);
0080             }
0081         } else {
0082             if (!localPath.startsWith(QLatin1Char('/'))) {
0083                 pathPattern.prepend(QLatin1Char('/') + localPath);
0084             } else {
0085                 pathPattern.prepend(localPath);
0086             }
0087         }
0088     }
0089     QUrl url;
0090 
0091     if (!wizard->property("usePredefinedProvider").isNull()) {
0092         if (service->property<bool>(QStringLiteral("X-DavGroupware-ProviderUsesSSL"))) {
0093             url.setScheme(QStringLiteral("https"));
0094         } else {
0095             url.setScheme(QStringLiteral("http"));
0096         }
0097 
0098         const QString hostPropertyName(QStringLiteral("X-DavGroupware-") + protocol + QStringLiteral("Host"));
0099         if (service->property<QString>(hostPropertyName).isEmpty()) {
0100             return {};
0101         }
0102 
0103         url.setHost(service->property<QString>(hostPropertyName));
0104         url.setPath(pathPattern);
0105     } else {
0106         if (wizard->field(QStringLiteral("connectionUseSecureConnection")).toBool()) {
0107             url.setScheme(QStringLiteral("https"));
0108         } else {
0109             url.setScheme(QStringLiteral("http"));
0110         }
0111 
0112         const QString host = wizard->field(QStringLiteral("connectionHost")).toString();
0113         if (host.isEmpty()) {
0114             return {};
0115         }
0116         const QStringList hostParts = host.split(QLatin1Char(':'));
0117         url.setHost(hostParts.at(0));
0118         url.setPath(pathPattern);
0119 
0120         if (hostParts.size() == 2) {
0121             int port = hostParts.at(1).toInt();
0122             if (port) {
0123                 url.setPort(port);
0124             }
0125         }
0126     }
0127     return url.toString();
0128 }
0129 
0130 /*
0131  * SetupWizard
0132  */
0133 
0134 SetupWizard::SetupWizard(QWidget *parent)
0135     : QWizard(parent)
0136 {
0137     setWindowTitle(i18nc("@title:window", "DAV groupware configuration wizard"));
0138     setWindowIcon(QIcon::fromTheme(QStringLiteral("folder-remote")));
0139     setPage(W_CredentialsPage, new CredentialsPage);
0140     setPage(W_PredefinedProviderPage, new PredefinedProviderPage);
0141     setPage(W_ServerTypePage, new ServerTypePage);
0142     setPage(W_ConnectionPage, new ConnectionPage);
0143     setPage(W_CheckPage, new CheckPage);
0144 }
0145 
0146 QString SetupWizard::displayName() const
0147 {
0148     const QString desktopFilePath = property("providerDesktopFilePath").toString();
0149     if (desktopFilePath.isEmpty()) {
0150         return {};
0151     }
0152 
0153     KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
0154     if (!service) {
0155         return {};
0156     }
0157 
0158     return service->name();
0159 }
0160 
0161 SetupWizard::Url::List SetupWizard::urls() const
0162 {
0163     Url::List urls;
0164 
0165     const QString desktopFilePath = property("providerDesktopFilePath").toString();
0166     if (desktopFilePath.isEmpty()) {
0167         return urls;
0168     }
0169 
0170     KService::Ptr service = KService::serviceByStorageId(desktopFilePath);
0171     if (!service) {
0172         return urls;
0173     }
0174 
0175     const QStringList supportedProtocols = service->property<QStringList>(QStringLiteral("X-DavGroupware-SupportedProtocols"));
0176     for (const QString &protocol : supportedProtocols) {
0177         Url url;
0178 
0179         if (protocol == QLatin1StringView("CalDav")) {
0180             url.protocol = KDAV::CalDav;
0181         } else if (protocol == QLatin1StringView("CardDav")) {
0182             url.protocol = KDAV::CardDav;
0183         } else if (protocol == QLatin1StringView("GroupDav")) {
0184             url.protocol = KDAV::GroupDav;
0185         } else {
0186             return urls;
0187         }
0188 
0189         const QString urlStr = settingsToUrl(this, protocol);
0190 
0191         if (!urlStr.isEmpty()) {
0192             url.url = urlStr;
0193             url.userName = QStringLiteral("$default$");
0194             urls << url;
0195         }
0196     }
0197 
0198     return urls;
0199 }
0200 
0201 /*
0202  * CredentialsPage
0203  */
0204 
0205 CredentialsPage::CredentialsPage(QWidget *parent)
0206     : QWizardPage(parent)
0207     , mUserName(new QLineEdit(this))
0208     , mPassword(new KPasswordLineEdit(this))
0209 {
0210     setTitle(i18n("Login Credentials"));
0211     setSubTitle(i18n("Enter your credentials to login to the groupware server"));
0212 
0213     auto layout = new QFormLayout(this);
0214 
0215     layout->addRow(i18n("User:"), mUserName);
0216     registerField(QStringLiteral("credentialsUserName*"), mUserName);
0217 
0218     mPassword->setRevealPasswordAvailable(KAuthorized::authorize(QStringLiteral("lineedit_reveal_password")));
0219     layout->addRow(i18n("Password:"), mPassword);
0220     registerField(QStringLiteral("credentialsPassword*"), mPassword, "password", SIGNAL(passwordChanged(QString)));
0221 }
0222 
0223 int CredentialsPage::nextId() const
0224 {
0225     QString userName = field(QStringLiteral("credentialsUserName")).toString();
0226     if (userName.endsWith(QLatin1StringView("@yahoo.com"))) {
0227         const QString maybeYahooFile =
0228             QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("akonadi/davgroupware-providers/yahoo.desktop"));
0229 
0230         if (maybeYahooFile.isEmpty()) {
0231             return SetupWizard::W_ServerTypePage;
0232         }
0233 
0234         const KDesktopFile yahooProvider(maybeYahooFile);
0235 
0236         wizard()->setProperty("usePredefinedProvider", true);
0237         wizard()->setProperty("predefinedProviderName", yahooProvider.readName());
0238         wizard()->setProperty("providerDesktopFilePath", maybeYahooFile);
0239         return SetupWizard::W_PredefinedProviderPage;
0240     } else {
0241         return SetupWizard::W_ServerTypePage;
0242     }
0243 }
0244 
0245 /*
0246  * PredefinedProviderPage
0247  */
0248 
0249 PredefinedProviderPage::PredefinedProviderPage(QWidget *parent)
0250     : QWizardPage(parent)
0251     , mLabel(new QLabel(this))
0252     , mProviderGroup(new QButtonGroup(this))
0253     , mUseProvider(new QRadioButton(this))
0254     , mDontUseProvider(new QRadioButton(i18n("No, choose another server"), this))
0255 {
0256     setTitle(i18n("Predefined provider found"));
0257     setSubTitle(i18n("Select if you want to use the auto-detected provider"));
0258 
0259     auto layout = new QVBoxLayout(this);
0260 
0261     layout->addWidget(mLabel);
0262 
0263     mProviderGroup->setExclusive(true);
0264 
0265     mProviderGroup->addButton(mUseProvider);
0266     mUseProvider->setChecked(true);
0267     layout->addWidget(mUseProvider);
0268 
0269     mProviderGroup->addButton(mDontUseProvider);
0270     layout->addWidget(mDontUseProvider);
0271 }
0272 
0273 void PredefinedProviderPage::initializePage()
0274 {
0275     mLabel->setText(
0276         i18n("Based on the email address you used as a login, this wizard\n"
0277              "can configure automatically an account for %1 services.\n"
0278              "Do you wish to do so?",
0279              wizard()->property("predefinedProviderName").toString()));
0280 
0281     mUseProvider->setText(i18n("Yes, use %1 as provider", wizard()->property("predefinedProviderName").toString()));
0282 }
0283 
0284 int PredefinedProviderPage::nextId() const
0285 {
0286     if (mUseProvider->isChecked()) {
0287         return SetupWizard::W_CheckPage;
0288     } else {
0289         wizard()->setProperty("usePredefinedProvider", QVariant());
0290         wizard()->setProperty("providerDesktopFilePath", QVariant());
0291         return SetupWizard::W_ServerTypePage;
0292     }
0293 }
0294 
0295 /*
0296  * ServerTypePage
0297  */
0298 
0299 bool compareServiceOffers(const QPair<QString, QString> &off1, const QPair<QString, QString> &off2)
0300 {
0301     return off1.first.toLower() < off2.first.toLower();
0302 }
0303 
0304 ServerTypePage::ServerTypePage(QWidget *parent)
0305     : QWizardPage(parent)
0306 {
0307     setTitle(i18n("Groupware Server"));
0308     setSubTitle(i18n("Select the groupware server the resource shall be configured for"));
0309 
0310     mProvidersCombo = new QComboBox(this);
0311     mProvidersCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0312 
0313     const QStringList dirs =
0314         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("akonadi/davgroupware-providers"), QStandardPaths::LocateDirectory);
0315     const QStringList providers = KFileUtils::findAllUniqueFiles(dirs, QStringList{QStringLiteral("*.desktop")});
0316 
0317     QList<QPair<QString, QString>> offers;
0318     offers.reserve(providers.count());
0319     for (const QString &fileName : providers) {
0320         const KDesktopFile provider(fileName);
0321         offers.append(QPair<QString, QString>(provider.readName(), fileName));
0322     }
0323     std::sort(offers.begin(), offers.end(), compareServiceOffers);
0324     QListIterator<QPair<QString, QString>> it(offers);
0325     while (it.hasNext()) {
0326         QPair<QString, QString> p = it.next();
0327         mProvidersCombo->addItem(p.first, p.second);
0328     }
0329     registerField(QStringLiteral("provider"), mProvidersCombo, "currentText");
0330 
0331     auto layout = new QVBoxLayout(this);
0332 
0333     mServerGroup = new QButtonGroup(this);
0334     mServerGroup->setExclusive(true);
0335 
0336     auto hLayout = new QHBoxLayout;
0337     auto button = new QRadioButton(i18n("Use one of those servers:"), this);
0338     registerField(QStringLiteral("templateConfiguration"), button);
0339     mServerGroup->addButton(button);
0340     mServerGroup->setId(button, 0);
0341     button->setChecked(true);
0342     hLayout->addWidget(button);
0343     hLayout->addWidget(mProvidersCombo);
0344     layout->addLayout(hLayout);
0345 
0346     button = new QRadioButton(i18n("Configure the resource manually"), this);
0347     connect(button, &QRadioButton::toggled, this, &ServerTypePage::manualConfigToggled);
0348     registerField(QStringLiteral("manualConfiguration"), button);
0349     mServerGroup->addButton(button);
0350     mServerGroup->setId(button, 1);
0351     layout->addWidget(button);
0352 
0353     layout->addStretch(1);
0354 }
0355 
0356 void ServerTypePage::manualConfigToggled(bool state)
0357 {
0358     setFinalPage(state);
0359     wizard()->button(QWizard::NextButton)->setEnabled(!state);
0360 }
0361 
0362 bool ServerTypePage::validatePage()
0363 {
0364     QVariant desktopFilePath = mProvidersCombo->itemData(mProvidersCombo->currentIndex());
0365     if (desktopFilePath.isNull()) {
0366         return false;
0367     } else {
0368         wizard()->setProperty("providerDesktopFilePath", desktopFilePath);
0369         return true;
0370     }
0371 }
0372 
0373 /*
0374  * ConnectionPage
0375  */
0376 
0377 ConnectionPage::ConnectionPage(QWidget *parent)
0378     : QWizardPage(parent)
0379 {
0380     setTitle(i18n("Connection"));
0381     setSubTitle(i18n("Enter the connection information for the groupware server"));
0382 
0383     mLayout = new QFormLayout(this);
0384     const QRegularExpression hostnameRegexp(QStringLiteral("^[a-z0-9][.a-z0-9-]*[a-z0-9](?::[0-9]+)?$"));
0385     mHost = new QLineEdit(this);
0386     registerField(QStringLiteral("connectionHost*"), mHost);
0387     mHost->setValidator(new QRegularExpressionValidator(hostnameRegexp, this));
0388     mLayout->addRow(i18n("Host"), mHost);
0389 
0390     mPath = new QLineEdit(this);
0391     mLayout->addRow(i18n("Installation path"), mPath);
0392     registerField(QStringLiteral("installationPath"), mPath);
0393 
0394     mUseSecureConnection = new QCheckBox(i18n("Use secure connection"));
0395     mUseSecureConnection->setChecked(true);
0396     registerField(QStringLiteral("connectionUseSecureConnection"), mUseSecureConnection);
0397     mLayout->addRow(QString(), mUseSecureConnection);
0398 
0399     connect(mHost, &QLineEdit::textChanged, this, &ConnectionPage::urlElementChanged);
0400     connect(mPath, &QLineEdit::textChanged, this, &ConnectionPage::urlElementChanged);
0401     connect(mUseSecureConnection, &QCheckBox::toggled, this, &ConnectionPage::urlElementChanged);
0402 }
0403 
0404 void ConnectionPage::initializePage()
0405 {
0406     KService::Ptr service = KService::serviceByStorageId(wizard()->property("providerDesktopFilePath").toString());
0407     if (!service) {
0408         return;
0409     }
0410 
0411     const QString providerInstallationPath = service->property<QString>(QStringLiteral("X-DavGroupware-InstallationPath"));
0412     if (!providerInstallationPath.isEmpty()) {
0413         mPath->setText(providerInstallationPath);
0414     }
0415 
0416     const QStringList supportedProtocols = service->property<QStringList>(QStringLiteral("X-DavGroupware-SupportedProtocols"));
0417 
0418     mPreviewLayout = new QFormLayout;
0419     mLayout->addRow(mPreviewLayout);
0420 
0421     if (supportedProtocols.contains(QLatin1StringView("CalDav"))) {
0422         mCalDavUrlLabel = new QLabel(i18n("Final URL (CalDav)"));
0423         mCalDavUrlPreview = new QLabel;
0424         mPreviewLayout->addRow(mCalDavUrlLabel, mCalDavUrlPreview);
0425     }
0426     if (supportedProtocols.contains(QLatin1StringView("CardDav"))) {
0427         mCardDavUrlLabel = new QLabel(i18n("Final URL (CardDav)"));
0428         mCardDavUrlPreview = new QLabel;
0429         mPreviewLayout->addRow(mCardDavUrlLabel, mCardDavUrlPreview);
0430     }
0431     if (supportedProtocols.contains(QLatin1StringView("GroupDav"))) {
0432         mGroupDavUrlLabel = new QLabel(i18n("Final URL (GroupDav)"));
0433         mGroupDavUrlPreview = new QLabel;
0434         mPreviewLayout->addRow(mGroupDavUrlLabel, mGroupDavUrlPreview);
0435     }
0436 }
0437 
0438 void ConnectionPage::cleanupPage()
0439 {
0440     delete mPreviewLayout;
0441 
0442     if (mCalDavUrlPreview) {
0443         delete mCalDavUrlLabel;
0444         delete mCalDavUrlPreview;
0445         mCalDavUrlPreview = nullptr;
0446     }
0447 
0448     if (mCardDavUrlPreview) {
0449         delete mCardDavUrlLabel;
0450         delete mCardDavUrlPreview;
0451         mCardDavUrlPreview = nullptr;
0452     }
0453 
0454     if (mGroupDavUrlPreview) {
0455         delete mGroupDavUrlLabel;
0456         delete mGroupDavUrlPreview;
0457         mGroupDavUrlPreview = nullptr;
0458     }
0459 
0460     QWizardPage::cleanupPage();
0461 }
0462 
0463 void ConnectionPage::urlElementChanged()
0464 {
0465     if (mHost->text().isEmpty()) {
0466         if (mCalDavUrlPreview) {
0467             mCalDavUrlPreview->setText(QStringLiteral("-"));
0468         }
0469         if (mCardDavUrlPreview) {
0470             mCardDavUrlPreview->setText(QStringLiteral("-"));
0471         }
0472         if (mGroupDavUrlPreview) {
0473             mGroupDavUrlPreview->setText(QStringLiteral("-"));
0474         }
0475     } else {
0476         if (mCalDavUrlPreview) {
0477             mCalDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("CalDav")));
0478         }
0479         if (mCardDavUrlPreview) {
0480             mCardDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("CardDav")));
0481         }
0482         if (mGroupDavUrlPreview) {
0483             mGroupDavUrlPreview->setText(settingsToUrl(this->wizard(), QStringLiteral("GroupDav")));
0484         }
0485     }
0486 }
0487 
0488 /*
0489  * CheckPage
0490  */
0491 
0492 CheckPage::CheckPage(QWidget *parent)
0493     : QWizardPage(parent)
0494     , mStatusLabel(new QTextBrowser(this))
0495 {
0496     setTitle(i18n("Test Connection"));
0497     setSubTitle(i18n("You can test now whether the groupware server can be accessed with the current configuration"));
0498     setFinalPage(true);
0499 
0500     auto layout = new QVBoxLayout(this);
0501 
0502     auto button = new QPushButton(i18n("Test Connection"), this);
0503     layout->addWidget(button);
0504 
0505     layout->addWidget(mStatusLabel);
0506 
0507     connect(button, &QRadioButton::clicked, this, &CheckPage::checkConnection);
0508 }
0509 
0510 void CheckPage::checkConnection()
0511 {
0512     mStatusLabel->clear();
0513 
0514     KDAV::DavUrl::List davUrls;
0515 
0516     // convert list of SetupWizard::Url to list of KDAV::DavUrl
0517     const SetupWizard::Url::List urls = static_cast<SetupWizard *>(wizard())->urls();
0518     for (const SetupWizard::Url &url : urls) {
0519         KDAV::DavUrl davUrl;
0520         davUrl.setProtocol(url.protocol);
0521 
0522         QUrl serverUrl(url.url);
0523         serverUrl.setUserName(wizard()->field(QStringLiteral("credentialsUserName")).toString());
0524         serverUrl.setPassword(wizard()->field(QStringLiteral("credentialsPassword")).toString());
0525         davUrl.setUrl(serverUrl);
0526 
0527         davUrls << davUrl;
0528     }
0529 
0530     // start the dav collections fetch job to test connectivity
0531     auto job = new KDAV::DavCollectionsMultiFetchJob(davUrls, this);
0532     connect(job, &KDAV::DavCollectionsMultiFetchJob::result, this, &CheckPage::onFetchDone);
0533     job->start();
0534 }
0535 
0536 void CheckPage::onFetchDone(KJob *job)
0537 {
0538     QString msg;
0539     QPixmap icon;
0540 
0541     if (job->error()) {
0542         msg = i18n("An error occurred: %1", job->errorText());
0543         icon = QIcon::fromTheme(QStringLiteral("dialog-close")).pixmap(16, 16);
0544     } else {
0545         msg = i18n("Connected successfully");
0546         icon = QIcon::fromTheme(QStringLiteral("dialog-ok-apply")).pixmap(16, 16);
0547     }
0548 
0549     mStatusLabel->setHtml(QStringLiteral("<html><body><img src=\"icon\"> %1</body></html>").arg(msg));
0550     mStatusLabel->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("icon")), QVariant(icon));
0551 }
0552 
0553 #include "moc_setupwizard.cpp"