Welcome to mirror list, hosted at ThFree Co, Russian Federation.

httpcredentials_p.h « creds « libsync « src - github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 779b4b4fddf5607e033edda7256a4114bad8541f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#pragma once

#include "httpcredentials.h"
#include "common/depreaction.h"

#include "account.h"
#include "configfile.h"
#include "theme.h"

#include <QApplication>
#include <QLoggingCategory>

#include <qt5keychain/keychain.h>

OC_DISABLE_DEPRECATED_WARNING

Q_DECLARE_LOGGING_CATEGORY(lcHttpLegacyCredentials)

namespace {
void addSettingsToJob(QKeychain::Job *job)
{
    auto settings = OCC::ConfigFile::settingsWithGroup(OCC::Theme::instance()->appName());
    settings->setParent(job); // make the job parent to make setting deleted properly
    job->setSettings(settings.release());
}


QString keychainKey(const QString &url, const QString &user, const QString &accountId)
{
    QString u(url);
    if (u.isEmpty()) {
        qCWarning(lcHttpLegacyCredentials) << "Empty url in keyChain, error!";
        return {};
    }
    if (user.isEmpty()) {
        qCWarning(lcHttpLegacyCredentials) << "Error: User is empty!";
        return {};
    }

    if (!u.endsWith(QLatin1Char('/'))) {
        u.append(QLatin1Char('/'));
    }

    QString key = user + QLatin1Char(':') + u;
    if (!accountId.isEmpty()) {
        key += QLatin1Char(':') + accountId;
    }
#ifdef Q_OS_WIN
    // On Windows the credential keys aren't namespaced properly
    // by qtkeychain. To work around that we manually add namespacing
    // to the generated keys. See #6125.
    // It's safe to do that since the key format is changing for 2.4
    // anyway to include the account ids. That means old keys can be
    // migrated to new namespaced keys on windows for 2.4.
    key.prepend(OCC::Theme::instance()->appNameGUI() + QLatin1Char('_'));
#endif
    return key;
}
} // ns

namespace OCC {

class HttpLegacyCredentials : public QObject
{
    Q_OBJECT

    auto isOAuthC()
    {
        return QStringLiteral("oauth");
    }

public:
    HttpLegacyCredentials(HttpCredentials *parent)
        : QObject(parent)
        , _parent(parent)
    {
    }

    void fetchFromKeychainHelper()
    {
        qCInfo(lcHttpLegacyCredentials) << "Started migration of < 2.8 credentials to 2.9+";
        slotReadPasswordFromKeychain();
    }

private:
    HttpCredentials *_parent;
    bool _retryOnKeyChainError = true;

    bool _keychainMigration = false;

    bool keychainUnavailableRetryLater(QKeychain::ReadPasswordJob *incoming)
    {
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
        Q_ASSERT(!incoming->insecureFallback()); // If insecureFallback is set, the next test would be pointless
        if (_retryOnKeyChainError && (incoming->error() == QKeychain::NoBackendAvailable || incoming->error() == QKeychain::OtherError)) {
            // Could be that the backend was not yet available. Wait some extra seconds.
            // (Issues #4274 and #6522)
            // (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
            qCInfo(lcHttpLegacyCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incoming->errorString();
            QTimer::singleShot(10000, this, &HttpLegacyCredentials::fetchFromKeychainHelper);
            _retryOnKeyChainError = false;
            return true;
        }
#else
        Q_UNUSED(incoming);
#endif
        _retryOnKeyChainError = false;
        return false;
    }

    void slotReadPasswordFromKeychain()
    {
        const QString kck = keychainKey(
            _parent->_account->url().toString(),
            _parent->_user,
            _keychainMigration ? QString() : _parent->_account->id());

        QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(Theme::instance()->appName());
        addSettingsToJob(job);
        job->setInsecureFallback(false);
        job->setKey(kck);
        connect(job, &QKeychain::ReadPasswordJob::finished, this, &HttpLegacyCredentials::slotReadJobDone);
        job->start();
    }


    void slotReadJobDone(QKeychain::Job *incoming)
    {
        auto job = qobject_cast<QKeychain::ReadPasswordJob *>(incoming);
        QKeychain::Error error = job->error();

        // If we can't find the credentials at the keys that include the account id,
        // try to read them from the legacy locations that don't have a account id.
        if (!_keychainMigration && error == QKeychain::EntryNotFound) {
            qCWarning(lcHttpLegacyCredentials)
                << "Could not find keychain entries, attempting to read from legacy locations";
            _keychainMigration = true;
            fetchFromKeychainHelper();
            return;
        }

        bool isOauth = _parent->_account->credentialSetting(isOAuthC()).toBool();
        if (isOauth) {
            _parent->_refreshToken = job->textData();
        } else {
            _parent->_password = job->textData();
        }

        if (_parent->_user.isEmpty()) {
            qCWarning(lcHttpLegacyCredentials) << "Strange: User is empty!";
        }

        if (!_parent->_refreshToken.isEmpty() && error == QKeychain::NoError) {
            _parent->refreshAccessToken();
        } else if (!_parent->_password.isEmpty() && error == QKeychain::NoError) {
            // All cool, the keychain did not come back with error.
            // Still, the password can be empty which indicates a problem and
            // the password dialog has to be opened.
            _parent->_ready = true;
            emit _parent->fetched();
        } else {
            // we come here if the password is empty or any other keychain
            // error happend.

            _parent->_fetchErrorString = job->error() != QKeychain::EntryNotFound ? job->errorString() : QString();

            _parent->_password = QString();
            _parent->_ready = false;
            emit _parent->fetched();
        }

        // If keychain data was read from legacy location, wipe these entries and store new ones
        deleteOldKeychainEntries();
        if (_parent->_ready) {
            _parent->persist();
            qCWarning(lcHttpLegacyCredentials) << "Migrated old keychain entries";
        }
        deleteLater();
    }

    void slotWriteJobDone(QKeychain::Job *job)
    {
        if (job && job->error() != QKeychain::NoError) {
            qCWarning(lcHttpLegacyCredentials) << "Error while writing password"
                                               << job->error() << job->errorString();
        }
    }

    void deleteOldKeychainEntries()
    {
        // oooold
        auto startDeleteJob = [this](const QString &key, bool migratePreId = false) {
            QKeychain::DeletePasswordJob *job = new QKeychain::DeletePasswordJob(Theme::instance()->appName());
            addSettingsToJob(job);
            job->setInsecureFallback(true);
            job->setKey(keychainKey(_parent->_account->url().toString(), key, migratePreId ? QString() : _parent->_account->id()));
            job->start();
            connect(job, &QKeychain::DeletePasswordJob::finished, this, [job] {
                if (job->error() != QKeychain::NoError) {
                    qCWarning(lcHttpLegacyCredentials) << "Failed to delete legacy credentials" << job->key() << job->errorString();
                }
            });
        };

        // old
        startDeleteJob(_parent->_user, true);
        // pre 2.8
        startDeleteJob(_parent->_user);
    }
};

}

OC_ENABLE_DEPRECATED_WARNING