Authority Issues: Gotchas with the Security Framework

You see those pesky authorization dialogs every so often, right? "Finder requires that you type your password." "BBEdit requires that you type your password." This means the application involved is using the Security framework to perform an action with, say, administrative privileges that it wouldn’t normally have.

If you turn down the Details disclosure triangle in such a dialog, you see the name of the app and an arcane Requested Right string.

These rights should be added to what’s called the "policy database," stored in the file /etc/authorization, when the application that wants to use them is first installed. For a full explanation, see Performing Privileged Operations With Authorization Services, but for this post, I’m interested in the API you use to add those rights.

Here’s the example given in the AuthorizationDB.h header in the Security framework.

OSStatus status =
   AuthorizationRightSet(
      NULL,
      "com.ifoo.ifax.send",
      CFSTR(kAuthorizationRuleIsAdmin),
      CFSTR("Authorize sending of a fax"),
      NULL,
      NULL);

Looks reasonable, right?

Bzzzt. Sorry! If you actually try to run this code, at least on Mac OS X 10.4 "Tiger," you get a −2147418108 error. What’s a −2147418108 error? No clue. But it’s not good.

The first parameter is of type AuthorizationRef. You get a valid authorization reference if the user types her password into one of those dialogs, but you don’t need special permission to add a new right to the policy database. Hence the null value in the example. But it doesn’t work.

Here’s what you need to do instead:

AuthorizationRef authorization = NULL;

OSStatus result =
   AuthorizationCreate(
      NULL, NULL,
      kAuthorizationFlagDefaults,
      &authorization);

if (result == noErr)
{
   result =
      AuthorizationRightSet(
         authorization,
         "com.ifoo.ifax.send",
         CFSTR(kAuthorizationRuleAuthenticateAsAdmin),
         CFSTR("Authorize sending of a fax"),
         NULL,
         NULL);

   AuthorizationFree(
      authorization,
      kAuthorizationFlagDefaults);
}

AuthorizationCreate() actually has a whole bunch of options you can use, but here I’m using it with minimal values to create a blank, good-for-nothing AuthorizationRef. Even so, that’s good enough for AuthorizationRightSet(). With this code, there’s no −2147418108 error.

So we’re done? Not yet!

You’ll notice above that I changed the third, "rule" parameter of AuthorizationRightSet() from the kAuthorizationRuleIsAdmin in the original sample to kAuthorizationRuleAuthenticateAsAdmin. With that rule, you are shown an authorization dialog when I use AuthorizationCreate() like so:

AuthorizationItem myItems =
   { "com.ifoo.ifax.send", 0, NULL, 0 };

AuthorizationRights myRights =
   { 0, &myItems };

AuthorizationFlags myFlags =
   kAuthorizationFlagInteractionAllowed |
   kAuthorizationFlagExtendRights;

AuthorizationRef authorization = NULL;

OSStatus result =
   AuthorizationCreate(&myRights, kAuthorizationEmptyEnvironment,
      myFlags, &authorization);

This call’s a lot more complicated, eh? But it boils down to trying to get a valid authorization for the right we added.

Here’s the dialog you see in response:

Authorization dialog with erroneous caption

Oops! If you fill in the fourth, "description key" parameter of AuthorizationRightSet() with a string as shown in the header example, that "description key" is concatenated with the default system prompt in any authorization dialog that refers to that right.

Passing NULL for the "description key" parameter uses just the default system prompt, which works much better.

Now we’re done.

File Imprecations

There’s no dedicated widget in Cocoa for specifying and displaying a file.

Both Cocoa and Carbon apps tend to cobble together custom UI for the task. A “Save” or “Open” button that triggers a Navigation Services dialog, plus a path text field, and sometimes an icon. Apps tend to be internally consistent – CodeWarrior uses the same aggregate widget everywhere, for instance – but none of them quite match each other.

Cocoa apps often do let you edit the path by hand. It seems to me that this reflects the greater NeXT reliance on the command line, and I can see the utility of it sometimes. But I dislike the potential to erase a path by hitting the delete key by mistake. It also seems to me that this sort of thing is better suited to developer apps, where the user can more safely be assumed to have command line expertise.

Carbon apps, with the old Toolbox deprecation of file paths, generally never allow users to type in the path of the file they want: you must use the Open or Save dialogs to specify the file, though the application might then show you the path afterward.

I can actually see something in-between: maybe in the Save/Open dialog, there is a text field, which reflects the full path of the file/folder you’ve currently chosen. You can type in it, and once a name matches, the Navigation Services portion of the dialog moves to that location specified by your typing. Maybe that’s overkill, but it is the kind of marriage of command-line and UI that I’d really like to see. Toss in tab-completion, and you’re all set!

