mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
Use security-scoped bookmarks to keep track of data directory settings on macOS
This enables sandboxed apps to maintain access to user-selected items. In addition, for both sandboxed and nonsandboxed apps it can keep track of directories even if they are moved or renamed, and can remember access to directories in "sensitive" locations (such as the Documents folder or external drives). Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
This commit is contained in:
parent
7e8cf628e8
commit
710789b701
8 changed files with 382 additions and 7 deletions
|
|
@ -20,6 +20,12 @@
|
|||
#include "settings/Setting.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDir>
|
||||
#include <utility>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||
#endif
|
||||
|
||||
SettingsObject::SettingsObject(QObject* parent) : QObject(parent) {}
|
||||
|
||||
|
|
@ -78,9 +84,17 @@ std::shared_ptr<Setting> SettingsObject::getSetting(const QString& id) const
|
|||
return m_settings[id];
|
||||
}
|
||||
|
||||
QVariant SettingsObject::get(const QString& id) const
|
||||
QVariant SettingsObject::get(const QString& id)
|
||||
{
|
||||
auto setting = getSetting(id);
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// for macOS, use a security scoped bookmark for the paths
|
||||
if (id.endsWith("Dir")) {
|
||||
return { getPathFromBookmark(id) };
|
||||
}
|
||||
#endif
|
||||
|
||||
return (setting ? setting->get() : QVariant());
|
||||
}
|
||||
|
||||
|
|
@ -90,11 +104,105 @@ bool SettingsObject::set(const QString& id, QVariant value)
|
|||
if (!setting) {
|
||||
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||
return false;
|
||||
} else {
|
||||
setting->set(value);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// for macOS, keep a security scoped bookmark for the paths
|
||||
if (value.userType() == QMetaType::QString && id.endsWith("Dir")) {
|
||||
setPathWithBookmark(id, value.toString());
|
||||
}
|
||||
#endif
|
||||
|
||||
setting->set(std::move(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
QString SettingsObject::getPathFromBookmark(const QString& id)
|
||||
{
|
||||
auto setting = getSetting(id);
|
||||
if (!setting) {
|
||||
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||
return "";
|
||||
}
|
||||
|
||||
// there is no need to use bookmarks if the default value is used or the directory is within the data directory (already can access)
|
||||
if (setting->get() == setting->defValue() || QDir(setting->get().toString()).absolutePath().startsWith(QDir::current().absolutePath())) {
|
||||
return setting->get().toString();
|
||||
}
|
||||
|
||||
auto bookmarkId = id + "Bookmark";
|
||||
auto bookmarkSetting = getSetting(bookmarkId);
|
||||
if (!bookmarkSetting) {
|
||||
qCritical() << QString("Error changing setting %1. Bookmark setting doesn't exist.").arg(id);
|
||||
return "";
|
||||
}
|
||||
|
||||
QByteArray bookmark = bookmarkSetting->get().toByteArray();
|
||||
if (bookmark.isEmpty()) {
|
||||
qDebug() << "Creating bookmark for" << id << "at" << setting->get().toString();
|
||||
setPathWithBookmark(id, setting->get().toString());
|
||||
return setting->get().toString();
|
||||
}
|
||||
bool stale;
|
||||
QUrl url = m_sandboxedFileAccess.securityScopedBookmarkToURL(bookmark, stale);
|
||||
if (url.isValid()) {
|
||||
if (stale) {
|
||||
setting->set(url.path());
|
||||
bookmarkSetting->set(bookmark);
|
||||
}
|
||||
|
||||
m_sandboxedFileAccess.startUsingSecurityScopedBookmark(bookmark, stale);
|
||||
// already did a stale check, no need to do it again
|
||||
|
||||
// convert to relative path to current directory if `url` is a descendant of the current directory
|
||||
QDir currentDir = QDir::current().absolutePath();
|
||||
return url.path().startsWith(currentDir.absolutePath()) ? currentDir.relativeFilePath(url.path()) : url.path();
|
||||
}
|
||||
|
||||
return setting->get().toString();
|
||||
}
|
||||
|
||||
bool SettingsObject::setPathWithBookmark(const QString& id, const QString& path)
|
||||
{
|
||||
auto setting = getSetting(id);
|
||||
if (!setting) {
|
||||
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
qCritical() << QString("Error changing setting %1. Path doesn't exist.").arg(id);
|
||||
return false;
|
||||
}
|
||||
QString absolutePath = dir.absolutePath();
|
||||
QString bookmarkId = id + "Bookmark";
|
||||
std::shared_ptr<Setting> bookmarkSetting = getSetting(bookmarkId);
|
||||
// there is no need to use bookmarks if the default value is used or the directory is within the data directory (already can access)
|
||||
if (path == setting->defValue().toString() || absolutePath.startsWith(QDir::current().absolutePath())) {
|
||||
bookmarkSetting->reset();
|
||||
return true;
|
||||
}
|
||||
QByteArray bytes = m_sandboxedFileAccess.pathToSecurityScopedBookmark(absolutePath);
|
||||
if (bytes.isEmpty()) {
|
||||
qCritical() << QString("Failed to create bookmark for %1 - no access?").arg(id);
|
||||
// TODO: show an alert to the user asking them to reselect the directory
|
||||
return false;
|
||||
}
|
||||
auto oldBookmark = bookmarkSetting->get().toByteArray();
|
||||
m_sandboxedFileAccess.stopUsingSecurityScopedBookmark(oldBookmark);
|
||||
if (!bytes.isEmpty() && bookmarkSetting) {
|
||||
bookmarkSetting->set(bytes);
|
||||
bool stale;
|
||||
m_sandboxedFileAccess.startUsingSecurityScopedBookmark(bytes, stale);
|
||||
// just created the bookmark, it shouldn't be stale
|
||||
}
|
||||
|
||||
setting->set(path);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void SettingsObject::reset(const QString& id) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
#include <QVariant>
|
||||
#include <memory>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||
#endif
|
||||
|
||||
class Setting;
|
||||
class SettingsObject;
|
||||
|
||||
|
|
@ -119,7 +123,27 @@ class SettingsObject : public QObject {
|
|||
* \return The setting's value as a QVariant.
|
||||
* If no setting with the given ID exists, returns an invalid QVariant.
|
||||
*/
|
||||
QVariant get(const QString& id) const;
|
||||
QVariant get(const QString& id);
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
/*!
|
||||
* \brief Get the path to the file or directory represented by the bookmark stored in the associated setting.
|
||||
* \param id The setting ID of the relevant directory - this should not include "Bookmark" at the end.
|
||||
* \return A path to the file or directory represented by the bookmark.
|
||||
* If a bookmark is not valid or stored, use default logic (directly return the stored path).
|
||||
* This can attempt to create a bookmark if the path is accessible and the bookmark is not valid.
|
||||
*/
|
||||
QString getPathFromBookmark(const QString& id);
|
||||
/*!
|
||||
* \brief Set a security-scoped bookmark to the provided path for the associated setting.
|
||||
* \param id The setting ID of the relevant directory - this should not include "Bookmark" at the end.
|
||||
* \param path The new desired path.
|
||||
* \return A boolean indicating whether a bookmark was successfully set.
|
||||
* The path needs to be accessible to the launcher before calling this function. For example,
|
||||
* it could come from a user selection in an open panel.
|
||||
*/
|
||||
bool setPathWithBookmark(const QString& id, const QString& path);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Sets the value of the setting with the given ID.
|
||||
|
|
@ -207,6 +231,9 @@ class SettingsObject : public QObject {
|
|||
|
||||
private:
|
||||
QMap<QString, std::shared_ptr<Setting>> m_settings;
|
||||
#ifdef Q_OS_MACOS
|
||||
SecurityBookmarkFileAccess m_sandboxedFileAccess;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool m_suspendSave = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue