How to Trash items that are not owned by the current user, the same way the Finder does? - macos

I am writing a tool that offers the option to trash selected items (files, folders). Usually, I'd call -[NSFileManager trashItemAtURL:...] for each of those items, as it's also explained in this and in this SO question.
However, these do not work when trying to delete files from a directory owned by a different user, such as root. My tool shall offer the same option as the Finder in this case, i.e. ask the user to authorize the operation by providing the credentials of an Admin user, and then my app would move the items to the Trash like the Finder does.
I've tried solving this by using a privileged helper, as outline by the EvenBetterAuthorizationSample example code, using launchd, SMJobBless and XPC Services.
The problem with that is, however, that the privileged helper runs as the root user, without knowledge of the current user my app runs under. The result is that, when it trashes a file, it ends up in the root user's Trash folder and not, as the Finder would do it, in the user's Trash folder.
How do I solve this, i.e. how do I move items not owned by the user to the current user's trash instead of the root user's Trash?
Is there some trick I can use that would let me keep using one of the existing trash oder recycle functions?
Doing the move myself is not going to work properly because for Put Back to work, the Trash's .DS_Store file would need to be updated, and there's no API for that, AFAIK.

I have almost found a solution:
Analysis
When the helper is run, e.g. from launchd, or via AuthorizationExecuteWithPrivileges (under macOS 10.15), it may be running as root, with no knowledge of the logged-in user, hence it cannot determine the user's Trash folder.
Oddly, the Environment variables (see man env) may even show the current user's name and home dir, yet the real user id, which one can query with getuid(), will return 0 (root), which also results in NSUserName()and NSHomeDirectory() returning the root user's information. And it appears that trashItemAtURL and related functions rely on NSHomeDirectory() to determine the Trash folder location.
Half-working solution
Fortunately, there is a way to change the real user id, with setreuid.
Therefore, in my testing, when I call setreuid (501, 0) (501 being the uid of the currently logged-in user), then trashItemAtURL does move the file to the user's Trash folder, indeed, along with automatic renaming where necessary.
However, this does not make the Put Back work, the way it would when trashing the same file using the Finder.
Making Put Back work
Looks like the reason for Put Back not working comes from a deeper issue: It appears to be a long-standing bug in the macOS framework, see this bug report.
Which basically means: This is the best we can get out of it until Apple fixes the underlying bug.
The only working alternative to make Put Back work is to ask the Finder to trash the items using AppleEvents / AppleScript.

Related

Sandbox Issue: Loading a Movie automatically when a document is loaded