I’m thinking about this because I’m thinking about how I would modify the UI of TADS Workbench for Macintosh if (more like when) I decide to take over its maintenance. I had my own widget in my Isthmus framework to specify and show a file – I even had a specialization of that widget to deal with special file paths, like the “{Compiler}” and “{Project}” paths in CodeWarrior. I will either have to redo that work for TADS Workbench, discard the concept completely, or – most likely – figure out a more scaled-down version. After all, it was trying to make things perfect that doomed Isthmus.

High Noon: The Shaming of MoreIsBetter

An image well, a Classic application, and a pair of utility libraries walk into a bar….

Hm, not bad – the punchline is left as an exercise for the reader – but I have in mind not so much humor as some sort of confrontation or showdown:

The Find_icon library keeps one hand poised above its holster and eyes its rival, the MoreIsBetter library, standing across the dusty town square….

Who wins at high noon?

Well, let’s back up a step: what are they fighting about? A demure image well stands to one side, hoping against hope that someone will bring it the file icon it so richly deserves.

Getting a file icon can be very easy indeed, nothing to fight over: GetIconRefFromFile() works from 8.5 on, including OS X.

But if you want to support earlier than 8.5 (and I do, for reasons I won’t go into right now), you need a way to get that file icon for every kind of file system object: a file, a folder, a file with a custom icon, etc. etc.

The first library I found that solves this problem is Find_icon, by James W. Walker, available from http://jwwalker.com/pages/find_icon.html. As with the best of these kinds of libraries, it has a rich history of support and fixes.

However, its contender, MoreIsBetter, available from http://developer.apple.com/samplecode/MoreIsBetter/MoreIsBetter.html, should have left it lying in the dirt after the first shot. Why? It has an even more illustrious history, is officially supported by Apple, and has been worked on by some of the sharpest developers there. But, for finding file icons, it can’t shoot straight.

For one, it doesn’t work properly in Panther. Now, this may be because I’m using a Classic application to talk to the OS X Finder (sending an Apple event to the Finder is the generally accepted way to get a file icon). Classic applications certainly aren’t Apple’s priority any more, but I think there’s a decent chance it won’t work from OS X apps either – I would bet it just hasn’t been officially tested under Panther.

For two, when I used it under earlier OSes, I found an egregious programming error – something I caught in my first try. Isn’t anyone else testing this thing? (I’ll send in a bug report, don’t worry.)

For three, even under older OSes, the icons it retrieves are garbage images. Unusable. But it reports no error. And I don’t have time to debug it right now.

I suspect no one’s testing this because MoreIsBetter is currently listed under “Legacy Technologies”. While MoreIsBetter does support older OSes (I was able to get it to compile for 68K), it does proudly support Carbon and OS X. In fact, it provides many functions that Carbon doesn’t and that would be painful to code yourself.

So why is it abandoned under “Legacy”? I don’t know, and it’s a shame. This gunslinger shouldn’t be retired just yet.

Addendum: I’ve been looking around for Carbon resources the same way I’m gathering Cocoa resources on the Web, and MoreIsBetter is a good example of why I can’t find them: the best examples are often labeled “legacy” or merely “Macintosh”, since they date back to a time when the Toolbox APIs that preceded Carbon were all there was of Macintosh APIs. So when I do finally present my Carbon links, don’t expect them to label themselves that way.

Exchange Files Gotcha

Summary: FSpExchangeFiles() will happily exchange two files even if one is already open for writing, which can lead to some bad behavior.

In your cool whiz-bang application, you’re implementing “Save As” with the following steps:

  • Save document data to a temporary file.
  • If file exists at “Save As” location, swap the temporary file with the real file.
  • Delete the temporary file.

This works swimmingly — unless the real file is open for writing in another application. Let’s call that app “BusyBody”.

You’d think, in that case, you’d get an OS error when you attempt to swap files, wouldn’t you? The filesystem should prevent such access when a file’s open…shouldn’t it?

Turns out it doesn’t prevent such access. It will happily swap open-for-writing files all the live-long day. You won’t get an error until you get to the last step. Then, the OS will tell you the temp file is “busy” (fBsyErr), because, as far as it’s concerned, the temp file is open in BusyBody.

So, if your app is handling errors correctly, at that point it will tell the user “Can’t do that, file’s busy.” But the damage has already been done.

When the user tries to “Save As” again to the same file — because she’s an idiot, or she’s a QA tester — the save succeeds. Why? Since BusyBody thinks it’s got that temp file open, the real file is free and clear for writing. This might not be a problem, but if BusyBody attempts to save again, instead of saving to where the user thinks it should save to, it will save to an invisible temp file. Oops!

The easiest solution to this that I can see is that, before you attempt such a switch, try to opening for writing the real file. If there’s an error with that open step, stop and signal the user, before any harm is done. That works as it should!

I wonder how many developers do the “right thing” by using FSpExchangeFiles(), but fail to check for open files first? Hopefully not many.

Note: I have not tried this with FSExchangeObjects(), though I’ll be getting to that. When I know, I’ll update this!