Skip to content

Improved fetch and delete PFObject validation logic. #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
16 changes: 7 additions & 9 deletions Parse/Internal/Object/Controller/PFObjectController.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ + (instancetype)controllerWithDataSource:(id<PFCommandRunnerProvider>)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) {
Expand Down Expand Up @@ -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) {
Expand Down
28 changes: 15 additions & 13 deletions Parse/Internal/Object/PFObjectPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

///--------------------------------------
Expand Down Expand Up @@ -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
///--------------------------------------
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 7 additions & 3 deletions Parse/PFInstallation.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#import "PFPushPrivate.h"
#import "PFQueryPrivate.h"
#import "Parse_Private.h"
#import "PFErrorUtilities.h"

@implementation PFInstallation (Private)

Expand All @@ -51,9 +52,12 @@ - (void)_clearDeviceToken {
[super removeObjectForKey:PFInstallationKeyDeviceToken];
}

// Check security on delete.
- (void)checkDeleteParams {
PFConsistencyAssert(NO, @"Installations cannot be deleted.");
- (BFTask<PFVoid> *)_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.
Expand Down
55 changes: 33 additions & 22 deletions Parse/PFObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
}

///--------------------------------------
Expand Down Expand Up @@ -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];
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 <PFVoid> *) *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];
}

Expand Down
39 changes: 24 additions & 15 deletions Parse/PFUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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