Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Description: Development ToolKit Core Utilities (DTK6 with Qt6)
Package: libdtk6core-dev
Architecture: any
Depends: libdtk6core( =${binary:Version}),
libdtk6core-bin( =${binary:Version}),
libdtkcommon-dev(>=5.6.16), libdtk6log-dev
Build-Profiles: <!nodtk6>
Description: Development ToolKit Core Devel Library (DTK6 with Qt6)
Expand Down Expand Up @@ -84,6 +85,7 @@ Description: Development ToolKit Core Utilities (DTK5 with Qt5)
Package: libdtkcore-dev
Architecture: any
Depends: libdtkcore5( =${binary:Version}),
libdtkcore5-bin( =${binary:Version}),
libdtkcommon-dev(>=5.6.16), libdtklog-dev
Build-Profiles: <!nodtk5>
Description: Development ToolKit Core Devel Library (DTK5 with Qt5)
Expand Down
6 changes: 4 additions & 2 deletions include/global/dconfigfile.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021 - 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2021 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand Down Expand Up @@ -35,7 +35,9 @@ class LIBDTKCORESHARED_EXPORT DConfigFile : public DObject{

enum Permissions {
ReadOnly,
ReadWrite
ReadWrite,
AuthorizedReadOnly,
AuthorizedReadWrite,
};

enum Visibility {
Expand Down
101 changes: 80 additions & 21 deletions src/dconfigfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCollator>

Check warning on line 21 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCollator> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 22 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QRegularExpression>

Check warning on line 23 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QRegularExpression> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QSet>

Check warning on line 24 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QSet> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <unistd.h>

Check warning on line 26 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <unistd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <pwd.h>

Check warning on line 27 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <pwd.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

// https://gitlabwh.uniontech.com/wuhan/se/deepin-specifications/-/issues/3

Expand Down Expand Up @@ -386,8 +387,13 @@
{
DConfigFile::Permissions p = DConfigFile::ReadOnly;
const auto &tmp = values[key][QLatin1String("permissions")].toString();
if (tmp == QLatin1String("readwrite"))
if (tmp == QLatin1String("readwrite")) {
p = DConfigFile::ReadWrite;
} else if (tmp == QLatin1String("authorizedreadonly")) {
p = DConfigFile::AuthorizedReadOnly;
} else if (tmp == QLatin1String("authorizedreadwrite")) {
Comment on lines +393 to +394

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Metadata entries with permissions: "authorizedreadonly" are parsed as AuthorizedReadOnly, but cacheValue() only rejects exactly ReadOnly; cached values therefore override the default for authorized-read-only settings just like writable settings. The public cache access path has no authorization check, so unauthorized callers can use the cache to override these settings.

Triggers: When a configuration item uses authorizedreadonly metadata and a cache contains a value for it.

Suggested fix: Handle AuthorizedReadOnly separately in cacheValue() and enforce the intended authorization policy before accepting a cached override.

p = DConfigFile::AuthorizedReadWrite;
}

return p;
}
Expand Down Expand Up @@ -1080,7 +1086,9 @@
@~english
@fn void setCachePathPrefix(const QString &prefix) = 0;
@brief Set cache's prefix path, it's access permissions is considered by caller,
and it needs to distinguish the paths of different caches by caller.
and it needs to distinguish the paths of different caches by caller. When a
prefix is set, load the legacy cache first and then overlay the cache stored
under the prefix. Saving always uses the prefix path.
@param prefix cache's prefix path.
*/

Expand All @@ -1106,9 +1114,10 @@
return values.keyList();
}

inline QString applicationCacheDir(const QString &localPrefix, const QString &suffix) const
inline QString applicationCacheDir(const QString &localPrefix, const QString &suffix,
const QString &pathPrefix) const
{
QString prefix(cachePrefix);
QString prefix(pathPrefix);
if (prefix.isEmpty()) {
// If target user is current user or system user, then get the home path by environment variable first.
QString homePath;
Expand All @@ -1129,16 +1138,17 @@

inline QString applicationCacheDir(const QString &localPrefix) const
{
return applicationCacheDir(localPrefix, QString());
return applicationCacheDir(localPrefix, QString(), cachePrefix);
}

inline QString cacheDir(const QString &basePath) {
QDir dir(basePath + configKey.subpath);
return dir.filePath(configKey.fileName + FILE_SUFFIX);
}

inline QString globalCacheDir(const QString &localPrefix) const {
QString prefix(cachePrefix);
inline QString globalCacheDir(const QString &localPrefix, const QString &pathPrefix) const
{
QString prefix(pathPrefix);
if (prefix.isEmpty()) {
// TODO `DSG_APP_DATA` is not set and `appid` is not captured in `DStandardPaths::path`.
QString appDataDir = DStandardPaths::path(DStandardPaths::DSG::AppData);
Expand All @@ -1161,20 +1171,33 @@
return QDir::cleanPath(QString("%1/%2/%3").arg(localPrefix, prefix, configKey.appId));
}

QString getCacheDir(const QString &localPrefix = QString())
QString getCacheDir(const QString &localPrefix, const QString &pathPrefix) const
{
if (isGlobal()) {
const QString &dir = globalCacheDir(localPrefix);
const QString &dir = globalCacheDir(localPrefix, pathPrefix);
if (!dir.isEmpty())
return dir;

// Not supported the global config, fallback the config cache data to user directory.
return applicationCacheDir(localPrefix, "-fake-global");
return applicationCacheDir(localPrefix, "-fake-global", pathPrefix);
} else {
return applicationCacheDir(localPrefix);
return applicationCacheDir(localPrefix, QString(), pathPrefix);
}
}

QString getCacheDir(const QString &localPrefix = QString()) const
{
return getCacheDir(localPrefix, cachePrefix);
}

enum class CacheLoadStatus {
NotFound,
Loaded,
Invalid
};

CacheLoadStatus loadCache(const QString &dir, QSet<QString> *loadedKeys = nullptr);

bool load(const QString &localPrefix = QString()) override;

bool isGlobal() const override
Expand Down Expand Up @@ -1235,28 +1258,62 @@

bool DConfigCacheImpl::load(const QString &localPrefix)
{
// cache 文件要严格匹配 subpath
const QString &dir = getCacheDir(localPrefix);
if (dir.isEmpty()) {
return true;
const QString newCacheDir = getCacheDir(localPrefix);
if (cachePrefix.isEmpty())
return loadCache(newCacheDir) != CacheLoadStatus::Invalid;

const QString legacyCacheDir = getCacheDir(localPrefix, QString());
if (legacyCacheDir == newCacheDir)
return loadCache(newCacheDir) != CacheLoadStatus::Invalid;

QSet<QString> legacyKeys;
const auto legacyStatus = loadCache(legacyCacheDir, &legacyKeys);
if (legacyStatus == CacheLoadStatus::Invalid) {
qCWarning(cfLog, "Failed to load legacy cache from \"%s\"; continue with the new cache.",
qPrintable(legacyCacheDir));
legacyKeys.clear();
}

QSet<QString> newKeys;
const auto newStatus = loadCache(newCacheDir, &newKeys);
if (newStatus == CacheLoadStatus::Invalid)
return false;

if (legacyStatus == CacheLoadStatus::Loaded) {
for (const QString &key : legacyKeys) {
if (!newKeys.contains(key)) {

Check warning on line 1284 in src/dconfigfile.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Consider using std::any_of algorithm instead of a raw loop.
cacheChanged = true;
break;
}
}
}

return true;
}

DConfigCacheImpl::CacheLoadStatus DConfigCacheImpl::loadCache(const QString &dir,
QSet<QString> *loadedKeys)
{
// cache 文件要严格匹配 subpath
if (dir.isEmpty())
return CacheLoadStatus::NotFound;

QScopedPointer<QFile> cache(loadFile(dir,
configKey.subpath,
configKey.fileName + FILE_SUFFIX,
false));
if (!cache) {
return true;
}
if (!cache)
return CacheLoadStatus::NotFound;

const JsonParseResult pr = loadJsonFile(cache.data());
const QJsonDocument &doc = pr.doc;

if (doc.isObject()) {
const QJsonObject &root = doc.object();
if (!checkMagic(root, MAGIC_CACHE))
return false;
return CacheLoadStatus::Invalid;
if (!checkVersion(root, DConfigFile::supportedVersion()))
return false;
return CacheLoadStatus::Invalid;

auto &&contents = root[QLatin1String("contents")].toObject();
auto i = contents.constBegin();
Expand All @@ -1273,9 +1330,11 @@
jsonValueToVariant(valueField, isOriginalValueFloat(pr.raw, i.key(), valueField)));
}
values.update(i.key(), vh);
if (loadedKeys)
loadedKeys->insert(i.key());
}
}
return true;
return CacheLoadStatus::Loaded;
}

bool DConfigCacheImpl::save(const QString &localPrefix, QJsonDocument::JsonFormat format, bool sync)
Expand Down
Loading
Loading