Open hugomelis opened 11 years ago
First than all if you set a defualt ACL changing the ACL of an object wont work as the the new ACL you create uses the defualt as a template, second, the ACL should be set in a different section any of the methods in FTASync seems inadequate so I recommend to create a helper method [FTASyncParent insertLocalEntityForClass:@"ClassName"] there you create all the variables you need and there you set the default ACL behavior then some other object adds the new ACL when need it. For your app, what you need to do its to create a role per user in a helper method that creates a role like @"userIdClassNameRole" then whenever you need to grant acces to that object just add a user to that role, it's actually pretty easy and works like a charm. (This way every user has sharing options for any entity you create)
Also remember to use PFObject *remoteObject = FTASyncParentSbclassObject.remoteObject [remoteObject doStuff] some how if you use [FTASyncParentSbclassObject.remoteObject doStuff] the custom accessor doesn't gets called and changes doesn't reflect in parse I had suffer hours with that issue.
(id)insertLocalEntityForClassName:(NSString)className { NSManagedObjectContext moc_ = [NSManagedObjectContext defaultContext];
id managedObject = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:moc_];
if ([managedObject respondsToSelector:@selector(ownerId)]) { [managedObject setOwnerId:[PFUser currentUser].objectId]; }
FTASyncParent *localObject = (id)managedObject; localObject.defaultACL = YES;
return managedObject;
} //You need to add the property so whenever you are locally creating a new object, you can set a defaultACL this to avoid critical changes in FTASync
(PFObject )remoteObject { //Need this property so that the same PFObject can be used multiple places when I have a new local object and // don't have an objectId to call (PFObject)objectWithoutDataWithClassName:objectId: if (self.isTraversing) { return _remoteObject; }
if (!_remoteObject || self.objectId) { _remoteObject = [PFObject objectWithClassName:[self parseClassname]]; self.traversing = YES; [self updateRemoteObject:self.remoteObject]; if (self.defaultACL) { [WSAACLLogicsController setDefaultACLForObject:_remoteObject]; self.defaultACL = NO; } self.traversing = NO; }
return _remoteObject; }
(BOOL)createInitialUserRoles {
NSMutableArray *objectsToSync = [[NSMutableArray alloc] init];
NSString personalDataRoleIdentifier = [NSString stringWithFormat:@"%@WSAUserPersonalDataRole",[PFUser currentUser].objectId]; PFRole userPersonalDataRole = [PFRole roleWithName:personalDataRoleIdentifier]; PFACL *ACL = [PFACL ACL]; [userPersonalDataRole setACL:ACL];
[objectsToSync addObject:userPersonalDataRole];
BOOL __block success; [PFObject saveAllInBackground:objectsToSync block:^(BOOL succeeded, NSError *error) { success = succeeded; }];
return success; }
(void)setDefaultACLForObject:(PFObject *)object {
NSString className = [object className]; NSString roleIdentifier = [NSString stringWithFormat:@"%@%@Role",[PFUser currentUser].objectId, className];
if ([className isEqualToString:@"WSAUserPersonalData"]) { PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]]; [userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier];
[object setACL:userPersonalDataACL];
return;
} }
Its very important to set the ownerId property in FTASyncParent class, and add it in the [FTAParseSync getObjectsOfClass:(NSString )className updatedSince:(NSDate )lastUpdate] query if you alter the ACL for objects and you get not only user created objects the FTASync will make a mess in your data base, make sure to sync only the objects created and owned for the user.
Also remember that if you use localEntitly.remoteObject in that moment all the properties will be copied and it will require more code so best practice its to use it after all local properties are set, also, set the custom ACL at the end for the same reasons
Thanks Cleqo! I will try this stuff this weekend, and will report back with the results! Thanks for your time and effort...
Well yesterday and today I have been trying to get this to work in my app, but I cannot seem to fill in the last gaps in the code. I will try to explain what I did so far:
1) I added the method: "insertLocalEntityForClassName" into the FTAParentSync class. I changed the following line from: NSManagedObjectContext moc_ = [NSManagedObjectContext defaultContext]; to: NSManagedObjectContext moc_ = [NSManagedObjectContext MR_defaultContext]; because of an error, and added the following property to the .h file: @property (nonatomic) BOOL defaultACL; and did a @synthesize in the .m file (I guess it is not needed in the datamodel?) After that I replaced the existing method: "remoteObject" with the one you supplied. Then I added the method: "setDefaultACLForObject" to FTASyncPartent.m And after that the method: "createInitialUserRoles" also to the FTASyncParent.m class.
Then I kind of seem to miss where to call the "createInitialUserRoles", and the "insertLocalEntityForClassName" method from?
Also about the adding of the ownerId, do I need to put that in the datamodel? Or is it just a property in the class? I guess for the query to work in FTAParseSync I have to add something like this: [query whereKey:@"ownerId" equalTo:] but I cannot seem to find out where to get the ownerId from?
Many questions, hopefully you can help me out with! Thanks...
yeah sorry for that, but as i told you i was still working on it, and it resulted to be a hell, working with roles its not as easy as it seems, as you have to retrieve it for everything, at the end the structure its quite difficult im thinking in posting a FTASync plus role per class per object, so any object created by an user can have Friend, public, private or selected users only ACL, after two days i made it work but its quite complex, the create initial users roles its used once, when you sign up the user for first time, after that you have to create a complex structure of roles to make it work correctly, the ownerId indeed goes directly in FTASynParent model so every object inherited also have it, the "insertLocalEntityForClassName" method goes in _FTASyncParent so you call it everywhere and it will be an auto sync object, its quite difficult but it works quite nice actually and the query constraint is right, just ensure to add it in both cases when there is a last update of the class and when doesn't.
The defaultACL needs to be out of model, thats the easier way to not affect any of the delicate structure in FTASync
You can also add an shouldSync property to FTASyncParent core data model, and decide in realtime when to sync local objects, its quite nice and requires minimum changes to make in the predicates of FTASyncHandler, but it will only work in objects without relationships or where all objects in relationships are set to shouldSync = NO
in my app the structure for roles ended been something like this:
(BOOL)createInitialUserRoles {
NSMutableArray objectsToSync = [[NSMutableArray alloc] init]; NSMutableArray defaultRolesBehavior = [[NSMutableArray alloc] init];
PFACL userACL = [PFACL ACLWithUser:[PFUser currentUser]]; PFACL publicACL = [PFACL ACL];
[publicACL setPublicReadAccess:YES]; [publicACL setPublicWriteAccess:YES];
//creates the friend role
NSString friendRoleIdentifier = [NSString stringWithFormat:@"%@WSAFriendRole",[PFUser currentUser].objectId]; PFRole friendRole = [PFRole roleWithName:friendRoleIdentifier acl:userACL];
[objectsToSync addObject:friendRole];
//sets the personal data role
NSString personalDataRoleIdentifier = [NSString stringWithFormat:@"%@WSAUserPersonalDataReaderRole",[PFUser currentUser].objectId]; PFRole userPersonalDataRole = [PFRole roleWithName:personalDataRoleIdentifier acl:userACL];
[objectsToSync addObject:userPersonalDataRole]; [defaultRolesBehavior addObject:userPersonalDataRole];
NSString publicPersonalDataRoleIdentifier = [NSString stringWithFormat:@"%@WSAPublicUserPersonalDataReaderRole",[PFUser currentUser].objectId]; PFRole publicUserPersonalDataRole = [PFRole roleWithName:publicPersonalDataRoleIdentifier acl:publicACL];
[objectsToSync addObject:publicUserPersonalDataRole];
NSString selectedPersonalDataRoleIdentifier = [NSString stringWithFormat:@"%@WSASelectedUserPersonalDataReaderRole",[PFUser currentUser].objectId]; PFRole selectedUserPersonalDataRole = [PFRole roleWithName:selectedPersonalDataRoleIdentifier acl:userACL];
[objectsToSync addObject:selectedUserPersonalDataRole];
// sets the general balance role
NSString balanceRoleIdentifier = [NSString stringWithFormat:@"%@WSAGeneralBalanceReaderRole",[PFUser currentUser].objectId]; PFRole generalBalanceRole = [PFRole roleWithName:balanceRoleIdentifier acl:userACL];
[objectsToSync addObject:generalBalanceRole]; [defaultRolesBehavior addObject:generalBalanceRole];
NSString publicBalanceRoleIdentifier = [NSString stringWithFormat:@"%@WSAPublicGeneralBalanceReaderRole",[PFUser currentUser].objectId]; PFRole publicGeneralBalanceRole = [PFRole roleWithName:publicBalanceRoleIdentifier acl:publicACL];
[objectsToSync addObject:publicGeneralBalanceRole];
NSString selectedBalanceRoleIdentifier = [NSString stringWithFormat:@"%@WSASelectedGeneralBalanceReaderRole",[PFUser currentUser].objectId]; PFRole selectedGeneralBalanceRole = [PFRole roleWithName:selectedBalanceRoleIdentifier acl:userACL];
[objectsToSync addObject:selectedGeneralBalanceRole];
//sets the tickets role
NSString ticketsRoleIdentifier = [NSString stringWithFormat:@"%@WSATicketReaderRole",[PFUser currentUser].objectId]; PFRole ticketsRole = [PFRole roleWithName:ticketsRoleIdentifier acl:userACL];
[objectsToSync addObject:ticketsRole]; [defaultRolesBehavior addObject:ticketsRole];
NSString publicTicketsRoleIdentifier = [NSString stringWithFormat:@"%@WSAPublicTicketReaderRole",[PFUser currentUser].objectId]; PFRole publicTicketsRole = [PFRole roleWithName:publicTicketsRoleIdentifier acl:publicACL];
[objectsToSync addObject:publicTicketsRole];
NSString selectedTicketsRoleIdentifier = [NSString stringWithFormat:@"%@WSASelectedTicketReaderRole",[PFUser currentUser].objectId]; PFRole selectedTicketsRole = [PFRole roleWithName:selectedTicketsRoleIdentifier acl:userACL];
[objectsToSync addObject:selectedTicketsRole];
//sets the comment reader role
NSString commentReaderRolerIdentifier = [NSString stringWithFormat:@"%@WSACommentReaderRole",[PFUser currentUser].objectId]; PFRole commentReaderRole = [PFRole roleWithName:commentReaderRolerIdentifier acl:userACL];
[objectsToSync addObject:commentReaderRole]; [defaultRolesBehavior addObject:commentReaderRole];
NSString publicCommentReaderRolerIdentifier = [NSString stringWithFormat:@"%@WSAPublicCommentReaderRole",[PFUser currentUser].objectId]; PFRole publicCommentReaderRole = [PFRole roleWithName:publicCommentReaderRolerIdentifier acl:publicACL];
[objectsToSync addObject:publicCommentReaderRole];
NSString selectedCommentReaderRolerIdentifier = [NSString stringWithFormat:@"%@WSASelectedCommentReaderRole",[PFUser currentUser].objectId]; PFRole selectedCommentReaderRole = [PFRole roleWithName:selectedCommentReaderRolerIdentifier acl:userACL];
[objectsToSync addObject:selectedCommentReaderRole];
//sets the comment writer role
NSString commentWriterRoleIdentifier = [NSString stringWithFormat:@"%@WSACommentWriterRole",[PFUser currentUser].objectId]; PFRole commentWriterRole = [PFRole roleWithName:commentWriterRoleIdentifier acl:userACL];
[objectsToSync addObject:commentWriterRole]; [defaultRolesBehavior addObject:commentWriterRole];
NSString publicCommentWriterRoleIdentifier = [NSString stringWithFormat:@"%@WSAPublicCommentWriterRole",[PFUser currentUser].objectId]; PFRole publicCommentWriterRole = [PFRole roleWithName:publicCommentWriterRoleIdentifier acl:publicACL];
[objectsToSync addObject:publicCommentWriterRole];
NSString selectedCommentWriterRoleIdentifier = [NSString stringWithFormat:@"%@WSASelectedCommentWriterRole",[PFUser currentUser].objectId]; PFRole selectedCommentWriterRole = [PFRole roleWithName:selectedCommentWriterRoleIdentifier acl:userACL];
[objectsToSync addObject:selectedCommentWriterRole];
//sets the wall reader role
NSString wallReaderRoleIdentifier = [NSString stringWithFormat:@"%@WSAWallReaderRole",[PFUser currentUser].objectId]; PFRole wallReaderRole = [PFRole roleWithName:wallReaderRoleIdentifier acl:userACL];
[objectsToSync addObject:wallReaderRole]; [defaultRolesBehavior addObject:wallReaderRole];
NSString publicWallReaderRoleIdentifier = [NSString stringWithFormat:@"%@WSAPublicWallReaderRole",[PFUser currentUser].objectId]; PFRole publicWallReaderRole = [PFRole roleWithName:publicWallReaderRoleIdentifier acl:publicACL];
[objectsToSync addObject:publicWallReaderRole];
NSString selectedWallReaderRoleIdentifier = [NSString stringWithFormat:@"%@WSASelectedWallReaderRole",[PFUser currentUser].objectId]; PFRole selectedWallReaderRole = [PFRole roleWithName:selectedWallReaderRoleIdentifier acl:userACL];
[objectsToSync addObject:selectedWallReaderRole];
//sets the machine role
NSString machineRoleIdentifier = [NSString stringWithFormat:@"%@MachineRole",[PFUser currentUser].objectId]; PFRole machineRole = [PFRole roleWithName:machineRoleIdentifier acl:userACL];
[objectsToSync addObject:machineRole];
[[PFUser currentUser] setObject:objectsToSync forKey:@"roles"];
[[PFUser currentUser] saveInBackground];
BOOL __block success; [PFObject saveAllInBackground:objectsToSync block:^(BOOL succeeded, NSError *error) { if (succeeded) { //sets default public acces for reading comments
for (PFRole *role in defaultRolesBehavior) {
PFRelation *relation = [role roles];
[relation addObject:friendRole];
}
PFRelation *publicReadingCommentsRelation = [commentReaderRole roles];
[publicReadingCommentsRelation addObject:publicCommentReaderRole];
//sets default public writing for writing comments
PFRelation *publicCommentsWritingRelation = [commentWriterRole roles];
[publicCommentsWritingRelation addObject:publicCommentWriterRole];
//sets default public reading for others comments
PFRelation *publicWallReadingRelation = [wallReaderRole roles];
[publicWallReadingRelation addObject:publicWallReaderRole];
[defaultRolesBehavior addObjectsFromArray:@[commentReaderRole, commentWriterRole, wallReaderRole]];
//saves the newly created roles and add the relations
[PFObject saveAllInBackground:defaultRolesBehavior block:^(BOOL succeeded, NSError *error) {
success = succeeded;
}];
}
}];
return success; }
after that i have to retrieve each role, and assign it every time to grant public acces controlled post creation, my app will scale to a point if i don't do that, changing ACL will be impossible in a future. In your case it will be probably much more easier as you only have to assign a new user to the role ACL and a new user to the "users" PFRelation
the sets default ACL behavior in my app goes like this, but you use it as you need it
(void)setDefaultACLForObject:(PFObject *)object {
NSString *className = [object className];
if ([className isEqualToString:@"WSAUserPersonalData"]) { NSString *roleIdentifier = [NSString stringWithFormat:@"%@WSAUserPersonalDataReaderRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]];
[userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier];
[userPersonalDataACL setPublicReadAccess:NO];
[userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
return;
}
//Used to see the personal data of a user if ([className isEqualToString:@"WSAGraphicValue"] || [className isEqualToString:@"WSAGraphic"] || [className isEqualToString:@"WSAAccount"] || [className isEqualToString:@"WSABadges"]) {
NSString *roleIdentifier = [NSString stringWithFormat:@"%@WSAGeneralBalanceReaderRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]];
[userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier];
[userPersonalDataACL setPublicReadAccess:NO];
[userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
return;
}
//Used to see the deatailed info in a user if ([className isEqualToString:@"WSATicket"] || [className isEqualToString:@"WSATicketFile"]) {
NSString *roleIdentifier = [NSString stringWithFormat:@"%@WSATicketReaderRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]];
[userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier];
[userPersonalDataACL setPublicReadAccess:NO];
[userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
return;
} //used to give and empty ACL but writing options to the user if ([className isEqualToString:@"WSALocation"] || [className isEqualToString:@"WSATag"] || [className isEqualToString:@"WSACategory"]) {
return;
}
if ([className isEqualToString:@"WSAComment"] || [className isEqualToString:@"WSAAgree"]) { NSString *roleIdentifier = [NSString stringWithFormat:@"%@WSACommentReaderRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]];
[userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier];
[userPersonalDataACL setPublicReadAccess:NO];
[userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
return;
}
if ([className isEqualToString:@"WSAWall"]) { NSString roleWriterIdentifier = [NSString stringWithFormat:@"%@WSACommentWriterRole",[PFUser currentUser].objectId]; NSString roleReaderIdentifier = [NSString stringWithFormat:@"%@WSAWallReaderRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]];
[userPersonalDataACL setWriteAccess:YES forRoleWithName:roleWriterIdentifier];
[userPersonalDataACL setReadAccess:YES forRoleWithName:roleReaderIdentifier];
[userPersonalDataACL setPublicReadAccess:NO];
[userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
return;
}
//machine and user exclusive ACL
NSString *roleIdentifier = [NSString stringWithFormat:@"%@MachineRole",[PFUser currentUser].objectId];
PFACL *userPersonalDataACL = [PFACL ACLWithUser:[PFUser currentUser]]; [userPersonalDataACL setReadAccess:YES forRoleWithName:roleIdentifier]; [userPersonalDataACL setWriteAccess:YES forRoleWithName:roleIdentifier]; [userPersonalDataACL setPublicReadAccess:NO]; [userPersonalDataACL setPublicWriteAccess:NO];
[object setACL:userPersonalDataACL];
}
This its much more complex than you need, but it will give you an idea of what you can do with ACL and FTASync,
IMPORTANT you can see i add a roles array to the user object, thats it because to add users to an object or set subroles you need to fetch the role to work, remember than relations can only be set with objects already saved in parse (thats why i set the sub roles after creation in the create default roles for user)
This structure what it does, its to create a parent role with read access or write access wich only the user can acces, then it creates sub roles that will inherit that permissions as the user wants it, and a public role that its granted to every person that wants to see an object that could be protected for the user, that way, if the user actually grant public permissions, i just have to add the public role to the parent role, and everyone can read or write the objects, the public role ACL needs to be public also, so every one can retrieve it and get the roles theme selfs.
p.d. i'm mexican sorry for my english :)
I almost got it to work correctly (I think), but am still learning on the ways you have created this system. Hard thing is though I cannot test it very well because I have no PRO account on parse.com and you seem to can only have 1 customer role then. Are you on a PRO account?
I will continue tomorrow on this project and will report back any new findings! Thanks anyway for the support so far!! And by the way I am not a native English speaker as well, I am from Holland.. ;)
Oh, yeah you need a pro account to create more than one role i forgot about that even when doing my early testing
For my app I need to have a way to give other users access to an entity if they are invited. So a user can invite another user to have access on an entity. I found out Parse.com has an excellent way of dealing with this in the form of ACL's.
I would love to use these ACL's in the way they are meant to be used and expand the standard use of the default ACL for all records, but I cannot seem to find out where to put the code to fill them.
My first guess was to put them into this method:
In that method I can check which entity is being updated and then add the specific user defined ACL for that entity. I am not sure if that is the way this was intended? Could you please give me some pointers on this. Maybe there is a way better (nicer) way to handle this, maybe even not with the ACL's.