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 }