From 29ad7d96951a43af0aa51b769a21c624c5b24c97 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Wed, 26 Apr 2017 16:47:38 +0200 Subject: If silent Sparkle update impossible do not auto download updates With this change, Swift will check at the start if it has sufficient permissions to write to the location where Swift is currently installed and also check if it can change permissions of a temporary file to the permissions of the current Swift installation. This should prevent an authentication dialog on the exit of Swift which does not leave a single clue to what application the dialog belongs to. Test-Information: Verified that the log output indicates a possible silent update for admin users and the impossibility of a silent update for non-admin users and a Swift installed to /Applications folder. All unit tests pass on macOS 10.12.4 with Qt 5.4.2. Change-Id: I5f03358ac67630565b3c624da157b1eeea14356d diff --git a/SwifTools/AutoUpdater/AutoUpdater.h b/SwifTools/AutoUpdater/AutoUpdater.h index a125229..fdd3a91 100644 --- a/SwifTools/AutoUpdater/AutoUpdater.h +++ b/SwifTools/AutoUpdater/AutoUpdater.h @@ -28,6 +28,7 @@ namespace Swift { virtual void setAppcastFeed(const std::string& appcastFeed) = 0; virtual void checkForUpdates() = 0; virtual State getCurrentState() = 0; + virtual bool applicationInstallationLocationWritable() = 0; public: /** diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.h b/SwifTools/AutoUpdater/SparkleAutoUpdater.h index 48b75e5..26e08da 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.h +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.h @@ -21,11 +21,12 @@ namespace Swift { class SparkleAutoUpdater : public AutoUpdater { public: SparkleAutoUpdater(const std::string& appcastFeed); - ~SparkleAutoUpdater(); + ~SparkleAutoUpdater() override; - void setAppcastFeed(const std::string& appcastFeed); - void checkForUpdates(); - State getCurrentState(); + void setAppcastFeed(const std::string& appcastFeed) override; + void checkForUpdates() override; + State getCurrentState() override; + bool applicationInstallationLocationWritable() override; private: void setCurrentState(State updatedState); diff --git a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm index 4cf5837..b4a4c05 100644 --- a/SwifTools/AutoUpdater/SparkleAutoUpdater.mm +++ b/SwifTools/AutoUpdater/SparkleAutoUpdater.mm @@ -6,9 +6,15 @@ #include + +#include +#include + #include #include +#include + #include #include @@ -34,7 +40,13 @@ SparkleAutoUpdater::SparkleAutoUpdater(const std::string& appcastFeed) : d(new P [d->updater setAutomaticallyChecksForUpdates: true]; // Automatically check for an update after a day. [d->updater setUpdateCheckInterval: 86400]; - [d->updater setAutomaticallyDownloadsUpdates: true]; + + auto canDoSilentUpdates = applicationInstallationLocationWritable(); + [d->updater setAutomaticallyDownloadsUpdates: canDoSilentUpdates]; + + SWIFT_LOG(debug) << (canDoSilentUpdates ? + "The current running user has enough permissions to do a silent update." : + "The current running user has insufficient permissions to do a silent update.") << std::endl; setAppcastFeed(appcastFeed); } @@ -67,4 +79,80 @@ AutoUpdater::State SparkleAutoUpdater::getCurrentState() { return d->currentState; } +bool Swift::SparkleAutoUpdater::applicationInstallationLocationWritable() { + auto bundlePath = ns2StdString([[NSBundle mainBundle] bundlePath]); + + auto createTemporaryFile = [](const boost::filesystem::path& parentFolder) { + boost::optional tempFilePath; + boost::system::error_code error; + + if (!boost::filesystem::is_directory(parentFolder, error) || error) { + return tempFilePath; + } + auto uniquePath = boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%", error); + if (error) { + return tempFilePath; + } + auto testFilePath = parentFolder / uniquePath; + + boost::filesystem::ofstream testFile(testFilePath, std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc); + if (testFile) { + testFile.close(); + tempFilePath = testFilePath; + } + + return tempFilePath; + }; + + auto isDirectoryWritable = [&](const boost::filesystem::path& path) { + auto bundleTestFilePath = createTemporaryFile(path); + if (!bundleTestFilePath) { + return false; + } + + boost::system::error_code error; + if (!boost::filesystem::remove(bundleTestFilePath.get(), error) || error) { + // File did not exist in the first place or error while removing it. + return false; + } + + return true; + }; + + auto applyMatchingPermissions = [](const boost::filesystem::path& permissionsFrom, const boost::filesystem::path& applyTo) { + auto permissions = boost::filesystem::status(permissionsFrom).permissions(); + + boost::system::error_code error; + boost::filesystem::permissions(applyTo, permissions, error); + + return !error; + }; + + auto canChangePermissionsOnTemporaryFile = [&](const boost::filesystem::path& pathToCreateTemporaryFileUnder, const boost::filesystem::path& pathToTakePermissionsFrom) { + auto temporaryFilePath = createTemporaryFile(pathToCreateTemporaryFileUnder); + if (!temporaryFilePath) { + return false; + } + + boost::system::error_code error; + auto fileExists = boost::filesystem::exists(temporaryFilePath.get(), error); + if (!fileExists || error) { + return false; + } + + auto successfullPermissionCopy = applyMatchingPermissions(pathToTakePermissionsFrom, temporaryFilePath.get()); + + boost::filesystem::remove(temporaryFilePath.get(), error); + + return successfullPermissionCopy; + }; + + auto bundleBoostPath = boost::filesystem::path(bundlePath); + if (!isDirectoryWritable(bundleBoostPath.parent_path()) || !isDirectoryWritable(bundleBoostPath)) { + return false; + } + + return canChangePermissionsOnTemporaryFile(bundleBoostPath.parent_path(), bundleBoostPath); +} + } -- cgit v0.10.2-6-g49f6