File indexing completed on 2025-01-05 04:54:57

0001 /*
0002     Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
0003 
0004     This library is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This library is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU Library General Public License
0015     along with this library; see the file COPYING.LIB.  If not, write to the
0016     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301, USA.
0018 */
0019 #include "accountsettings.h"
0020 
0021 #include <sink/store.h>
0022 #include <sink/log.h>
0023 #include <QDebug>
0024 #include <QDir>
0025 #include <QUrl>
0026 
0027 #include "keyring.h"
0028 #include <sink/crypto.h>
0029 
0030 using namespace Sink;
0031 using namespace Sink::ApplicationDomain;
0032 
0033 AccountSettings::AccountSettings(QObject *parent)
0034     : QObject(parent)
0035 {
0036 }
0037 
0038 void AccountSettings::setAccountType(const QByteArray &type)
0039 {
0040     mAccountType = type;
0041 }
0042 
0043 QByteArray AccountSettings::accountType() const
0044 {
0045     return mAccountType;
0046 }
0047 
0048 void AccountSettings::setAccountIdentifier(const QByteArray &id)
0049 {
0050     if (id.isEmpty()) {
0051         return;
0052     }
0053     mAccountIdentifier = id;
0054 
0055     //Clear
0056     mIcon = QString();
0057     mName = QString();
0058     mImapServer = QString();
0059     mImapUsername = QString();
0060     mImapAuthenticationMode = QString();
0061     mImapStarttls = false;
0062     mSmtpServer = QString();
0063     mSmtpUsername = QString();
0064     mCardDavServer = QString();
0065     mCardDavUsername = QString();
0066     mCalDavServer = QString();
0067     mCalDavUsername = QString();
0068     mPath = QString();
0069     emit changed();
0070     emit imapResourceChanged();
0071     emit smtpResourceChanged();
0072     emit cardDavResourceChanged();
0073     emit calDavResourceChanged();
0074     emit pathChanged();
0075 
0076     load();
0077 
0078 }
0079 
0080 QByteArray AccountSettings::accountIdentifier() const
0081 {
0082     return mAccountIdentifier;
0083 }
0084 
0085 void AccountSettings::setPath(const QUrl &path)
0086 {
0087     auto normalizedPath = path.path();
0088     if (mPath != normalizedPath) {
0089         mPath = normalizedPath;
0090         emit pathChanged();
0091     }
0092 }
0093 
0094 QUrl AccountSettings::path() const
0095 {
0096     return QUrl(mPath);
0097 }
0098 
0099 QValidator *AccountSettings::pathValidator() const
0100 {
0101     class PathValidator : public QValidator {
0102         State validate(QString &input, int &pos) const {
0103             Q_UNUSED(pos);
0104             if (!input.isEmpty() && QDir(input).exists()) {
0105                 return Acceptable;
0106             } else {
0107                 return Intermediate;
0108             }
0109         }
0110     };
0111     static PathValidator *pathValidator = new PathValidator;
0112     return pathValidator;
0113 }
0114 
0115 QValidator *AccountSettings::imapServerValidator() const
0116 {
0117     class ImapServerValidator : public QValidator {
0118         State validate(QString &input, int &pos) const {
0119             Q_UNUSED(pos);
0120             // imaps://mainserver.example.net:475
0121             const QUrl url(input);
0122             static QSet<QString> validProtocols{{"imap"}, {"imaps"}};
0123             if (url.isValid() && validProtocols.contains(url.scheme().toLower())) {
0124                 return Acceptable;
0125             } else {
0126                 return Intermediate;
0127             }
0128         }
0129     };
0130     static ImapServerValidator *validator = new ImapServerValidator;
0131     return validator;
0132 }
0133 
0134 QValidator *AccountSettings::smtpServerValidator() const
0135 {
0136     class SmtpServerValidator : public QValidator {
0137         State validate(QString &input, int &pos) const {
0138             Q_UNUSED(pos);
0139             // smtps://mainserver.example.net:475
0140             const QUrl url(input);
0141             static QSet<QString> validProtocols = QSet<QString>() << "smtp" << "smtps";
0142             if (url.isValid() && validProtocols.contains(url.scheme().toLower())) {
0143                 return Acceptable;
0144             } else {
0145                 return Intermediate;
0146             }
0147         }
0148     };
0149     static SmtpServerValidator *validator = new SmtpServerValidator;
0150     return validator;
0151 }
0152 
0153 void AccountSettings::saveAccount()
0154 {
0155     if (mAccountIdentifier.isEmpty()) {
0156         auto account = ApplicationDomainType::createEntity<SinkAccount>();
0157         mAccountIdentifier = account.identifier();
0158         Q_ASSERT(!mAccountType.isEmpty());
0159         account.setAccountType(mAccountType);
0160         account.setName(mName);
0161         account.setIcon(mIcon);
0162         Store::create(account)
0163             .onError([](const KAsync::Error &error) {
0164                 qWarning() << "Error while creating account: " << error.errorMessage;;
0165             })
0166             .exec().waitForFinished();
0167     } else {
0168         qDebug() << "Saving account " << mAccountIdentifier;
0169         Q_ASSERT(!mAccountIdentifier.isEmpty());
0170         SinkAccount account(mAccountIdentifier);
0171         account.setAccountType(mAccountType);
0172         account.setName(mName);
0173         account.setIcon(mIcon);
0174         Q_ASSERT(!account.identifier().isEmpty());
0175         Store::modify(account)
0176             .onError([](const KAsync::Error &error) {
0177                 qWarning() << "Error while creating account: " << error.errorMessage;;
0178             })
0179             .exec().waitForFinished();
0180     }
0181 }
0182 
0183 void AccountSettings::loadAccount()
0184 {
0185     Q_ASSERT(!mAccountIdentifier.isEmpty());
0186     Store::fetchOne<SinkAccount>(Query().filter(mAccountIdentifier).request<SinkAccount::Icon>().request<SinkAccount::Name>().request<SinkAccount::AccountType>())
0187         .then([this](const SinkAccount &account) {
0188             mAccountType = account.getAccountType().toLatin1();
0189             mIcon = account.getIcon();
0190             mName = account.getName();
0191             emit changed();
0192         }).onError([](const KAsync::Error &error) {
0193             qWarning() << "Failed to load the account: " << error.errorMessage;
0194         }).exec().waitForFinished();
0195 }
0196 
0197 void AccountSettings::loadImapResource()
0198 {
0199     Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).filter<SinkResource::ResourceType>("sink.imap"))
0200         .then([this](const SinkResource &resource) {
0201             mImapIdentifier = resource.identifier();
0202             mImapServer = resource.getProperty("server").toString();
0203             mImapUsername = resource.getProperty("username").toString();
0204             mImapStarttls = resource.getProperty("starttls").toBool();
0205             mImapAuthenticationMode = resource.getProperty("authenticationMode").toString();
0206             emit imapResourceChanged();
0207         }).onError([](const KAsync::Error &error) {
0208             qWarning() << "Failed to load the imap resource: " << error.errorMessage;
0209         }).exec().waitForFinished();
0210 }
0211 
0212 void AccountSettings::loadMaildirResource()
0213 {
0214     Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).filter<SinkResource::ResourceType>("sink.maildir"))
0215         .then([this](const SinkResource &resource) {
0216             mMaildirIdentifier = resource.identifier();
0217             mPath = resource.getProperty("path").toString();
0218             emit pathChanged();
0219         }).onError([](const KAsync::Error &error) {
0220             SinkWarning() << "Failed to load the maildir resource: " << error.errorMessage;
0221         }).exec().waitForFinished();
0222 }
0223 
0224 void AccountSettings::loadMailtransportResource()
0225 {
0226     Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).filter<SinkResource::ResourceType>("sink.mailtransport"))
0227         .then([this](const SinkResource &resource) {
0228             mMailtransportIdentifier = resource.identifier();
0229             mSmtpServer = resource.getProperty("server").toString();
0230             mSmtpUsername = resource.getProperty("username").toString();
0231             emit smtpResourceChanged();
0232         }).onError([](const KAsync::Error &error) {
0233             SinkWarning() << "Failed to load the smtp resource: " << error.errorMessage;
0234         }).exec().waitForFinished();
0235 }
0236 
0237 void AccountSettings::loadIdentity()
0238 {
0239     //FIXME this assumes that we only ever have one identity per account
0240     Store::fetchOne<Identity>(Query().filter<Identity::Account>(mAccountIdentifier))
0241         .then([this](const Identity &identity) {
0242             mIdentityIdentifier = identity.identifier();
0243             mUsername = identity.getName();
0244             mEmailAddress = identity.getAddress();
0245             emit identityChanged();
0246         }).onError([](const KAsync::Error &error) {
0247             SinkWarning() << "Failed to load the identity: " << error.errorMessage;
0248         }).exec().waitForFinished();
0249 }
0250 
0251 void AccountSettings::loadCardDavResource()
0252 {
0253     Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).filter<SinkResource::ResourceType>("sink.carddav"))
0254         .then([this](const SinkResource &resource) {
0255             mCardDavIdentifier = resource.identifier();
0256             mCardDavServer = resource.getProperty("server").toString();
0257             mCardDavUsername = resource.getProperty("username").toString();
0258             emit cardDavResourceChanged();
0259         }).onError([](const KAsync::Error &error) {
0260             qWarning() << "Failed to load the CardDAV resource: " << error.errorMessage;
0261         }).exec().waitForFinished();
0262 }
0263 
0264 void AccountSettings::loadCalDavResource()
0265 {
0266     Store::fetchOne<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier).filter<SinkResource::ResourceType>("sink.caldav"))
0267         .then([this](const SinkResource &resource) {
0268             mCalDavIdentifier = resource.identifier();
0269             mCalDavServer = resource.getProperty("server").toString();
0270             mCalDavUsername = resource.getProperty("username").toString();
0271             emit calDavResourceChanged();
0272         }).onError([](const KAsync::Error &error) {
0273             qWarning() << "Failed to load the CalDAV resource: " << error.errorMessage;
0274         }).exec().waitForFinished();
0275 }
0276 
0277 
0278 template<typename ResourceType>
0279 static QByteArray saveResource(const QByteArray &accountIdentifier, const QByteArray &identifier, const std::map<QByteArray, QVariant> &properties)
0280 {
0281     if (!identifier.isEmpty()) {
0282         SinkResource resource(identifier);
0283         for (const auto &pair : properties) {
0284             resource.setProperty(pair.first, pair.second);
0285         }
0286         Store::modify(resource)
0287             .onError([](const KAsync::Error &error) {
0288                 SinkWarning() << "Error while modifying resource: " << error.errorMessage;
0289             })
0290             .exec().waitForFinished();
0291     } else {
0292         auto resource = ResourceType::create(accountIdentifier);
0293         auto newIdentifier = resource.identifier();
0294         for (const auto &pair : properties) {
0295             resource.setProperty(pair.first, pair.second);
0296         }
0297         Store::create(resource)
0298             .onError([](const KAsync::Error &error) {
0299                 SinkWarning() << "Error while creating resource: " << error.errorMessage;
0300             })
0301             .exec().waitForFinished();
0302         return newIdentifier;
0303     }
0304     return identifier;
0305 }
0306 
0307 void AccountSettings::saveImapResource()
0308 {
0309     mImapIdentifier = saveResource<ImapResource>(mAccountIdentifier, mImapIdentifier, {
0310             {"server", mImapServer},
0311             {"username", mImapUsername},
0312             {"starttls", mImapStarttls},
0313             {"authenticationMode", mImapAuthenticationMode}
0314         });
0315 }
0316 
0317 void AccountSettings::saveCardDavResource()
0318 {
0319     mCardDavIdentifier = saveResource<CardDavResource>(mAccountIdentifier, mCardDavIdentifier, {
0320             {"server", mCardDavServer},
0321             {"username", mCardDavUsername}
0322         });
0323 }
0324 
0325 void AccountSettings::saveCalDavResource()
0326 {
0327     mCalDavIdentifier = saveResource<CalDavResource>(mAccountIdentifier, mCalDavIdentifier, {
0328             {"server", mCalDavServer},
0329             {"username", mCalDavUsername}
0330         });
0331 }
0332 
0333 void AccountSettings::saveMaildirResource()
0334 {
0335     mMaildirIdentifier = saveResource<MaildirResource>(mAccountIdentifier, mMaildirIdentifier, {
0336             {"path", mPath},
0337         });
0338 }
0339 
0340 void AccountSettings::saveMailtransportResource()
0341 {
0342     mMailtransportIdentifier = saveResource<MailtransportResource>(mAccountIdentifier, mMailtransportIdentifier, {
0343             {"server", mSmtpServer},
0344             {"username", mSmtpUsername}
0345         });
0346 }
0347 
0348 void AccountSettings::login(const QVariantMap &secrets)
0349 {
0350     // We'll attempt to store your account secrets using a key matching the email address.
0351     const auto accountSecret = secrets.value("accountSecret").toString();
0352     Store::fetchAll<SinkResource>(Query().filter<SinkResource::Account>(mAccountIdentifier))
0353         .then([=](const QList<SinkResource::Ptr> &resources) {
0354             Kube::AccountKeyring keyring{mAccountIdentifier};
0355             for (const auto &resource : resources) {
0356                 keyring.addPassword(resource->identifier(), accountSecret);
0357             }
0358             const auto keys = Crypto::findKeys({{mEmailAddress}}, true);
0359             if (!keys.empty()) {
0360                 qInfo() << "Storing account secrets.";
0361                 keyring.save(keys);
0362             } else {
0363                 qInfo() << "Failed to find a GPG key for " << mEmailAddress << ". Not storing account secrets.";
0364             }
0365         }).onError([](const KAsync::Error &error) {
0366             qWarning() << "Failed to load any account resources resource: " << error;
0367         }).exec();
0368 }
0369 
0370 void AccountSettings::saveIdentity()
0371 {
0372     if (!mIdentityIdentifier.isEmpty()) {
0373         Identity identity(mIdentityIdentifier);
0374         identity.setName(mUsername);
0375         identity.setAddress(mEmailAddress);
0376         Store::modify(identity)
0377         .onError([](const KAsync::Error &error) {
0378             SinkWarning() << "Error while modifying identity: " << error.errorMessage;
0379         })
0380         .exec().waitForFinished();
0381     } else {
0382         auto identity = ApplicationDomainType::createEntity<Identity>();
0383         mIdentityIdentifier = identity.identifier();
0384         identity.setAccount(mAccountIdentifier);
0385         identity.setName(mUsername);
0386         identity.setAddress(mEmailAddress);
0387         Store::create(identity)
0388         .onError([](const KAsync::Error &error) {
0389             SinkWarning() << "Error while creating identity: " << error.errorMessage;
0390         })
0391         .exec().waitForFinished();
0392     }
0393 }
0394 
0395 void AccountSettings::removeResource(const QByteArray &identifier)
0396 {
0397     if (identifier.isEmpty()) {
0398         SinkWarning() << "We're missing an identifier";
0399     } else {
0400         SinkResource resource(identifier);
0401         Store::remove(resource)
0402         .onError([](const KAsync::Error &error) {
0403             SinkWarning() << "Error while removing resource: " << error.errorMessage;
0404         })
0405         .exec().waitForFinished();
0406     }
0407 }
0408 
0409 void AccountSettings::removeAccount()
0410 {
0411     if (mAccountIdentifier.isEmpty()) {
0412         SinkWarning() << "We're missing an identifier";
0413     } else {
0414         SinkAccount account(mAccountIdentifier);
0415         Store::remove(account)
0416         .onError([](const KAsync::Error &error) {
0417             SinkWarning() << "Error while removing account: " << error.errorMessage;
0418         })
0419         .exec().waitForFinished();
0420     }
0421 }
0422 
0423 void AccountSettings::removeIdentity()
0424 {
0425     if (mIdentityIdentifier.isEmpty()) {
0426         SinkWarning() << "We're missing an identifier";
0427     } else {
0428         Identity identity(mIdentityIdentifier);
0429         Store::remove(identity)
0430         .onError([](const KAsync::Error &error) {
0431             SinkWarning() << "Error while removing identity: " << error.errorMessage;
0432         })
0433         .exec().waitForFinished();
0434     }
0435 }
0436 
0437 void AccountSettings::load()
0438 {
0439     loadAccount();
0440     loadImapResource();
0441     loadMailtransportResource();
0442     loadCardDavResource();
0443     loadCalDavResource();
0444     loadIdentity();
0445 }
0446 
0447 void AccountSettings::save()
0448 {
0449     saveAccount();
0450     saveImapResource();
0451     saveMailtransportResource();
0452     saveCardDavResource();
0453     saveCalDavResource();
0454     saveIdentity();
0455 }
0456 
0457 void AccountSettings::remove()
0458 {
0459     removeResource(mMailtransportIdentifier);
0460     removeResource(mImapIdentifier);
0461     removeResource(mCardDavIdentifier);
0462     removeResource(mCalDavIdentifier);
0463     removeIdentity();
0464     removeAccount();
0465 }
0466