Warning, /graphics/krita/libs/macosutils/KisMacosSecurityBookmarkManager.mm is written in an unsupported language. File is not indexed.

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2023 Ivan Santa MarĂ­a <ghevan@gmail.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #import "KisMacosSecurityBookmarkManager.h"
0008 
0009 #import <Foundation/Foundation.h>
0010 #import <Security/SecCode.h>
0011 
0012 #ifdef KIS_STANDALONE
0013 #import <AppKit/AppKit.h>
0014 #endif
0015 
0016 #include <QStandardPaths>
0017 #include <QSettings>
0018 #include <QHash>
0019 #include <QVariant>
0020 #include <QString>
0021 #include <QUrl>
0022 #include <QMetaEnum>
0023 
0024 #include <QMessageBox>
0025 #include <QFileDialog>
0026 
0027 #include <QDebug>
0028 
0029 #include <klocalizedstring.h>
0030 
0031 #include "KisMacosEntitlements.h"
0032 
0033 
0034 #if ! __has_feature(objc_arc)
0035 #error "Enable ARC to compile this file"
0036 #endif
0037 
0038 Q_GLOBAL_STATIC(KisMacosSecurityBookmarkManager, s_instance)
0039 
0040 class KisMacosSecurityBookmarkManager::Private
0041 {
0042 public:
0043     Private()
0044         : securedFiles(QHash<QString,QString>())
0045         , entitlements(KisMacosEntitlements())
0046     {
0047     }
0048 
0049     ~Private()
0050     {
0051     }
0052 
0053     KisMacosEntitlements entitlements;
0054     QHash<QString, QString> securedFiles;
0055 };
0056 
0057 KisMacosSecurityBookmarkManager* KisMacosSecurityBookmarkManager::instance()
0058 {
0059     return s_instance;
0060 }
0061 
0062 KisMacosSecurityBookmarkManager::KisMacosSecurityBookmarkManager()
0063     : m_d(new Private())
0064 {
0065     loadSecurityScopedResources();
0066     startAccessingSecurityScopedResources();
0067 }
0068 
0069 KisMacosSecurityBookmarkManager::~KisMacosSecurityBookmarkManager()
0070 {
0071     stopAccessingSecurityScopedResources();
0072 }
0073 
0074 bool KisMacosSecurityBookmarkManager::parentDirHasPermissions(const QString &path)
0075 {
0076     bool contained = false;
0077     Q_FOREACH(QString key, m_d->securedFiles.keys()) {
0078         if(path.contains(key)) {
0079             contained = true;
0080             break;
0081         }
0082     }
0083     return contained;
0084 }
0085 
0086 void KisMacosSecurityBookmarkManager::createBookmarkFromPath(const QString &path, const QString &refpath, SecurityBookmarkType type)
0087 {
0088     if(path.isEmpty()) {
0089         return;
0090     }
0091     NSURLBookmarkCreationOptions
0092         options = m_d->entitlements.hasEntitlement(KisMacosEntitlements::Entitlements::BookmarkScopeApp) ? NSURLBookmarkCreationWithSecurityScope : 0;
0093     NSError *err = nil;
0094 
0095     NSString *pathEscaped = [path.toNSString() stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
0096     NSURL *url = [NSURL URLWithString:pathEscaped];
0097 
0098     NSURL *refurl = nil;
0099     if (!refpath.isEmpty()) {
0100         NSString *refpathEscaped = [refpath.toNSString() stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
0101         refurl = [NSURL URLWithString:refpathEscaped];
0102     }
0103 
0104     // working with paths as strings
0105     // this assumes user used NSOpenPanel on the path itself
0106 
0107     // Create bookmark
0108     NSData *bookmark = [url bookmarkDataWithOptions: options includingResourceValuesForKeys: nil relativeToURL: refurl error:&err];
0109     NSString *bookmarkString = [bookmark base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
0110     NSLog(@" path: %@\n err: %@", pathEscaped, err);
0111 
0112     if (!err) {
0113         [url startAccessingSecurityScopedResource];
0114 
0115         QString base64Data = QString::fromNSString(bookmarkString);
0116         // write to file
0117         const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0118         QSettings kritarc(configPath + QStringLiteral("/securitybookmarkrc"), QSettings::NativeFormat);
0119 
0120         // get array current size
0121         int size = kritarc.beginReadArray(securityBookmarkTypeToString(type));
0122         kritarc.endArray();
0123 
0124         kritarc.beginWriteArray(securityBookmarkTypeToString(type));
0125         kritarc.setArrayIndex(size);
0126         kritarc.setValue("path",path);
0127         kritarc.setValue("base64", base64Data);
0128         kritarc.endArray();
0129 
0130         // Finally add to hashmap
0131         m_d->securedFiles[path] = base64Data;
0132     }
0133 
0134     return;
0135 }
0136 
0137 void KisMacosSecurityBookmarkManager::loadKeysFromArray(SecurityBookmarkType arrayKey)
0138 {
0139     const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0140     QSettings kritarc(configPath + QStringLiteral("/securitybookmarkrc"), QSettings::NativeFormat);
0141 
0142     int size = kritarc.beginReadArray(securityBookmarkTypeToString(arrayKey));
0143     for (int i = 0; i < size; i++) {
0144         kritarc.setArrayIndex(i);
0145         QString key = kritarc.value("path").toString();
0146         m_d->securedFiles[key] = kritarc.value("base64").toString();
0147     }
0148     kritarc.endArray();
0149 }
0150 
0151 void KisMacosSecurityBookmarkManager::loadSecurityScopedResources()
0152 {
0153     loadKeysFromArray(SecurityBookmarkType::File);
0154     loadKeysFromArray(SecurityBookmarkType::Directory);
0155 }
0156 
0157 QUrl KisMacosSecurityBookmarkManager::decodeBookmarkToURL(QString encodedPath) {
0158     NSString *bookmarkString = encodedPath.toNSString();
0159     NSData *decodedBookmark = [[NSData alloc] initWithBase64EncodedString: bookmarkString options:NSDataBase64DecodingIgnoreUnknownCharacters];
0160 
0161     NSError *err = nil;
0162 
0163     // Resolve the decoded bookmark data into a security-scoped URL.
0164     NSURL *url =[NSURL URLByResolvingBookmarkData: decodedBookmark options: NSURLBookmarkResolutionWithSecurityScope relativeToURL: nil bookmarkDataIsStale: nil error:&err];
0165     return QUrl::fromNSURL(url);
0166 }
0167 
0168 void KisMacosSecurityBookmarkManager::startAccessingSecurityScopedResources()
0169 {
0170     Q_FOREACH(QString encodedPath, m_d->securedFiles.values()) {
0171         NSURL *url = decodeBookmarkToURL(encodedPath).toNSURL();
0172         [url startAccessingSecurityScopedResource];
0173     }
0174 }
0175 
0176 void KisMacosSecurityBookmarkManager::stopAccessingSecurityScopedResources()
0177 {
0178     Q_FOREACH(QString encodedPath, m_d->securedFiles.values()) {
0179         NSURL *url = decodeBookmarkToURL(encodedPath).toNSURL();
0180         [url stopAccessingSecurityScopedResource];
0181     }
0182 }
0183 
0184 bool KisMacosSecurityBookmarkManager::requestAccessToDir(const QString &path)
0185 {
0186     bool fileSelected = false;
0187     QUrl fileURL = QUrl(path);
0188 
0189     NSString *nsStdPath = path.toNSString();
0190 #ifdef KIS_STANDALONE
0191     NSAlert *alert = [[NSAlert alloc] init];
0192     [alert setAlertStyle:NSAlertStyleInformational];
0193     [alert setMessageText:[NSString stringWithFormat:@"The file %@ is located in a directory where the application has no permission, please give the permission to the container folder or a higher one to allow krita to save temporary bakcups next to your file", [nsStdPath lastPathComponent]]];
0194     [alert runModal];
0195 
0196 
0197     NSOpenPanel *panel = [NSOpenPanel openPanel];
0198     panel.canChooseFiles = false;
0199     panel.canChooseDirectories = true;
0200     NSURL *startLoc = [[NSURL alloc] initFileURLWithPath:nsStdPath ];
0201     panel.directoryURL = startLoc;
0202 
0203     if ([panel runModal] == NSModalResponseOK) {
0204         static NSURL *fileUrl = [panel URL];
0205         NSString *pathString = [fileUrl absoluteString];
0206 
0207         QString filepath = QString::fromNSString(pathString);
0208         createBookmarkFromPath(filepath, QString(), SecurityBookmarkType::Directory);
0209         fileSelected = true;
0210     }
0211 
0212 #else
0213     QMessageBox msgBox;
0214     msgBox.setText(i18n("The file %1 is located in a directory where the application has no permissions, please give krita permission to this directory or a higher one to allow krita to save temporary backups next to your file", fileURL.fileName()));
0215     msgBox.setInformativeText(i18n("The directory you select will grant krita permissions to all files and directories contained in it"));
0216     msgBox.setStandardButtons(QMessageBox::Ok);
0217     msgBox.setDefaultButton(QMessageBox::Ok);
0218     int ret = msgBox.exec();
0219 
0220     QUrl dirUrl = QFileDialog::getExistingDirectoryUrl(0, QString(), QUrl(path));
0221 
0222     if (!dirUrl.isEmpty()) {
0223         QString filepath = dirUrl.toDisplayString(QUrl::None);
0224         createBookmarkFromPath(filepath, QString(), SecurityBookmarkType::Directory);
0225         fileSelected = true;
0226     }
0227 #endif
0228     return fileSelected;
0229 }
0230 
0231 QString KisMacosSecurityBookmarkManager::securityBookmarkTypeToString(const SecurityBookmarkType type)
0232 {
0233     return QMetaEnum::fromType<SecurityBookmarkType>().valueToKey(type);
0234 }
0235 
0236 void KisMacosSecurityBookmarkManager::addBookmarkAndCheckParentDir(const QUrl &url)
0237 {
0238     const QString path = url.toDisplayString(QUrl::None);
0239     qDebug() << "1 inserting to sandbox" << url << path;
0240     if (!parentDirHasPermissions(path)) {
0241         // we can't force the user to select a directory in particular
0242         // we add the bookmark even if the file root directory was seleted
0243         createBookmarkFromPath(path, QString());
0244 
0245         requestAccessToDir(path);
0246     }
0247 }
0248 
0249 void KisMacosSecurityBookmarkManager::slotCreateBookmark(const QString &path)
0250 {
0251     // necessary convert to get proper location
0252     QUrl url = QUrl::fromLocalFile(path);
0253     QString pathString = url.toDisplayString();
0254     if (!m_d->securedFiles.contains(pathString)) {
0255         createBookmarkFromPath(pathString, QString());
0256     }
0257 }
0258 
0259 bool KisMacosSecurityBookmarkManager::isSandboxed()
0260 {
0261     return m_d->entitlements.sandbox();
0262 }