I have a somewhat unique situation, and while I understand what the problem is, I'm not quite sure what the best workaround may be so I thought I would consult the hive mind. :)
Scenario:
I am working on an application (OS X) that is document based and uses Quicktime. When a new document is created, the user selects a movie to view within the document window. The user can then save this document. When the user later attempts to open the document, nothing happens. It just doesn't open.
What I know so far:
The problem is with the way Sandbox operates, and my limited experience working with it (my first attempt.) I have enabled Sandbox and have the app permissions set to allow any user initiated file operations, and also added permissions for the app to access the Movies folder.
When the user loads a document and the Movie is in the Movies folder, it opens and works correctly. However, if the Movie is anywhere else on the users drive, it does NOT work and performs as described above.
I realized this is because while the user can open the document (user initiated), sandbox is blocking the application when it attempts to then open the linked movie automatically (NOT user initiated) unless the movie is located within the Movies folder.
Since the application is designed to work with large files, it seems like a big limitation to force users to copy the files into their movie folder and leave them there.
(I have not yet tried working with a movie located on an external drive, but suspect that it would work as it's not a system drive.)
Question:
Is there a way to work around this, since the user initiated the task of opening the movie originally?
If not, is there perhaps a way to have the application ask the user to confirm that the movie should be loaded... perhaps like presenting an OPEN dialog box with the movie already selected so they can just click OK? This might be the best solution.
Another option might be to automatically copy the movie into the Movies folder when the user initially selects it, but again it's not really idea for my situation...
Any thoughts would be appreciated!
As you describe it, your App has privileges to access files within its own container and in the Movies folder. To access arbitrary movie files you have to:
Let the user initiate the access to a file, i.e. when a new document is created the user has to use a file chooser to access a movie file. The privileges of your sandbox are then extended to allow access to the selected file.
Those access rights are temporarily. To persist them over App restarts you have to save and restore the access right via security scoped bookmarks.
Refer to Apples documentation on PowerBox and on Security Scoped Bookmarks
Also Session 710 from WWDC 2013 might be helpful.

Distinguish things like .app/.xcodeproj and actual folders in Cocoa

I am trying to build an alternative file manager that works similar to the default Finder in Cocoa.
As you can imagine, the app needs to show a list of files/subfolders in certain directory, and when the user click on an item, it checks whether it's a folder or a file that a user has clicked. If it's a folder, the app simply shows the content of the folder. If it's a file, it will be opened with the default application.
I used NSFileManager.file​Exists(at​Path:​is​Directory:​) to determine if an item at certain path is a folder. This works well in most cases, but for things like something.app or project.xcodeproj, they are also considered as directories according to the method.
I know it's true that technically they are just folders, but is there a way in Cocoa to distinguish them from actual folders?
Use (NS)URL.
Get the values for resource keys isDirectoryKey and isPackageKey via resourceValues(forKeys.
In case of bundles isPackage is true.

Securely delete NSURL file and show percent in NSProgressIndicator

I am trying to create a "document shredder" application for Mac. I have the NSURL of the files that the user selected, but how would I go about securely deleting said files and passing the progress along to a progress bar?
You want to look at the posix layer calls for file I/O, with those you can overwrite files and implement any algorithm for secure deletion you choose.
If you just need to securely delete the file and are not concerned over showing progress in a GUI you can invoke the srm (secure remove) command using NSTask. For details on the srm enter man srm in a Terminal window.
If you do want to show progress in a GUI then you can convert the output of that command, captured with NSTask and display it as you choose. Or Apple provide the source to srm, you can find it here on the Apple Open Source site and you can incorporate code/algorithms from that into your own code and provide progress indication in your GUI.
HTH
There is no direct way to "securely delete" files from the Cocoa API.
You can use NSFileManager to move files into the trash can, which then leaves it up to the user to delete, or securely delete, the files via the Finder menu.
You could also use NSFileManager to move files into the trash and then use AppleScript to send Finder a message to delete/securely delete files, however, this is generally not a good idea unless your user has specifically requested you empty their trash as you may delete things they are not ready to have deleted.
Otherwise, there does not seem to be an easy or direct way to accomplish a secure delete of files as with the standard API there is no guarantee that the bytes you write out to disk are going to overwrite the ones that existed before.
You may also want to check out this previous question here on SO in relation to why secure delete is not that secure:
how to write back to an existing file, ensuring the bits on the disk get overwritten in OS X

How to edit specific files in /Library folder on Mac with Apple Sandbox?

From Apple's documentation, I didn't understand if there's any way to get pre-approved entitlements to edit files in a specific folder located in the system /Library folder, without asking the user to choose this specific location via NSOpenPanel or similar.
I don't mind, and would actually like, to ask for the user's credentials to make such a change, at specific points in the application's lifetime; but letting the user choose which folder to modify just doesn't make sense in my case.
Found it - the application needs a File Access Temporary Exception entitlement.

Going over all user profiles during installation

I'm creating a Windows application that needs to add an entry to the Send-To explorer context menu, and needs to do so for all users. Since the Send-To folder is specific for each user, with no common folder for all users, I'm left with two choices:
I can go over all user profiles, as well as the default user profile, find the SendTo folder and add the shortcut to it. This will ensure the shortcut is deleted during uninstall.
Or, I can make sure the shortcut is in the SendTo folder each time a user logs in (by adding my application to the start-up folder of all users). This will make my life a lot easier during installation, but when the application is uninstalled, all those shortcuts will not be removed.
So either way, I need a way to find the SendTo folder of all users. I can scan HKEY_USERS and find the SendTo folder of each user (it might not be in the default location, the user can move it), but how do I find out the user profile's root folder? The registry has something like %USERPROFILE\AppData\Roaming... for the SendTo folder. How can I figure out what %USERPROFILE% is for another user?
Thanks.
Easier approach: use the launch sequence of the exe itself to check whether the shortcut it present on launch, and create it if not.
Uninstall for all users is best handled using ActiveSetup which will run "something runnable" once for each user logging onto the machine. In your case a simple batch command could do the job.
If you do chose this uninstall approach, you must make sure that your msi installer checks for this uninstall key and deletes it on install - otherwise you have a delete operation scheduled for the shortcut the next time a user logs on.
Also keep in mind that each install should use a different entry in ActiveSetup to ensure that the shortcut creation is re-run for a user who has had it uninstalled already. This last part might be slightly incomprehensible before you read more about ActiveSetup: http://www.etlengineering.com/installer/activesetup.txt
You can create a custom action inside MSI, which will go through all user profiles and remove your shortcuts from SendTo folder. All users profile you can find, just scan all folders in %systemdrive%\Users folders in Windows7 (Vista), or Documents and Settings in Windows XP.
Or you can use ActiveSetup mechanism for this purpose, create some script (application) which remove your shortcut from SendTo folder, when user will log in to system next time.

Resources