keepassxc

Форк
0
/
OpVaultReaderSections.cpp 
168 строк · 6.6 Кб
1
/*
2
 *  Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
3
 *
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU General Public License as published by
6
 *  the Free Software Foundation, either version 2 or (at your option)
7
 *  version 3 of the License.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17

18
#include "OpVaultReader.h"
19

20
#include "core/Entry.h"
21
#include "core/Totp.h"
22

23
#include <QDebug>
24
#include <QJsonArray>
25
#include <QJsonObject>
26
#include <QUrlQuery>
27

28
namespace
29
{
30
    QDateTime resolveDate(const QString& kind, const QJsonValue& value)
31
    {
32
        QDateTime date;
33
        if (kind == "monthYear") {
34
            // 1Password programmers are sadistic...
35
            auto dateValue = QString::number(value.toInt());
36
            date = QDateTime::fromString(dateValue, "yyyyMM");
37
            date.setTimeSpec(Qt::UTC);
38
        } else if (value.isString()) {
39
            date = QDateTime::fromTime_t(value.toString().toUInt(), Qt::UTC);
40
        } else {
41
            date = QDateTime::fromTime_t(value.toInt(), Qt::UTC);
42
        }
43
        return date;
44
    }
45
} // namespace
46

47
void OpVaultReader::fillFromSection(Entry* entry, const QJsonObject& section)
48
{
49
    const auto uuid = entry->uuid();
50
    auto sectionTitle = section["title"].toString();
51

52
    if (!section.contains("fields")) {
53
        auto sectionName = section["name"].toString();
54
        if (!(sectionName.toLower() == "linked items" && sectionTitle.toLower() == "related items")) {
55
            qWarning() << R"(Skipping "fields"-less Section in UUID ")" << uuid << "\": <<" << section << ">>";
56
        }
57
        return;
58
    } else if (!section["fields"].isArray()) {
59
        qWarning() << R"(Skipping non-Array "fields" in UUID ")" << uuid << "\"\n";
60
        return;
61
    }
62

63
    QJsonArray sectionFields = section["fields"].toArray();
64
    for (const QJsonValue sectionField : sectionFields) {
65
        if (!sectionField.isObject()) {
66
            qWarning() << R"(Skipping non-Object "fields" in UUID ")" << uuid << "\": << " << sectionField << ">>";
67
            continue;
68
        }
69
        fillFromSectionField(entry, sectionTitle, sectionField.toObject());
70
    }
71
}
72

73
void OpVaultReader::fillFromSectionField(Entry* entry, const QString& sectionName, const QJsonObject& field)
74
{
75
    if (!field.contains("v")) {
76
        // for our purposes, we don't care if there isn't a value in the field
77
        return;
78
    }
79

80
    // Ignore "a" and "inputTraits" fields, they don't apply to KPXC
81

82
    auto attrName = resolveAttributeName(sectionName, field["n"].toString(), field["t"].toString());
83
    auto attrValue = field.value("v").toString();
84
    auto kind = field["k"].toString();
85

86
    if (attrName.startsWith("TOTP_")) {
87
        if (entry->hasTotp()) {
88
            // Store multiple TOTP definitions as additional otp attributes
89
            int i = 0;
90
            QString name("otp");
91
            auto attributes = entry->attributes()->keys();
92
            while (attributes.contains(name)) {
93
                name = QString("otp_%1").arg(++i);
94
            }
95
            entry->attributes()->set(name, attrValue, true);
96
        } else if (attrValue.startsWith("otpauth://")) {
97
            QUrlQuery query(attrValue);
98
            // at least as of 1Password 7, they don't append the digits= and period= which totp.cpp requires
99
            if (!query.hasQueryItem("digits")) {
100
                query.addQueryItem("digits", QString("%1").arg(Totp::DEFAULT_DIGITS));
101
            }
102
            if (!query.hasQueryItem("period")) {
103
                query.addQueryItem("period", QString("%1").arg(Totp::DEFAULT_STEP));
104
            }
105
            attrValue = query.toString(QUrl::FullyEncoded);
106
            entry->setTotp(Totp::parseSettings(attrValue));
107
        } else {
108
            entry->setTotp(Totp::parseSettings({}, attrValue));
109
        }
110

111
    } else if (attrName.startsWith("expir", Qt::CaseInsensitive)) {
112
        QDateTime expiry = resolveDate(kind, field.value("v"));
113
        if (expiry.isValid()) {
114
            entry->setExpiryTime(expiry);
115
            entry->setExpires(true);
116
        } else {
117
            qWarning() << QString("[%1] Invalid expiration date found: %2").arg(entry->title(), attrValue);
118
        }
119
    } else {
120
        if (kind == "date" || kind == "monthYear") {
121
            QDateTime date = resolveDate(kind, field.value("v"));
122
            if (date.isValid()) {
123
                entry->attributes()->set(attrName, date.toString(Qt::SystemLocaleShortDate));
124
            } else {
125
                qWarning()
126
                    << QString("[%1] Invalid date attribute found: %2 = %3").arg(entry->title(), attrName, attrValue);
127
            }
128
        } else if (kind == "address") {
129
            // Expand address into multiple attributes
130
            auto addrFields = field.value("v").toObject().toVariantMap();
131
            for (auto& part : addrFields.keys()) {
132
                entry->attributes()->set(attrName + QString("_%1").arg(part), addrFields.value(part).toString());
133
            }
134
        } else {
135
            if (entry->attributes()->hasKey(attrName)) {
136
                // Append a random string to the attribute name to avoid collisions
137
                attrName += QString("_%1").arg(QUuid::createUuid().toString().mid(1, 5));
138
            }
139
            entry->attributes()->set(attrName, attrValue, (kind == "password" || kind == "concealed"));
140
        }
141
    }
142
}
143

144
QString OpVaultReader::resolveAttributeName(const QString& section, const QString& name, const QString& text)
145
{
146
    // Special case for TOTP
147
    if (name.startsWith("TOTP_")) {
148
        return name;
149
    }
150

151
    auto lowName = name.toLower();
152
    auto lowText = text.toLower();
153
    if (section.isEmpty() || name.startsWith("address")) {
154
        // Empty section implies these are core attributes
155
        // try to find username, password, url
156
        if (lowName == "password" || lowText == "password") {
157
            return EntryAttributes::PasswordKey;
158
        } else if (lowName == "username" || lowText == "username") {
159
            return EntryAttributes::UserNameKey;
160
        } else if (lowName == "url" || lowText == "url" || lowName == "hostname" || lowText == "server"
161
                   || lowName == "website") {
162
            return EntryAttributes::URLKey;
163
        }
164
        return text;
165
    }
166

167
    return QString("%1_%2").arg(section, text);
168
}
169

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.