summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authormark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-30 18:16:18 +0000
committermark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-30 18:16:18 +0000
commit526657d45abd72067ff774c552cc4df5825fc742 (patch)
treecb5da516b3b90c0057eaff34b51697bd1ff9ea4a /chrome/browser/cocoa
parentac68bb0756f5bcb47cd79e832ef9ec6e5cb8bda0 (diff)
downloadchromium_src-526657d45abd72067ff774c552cc4df5825fc742.zip
chromium_src-526657d45abd72067ff774c552cc4df5825fc742.tar.gz
chromium_src-526657d45abd72067ff774c552cc4df5825fc742.tar.bz2
Allow authenticated installs from the disk image.
When launching from a read-only disk image, if no installed copy exists in /Applications, and the user chooses to install from the disk image: If it appears that there isn't permission to write to /Applications, prompt the user to authenticate as an administrator. If authentication is successful, copy the application as root, set its permissions, and promote Keystone to use a system ticket. BUG=40265 TEST=Remove the application from /Applications and test, as both an admin user and a normal user, installation directly from the disk image. When installation occurs after authentication, Chrome should be on a system ticket. Also, make sure that normal in-app Keystone promotion continues to work as expected. Review URL: http://codereview.chromium.org/1792013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46091 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/authorization_util.h8
-rw-r--r--chrome/browser/cocoa/authorization_util.mm (renamed from chrome/browser/cocoa/authorization_util.cc)65
-rwxr-xr-xchrome/browser/cocoa/install.sh123
-rw-r--r--chrome/browser/cocoa/install_from_dmg.mm176
-rw-r--r--chrome/browser/cocoa/keystone_glue.h20
-rw-r--r--chrome/browser/cocoa/keystone_glue.mm100
-rwxr-xr-xchrome/browser/cocoa/keystone_promote_postflight.sh5
7 files changed, 355 insertions, 142 deletions
diff --git a/chrome/browser/cocoa/authorization_util.h b/chrome/browser/cocoa/authorization_util.h
index 2d7c09d..9370949 100644
--- a/chrome/browser/cocoa/authorization_util.h
+++ b/chrome/browser/cocoa/authorization_util.h
@@ -23,12 +23,20 @@
// http://developer.apple.com/mac/library/samplecode/BetterAuthorizationSample/listing1.html
// (Look for "What's This About Zombies?")
+#include <CoreFoundation/CoreFoundation.h>
#include <Security/Authorization.h>
#include <stdio.h>
#include <sys/types.h>
namespace authorization_util {
+// Obtains an AuthorizationRef that can be used to run commands as root. If
+// necessary, prompts the user for authentication. If the user is prompted,
+// |prompt| will be used as the prompt string and an icon appropriate for the
+// application will be displayed in a prompt dialog. Note that the system
+// appends its own text to the prompt string. Returns NULL on failure.
+AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt);
+
// Calls straight through to AuthorizationExecuteWithPrivileges. If that
// call succeeds, |pid| will be set to the pid of the executed tool. If the
// pid can't be determined, |pid| will be set to -1. |pid| must not be NULL.
diff --git a/chrome/browser/cocoa/authorization_util.cc b/chrome/browser/cocoa/authorization_util.mm
index 434f1a3..709fcc1 100644
--- a/chrome/browser/cocoa/authorization_util.cc
+++ b/chrome/browser/cocoa/authorization_util.mm
@@ -4,16 +4,81 @@
#include "chrome/browser/cocoa/authorization_util.h"
+#import <Foundation/Foundation.h>
#include <sys/wait.h>
#include <string>
+#include "base/basictypes.h"
#include "base/eintr_wrapper.h"
#include "base/logging.h"
+#import "base/mac_util.h"
#include "base/string_util.h"
+#include "chrome/browser/cocoa/scoped_authorizationref.h"
namespace authorization_util {
+AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) {
+ // Create an empty AuthorizationRef.
+ scoped_AuthorizationRef authorization;
+ OSStatus status = AuthorizationCreate(NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults,
+ &authorization);
+ if (status != errAuthorizationSuccess) {
+ LOG(ERROR) << "AuthorizationCreate: " << status;
+ return NULL;
+ }
+
+ // Specify the "system.privilege.admin" right, which allows
+ // AuthorizationExecuteWithPrivileges to run commands as root.
+ AuthorizationItem right_items[] = {
+ {kAuthorizationRightExecute, 0, NULL, 0}
+ };
+ AuthorizationRights rights = {arraysize(right_items), right_items};
+
+ // product_logo_32.png is used instead of app.icns because Authorization
+ // Services can't deal with .icns files.
+ NSString* icon_path =
+ [mac_util::MainAppBundle() pathForResource:@"product_logo_32"
+ ofType:@"png"];
+ const char* icon_path_c = [icon_path fileSystemRepresentation];
+ size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0;
+
+ // The OS will append " Type an administrator's name and password to allow
+ // <CFBundleDisplayName> to make changes."
+ NSString* prompt_ns = reinterpret_cast<const NSString*>(prompt);
+ const char* prompt_c = [prompt_ns UTF8String];
+ size_t prompt_length = prompt_c ? strlen(prompt_c) : 0;
+
+ AuthorizationItem environment_items[] = {
+ {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0},
+ {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0}
+ };
+
+ AuthorizationEnvironment environment = {arraysize(environment_items),
+ environment_items};
+
+ AuthorizationFlags flags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagExtendRights |
+ kAuthorizationFlagPreAuthorize;
+
+ status = AuthorizationCopyRights(authorization,
+ &rights,
+ &environment,
+ flags,
+ NULL);
+ if (status != errAuthorizationSuccess) {
+ if (status != errAuthorizationCanceled) {
+ LOG(ERROR) << "AuthorizationCopyRights: " << status;
+ }
+ return NULL;
+ }
+
+ return authorization.release();
+}
+
OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization,
const char* tool_path,
AuthorizationFlags options,
diff --git a/chrome/browser/cocoa/install.sh b/chrome/browser/cocoa/install.sh
new file mode 100755
index 0000000..dc73fae
--- /dev/null
+++ b/chrome/browser/cocoa/install.sh
@@ -0,0 +1,123 @@
+#!/bin/bash -p
+
+# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Called by the application to install in a new location. Generally, this
+# means that the application is running from a disk image and wants to be
+# copied to /Applications. The application, when running from the disk image,
+# will call this script to perform the copy.
+#
+# This script will be run as root if the application determines that it would
+# not otherwise have permission to perform the copy.
+#
+# When running as root, this script will be invoked with the real user ID set
+# to the user's ID, but the effective user ID set to 0 (root). bash -p is
+# used on the first line to prevent bash from setting the effective user ID to
+# the real user ID (dropping root privileges).
+
+set -e
+
+# This script may run as root, so be paranoid about things like ${PATH}.
+export PATH="/usr/bin:/usr/sbin:/bin:/sbin"
+
+# If running as root, output the pid to stdout before doing anything else.
+# See chrome/browser/cocoa/authorization_util.h.
+if [ ${EUID} -eq 0 ] ; then
+ echo "${$}"
+fi
+
+if [ ${#} -ne 2 ] ; then
+ echo "usage: ${0} SRC DEST" >& 2
+ exit 2
+fi
+
+SRC=${1}
+DEST=${2}
+
+# Make sure that SRC is an absolute path and that it exists.
+if [ -z "${SRC}" ] || [ "${SRC:0:1}" != "/" ] || [ ! -d "${SRC}" ] ; then
+ echo "${0}: source ${SRC} sanity check failed" >& 2
+ exit 3
+fi
+
+# Make sure that DEST is an absolute path and that it doesn't yet exist.
+if [ -z "${DEST}" ] || [ "${DEST:0:1}" != "/" ] || [ -e "${DEST}" ] ; then
+ echo "${0}: destination ${DEST} sanity check failed" >& 2
+ exit 4
+fi
+
+# Do the copy.
+rsync -lrpt "${SRC}/" "${DEST}"
+
+# The remaining steps are not considered critical.
+set +e
+
+# Notify LaunchServices.
+/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister "${DEST}"
+
+# If this script is not running as root and the application is installed
+# somewhere under /Applications, try to make it writable by all admin users.
+# This will allow other admin users to update the application from their own
+# user Keystone instances even if the Keystone ticket is not promoted to
+# system level.
+#
+# If the script is not running as root and the application is not installed
+# under /Applications, it might not be in a system-wide location, and it
+# probably won't be something that other users on the system are running, so
+# err on the side of safety and don't make it group-writable.
+#
+# If this script is running as root, a Keystone ticket promotion is expected,
+# and future updates can be expected to be applied as root, so
+# admin-writeability is not a concern. Set the entire thing to be owned by
+# root in that case, regardless of where it's installed, and drop any group
+# and other write permission.
+#
+# If this script is running as a user that is not a member of the admin group,
+# the chgrp operation will not succeed. Tolerate that case, because it's
+# better than the alternative, which is to make the application
+# world-writable.
+CHMOD_MODE="a+rX,u+w,go-w"
+if [ ${EUID} -ne 0 ] ; then
+ if [ "${DEST:0:14}" = "/Applications/" ] &&
+ chgrp -Rh admin "${DEST}" >& /dev/null ; then
+ CHMOD_MODE="a+rX,ug+w,o-w"
+ fi
+else
+ chown -Rh root:wheel "${DEST}" >& /dev/null
+fi
+
+chmod -R "${CHMOD_MODE}" "${DEST}" >& /dev/null
+
+# On the Mac, or at least on HFS+, symbolic link permissions are significant,
+# but chmod -R and -h can't be used together. Do another pass to fix the
+# permissions on any symbolic links.
+find "${DEST}" -type l -exec chmod -h "${CHMOD_MODE}" {} + >& /dev/null
+
+# Host OS version check, to be able to take advantage of features on newer
+# systems and fall back to slow ways of doing things on older systems.
+OS_VERSION=$(sw_vers -productVersion)
+OS_MAJOR=$(sed -Ene 's/^([0-9]+).*/\1/p' <<< ${OS_VERSION})
+OS_MINOR=$(sed -Ene 's/^([0-9]+)\.([0-9]+).*/\2/p' <<< ${OS_VERSION})
+
+# Because this script is launched by the application itself, the installation
+# process inherits the quarantine bit (LSFileQuarantineEnabled). Any files or
+# directories created during the update will be quarantined in that case,
+# which may cause Launch Services to display quarantine UI. That's bad,
+# especially if it happens when the outer .app launches a quarantined inner
+# helper. Since the user approved the application launch if quarantined, it
+# it can be assumed that the installed copy should not be quarantined. Use
+# xattr to drop the quarantine attribute.
+QUARANTINE_ATTR=com.apple.quarantine
+if [ ${OS_MAJOR} -gt 10 ] ||
+ ([ ${OS_MAJOR} -eq 10 ] && [ ${OS_MINOR} -ge 6 ]) ; then
+ # On 10.6, xattr supports -r for recursive operation.
+ xattr -d -r "${QUARANTINE_ATTR}" "${DEST}" >& /dev/null
+else
+ # On earlier systems, xattr doesn't support -r, so run xattr via find.
+ find "${DEST}" -exec xattr -d "${QUARANTINE_ATTR}" {} + >& /dev/null
+fi
+
+# Great success!
+exit 0
diff --git a/chrome/browser/cocoa/install_from_dmg.mm b/chrome/browser/cocoa/install_from_dmg.mm
index d6e28f0..27ffa38 100644
--- a/chrome/browser/cocoa/install_from_dmg.mm
+++ b/chrome/browser/cocoa/install_from_dmg.mm
@@ -17,8 +17,11 @@
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/logging.h"
+#import "base/mac_util.h"
#include "base/scoped_nsautorelease_pool.h"
-#include "base/sys_info.h"
+#include "chrome/browser/cocoa/authorization_util.h"
+#include "chrome/browser/cocoa/scoped_authorizationref.h"
+#import "chrome/browser/cocoa/keystone_glue.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
@@ -220,87 +223,85 @@ bool ShouldInstallDialog() {
return result == NSAlertFirstButtonReturn;
}
-// Copies source_path to target_path and performs any additional on-disk
-// bookkeeping needed to be able to launch target_path properly.
-bool InstallFromDiskImage(NSString* source_path, NSString* target_path) {
+// Potentially shows an authorization dialog to request authentication to
+// copy. If application_directory appears to be unwritable, attempts to
+// obtain authorization, which may result in the display of the dialog.
+// Returns NULL if authorization is not performed because it does not appear
+// to be necessary because the user has permission to write to
+// application_directory. Returns NULL if authorization fails.
+AuthorizationRef MaybeShowAuthorizationDialog(NSString* application_directory) {
NSFileManager* file_manager = [NSFileManager defaultManager];
+ if ([file_manager isWritableFileAtPath:application_directory]) {
+ return NULL;
+ }
- // For the purposes of this copy, the file manager's delegate shouldn't be
- // consulted at all. Clear the delegate and restore it after the copy is
- // done.
- id file_manager_delegate = [file_manager delegate];
- [file_manager setDelegate:nil];
-
- NSError* copy_error;
- bool copy_result = [file_manager copyItemAtPath:source_path
- toPath:target_path
- error:&copy_error];
+ NSString* prompt = l10n_util::GetNSStringFWithFixup(
+ IDS_INSTALL_FROM_DMG_AUTHENTICATION_PROMPT,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ return authorization_util::AuthorizationCreateToRunAsRoot(
+ reinterpret_cast<CFStringRef>(prompt));
+}
- [file_manager setDelegate:file_manager_delegate];
+// Invokes the installer program at installer_path to copy source_path to
+// target_path and perform any additional on-disk bookkeeping needed to be
+// able to launch target_path properly. If authorization_arg is non-NULL,
+// function will assume ownership of it, will invoke the installer with that
+// authorization reference, and will attempt Keystone ticket promotion.
+bool InstallFromDiskImage(AuthorizationRef authorization_arg,
+ NSString* installer_path,
+ NSString* source_path,
+ NSString* target_path) {
+ scoped_AuthorizationRef authorization(authorization_arg);
+ authorization_arg = NULL;
+ int exit_status;
+ if (authorization) {
+ const char* installer_path_c = [installer_path fileSystemRepresentation];
+ const char* source_path_c = [source_path fileSystemRepresentation];
+ const char* target_path_c = [target_path fileSystemRepresentation];
+ const char* arguments[] = {source_path_c, target_path_c, NULL};
+
+ OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait(
+ authorization,
+ installer_path_c,
+ kAuthorizationFlagDefaults,
+ arguments,
+ NULL, // pipe
+ &exit_status);
+ if (status != errAuthorizationSuccess) {
+ LOG(ERROR) << "AuthorizationExecuteWithPrivileges install: " << status;
+ return false;
+ }
+ } else {
+ NSArray* arguments = [NSArray arrayWithObjects:source_path,
+ target_path,
+ nil];
+
+ NSTask* task;
+ @try {
+ task = [NSTask launchedTaskWithLaunchPath:installer_path
+ arguments:arguments];
+ } @catch(NSException* exception) {
+ LOG(ERROR) << "+[NSTask launchedTaskWithLaunchPath:arguments:]: "
+ << [[exception description] UTF8String];
+ return false;
+ }
- if (!copy_result) {
- LOG(ERROR) << "-[NSFileManager copyItemAtPath:toPath:error:]: "
- << [[copy_error description] UTF8String];
- return false;
+ [task waitUntilExit];
+ exit_status = [task terminationStatus];
}
- // Since the application performed the copy, and the application has the
- // quarantine bit (LSFileQuarantineEnabled) set, the installed copy will
- // be quarantined. That's bad, because it will cause the quarantine dialog
- // to be displayed, possibly after a long delay, when the application is
- // relaunched. Use xattr to drop the quarantine attribute.
- //
- // There are three reasons not to use MDItemRemoveAttribute directly:
- // 1. MDItemRemoveAttribute is a private API.
- // 2. The operation needs to be recursive, and writing a bunch of code to
- // handle the recursion just to call a private API is annoying.
- // 3. All of this stuff will likely move into a shell script anyway, and
- // the shell script will have no choice but to use xattr.
-
- int32 os_major, os_minor, os_patch;
- base::SysInfo::OperatingSystemVersionNumbers(&os_major, &os_minor, &os_patch);
-
- const NSString* xattr_path = @"/usr/bin/xattr";
- const NSString* quarantine_attribute = @"com.apple.quarantine";
- NSString* launch_path;
- NSArray* arguments;
-
- if (os_major > 10 || (os_major == 10 && os_minor >= 6)) {
- // On 10.6, xattr supports -r for recursive operation.
- launch_path = xattr_path;
- arguments = [NSArray arrayWithObjects:@"-r",
- @"-d",
- quarantine_attribute,
- target_path,
- nil];
- } else {
- // On earlier systems, xattr doesn't support -r, so run xattr via find.
- launch_path = @"/usr/bin/find";
- arguments = [NSArray arrayWithObjects:target_path,
- @"-exec",
- xattr_path,
- @"-d",
- quarantine_attribute,
- @"{}",
- @"+",
- nil];
- }
-
- NSTask* task;
- @try {
- task = [NSTask launchedTaskWithLaunchPath:launch_path
- arguments:arguments];
- } @catch(NSException* exception) {
- LOG(ERROR) << "+[NSTask launchedTaskWithLaunchPath:arguments:]: "
- << [[exception description] UTF8String];
+ if (exit_status != 0) {
+ LOG(ERROR) << "install.sh: exit status " << exit_status;
return false;
}
- [task waitUntilExit];
- int status = [task terminationStatus];
- if (status != 0) {
- LOG(ERROR) << "/usr/bin/xattr: status " << status;
- return false;
+ if (authorization) {
+ // As long as an AuthorizationRef is available, promote the Keystone
+ // ticket. Inform KeystoneGlue of the new path to use.
+ KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
+ [keystone_glue setAppPath:target_path];
+ [keystone_glue promoteTicketWithAuthorization:authorization.release()
+ synchronous:YES];
}
return true;
@@ -319,9 +320,6 @@ bool LaunchInstalledApp(NSString* app_path) {
return false;
}
- // Use an empty dictionary for the environment.
- NSDictionary* environment = [NSDictionary dictionary];
-
const std::vector<std::string>& argv =
CommandLine::ForCurrentProcess()->argv();
NSMutableArray* arguments =
@@ -341,7 +339,6 @@ bool LaunchInstalledApp(NSString* app_path) {
struct LSApplicationParameters parameters = {0};
parameters.flags = kLSLaunchDefaults;
parameters.application = &app_fsref;
- parameters.environment = reinterpret_cast<CFDictionaryRef>(environment);
parameters.argv = reinterpret_cast<CFArrayRef>(arguments);
err = LSOpenApplication(&parameters, NULL);
@@ -401,13 +398,6 @@ bool MaybeInstallFromDiskImage() {
return false;
}
- // TODO(mark): When this happens, prompt for authentication.
- if (![file_manager isWritableFileAtPath:application_directory]) {
- LOG(INFO) << "Non-writable application directory at "
- << [application_directory UTF8String];
- return false;
- }
-
NSString* source_path = [[NSBundle mainBundle] bundlePath];
NSString* application_name = [source_path lastPathComponent];
NSString* target_path =
@@ -418,11 +408,27 @@ bool MaybeInstallFromDiskImage() {
return false;
}
+ NSString* installer_path =
+ [mac_util::MainAppBundle() pathForResource:@"install" ofType:@"sh"];
+ if (!installer_path) {
+ LOG(INFO) << "Could not locate install.sh";
+ return false;
+ }
+
if (!ShouldInstallDialog()) {
return false;
}
- if (!InstallFromDiskImage(source_path, target_path) ||
+ scoped_AuthorizationRef authorization(
+ MaybeShowAuthorizationDialog(application_directory));
+ // authorization will be NULL if it's deemed unnecessary or if
+ // authentication fails. In either case, try to install without privilege
+ // escalation.
+
+ if (!InstallFromDiskImage(authorization.release(),
+ installer_path,
+ source_path,
+ target_path) ||
!LaunchInstalledApp(target_path)) {
ShowErrorDialog();
return false;
diff --git a/chrome/browser/cocoa/keystone_glue.h b/chrome/browser/cocoa/keystone_glue.h
index 6ae4c1f..2848681 100644
--- a/chrome/browser/cocoa/keystone_glue.h
+++ b/chrome/browser/cocoa/keystone_glue.h
@@ -89,6 +89,10 @@ namespace {
// carried across threads.
scoped_AuthorizationRef authorization_;
+ // YES if a synchronous promotion operation is in progress (promotion during
+ // installation).
+ BOOL synchronousPromotion_;
+
// YES if an update was ever successfully installed by -installUpdate.
BOOL updateSuccessfullyInstalled_;
}
@@ -146,10 +150,22 @@ namespace {
- (BOOL)needsPromotion;
- (BOOL)wantsPromotion;
-// Requests authorization and promotes the Keystone ticket into the system
-// store. System Keystone will be installed if necessary.
+// Promotes the Keystone ticket into the system store. System Keystone will
+// be installed if necessary. If synchronous is NO, the promotion may occur
+// in the background. synchronous should be YES for promotion during
+// installation. The KeystoneGlue object assumes ownership of
+// authorization_arg.
+- (void)promoteTicketWithAuthorization:(AuthorizationRef)authorization_arg
+ synchronous:(BOOL)synchronous;
+
+// Requests authorization and calls -promoteTicketWithAuthorization: in
+// asynchronous mode.
- (void)promoteTicket;
+// Sets a new value for appPath. Used during installation to point a ticket
+// at the installed copy.
+- (void)setAppPath:(NSString*)appPath;
+
@end // @interface KeystoneGlue
@interface KeystoneGlue(ExposedForTesting)
diff --git a/chrome/browser/cocoa/keystone_glue.mm b/chrome/browser/cocoa/keystone_glue.mm
index 5cb125a..5baa400 100644
--- a/chrome/browser/cocoa/keystone_glue.mm
+++ b/chrome/browser/cocoa/keystone_glue.mm
@@ -692,65 +692,38 @@ const NSString* const kBrandKey = @"KSBrandID";
return;
}
- // Create an empty AuthorizationRef.
- scoped_AuthorizationRef authorization;
- OSStatus status = AuthorizationCreate(NULL,
- kAuthorizationEmptyEnvironment,
- kAuthorizationFlagDefaults,
- &authorization);
- if (status != errAuthorizationSuccess) {
- LOG(ERROR) << "AuthorizationCreate: " << status;
- return;
- }
-
- // Specify the "system.privilege.admin" right, which allows
- // AuthorizationExecuteWithPrivileges to run commands as root.
- AuthorizationItem rightItems[] = {
- {kAuthorizationRightExecute, 0, NULL, 0}
- };
- AuthorizationRights rights = {arraysize(rightItems), rightItems};
-
- // product_logo_32.png is used instead of app.icns because Authorization
- // Services requires an image that NSImage can read.
- NSString* iconPath =
- [mac_util::MainAppBundle() pathForResource:@"product_logo_32"
- ofType:@"png"];
- const char* iconPathC = [iconPath fileSystemRepresentation];
- size_t iconPathLength = iconPathC ? strlen(iconPathC) : 0;
-
- // The OS will append " Type an administrator's name and password to allow
- // <CFBundleDisplayName> to make changes."
NSString* prompt = l10n_util::GetNSStringFWithFixup(
IDS_PROMOTE_AUTHENTICATION_PROMPT,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
- const char* promptC = [prompt UTF8String];
- size_t promptLength = promptC ? strlen(promptC) : 0;
-
- AuthorizationItem environmentItems[] = {
- {kAuthorizationEnvironmentIcon, iconPathLength, (void*)iconPathC, 0},
- {kAuthorizationEnvironmentPrompt, promptLength, (void*)promptC, 0}
- };
-
- AuthorizationEnvironment environment = {arraysize(environmentItems),
- environmentItems};
-
- AuthorizationFlags flags = kAuthorizationFlagDefaults |
- kAuthorizationFlagInteractionAllowed |
- kAuthorizationFlagExtendRights |
- kAuthorizationFlagPreAuthorize;
-
- status = AuthorizationCopyRights(authorization,
- &rights,
- &environment,
- flags,
- NULL);
- if (status != errAuthorizationSuccess) {
- if (status != errAuthorizationCanceled) {
- LOG(ERROR) << "AuthorizationCopyRights: " << status;
- }
+ scoped_AuthorizationRef authorization(
+ authorization_util::AuthorizationCreateToRunAsRoot(
+ reinterpret_cast<CFStringRef>(prompt)));
+ if (!authorization.get()) {
return;
}
+ [self promoteTicketWithAuthorization:authorization.release() synchronous:NO];
+}
+
+- (void)promoteTicketWithAuthorization:(AuthorizationRef)authorization_arg
+ synchronous:(BOOL)synchronous {
+ scoped_AuthorizationRef authorization(authorization_arg);
+ authorization_arg = NULL;
+
+ if ([self asyncOperationPending]) {
+ // Starting a synchronous operation while an asynchronous one is pending
+ // could be trouble.
+ return;
+ }
+ if (!synchronous && ![self wantsPromotion]) {
+ // If operating synchronously, the call came from the installer, which
+ // means that a system ticket is required. Otherwise, only allow
+ // promotion if it's wanted.
+ return;
+ }
+
+ synchronousPromotion_ = synchronous;
+
[self updateStatus:kAutoupdatePromoting version:nil];
// TODO(mark): Remove when able!
@@ -773,6 +746,8 @@ const NSString* const kBrandKey = @"KSBrandID";
// operation itself is asynchronous too. http://b/2290009. Hopefully,
// the Keystone promotion code will just be changed to do what preflight
// now does, and then the preflight script can be removed instead.
+ // However, preflight operation (and promotion) should only be asynchronous
+ // if the synchronous parameter is NO.
NSString* preflightPath =
[mac_util::MainAppBundle() pathForResource:@"keystone_promote_preflight"
ofType:@"sh"];
@@ -787,7 +762,7 @@ const NSString* const kBrandKey = @"KSBrandID";
const char* arguments[] = {userBrandFile, systemBrandFile, NULL};
int exit_status;
- status = authorization_util::ExecuteWithPrivilegesAndWait(
+ OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait(
authorization,
preflightPathC,
kAuthorizationFlagDefaults,
@@ -837,7 +812,15 @@ const NSString* const kBrandKey = @"KSBrandID";
- (void)promotionComplete:(NSNotification*)notification {
NSDictionary* userInfo = [notification userInfo];
if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) {
- [self changePermissionsForPromotionAsync];
+ if (synchronousPromotion_) {
+ // Short-circuit: if performing a synchronous promotion, the promotion
+ // came from the installer, which already set the permissions properly.
+ // Rather than run a duplicate permission-changing operation, jump
+ // straight to "done."
+ [self changePermissionsForPromotionComplete];
+ } else {
+ [self changePermissionsForPromotionAsync];
+ }
} else {
authorization_.reset();
[self updateStatus:kAutoupdatePromoteFailed version:nil];
@@ -896,4 +879,11 @@ const NSString* const kBrandKey = @"KSBrandID";
[self updateStatus:kAutoupdatePromoted version:nil];
}
+- (void)setAppPath:(NSString*)appPath {
+ if (appPath != appPath_) {
+ [appPath_ release];
+ appPath_ = [appPath copy];
+ }
+}
+
@end // @implementation KeystoneGlue
diff --git a/chrome/browser/cocoa/keystone_promote_postflight.sh b/chrome/browser/cocoa/keystone_promote_postflight.sh
index 5eb85d2..510e5d2 100755
--- a/chrome/browser/cocoa/keystone_promote_postflight.sh
+++ b/chrome/browser/cocoa/keystone_promote_postflight.sh
@@ -13,6 +13,11 @@
# user's ID, but the effective user ID set to 0 (root). bash -p is used on
# the first line to prevent bash from setting the effective user ID to the
# real user ID (dropping root privileges).
+#
+# WARNING: This script is NOT currently run when the Keystone ticket is
+# promoted during application installation directly from the disk image,
+# because the installation process itself handles the same permission fix-ups
+# that this script normally would.
set -e