Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(367)

Side by Side Diff: chrome/browser/cocoa/keystone_glue.mm

Issue 437053: In-application Keystone ticket promotion (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/cocoa/keystone_glue.h ('k') | chrome/browser/cocoa/keystone_glue_unittest.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/app/keystone_glue.h" 5 #import "chrome/browser/cocoa/keystone_glue.h"
6 6
7 #include <sys/param.h>
8 #include <sys/mount.h>
9
10 #include <vector>
11
12 #import "app/l10n_util_mac.h"
7 #include "base/logging.h" 13 #include "base/logging.h"
8 #include "base/mac_util.h" 14 #include "base/mac_util.h"
9 #import "base/worker_pool_mac.h" 15 #import "base/worker_pool_mac.h"
16 #include "chrome/browser/cocoa/authorization_util.h"
10 #include "chrome/common/chrome_constants.h" 17 #include "chrome/common/chrome_constants.h"
18 #include "grit/chromium_strings.h"
19 #include "grit/generated_resources.h"
11 20
12 namespace { 21 namespace {
13 22
14 // Provide declarations of the Keystone registration bits needed here. From 23 // Provide declarations of the Keystone registration bits needed here. From
15 // KSRegistration.h. 24 // KSRegistration.h.
16 typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType; 25 typedef enum {
26 kKSPathExistenceChecker,
27 } KSExistenceCheckerType;
28
29 typedef enum {
30 kKSRegistrationUserTicket,
31 kKSRegistrationSystemTicket,
32 kKSRegistrationDontKnowWhatKindOfTicket,
33 } KSRegistrationTicketType;
34
35 NSString *KSRegistrationDidCompleteNotification =
36 @"KSRegistrationDidCompleteNotification";
37 NSString *KSRegistrationPromotionDidCompleteNotification =
38 @"KSRegistrationPromotionDidCompleteNotification";
17 39
18 NSString *KSRegistrationCheckForUpdateNotification = 40 NSString *KSRegistrationCheckForUpdateNotification =
19 @"KSRegistrationCheckForUpdateNotification"; 41 @"KSRegistrationCheckForUpdateNotification";
20 NSString *KSRegistrationStatusKey = @"Status"; 42 NSString *KSRegistrationStatusKey = @"Status";
21 NSString *KSRegistrationVersionKey = @"Version"; 43 NSString *KSRegistrationVersionKey = @"Version";
22 NSString *KSRegistrationUpdateCheckErrorKey = @"Error"; 44 NSString *KSRegistrationUpdateCheckErrorKey = @"Error";
23 45
24 NSString *KSRegistrationStartUpdateNotification = 46 NSString *KSRegistrationStartUpdateNotification =
25 @"KSRegistrationStartUpdateNotification"; 47 @"KSRegistrationStartUpdateNotification";
26 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful"; 48 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful";
27 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; 49 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled";
28 50
29 NSString *KSRegistrationRemoveExistingTag = @""; 51 NSString *KSRegistrationRemoveExistingTag = @"";
30 #define KSRegistrationPreserveExistingTag nil 52 #define KSRegistrationPreserveExistingTag nil
31 53
32 } // namespace 54 } // namespace
33 55
34 @interface KSRegistration : NSObject 56 @interface KSRegistration : NSObject
35 57
36 + (id)registrationWithProductID:(NSString*)productID; 58 + (id)registrationWithProductID:(NSString*)productID;
37 59
38 - (BOOL)registerWithVersion:(NSString*)version 60 - (BOOL)registerWithVersion:(NSString*)version
39 existenceCheckerType:(KSExistenceCheckerType)xctype 61 existenceCheckerType:(KSExistenceCheckerType)xctype
40 existenceCheckerString:(NSString*)xc 62 existenceCheckerString:(NSString*)xc
41 serverURLString:(NSString*)serverURLString 63 serverURLString:(NSString*)serverURLString
42 preserveTTToken:(BOOL)preserveToken 64 preserveTTToken:(BOOL)preserveToken
43 tag:(NSString*)tag; 65 tag:(NSString*)tag;
44 66
67 - (BOOL)promoteWithVersion:(NSString*)version
68 existenceCheckerType:(KSExistenceCheckerType)xctype
69 existenceCheckerString:(NSString*)xc
70 serverURLString:(NSString*)serverURLString
71 preserveTTToken:(BOOL)preserveToken
72 tag:(NSString*)tag
73 authorization:(AuthorizationRef)authorization;
74
45 - (void)setActive; 75 - (void)setActive;
46 - (void)checkForUpdate; 76 - (void)checkForUpdate;
47 - (void)startUpdate; 77 - (void)startUpdate;
78 - (KSRegistrationTicketType)ticketType;
48 79
49 @end // @interface KSRegistration 80 @end // @interface KSRegistration
50 81
51 @interface KeystoneGlue(Private) 82 @interface KeystoneGlue(Private)
52 83
84 // Called when Keystone registration completes.
85 - (void)registrationComplete:(NSNotification*)notification;
86
53 // Called periodically to announce activity by pinging the Keystone server. 87 // Called periodically to announce activity by pinging the Keystone server.
54 - (void)markActive:(NSTimer*)timer; 88 - (void)markActive:(NSTimer*)timer;
55 89
56 // Called when an update check or update installation is complete. Posts the 90 // Called when an update check or update installation is complete. Posts the
57 // kAutoupdateStatusNotification notification to the default notification 91 // kAutoupdateStatusNotification notification to the default notification
58 // center. 92 // center.
59 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version; 93 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version;
60 94
61 // These three methods are used to determine the version of the application 95 // These three methods are used to determine the version of the application
62 // currently installed on disk, compare that to the currently-running version, 96 // currently installed on disk, compare that to the currently-running version,
63 // decide whether any updates have been installed, and call 97 // decide whether any updates have been installed, and call
64 // -updateStatus:version:. 98 // -updateStatus:version:.
65 // 99 //
66 // In order to check the version on disk, the installed application's 100 // In order to check the version on disk, the installed application's
67 // Info.plist dictionary must be read; in order to see changes as updates are 101 // Info.plist dictionary must be read; in order to see changes as updates are
68 // applied, the dictionary must be read each time, bypassing any caches such 102 // applied, the dictionary must be read each time, bypassing any caches such
69 // as the one that NSBundle might be maintaining. Reading files can be a 103 // as the one that NSBundle might be maintaining. Reading files can be a
70 // blocking operation, and blocking operations are to be avoided on the main 104 // blocking operation, and blocking operations are to be avoided on the main
71 // thread. I'm not quite sure what jank means, but I bet that a blocked main 105 // thread. I'm not quite sure what jank means, but I bet that a blocked main
72 // thread would cause some of it. 106 // thread would cause some of it.
73 // 107 //
74 // -determineUpdateStatusAsync is called on the main thread to initiate the 108 // -determineUpdateStatusAsync is called on the main thread to initiate the
75 // operation. It performs initial set-up work that must be done on the main 109 // operation. It performs initial set-up work that must be done on the main
76 // thread and arranges for -determineUpdateStatusAtPath: to be called on a 110 // thread and arranges for -determineUpdateStatus to be called on a work queue
77 // work queue thread managed by NSOperationQueue. 111 // thread managed by NSOperationQueue.
78 // -determineUpdateStatusAtPath: then reads the Info.plist, gets the version 112 // -determineUpdateStatus then reads the Info.plist, gets the version from the
79 // from the CFBundleShortVersionString key, and performs 113 // CFBundleShortVersionString key, and performs
80 // -determineUpdateStatusForVersion: on the main thread. 114 // -determineUpdateStatusForVersion: on the main thread.
81 // -determineUpdateStatusForVersion: does the actual comparison of the version 115 // -determineUpdateStatusForVersion: does the actual comparison of the version
82 // on disk with the running version and calls -updateStatus:version: with the 116 // on disk with the running version and calls -updateStatus:version: with the
83 // results of its analysis. 117 // results of its analysis.
84 - (void)determineUpdateStatusAsync; 118 - (void)determineUpdateStatusAsync;
85 - (void)determineUpdateStatusAtPath:(NSString*)appPath; 119 - (void)determineUpdateStatus;
86 - (void)determineUpdateStatusForVersion:(NSString*)version; 120 - (void)determineUpdateStatusForVersion:(NSString*)version;
87 121
122 // Returns YES if registration_ is definitely on a user ticket. If definitely
123 // on a system ticket, or uncertain of ticket type (due to an older version
124 // of Keystone being used), returns NO.
125 - (BOOL)isUserTicket;
126
127 // Called when ticket promotion completes.
128 - (void)promotionComplete:(NSNotification*)notification;
129
130 // Changes the application's ownership and permissions so that all files are
131 // owned by root:wheel and all files and directories are writable only by
132 // root, but readable and executable as needed by everyone.
133 // -changePermissionsForPromotionAsync is called on the main thread by
134 // -promotionComplete. That routine calls
135 // -changePermissionsForPromotionWithTool: on a work queue thread. When done,
136 // -changePermissionsForPromotionComplete is called on the main thread.
137 - (void)changePermissionsForPromotionAsync;
138 - (void)changePermissionsForPromotionWithTool:(NSString*)toolPath;
139 - (void)changePermissionsForPromotionComplete;
140
88 @end // @interface KeystoneGlue(Private) 141 @end // @interface KeystoneGlue(Private)
89 142
90 const NSString* const kAutoupdateStatusNotification = 143 const NSString* const kAutoupdateStatusNotification =
91 @"AutoupdateStatusNotification"; 144 @"AutoupdateStatusNotification";
92 const NSString* const kAutoupdateStatusStatus = @"status"; 145 const NSString* const kAutoupdateStatusStatus = @"status";
93 const NSString* const kAutoupdateStatusVersion = @"version"; 146 const NSString* const kAutoupdateStatusVersion = @"version";
94 147
95 @implementation KeystoneGlue 148 @implementation KeystoneGlue
96 149
97 // TODO(jrg): use base::SingletonObjC<KeystoneGlue> 150 + (id)defaultKeystoneGlue {
98 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked 151 static bool sTriedCreatingDefaultKeystoneGlue = false;
152 // TODO(jrg): use base::SingletonObjC<KeystoneGlue>
153 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
99 154
100 + (id)defaultKeystoneGlue { 155 if (!sTriedCreatingDefaultKeystoneGlue) {
101 if (!sDefaultKeystoneGlue) { 156 sTriedCreatingDefaultKeystoneGlue = true;
157
102 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init]; 158 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init];
103 [sDefaultKeystoneGlue loadParameters]; 159 [sDefaultKeystoneGlue loadParameters];
104 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) { 160 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) {
105 [sDefaultKeystoneGlue release]; 161 [sDefaultKeystoneGlue release];
106 sDefaultKeystoneGlue = nil; 162 sDefaultKeystoneGlue = nil;
107 } 163 }
108 } 164 }
109 return sDefaultKeystoneGlue; 165 return sDefaultKeystoneGlue;
110 } 166 }
111 167
112 - (id)init { 168 - (id)init {
113 if ((self = [super init])) { 169 if ((self = [super init])) {
114 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 170 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
115 171
116 [center addObserver:self 172 [center addObserver:self
173 selector:@selector(registrationComplete:)
174 name:KSRegistrationDidCompleteNotification
175 object:nil];
176
177 [center addObserver:self
178 selector:@selector(promotionComplete:)
179 name:KSRegistrationPromotionDidCompleteNotification
180 object:nil];
181
182 [center addObserver:self
117 selector:@selector(checkForUpdateComplete:) 183 selector:@selector(checkForUpdateComplete:)
118 name:KSRegistrationCheckForUpdateNotification 184 name:KSRegistrationCheckForUpdateNotification
119 object:nil]; 185 object:nil];
120 186
121 [center addObserver:self 187 [center addObserver:self
122 selector:@selector(installUpdateComplete:) 188 selector:@selector(installUpdateComplete:)
123 name:KSRegistrationStartUpdateNotification 189 name:KSRegistrationStartUpdateNotification
124 object:nil]; 190 object:nil];
125 } 191 }
126 192
127 return self; 193 return self;
128 } 194 }
129 195
130 - (void)dealloc { 196 - (void)dealloc {
197 [productID_ release];
198 [appPath_ release];
131 [url_ release]; 199 [url_ release];
132 [productID_ release];
133 [version_ release]; 200 [version_ release];
134 [channel_ release]; 201 [channel_ release];
135 [registration_ release]; 202 [registration_ release];
136 [[NSNotificationCenter defaultCenter] removeObserver:self]; 203 [[NSNotificationCenter defaultCenter] removeObserver:self];
137 [super dealloc]; 204 [super dealloc];
138 } 205 }
139 206
140 - (NSDictionary*)infoDictionary { 207 - (NSDictionary*)infoDictionary {
141 // Use mac_util::MainAppBundle() to get the app framework's dictionary. 208 // Use mac_util::MainAppBundle() to get the app framework's dictionary.
142 return [mac_util::MainAppBundle() infoDictionary]; 209 return [mac_util::MainAppBundle() infoDictionary];
143 } 210 }
144 211
145 - (void)loadParameters { 212 - (void)loadParameters {
146 NSDictionary* infoDictionary = [self infoDictionary]; 213 NSDictionary* infoDictionary = [self infoDictionary];
214
215 // Use [NSBundle mainBundle] to get the application's own bundle identifier
216 // and path, not the framework's. For auto-update, the application is
217 // what's significant here: it's used to locate the outermost part of the
218 // application for the existence checker and other operations that need to
219 // see the entire application bundle.
220 NSBundle* appBundle = [NSBundle mainBundle];
221
222 NSString* productID = [infoDictionary objectForKey:@"KSProductID"];
223 if (productID == nil) {
224 productID = [appBundle bundleIdentifier];
225 }
226
227 NSString* appPath = [appBundle bundlePath];
147 NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"]; 228 NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"];
148 NSString* product = [infoDictionary objectForKey:@"KSProductID"];
149 if (product == nil) {
150 // Use [NSBundle mainBundle] to fall back to the app's own bundle
151 // identifier, not the app framework's.
152 product = [[NSBundle mainBundle] bundleIdentifier];
153 }
154 NSString* version = [infoDictionary objectForKey:@"KSVersion"]; 229 NSString* version = [infoDictionary objectForKey:@"KSVersion"];
155 if (!product || !url || !version) { 230
231 if (!productID || !appPath || !url || !version) {
156 // If parameters required for Keystone are missing, don't use it. 232 // If parameters required for Keystone are missing, don't use it.
157 return; 233 return;
158 } 234 }
159 235
160 NSString* channel = [infoDictionary objectForKey:@"KSChannelID"]; 236 NSString* channel = [infoDictionary objectForKey:@"KSChannelID"];
161 // The stable channel has no tag. If updating to stable, remove the 237 // The stable channel has no tag. If updating to stable, remove the
162 // dev and beta tags since we've been "promoted". 238 // dev and beta tags since we've been "promoted".
163 if (channel == nil) 239 if (channel == nil)
164 channel = KSRegistrationRemoveExistingTag; 240 channel = KSRegistrationRemoveExistingTag;
165 241
242 productID_ = [productID retain];
243 appPath_ = [appPath retain];
166 url_ = [url retain]; 244 url_ = [url retain];
167 productID_ = [product retain];
168 version_ = [version retain]; 245 version_ = [version retain];
169 channel_ = [channel retain]; 246 channel_ = [channel retain];
170 } 247 }
171 248
172 - (BOOL)loadKeystoneRegistration { 249 - (BOOL)loadKeystoneRegistration {
173 if (!productID_ || !url_ || !version_) 250 if (!productID_ || !appPath_ || !url_ || !version_)
174 return NO; 251 return NO;
175 252
176 // Load the KeystoneRegistration framework bundle if present. It lives 253 // Load the KeystoneRegistration framework bundle if present. It lives
177 // inside the framework, so use mac_util::MainAppBundle(); 254 // inside the framework, so use mac_util::MainAppBundle();
178 NSString* ksrPath = 255 NSString* ksrPath =
179 [[mac_util::MainAppBundle() privateFrameworksPath] 256 [[mac_util::MainAppBundle() privateFrameworksPath]
180 stringByAppendingPathComponent:@"KeystoneRegistration.framework"]; 257 stringByAppendingPathComponent:@"KeystoneRegistration.framework"];
181 NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath]; 258 NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath];
182 [ksrBundle load]; 259 [ksrBundle load];
183 260
184 // Harness the KSRegistration class. 261 // Harness the KSRegistration class.
185 Class ksrClass = [ksrBundle classNamed:@"KSRegistration"]; 262 Class ksrClass = [ksrBundle classNamed:@"KSRegistration"];
186 KSRegistration* ksr = [ksrClass registrationWithProductID:productID_]; 263 KSRegistration* ksr = [ksrClass registrationWithProductID:productID_];
187 if (!ksr) 264 if (!ksr)
188 return NO; 265 return NO;
189 266
190 registration_ = [ksr retain]; 267 registration_ = [ksr retain];
191 return YES; 268 return YES;
192 } 269 }
193 270
194 - (void)registerWithKeystone { 271 - (void)registerWithKeystone {
195 // The existence checks should use the path to the app bundle, not the 272 [self updateStatus:kAutoupdateRegistering version:nil];
196 // app framework bundle, so use [NSBundle mainBundle] instead of 273
197 // mac_util::MainBundle(). 274 if (![registration_ registerWithVersion:version_
198 [registration_ registerWithVersion:version_ 275 existenceCheckerType:kKSPathExistenceChecker
199 existenceCheckerType:kKSPathExistenceChecker 276 existenceCheckerString:appPath_
200 existenceCheckerString:[[NSBundle mainBundle] bundlePath] 277 serverURLString:url_
201 serverURLString:url_ 278 preserveTTToken:YES
202 preserveTTToken:YES 279 tag:channel_]) {
203 tag:channel_]; 280 [self updateStatus:kAutoupdateRegisterFailed version:nil];
281 return;
282 }
283
284 // Upon completion, KSRegistrationDidCompleteNotification will be posted,
285 // and -registrationComplete: will be called.
204 286
205 // Mark an active RIGHT NOW; don't wait an hour for the first one. 287 // Mark an active RIGHT NOW; don't wait an hour for the first one.
206 [registration_ setActive]; 288 [registration_ setActive];
207 289
208 // Set up hourly activity pings. 290 // Set up hourly activity pings.
209 timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour 291 timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour
210 target:self 292 target:self
211 selector:@selector(markActive:) 293 selector:@selector(markActive:)
212 userInfo:registration_ 294 userInfo:registration_
213 repeats:YES]; 295 repeats:YES];
214 } 296 }
215 297
298 - (void)registrationComplete:(NSNotification*)notification {
299 NSDictionary* userInfo = [notification userInfo];
300 if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) {
301 [self updateStatus:kAutoupdateRegistered version:nil];
302 } else {
303 // Dump registration_?
304 [self updateStatus:kAutoupdateRegisterFailed version:nil];
305 }
306 }
307
216 - (void)stopTimer { 308 - (void)stopTimer {
217 [timer_ invalidate]; 309 [timer_ invalidate];
218 } 310 }
219 311
220 - (void)markActive:(NSTimer*)timer { 312 - (void)markActive:(NSTimer*)timer {
221 KSRegistration* ksr = [timer userInfo]; 313 KSRegistration* ksr = [timer userInfo];
222 [ksr setActive]; 314 [ksr setActive];
223 } 315 }
224 316
225 - (void)checkForUpdate { 317 - (void)checkForUpdate {
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 updateSuccessfullyInstalled_ = YES; 375 updateSuccessfullyInstalled_ = YES;
284 376
285 // Nothing in the notification dictionary reports the version that was 377 // Nothing in the notification dictionary reports the version that was
286 // installed. Figure it out based on what's on disk. 378 // installed. Figure it out based on what's on disk.
287 [self determineUpdateStatusAsync]; 379 [self determineUpdateStatusAsync];
288 } 380 }
289 } 381 }
290 382
291 // Runs on the main thread. 383 // Runs on the main thread.
292 - (void)determineUpdateStatusAsync { 384 - (void)determineUpdateStatusAsync {
293 // NSBundle is not documented as being thread-safe. Do NSBundle operations
294 // on the main thread before jumping over to a NSOperationQueue-managed
295 // thread to do blocking file input.
296 DCHECK([NSThread isMainThread]); 385 DCHECK([NSThread isMainThread]);
297 386
298 SEL selector = @selector(determineUpdateStatusAtPath:); 387 SEL selector = @selector(determineUpdateStatus);
299 NSString* appPath = [[NSBundle mainBundle] bundlePath];
300 NSInvocationOperation* operation = 388 NSInvocationOperation* operation =
301 [[[NSInvocationOperation alloc] initWithTarget:self 389 [[[NSInvocationOperation alloc] initWithTarget:self
302 selector:selector 390 selector:selector
303 object:appPath] autorelease]; 391 object:nil] autorelease];
304 392
305 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue]; 393 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue];
306 [operationQueue addOperation:operation]; 394 [operationQueue addOperation:operation];
307 } 395 }
308 396
309 // Runs on a thread managed by NSOperationQueue. 397 // Runs on a thread managed by NSOperationQueue.
310 - (void)determineUpdateStatusAtPath:(NSString*)appPath { 398 - (void)determineUpdateStatus {
311 DCHECK(![NSThread isMainThread]); 399 DCHECK(![NSThread isMainThread]);
312 400
313 NSString* appInfoPlistPath = 401 NSString* appInfoPlistPath =
314 [[appPath stringByAppendingPathComponent:@"Contents"] 402 [[appPath_ stringByAppendingPathComponent:@"Contents"]
315 stringByAppendingPathComponent:@"Info.plist"]; 403 stringByAppendingPathComponent:@"Info.plist"];
316 NSDictionary* infoPlist = 404 NSDictionary* infoPlist =
317 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath]; 405 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath];
318 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"]; 406 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"];
319 407
320 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:) 408 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:)
321 withObject:version 409 withObject:version
322 waitUntilDone:NO]; 410 waitUntilDone:NO];
323 } 411 }
324 412
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
376 } 464 }
377 465
378 - (AutoupdateStatus)recentStatus { 466 - (AutoupdateStatus)recentStatus {
379 NSDictionary* dictionary = [recentNotification_ userInfo]; 467 NSDictionary* dictionary = [recentNotification_ userInfo];
380 return static_cast<AutoupdateStatus>( 468 return static_cast<AutoupdateStatus>(
381 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); 469 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
382 } 470 }
383 471
384 - (BOOL)asyncOperationPending { 472 - (BOOL)asyncOperationPending {
385 AutoupdateStatus status = [self recentStatus]; 473 AutoupdateStatus status = [self recentStatus];
386 return status == kAutoupdateChecking || status == kAutoupdateInstalling; 474 return status == kAutoupdateRegistering ||
475 status == kAutoupdateChecking ||
476 status == kAutoupdateInstalling ||
477 status == kAutoupdatePromoting;
478 }
479
480 - (BOOL)isUserTicket {
481 return [registration_ ticketType] == kKSRegistrationUserTicket;
482 }
483
484 - (BOOL)isOnReadOnlyFilesystem {
485 const char* appPathC = [appPath_ fileSystemRepresentation];
486 struct statfs statfsBuf;
487
488 if (statfs(appPathC, &statfsBuf) != 0) {
489 PLOG(ERROR) << "statfs";
490 // Be optimistic about the filesystem's writability.
491 return NO;
492 }
493
494 return (statfsBuf.f_flags & MNT_RDONLY) != 0;
495 }
496
497 - (BOOL)needsPromotion {
498 if (![self isUserTicket] || [self isOnReadOnlyFilesystem]) {
499 return NO;
500 }
501
502 // Check the outermost bundle directory, the main executable path, and the
503 // framework directory. It may be enough to just look at the outermost
504 // bundle directory, but checking an interior file and directory can be
505 // helpful in case permissions are set differently only on the outermost
506 // directory. An interior file and directory are both checked because some
507 // file operations, such as Snow Leopard's Finder's copy operation when
508 // authenticating, may actually result in different ownership being applied
509 // to files and directories.
510 NSFileManager* fileManager = [NSFileManager defaultManager];
511 NSString* executablePath = [[NSBundle mainBundle] executablePath];
512 NSString* frameworkPath = [mac_util::MainAppBundle() bundlePath];
513 return ![fileManager isWritableFileAtPath:appPath_] ||
514 ![fileManager isWritableFileAtPath:executablePath] ||
515 ![fileManager isWritableFileAtPath:frameworkPath];
516 }
517
518 - (BOOL)wantsPromotion {
519 // -needsPromotion checks these too, but this method doesn't necessarily
520 // return NO just becuase -needsPromotion returns NO, so another check is
521 // needed here.
522 if (![self isUserTicket] || [self isOnReadOnlyFilesystem]) {
523 return NO;
524 }
525
526 if ([self needsPromotion]) {
527 return YES;
528 }
529
530 return [appPath_ hasPrefix:@"/Applications/"];
531 }
532
533 - (void)promoteTicket {
534 if ([self asyncOperationPending] || ![self wantsPromotion]) {
535 // Because there are multiple ways of reaching promoteTicket that might
536 // not lock each other out, it may be possible to arrive here while an
537 // asynchronous operation is pending, or even after promotion has already
538 // occurred. Just quietly return without doing anything.
539 return;
540 }
541
542 // Create an empty AuthorizationRef.
543 scoped_AuthorizationRef authorization;
544 OSStatus status = AuthorizationCreate(NULL,
545 kAuthorizationEmptyEnvironment,
546 kAuthorizationFlagDefaults,
547 &authorization);
548 if (status != errAuthorizationSuccess) {
549 LOG(ERROR) << "AuthorizationCreate: " << status;
550 return;
551 }
552
553 // Specify the "system.privilege.admin" right, which allows
554 // AuthorizationExecuteWithPrivileges to run commands as root.
555 AuthorizationItem rightItems[] = {
556 {kAuthorizationRightExecute, 0, NULL, 0}
557 };
558 AuthorizationRights rights = {arraysize(rightItems), rightItems};
559
560 // product_logo_32.png is used instead of app.icns because Authorization
561 // Services requires an image that NSImage can read.
562 NSString* iconPath =
563 [mac_util::MainAppBundle() pathForResource:@"product_logo_32"
564 ofType:@"png"];
565 const char* iconPathC = [iconPath fileSystemRepresentation];
566 size_t iconPathLength = iconPathC ? strlen(iconPathC) : 0;
567
568 // The OS will append " Type an administrator's name and password to allow
569 // <CFBundleDisplayName> to make changes."
570 NSString* prompt = l10n_util::GetNSStringFWithFixup(
571 IDS_PROMOTE_AUTHENTICATION_PROMPT,
572 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
573 const char* promptC = [prompt UTF8String];
574 size_t promptLength = promptC ? strlen(promptC) : 0;
575
576 AuthorizationItem environmentItems[] = {
577 {kAuthorizationEnvironmentIcon, iconPathLength, (void*)iconPathC, 0},
578 {kAuthorizationEnvironmentPrompt, promptLength, (void*)promptC, 0}
579 };
580
581 AuthorizationEnvironment environment = {arraysize(environmentItems),
582 environmentItems};
583
584 AuthorizationFlags flags = kAuthorizationFlagDefaults |
585 kAuthorizationFlagInteractionAllowed |
586 kAuthorizationFlagExtendRights |
587 kAuthorizationFlagPreAuthorize;
588
589 status = AuthorizationCopyRights(authorization,
590 &rights,
591 &environment,
592 flags,
593 NULL);
594 if (status != errAuthorizationSuccess) {
595 if (status != errAuthorizationCanceled) {
596 LOG(ERROR) << "AuthorizationCopyRights: " << status;
597 }
598 return;
599 }
600
601 [self updateStatus:kAutoupdatePromoting version:nil];
602
603 // TODO(mark): Remove when able!
604 //
605 // keystone_promote_preflight is hopefully temporary. It's here to ensure
606 // that the Keystone system ticket store is in a usable state for all users
607 // on the system. Ideally, Keystone's installer or another part of Keystone
608 // would handle this. The underlying problem is http://b/2285921, and it
609 // causes http://b/2289908, which this workaround addresses.
610 //
611 // This is run synchronously, which isn't optimal, but
612 // -[KSRegistration promoteWithVersion:...] is currently synchronous too,
613 // and this operation needs to happen before that one.
614 //
615 // TODO(mark): Make asynchronous. That only makes sense if the promotion
616 // operation itself is asynchronous too. http://b/2290009. Hopefully,
617 // the Keystone promotion code will just be changed to do what preflight
618 // now does, and then the preflight script can be removed instead.
619 NSString* preflightPath =
620 [mac_util::MainAppBundle() pathForResource:@"keystone_promote_preflight"
621 ofType:@"sh"];
622 const char* preflightPathC = [preflightPath fileSystemRepresentation];
623 const char* arguments[] = {NULL};
624
625 int exit_status;
626 status = authorization_util::ExecuteWithPrivilegesAndWait(
627 authorization,
628 preflightPathC,
629 kAuthorizationFlagDefaults,
630 arguments,
631 NULL, // pipe
632 &exit_status);
633 if (status != errAuthorizationSuccess) {
634 LOG(ERROR) << "AuthorizationExecuteWithPrivileges preflight: " << status;
635 [self updateStatus:kAutoupdatePromoteFailed version:nil];
636 return;
637 }
638 if (exit_status != 0) {
639 LOG(ERROR) << "keystone_promote_preflight status " << exit_status;
640 [self updateStatus:kAutoupdatePromoteFailed version:nil];
641 return;
642 }
643
644 // Hang on to the AuthorizationRef so that it can be used once promotion is
645 // complete. Do this before asking Keystone to promote the ticket, because
646 // -promotionComplete: may be called from inside the Keystone promotion
647 // call.
648 authorization_.swap(authorization);
649
650 if (![registration_ promoteWithVersion:version_
651 existenceCheckerType:kKSPathExistenceChecker
652 existenceCheckerString:appPath_
653 serverURLString:url_
654 preserveTTToken:YES
655 tag:channel_
656 authorization:authorization_]) {
657 [self updateStatus:kAutoupdatePromoteFailed version:nil];
658 authorization_.reset();
659 return;
660 }
661
662 // Upon completion, KSRegistrationPromotionDidCompleteNotification will be
663 // posted, and -promotionComplete: will be called.
664 }
665
666 - (void)promotionComplete:(NSNotification*)notification {
667 NSDictionary* userInfo = [notification userInfo];
668 if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) {
669 [self changePermissionsForPromotionAsync];
670 } else {
671 authorization_.reset();
672 [self updateStatus:kAutoupdatePromoteFailed version:nil];
673 }
674 }
675
676 - (void)changePermissionsForPromotionAsync {
677 // NSBundle is not documented as being thread-safe. Do NSBundle operations
678 // on the main thread before jumping over to a NSOperationQueue-managed
679 // thread to run the tool.
680 DCHECK([NSThread isMainThread]);
681
682 SEL selector = @selector(changePermissionsForPromotionWithTool:);
683 NSString* toolPath =
684 [mac_util::MainAppBundle() pathForResource:@"keystone_promote_postflight"
685 ofType:@"sh"];
686
687 NSInvocationOperation* operation =
688 [[[NSInvocationOperation alloc] initWithTarget:self
689 selector:selector
690 object:toolPath] autorelease];
691
692 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue];
693 [operationQueue addOperation:operation];
694 }
695
696 - (void)changePermissionsForPromotionWithTool:(NSString*)toolPath {
697 const char* toolPathC = [toolPath fileSystemRepresentation];
698
699 const char* appPathC = [appPath_ fileSystemRepresentation];
700 const char* arguments[] = {appPathC, NULL};
701
702 int exit_status;
703 OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait(
704 authorization_,
705 toolPathC,
706 kAuthorizationFlagDefaults,
707 arguments,
708 NULL, // pipe
709 &exit_status);
710 if (status != errAuthorizationSuccess) {
711 LOG(ERROR) << "AuthorizationExecuteWithPrivileges postflight: " << status;
712 } else if (exit_status != 0) {
713 LOG(ERROR) << "keystone_promote_postflight status " << exit_status;
714 }
715
716 SEL selector = @selector(changePermissionsForPromotionComplete);
717 [self performSelectorOnMainThread:selector
718 withObject:nil
719 waitUntilDone:NO];
720 }
721
722 - (void)changePermissionsForPromotionComplete {
723 authorization_.reset();
724
725 [self updateStatus:kAutoupdatePromoted version:nil];
387 } 726 }
388 727
389 @end // @implementation KeystoneGlue 728 @end // @implementation KeystoneGlue
OLDNEW
« no previous file with comments | « chrome/browser/cocoa/keystone_glue.h ('k') | chrome/browser/cocoa/keystone_glue_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698