diff options
-rw-r--r-- | chrome/browser/cocoa/keystone_glue.h | 11 | ||||
-rw-r--r-- | chrome/browser/cocoa/keystone_glue.mm | 157 | ||||
-rwxr-xr-x | chrome/browser/cocoa/keystone_promote_preflight.sh | 42 | ||||
-rwxr-xr-x | chrome/tools/build/mac/keystone_install.sh | 64 |
4 files changed, 265 insertions, 9 deletions
diff --git a/chrome/browser/cocoa/keystone_glue.h b/chrome/browser/cocoa/keystone_glue.h index 81d74a3..6ae4c1f 100644 --- a/chrome/browser/cocoa/keystone_glue.h +++ b/chrome/browser/cocoa/keystone_glue.h @@ -43,6 +43,16 @@ extern const NSString* const kAutoupdateStatusNotification; extern const NSString* const kAutoupdateStatusStatus; extern const NSString* const kAutoupdateStatusVersion; +namespace { + enum BrandFileType { + kBrandFileTypeNotDetermined = 0, + kBrandFileTypeNone, + kBrandFileTypeUser, + kBrandFileTypeSystem, + }; + +} // namespace + // KeystoneGlue is an adapter around the KSRegistration class, allowing it to // be used without linking directly against its containing KeystoneRegistration // framework. This is used in an environment where most builds (such as @@ -66,6 +76,7 @@ extern const NSString* const kAutoupdateStatusVersion; NSString* url_; NSString* version_; NSString* channel_; // Logically: Dev, Beta, or Stable. + BrandFileType brandFileType_; // And the Keystone registration itself, with the active timer KSRegistration* registration_; // strong diff --git a/chrome/browser/cocoa/keystone_glue.mm b/chrome/browser/cocoa/keystone_glue.mm index 468eca90..5cb125a 100644 --- a/chrome/browser/cocoa/keystone_glue.mm +++ b/chrome/browser/cocoa/keystone_glue.mm @@ -40,6 +40,8 @@ NSString* KSRegistrationPreserveTrustedTesterTokenKey = @"PreserveTTT"; NSString* KSRegistrationTagKey = @"Tag"; NSString* KSRegistrationTagPathKey = @"TagPath"; NSString* KSRegistrationTagKeyKey = @"TagKey"; +NSString* KSRegistrationBrandPathKey = @"BrandPath"; +NSString* KSRegistrationBrandKeyKey = @"BrandKey"; NSString *KSRegistrationDidCompleteNotification = @"KSRegistrationDidCompleteNotification"; @@ -59,6 +61,30 @@ NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; NSString *KSRegistrationRemoveExistingTag = @""; #define KSRegistrationPreserveExistingTag nil +// Constants for the brand file (uses an external file so it can survive updates +// to Chrome. + +#if defined(GOOGLE_CHROME_BUILD) +#define kBrandFileName @"Google Chrome Brand.plist"; +#elif defined(CHROMIUM_BUILD) +#define kBrandFileName @"Chromium Brand.plist"; +#else +#error Unknown branding +#endif + +// These directories are hardcoded in Keystone promotion preflight and the +// Keystone install script, so NSSearchPathForDirectoriesInDomains isn't used +// since the scripts couldn't use anything like that. +NSString* kBrandUserFile = @"~/Library/Google/" kBrandFileName; +NSString* kBrandSystemFile = @"/Library/Google/" kBrandFileName; + +NSString* UserBrandFilePath() { + return [kBrandUserFile stringByStandardizingPath]; +} +NSString* SystemBrandFilePath() { + return [kBrandSystemFile stringByStandardizingPath]; +} + } // namespace @interface KSRegistration : NSObject @@ -144,6 +170,9 @@ NSString *KSRegistrationRemoveExistingTag = @""; - (void)changePermissionsForPromotionWithTool:(NSString*)toolPath; - (void)changePermissionsForPromotionComplete; +// Returns the brand file path to use for Keystone. +- (NSString*)brandFilePath; + @end // @interface KeystoneGlue(Private) const NSString* const kAutoupdateStatusNotification = @@ -154,6 +183,7 @@ const NSString* const kAutoupdateStatusVersion = @"version"; namespace { const NSString* const kChannelKey = @"KSChannelID"; +const NSString* const kBrandKey = @"KSBrandID"; } // namespace @@ -258,6 +288,88 @@ const NSString* const kChannelKey = @"KSChannelID"; channel_ = [channel retain]; } +- (NSString*)brandFilePath { + DCHECK(version_ != nil) << "-loadParameters must be called first"; + + if (brandFileType_ == kBrandFileTypeNotDetermined) { + + // Default to none. + brandFileType_ = kBrandFileTypeNone; + + // Having a channel means Dev/Beta, so there is no brand code to go with + // those. + if ([channel_ length] == 0) { + + NSString* userBrandFile = UserBrandFilePath(); + NSString* systemBrandFile = SystemBrandFilePath(); + + NSFileManager* fm = [NSFileManager defaultManager]; + + // If there is a system brand file, use it. + if ([fm fileExistsAtPath:systemBrandFile]) { + // System + + // Use the system file that is there. + brandFileType_ = kBrandFileTypeSystem; + + // Clean up any old user level file. + if ([fm fileExistsAtPath:userBrandFile]) { + [fm removeItemAtPath:userBrandFile error:NULL]; + } + + } else { + // User + + NSDictionary* infoDictionary = [self infoDictionary]; + NSString* appBundleBrandID = [infoDictionary objectForKey:kBrandKey]; + + NSString* storedBrandID = nil; + if ([fm fileExistsAtPath:userBrandFile]) { + NSDictionary* storedBrandDict = + [NSDictionary dictionaryWithContentsOfFile:userBrandFile]; + storedBrandID = [storedBrandDict objectForKey:kBrandKey]; + } + + if ((appBundleBrandID != nil) && + (![storedBrandID isEqualTo:appBundleBrandID])) { + // App and store don't match, update store and use it. + NSDictionary* storedBrandDict = + [NSDictionary dictionaryWithObject:appBundleBrandID + forKey:kBrandKey]; + if ([storedBrandDict writeToFile:userBrandFile atomically:YES]) { + brandFileType_ = kBrandFileTypeUser; + } + } else if (storedBrandID) { + // Had stored brand, use it. + brandFileType_ = kBrandFileTypeUser; + } + } + } + + } + + NSString* result = nil; + switch (brandFileType_) { + case kBrandFileTypeUser: + result = UserBrandFilePath(); + break; + + case kBrandFileTypeSystem: + result = SystemBrandFilePath(); + break; + + case kBrandFileTypeNotDetermined: + NOTIMPLEMENTED(); + // Fall through + case kBrandFileTypeNone: + // Clear the value. + result = @""; + break; + + } + return result; +} + - (BOOL)loadKeystoneRegistration { if (!productID_ || !appPath_ || !url_ || !version_) return NO; @@ -292,6 +404,15 @@ const NSString* const kChannelKey = @"KSChannelID"; NSNumber* preserveTTToken = [NSNumber numberWithBool:YES]; NSString* tagPath = [self appInfoPlistPath]; + NSString* brandKey = kBrandKey; + NSString* brandPath = [self brandFilePath]; + + if ([brandPath length] == 0) { + // Brand path and brand key must be cleared together or ksadmin seems + // to throw an error. + brandKey = @""; + } + return [NSDictionary dictionaryWithObjectsAndKeys: version_, KSRegistrationVersionKey, xcType, KSRegistrationExistenceCheckerTypeKey, @@ -301,6 +422,8 @@ const NSString* const kChannelKey = @"KSChannelID"; channel_, KSRegistrationTagKey, tagPath, KSRegistrationTagPathKey, kChannelKey, KSRegistrationTagKeyKey, + brandPath, KSRegistrationBrandPathKey, + brandKey, KSRegistrationBrandKeyKey, nil]; } @@ -632,11 +755,15 @@ const NSString* const kChannelKey = @"KSChannelID"; // TODO(mark): Remove when able! // - // keystone_promote_preflight is hopefully temporary. It's here to ensure - // that the Keystone system ticket store is in a usable state for all users - // on the system. Ideally, Keystone's installer or another part of Keystone - // would handle this. The underlying problem is http://b/2285921, and it - // causes http://b/2289908, which this workaround addresses. + // keystone_promote_preflight will copy the current brand information out to + // the system level so all users can share the data as part of the ticket + // promotion. + // + // It will also ensure that the Keystone system ticket store is in a usable + // state for all users on the system. Ideally, Keystone's installer or + // another part of Keystone would handle this. The underlying problem is + // http://b/2285921, and it causes http://b/2289908, which this workaround + // addresses. // // This is run synchronously, which isn't optimal, but // -[KSRegistration promoteWithParameters:authorization:] is currently @@ -650,7 +777,14 @@ const NSString* const kChannelKey = @"KSChannelID"; [mac_util::MainAppBundle() pathForResource:@"keystone_promote_preflight" ofType:@"sh"]; const char* preflightPathC = [preflightPath fileSystemRepresentation]; - const char* arguments[] = {NULL}; + const char* userBrandFile = NULL; + const char* systemBrandFile = NULL; + if (brandFileType_ == kBrandFileTypeUser) { + // Running with user level brand file, promote to the system level. + userBrandFile = [UserBrandFilePath() fileSystemRepresentation]; + systemBrandFile = [SystemBrandFilePath() fileSystemRepresentation]; + } + const char* arguments[] = {userBrandFile, systemBrandFile, NULL}; int exit_status; status = authorization_util::ExecuteWithPrivilegesAndWait( @@ -678,6 +812,17 @@ const NSString* const kChannelKey = @"KSChannelID"; authorization_.swap(authorization); NSDictionary* parameters = [self keystoneParameters]; + + // If the brand file is user level, update parameters to point to the new + // system level file during promotion. + if (brandFileType_ == kBrandFileTypeUser) { + NSMutableDictionary* temp_parameters = + [[parameters mutableCopy] autorelease]; + [temp_parameters setObject:SystemBrandFilePath() + forKey:KSRegistrationBrandPathKey]; + parameters = temp_parameters; + } + if (![registration_ promoteWithParameters:parameters authorization:authorization_]) { [self updateStatus:kAutoupdatePromoteFailed version:nil]; diff --git a/chrome/browser/cocoa/keystone_promote_preflight.sh b/chrome/browser/cocoa/keystone_promote_preflight.sh index d1c4c59..4bf31e8 100755 --- a/chrome/browser/cocoa/keystone_promote_preflight.sh +++ b/chrome/browser/cocoa/keystone_promote_preflight.sh @@ -8,6 +8,10 @@ # environment for Keystone installation. Ultimately, these features should be # integrated directly into the Keystone installation. # +# If the two branding paths are given, then the branding information is also +# copied and the permissions on the system branding file are set to be owned by +# root, but readable by anyone. +# # Note that 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 @@ -25,11 +29,45 @@ export PATH="/usr/bin:/usr/sbin:/bin:/sbin" # chrome/browser/cocoa/authorization_util.h. echo "${$}" -if [ ${#} -ne 0 ] ; then - echo "usage: ${0}" >& 2 +if [ ${#} -ne 0 ] && [ ${#} -ne 2 ] ; then + echo "usage: ${0} [USER_BRAND SYSTEM_BRAND]" >& 2 exit 2 fi +if [ ${#} -eq 2 ] ; then + USER_BRAND="${1}" + SYSTEM_BRAND="${2}" + + # Make sure that USER_BRAND is an absolute path and that it exists. + if [ -z "${USER_BRAND}" ] || \ + [ "${USER_BRAND:0:1}" != "/" ] || \ + [ ! -f "${USER_BRAND}" ] ; then + echo "${0}: must provide an absolute path naming an existing user file" >& 2 + exit 3 + fi + + # Make sure that SYSTEM_BRAND is an absolute path. + if [ -z "${SYSTEM_BRAND}" ] || [ "${SYSTEM_BRAND:0:1}" != "/" ] ; then + echo "${0}: must provide an absolute path naming a system file" >& 2 + exit 4 + fi + + # Make sure the directory for the system brand file exists. + SYSTEM_BRAND_DIR=$(dirname "${SYSTEM_BRAND}") + if [ ! -e "${SYSTEM_BRAND_DIR}" ] ; then + mkdir -p "${SYSTEM_BRAND_DIR}" + # Permissions on this directory will be fixed up at the end of this script. + fi + + # Copy the brand file + cp "${USER_BRAND}" "${SYSTEM_BRAND}" >& /dev/null + + # Ensure the right ownership and permissions + chown "root:wheel" "${SYSTEM_BRAND}" >& /dev/null + chmod "a+r,u+w,go-w" "${SYSTEM_BRAND}" >& /dev/null + +fi + OWNER_GROUP="root:admin" CHMOD_MODE="a+rX,u+w,go-w" diff --git a/chrome/tools/build/mac/keystone_install.sh b/chrome/tools/build/mac/keystone_install.sh index 67e3a78..73f0920 100755 --- a/chrome/tools/build/mac/keystone_install.sh +++ b/chrome/tools/build/mac/keystone_install.sh @@ -196,6 +196,17 @@ function ksadmin_supports_tagpath_tagkey() { # return value. } +# Returns 0 (true) if ksadmin supports --tag-path, --tag-key, --brand-path, +# and --brand-key. +function ksadmin_supports_brandpath_brandkey() { + # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620. + # --tag-path and --tag-key are already supported if the brand arguments are + # also supported. + is_ksadmin_version_ge 1.0.8.1620 + # The return value of is_ksadmin_version_ge is used as this function's + # return value. +} + # The argument should be the disk image path. Make sure it exists. if [ $# -lt 1 ] || [ ! -d "${1}" ]; then exit 2 @@ -322,6 +333,11 @@ if [ ${EUID} -ne 0 ] ; then set -e fi +# Collect the current app brand, it will be use later. +BRAND_ID_KEY=KSBrandID +APP_BRAND=$(defaults read "${DEST}/Contents/Info" "${BRAND_ID_KEY}" 2>/dev/null || + true) + # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners # and groups, respectively, from the source, and that is undesirable in this # case. -D copies devices and special files; copying devices only works @@ -420,8 +436,54 @@ fi # beforehand. ksadmin_version >& /dev/null || true +# The brand information is stored differently depending on whether this is +# running for a system or user ticket. +BRAND_PATH_PLIST= +SET_BRAND_FILE_ACCESS=no +if [ ${EUID} -ne 0 ] ; then + # Using a user level ticket. + BRAND_PATH_PLIST=~/"Library/Google/Google Chrome Brand" +else + # Using a system level ticket. + BRAND_PATH_PLIST="/Library/Google/Google Chrome Brand" + SET_BRAND_FILE_ACCESS=yes +fi +# If the user manually updated their copy of Chrome, there might be new brand +# information in the app bundle, and that needs to be copied out into the +# file Keystone looks at. +BRAND_PATH="${BRAND_PATH_PLIST}.plist" +if [ -n "${APP_BRAND}" ] ; then + BRAND_PATH_DIR=$(dirname "${BRAND_PATH}") + if [ ! -e "${BRAND_PATH_DIR}" ] ; then + mkdir -p "${BRAND_PATH_DIR}" + fi + defaults write "${BRAND_PATH_PLIST}" "${BRAND_ID_KEY}" -string "${APP_BRAND}" + if [ "${SET_BRAND_FILE_ACCESS}" = "yes" ] ; then + chown "root:wheel" "${BRAND_PATH}" >& /dev/null + chmod "a+r,u+w,go-w" "${BRAND_PATH}" >& /dev/null + fi +fi +# Confirm that the brand file exists (it is optional) +if [ ! -f "${BRAND_PATH}" ] ; then + BRAND_PATH= + # ksadmin reports an error if brand-path is cleared but brand-key still has a + # value, so if there is no path, clear the key also. + BRAND_ID_KEY= +fi + # Notify Keystone. -if ksadmin_supports_tagpath_tagkey ; then +if ksadmin_supports_brandpath_brandkey ; then + ksadmin --register \ + -P "${PRODUCT_ID}" \ + --version "${NEW_VERSION_KS}" \ + --xcpath "${DEST}" \ + --url "${URL}" \ + --tag "${CHANNEL_ID}" \ + --tag-path "${DEST}/Contents/Info.plist" \ + --tag-key "${CHANNEL_ID_KEY}" \ + --brand-path "${BRAND_PATH}" \ + --brand-key "${BRAND_ID_KEY}" || exit 11 +elif ksadmin_supports_tagpath_tagkey ; then ksadmin --register \ -P "${PRODUCT_ID}" \ --version "${NEW_VERSION_KS}" \ |