From 4f7e4c179c6779bd6ca707f64fd608b25931b47f Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Tue, 15 Sep 2015 18:39:14 -0700 Subject: [PATCH] Improved fetch and delete PFObject validation logic. --- .../BatchController/PFObjectBatchController.m | 1 - .../Object/Controller/PFObjectController.m | 16 +++--- Parse/Internal/Object/PFObjectPrivate.h | 28 +++++----- Parse/PFInstallation.m | 10 +++- Parse/PFObject.m | 55 +++++++++++-------- Parse/PFUser.m | 39 ++++++++----- 6 files changed, 86 insertions(+), 63 deletions(-) diff --git a/Parse/Internal/Object/BatchController/PFObjectBatchController.m b/Parse/Internal/Object/BatchController/PFObjectBatchController.m index 5b530b0ed..5ad34ca2e 100644 --- a/Parse/Internal/Object/BatchController/PFObjectBatchController.m +++ b/Parse/Internal/Object/BatchController/PFObjectBatchController.m @@ -150,7 +150,6 @@ - (BFTask *)deleteObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)s - (PFRESTCommand *)_deleteCommandForObjects:(NSArray *)objects withSessionToken:(NSString *)sessionToken { NSMutableArray *commands = [NSMutableArray arrayWithCapacity:objects.count]; for (PFObject *object in objects) { - [object checkDeleteParams]; PFRESTCommand *deleteCommand = [PFRESTObjectCommand deleteObjectCommandForObjectState:object._state withSessionToken:sessionToken]; [commands addObject:deleteCommand]; diff --git a/Parse/Internal/Object/Controller/PFObjectController.m b/Parse/Internal/Object/Controller/PFObjectController.m index 0fd14fba8..ec5d45b5c 100644 --- a/Parse/Internal/Object/Controller/PFObjectController.m +++ b/Parse/Internal/Object/Controller/PFObjectController.m @@ -52,15 +52,11 @@ + (instancetype)controllerWithDataSource:(id)dataSource - (BFTask *)fetchObjectAsync:(PFObject *)object withSessionToken:(NSString *)sessionToken { @weakify(self); - return [[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ + return [[[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ + return [object _validateFetchAsync]; + }] continueWithSuccessBlock:^id(BFTask *task) { @strongify(self); - PFObjectState *state = [object._state copy]; - if (!state.objectId) { - NSError *error = [PFErrorUtilities errorWithCode:kPFErrorMissingObjectId - message:@"Can't fetch an object that hasn't been saved to the server."]; - return [BFTask taskWithError:error]; - } - PFRESTCommand *command = [PFRESTObjectCommand fetchObjectCommandForObjectState:state + PFRESTCommand *command = [PFRESTObjectCommand fetchObjectCommandForObjectState:[object._state copy] withSessionToken:sessionToken]; return [self _runFetchCommand:command forObject:object]; }] continueWithSuccessBlock:^id(BFTask *task) { @@ -89,7 +85,9 @@ - (BFTask *)processFetchResultAsync:(NSDictionary *)result forObject:(PFObject * - (BFTask *)deleteObjectAsync:(PFObject *)object withSessionToken:(nullable NSString *)sessionToken { @weakify(self); - return [BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ + return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ + return [object _validateDeleteAsync]; + }] continueWithSuccessBlock:^id(BFTask *task) { @strongify(self); PFObjectState *state = [object._state copy]; if (!state.objectId) { diff --git a/Parse/Internal/Object/PFObjectPrivate.h b/Parse/Internal/Object/PFObjectPrivate.h index a004e75f0..949e58911 100644 --- a/Parse/Internal/Object/PFObjectPrivate.h +++ b/Parse/Internal/Object/PFObjectPrivate.h @@ -48,18 +48,6 @@ objectId:(NSString *)objectId isComplete:(BOOL)complete; -///-------------------------------------- -/// @name Validation -///-------------------------------------- - -/*! - Validate the save eventually operation with the current state. - The result of this task is ignored. The error/cancellation/exception will prevent `saveEventually`. - - @returns Task that encapsulates the validtion. - */ -- (BFTask *)_validateSaveEventuallyAsync; - @optional ///-------------------------------------- @@ -107,6 +95,21 @@ #endif +///-------------------------------------- +/// @name Validation +///-------------------------------------- + +- (BFTask PF_GENERIC(PFVoid) *)_validateFetchAsync NS_REQUIRES_SUPER; +- (BFTask PF_GENERIC(PFVoid) *)_validateDeleteAsync NS_REQUIRES_SUPER; + +/*! + Validate the save eventually operation with the current state. + The result of this task is ignored. The error/cancellation/exception will prevent `saveEventually`. + + @returns Task that encapsulates the validation. + */ +- (BFTask PF_GENERIC(PFVoid) *)_validateSaveEventuallyAsync NS_REQUIRES_SUPER; + ///-------------------------------------- /// @name Pin ///-------------------------------------- @@ -177,7 +180,6 @@ ///-------------------------------------- #pragma mark - Validations ///-------------------------------------- -- (void)checkDeleteParams; - (void)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser; /*! Checks if Parse class name could be used to initialize a given instance of PFObject or it's subclass. diff --git a/Parse/PFInstallation.m b/Parse/PFInstallation.m index 724af35fc..edcafce4f 100644 --- a/Parse/PFInstallation.m +++ b/Parse/PFInstallation.m @@ -27,6 +27,7 @@ #import "PFPushPrivate.h" #import "PFQueryPrivate.h" #import "Parse_Private.h" +#import "PFErrorUtilities.h" @implementation PFInstallation (Private) @@ -51,9 +52,12 @@ - (void)_clearDeviceToken { [super removeObjectForKey:PFInstallationKeyDeviceToken]; } -// Check security on delete. -- (void)checkDeleteParams { - PFConsistencyAssert(NO, @"Installations cannot be deleted."); +- (BFTask *)_validateDeleteAsync { + return [[super _validateDeleteAsync] continueWithSuccessBlock:^id(BFTask PF_GENERIC(PFVoid) *task) { + NSError *error = [PFErrorUtilities errorWithCode:kPFErrorCommandUnavailable + message:@"Installation cannot be deleted"]; + return [BFTask taskWithError:error]; + }]; } // Validates a class name. We override this to only allow the installation class name. diff --git a/Parse/PFObject.m b/Parse/PFObject.m index efb576be5..bd8173706 100644 --- a/Parse/PFObject.m +++ b/Parse/PFObject.m @@ -798,11 +798,6 @@ - (BOOL)isDataAvailableForKey:(NSString *)key { #pragma mark - Validations ///-------------------------------------- -// Validations that are done on delete. For now, there is nothing. -- (void)checkDeleteParams { - return; -} - // Validations that are done on save. For now, there is nothing. - (void)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser { return; @@ -1531,8 +1526,6 @@ - (BFTask *)fetchAsync:(BFTask *)toAwait { } - (BFTask *)deleteAsync:(BFTask *)toAwait { - [self checkDeleteParams]; - PFCurrentUserController *controller = [[self class] currentUserController]; return [[controller getCurrentUserSessionTokenAsync] continueWithBlock:^id(BFTask *task) { NSString *sessionToken = task.result; @@ -1568,11 +1561,7 @@ - (PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes } - (PFRESTCommand *)_currentDeleteCommandWithSessionToken:(NSString *)sessionToken { - @synchronized (lock) { - [self checkDeleteParams]; - return [PFRESTObjectCommand deleteObjectCommandForObjectState:self._state - withSessionToken:sessionToken]; - } + return [PFRESTObjectCommand deleteObjectCommandForObjectState:self._state withSessionToken:sessionToken]; } ///-------------------------------------- @@ -1797,9 +1786,24 @@ + (PFObjectState *)_newObjectStateWithParseClassName:(NSString *)className return [PFObjectState stateWithParseClassName:className objectId:objectId isComplete:complete]; } -#pragma mark Validation +///-------------------------------------- +#pragma mark - Validation +///-------------------------------------- -- (BFTask *)_validateSaveEventuallyAsync { +- (BFTask PF_GENERIC(PFVoid) *)_validateFetchAsync { + if (!self._state.objectId) { + NSError *error = [PFErrorUtilities errorWithCode:kPFErrorMissingObjectId + message:@"Can't fetch an object that hasn't been saved to the server."]; + return [BFTask taskWithError:error]; + } + return [BFTask taskWithResult:nil]; +} + +- (BFTask PF_GENERIC(PFVoid) *)_validateDeleteAsync { + return [BFTask taskWithResult:nil]; +} + +- (BFTask PF_GENERIC(PFVoid) *)_validateSaveEventuallyAsync { return [BFTask taskWithResult:nil]; } @@ -2012,9 +2016,10 @@ - (void)saveEventually:(PFBooleanResultBlock)block { - (BFTask *)deleteEventually { return [[[_eventuallyTaskQueue enqueue:^BFTask *(BFTask *toAwait) { NSString *sessionToken = [PFUser currentSessionToken]; - return [toAwait continueAsyncWithBlock:^id(BFTask *task) { + return [[toAwait continueAsyncWithBlock:^id(BFTask *task) { + return [self _validateDeleteAsync]; + }] continueWithSuccessBlock:^id(BFTask *task) { @synchronized (lock) { - [self checkDeleteParams]; _deletingEventually += 1; PFOfflineStore *store = [Parse _currentManager].offlineStore; @@ -2472,12 +2477,18 @@ + (BOOL)deleteAll:(NSArray *)objects error:(NSError **)error { NSArray *uniqueObjects = [PFObjectBatchController uniqueObjectsArrayFromArray:deleteObjects usingFilter:^BOOL(PFObject *object) { return (object.objectId != nil); }]; - [uniqueObjects makeObjectsPerformSelector:@selector(checkDeleteParams)]; // TODO: (nlutsenko) Make it async? - return [self _enqueue:^BFTask *(BFTask *toAwait) { - return [toAwait continueAsyncWithBlock:^id(BFTask *task) { - return [[self objectBatchController] deleteObjectsAsync:uniqueObjects withSessionToken:sessionToken]; - }]; - } forObjects:uniqueObjects]; + NSMutableArray PF_GENERIC(BFTask *) *validationTasks = [NSMutableArray array]; + for (PFObject *object in uniqueObjects) { + [validationTasks addObject:[object _validateDeleteAsync]]; + } + return [[BFTask taskForCompletionOfAllTasks:validationTasks] continueWithSuccessBlock:^id(BFTask *task) { + return [self _enqueue:^BFTask *(BFTask *toAwait) { + return [toAwait continueAsyncWithBlock:^id(BFTask *task) { + return [[self objectBatchController] deleteObjectsAsync:uniqueObjects + withSessionToken:sessionToken]; + }]; + } forObjects:uniqueObjects]; + }]; }] continueWithSuccessResult:@YES]; } diff --git a/Parse/PFUser.m b/Parse/PFUser.m index 9ac146051..d7a6eacc1 100644 --- a/Parse/PFUser.m +++ b/Parse/PFUser.m @@ -92,12 +92,32 @@ + (BFTask *)_getCurrentUserSessionTokenAsync { #pragma mark - PFObject ///-------------------------------------- -// Check security on delete -- (void)checkDeleteParams { - PFConsistencyAssert(self.isAuthenticated, @"User cannot be deleted unless they have been authenticated via logIn or signUp"); - [super checkDeleteParams]; +#pragma mark Validation + +- (BFTask PF_GENERIC(PFVoid) *)_validateDeleteAsync { + return [[super _validateDeleteAsync] continueWithSuccessBlock:^id(BFTask PF_GENERIC(PFVoid) *task) { + if (!self.isAuthenticated) { + NSError *error = [PFErrorUtilities errorWithCode:kPFErrorUserCannotBeAlteredWithoutSession + message:@"User cannot be deleted unless they have been authenticated."]; + return [BFTask taskWithError:error]; + } + return nil; + }]; +} + +- (BFTask PF_GENERIC(PFVoid) *)_validateSaveEventuallyAsync { + return [[super _validateSaveEventuallyAsync] continueWithSuccessBlock:^id(BFTask PF_GENERIC(PFVoid) *task) { + if ([self isDirtyForKey:PFUserPasswordRESTKey]) { + NSError *error = [PFErrorUtilities errorWithCode:kPFErrorOperationForbidden + message:@"Unable to saveEventually a PFUser with dirty password."]; + return [BFTask taskWithError:error]; + } + return nil; + }]; } +#pragma mark Else + - (NSString *)displayClassName { if ([self isMemberOfClass:[PFUser class]]) { return @"PFUser"; @@ -1202,15 +1222,4 @@ + (PFObjectState *)_newObjectStateWithParseClassName:(NSString *)className return [PFUserState stateWithParseClassName:className objectId:objectId isComplete:complete]; } -#pragma mark Validation - -- (BFTask *)_validateSaveEventuallyAsync { - if ([self isDirtyForKey:PFUserPasswordRESTKey]) { - NSError *error = [PFErrorUtilities errorWithCode:kPFErrorOperationForbidden - message:@"Unable to saveEventually a PFUser with dirty password."]; - return [BFTask taskWithError:error]; - } - return [BFTask taskWithResult:nil]; -} - @end