bdunagan

Brian Dunagan

December 13 2009
System Preferences Pane Lock

Many panes in System Preferences require I authenticate myself to change settings. Apple has a consistent UI for this: the lock icon. Next to it is a little snippet of text telling me why it's useful: "Click the lock to make changes." I click it and authenticate, and it opens, enabling various buttons and fields in that pane. "Click the lock to prevent further changes." I click it again, and it locks, disabling those buttons and fields. This UI and underlying authentication process are included in Cocoa: SFAuthorizationView.

This tutorial creates a simple prefpane with a button and the lock UI, building on two earlier posts about root-level operations and debugging prefpanes. Clicking the button creates an empty file at /var/log/test.txt, an action that requires root-level privilege. The button is grayed out if the lock is locked; it enables when I unlock the lock. The source code for this project is available on Google Code.

</a>

Xcode

  1. To start, create a new Xcode project for a PreferencePane, listed in "Standard Apple Plug-ins".
  2. Add the following frameworks to the project: Security and SecurityInterface.
  3. In the header file, import the SecurityInterface framework and add two IBOutlets:
    #import <PreferencePanes/PreferencePanes.h>
    #import <SecurityInterface/SFAuthorizationView.h>
    
    @interface BDAuthorizePrefPanePref : NSPreferencePane  {
    	IBOutlet SFAuthorizationView *authView;
    	IBOutlet NSButton *touchButton;
    }
    
    - (BOOL)isUnlocked;
    - (IBAction)clickTouch:(id)sender;
    
    @end
  4. In the source file, setup the security interface and handle its actions:
    #import "BDAuthorizePrefPanePref.h"
    
    @implementation BDAuthorizePrefPanePref
    
    - (void) mainViewDidLoad {
    	// Setup security.
    	AuthorizationItem items = {kAuthorizationRightExecute, 0, NULL, 0};
    	AuthorizationRights rights = {1, &items};
    	[authView setAuthorizationRights:&rights];
    	authView.delegate = self;
    	[authView updateStatus:nil];
    
    	[touchButton setEnabled:[self isUnlocked]];
    }
    
    - (BOOL)isUnlocked {
    	return [authView authorizationState] == SFAuthorizationViewUnlockedState;
    }
    
    - (IBAction)clickTouch:(id)sender {
    	// Collect arguments into an array.
    	NSMutableArray *args = [NSMutableArray array];
    	[args addObject:@"-c"];
    	[args addObject:@" touch /var/log/test.txt"];
    
    	// Convert array into void-* array.
    	const char **argv = (const char **)malloc(sizeof(char *) * [args count] + 1);
    	int argvIndex = 0;
    	for (NSString *string in args) {
    		argv[argvIndex] = [string UTF8String];
    		argvIndex++;
    	}
    	argv[argvIndex] = nil;
    
    	OSErr processError = AuthorizationExecuteWithPrivileges([[authView authorization] authorizationRef], [@"/bin/sh" UTF8String], 
    															kAuthorizationFlagDefaults, (char *const *)argv, nil);
    	free(argv);
    
    	if (processError != errAuthorizationSuccess)
    		NSLog(@"Error: %d", processError);
    }
    
    //
    // SFAuthorization delegates
    //
    
    - (void)authorizationViewDidAuthorize:(SFAuthorizationView *)view {
    	[touchButton setEnabled:[self isUnlocked]];
    }
    
    - (void)authorizationViewDidDeauthorize:(SFAuthorizationView *)view {
    	[touchButton setEnabled:[self isUnlocked]];
    }
    
    @end
    </ol>

    Interface Builder

    1. Open the xib file in the Resources folder of the new project.
    2. Change the NSFileOwner's class to the main controller's class.
    3. Resize the window to W:440,H:120.
    4. Drop an NSView into the content view of the window.
    5. Resize the view to X:20,Y:20,W:400,:H:40.
    6. Change the view's class to SFAuthorizationView.
    7. Place an NSButton above the security view and link its action to clickTouch:.
    8. Link up the outlets for the button and the security view. </ol>
iPhone Tip: No NSHost UITabBarController from a XIB
LinkedIn GitHub Email