parse.com inherited ACL + roles - afterSave or beforeSave, tricky scenario - parse-platform

Here's what I am trying to achieve but somehow I am stuck and I am not sure what's the proper approach, I cannot find any good examples of such case so I am seeking your help.
Each registered user can add new object to class "List". Once the new item is created I call afterSave function and assign proper ACL creating new role ("membersOf_" + List.id). Next, user can add new object to class "Items", which will store List.id as a reference and ACL for item should be inherited from list. Lists and Items can be shared between multiple users of their choice. There are few problems in such case:
when creating new List, I need to create new role and assign creator to it and add such role to created List
when creating new Item, I need to pass List.id as a payload and validate with cloud code if current user can create such item (assigned to specified List) by checking first if he has proper permissions to List
if permission check is ok, I need give this item the same ACL as List has and proceed with saving
Here's my afterSave for List, creating role properly and assigning ACL to List object. (1) I am missing adding this role to user (creator)
Parse.Cloud.afterSave("List", function(request, response) {
var list = request.object;
var user = Parse.User.current();
if (list.existed()) {
// quit on update, proceed on create
return;
}
var roleName = "membersOf_" + list.id;
var listRole = new Parse.Role(roleName, new Parse.ACL(user));
return listRole.save().then(function(listRole) {
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setReadAccess(listRole, true);
acl.setWriteAccess(listRole, true);
var itemData = new Parse.Object("List", {
ACL: acl
});
return itemData.save('objectId', list.id);
});
// to do - add user to this role too
});
Here's my Item beforeSave to validate if user can actually create such object, I am checking if he can query the List table, if he get >0 results for such List that means he will be ok to adding an Item assigned to this List. (2) Missing ACL inheritance
Parse.Cloud.beforeSave("Item", function(request, response) {
var item = request.object;
var listId = request.object.get("list");
var user = Parse.User.current();
var List = Parse.Object.extend("List");
var query = new Parse.Query(List);
query.equalTo("objectId", listId);
query.first({
success: function(list) {
if (list.id) {
response.success();
}
else {
response.error('No such list or you don\'t have permission to perform this operation.');
}
},
error: function(error) {
response.error(error);
}
});
});
Can someone point me to the proper solution or help solve that puzzle? I am missing two things:
- (1) I need to add user (creator) to new role created in afterSave
- (2) I need to add the same ACL to Item, inherit it from List object
I have tried many things, passing ACL in afterSave for Item, modifying payload in beforeSave. Many different functions following documentation and different examples, but still no luck.
Any advice would be awesome!

Ok, I think I finally figured it out. Hopefully this will help someone in future.
Here are final beforeSave and afterSave functions adding user to specified role and assigning the same ACL to Item object
Parse.Cloud.afterSave("List", function(request, response) {
var list = request.object;
var user = Parse.User.current();
if (list.existed()) {
// quit on update, proceed on create
return;
}
var roleName = "membersOf_" + list.id;
var listRole = new Parse.Role(roleName, new Parse.ACL(user));
//+ adding user to role in this line:
listRole.relation("users").add(user);
return listRole.save().then(function(listRole) {
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setReadAccess(listRole, true);
acl.setWriteAccess(listRole, true);
var itemData = new Parse.Object("List", {
ACL: acl
});
return itemData.save('objectId', list.id);
});
// to do - add user to this role too
});
Parse.Cloud.beforeSave("Item", function(request, response) {
var item = request.object;
var listId = request.object.get("list");
var user = Parse.User.current();
// + modifying payload with the same ACL here
var acl = new Parse.ACL();
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setRoleWriteAccess("membersOf_" + listId, true);
acl.setRoleReadAccess("membersOf_" + listId, true);
item.set('ACL', acl);
var List = Parse.Object.extend("List");
var query = new Parse.Query(List);
query.equalTo("objectId", listId);
query.first({
success: function(list) {
if (list.id) {
response.success();
}
else {
response.error('No such list or you don\'t have permission to perform this operation.');
}
},
error: function(error) {
response.error(error);
}
});
});

Related

Parse Cloud Code Setting Relation On User Class

I have an issue with not getting some cloud code to run in Parse. Currently, I pass an objectId of another user who I want to set a review for. In client side, I am saving the review, and capturing that object. I pass that as well up to cloud code. Both show up there, but I cannot figure out the right way to set the relation on this user and apply that review that was created. Code snip is below. Any help would be appreciated before I bang my head against a wall.
Parse.Cloud.define("addReview", async (request, response) => {
let { userObjId, reviewObj } = request.params;
const query = new Parse.Query(Parse.User);
//get the user object to post review of
try{
let object = await query.get(userObjId, {useMasterKey: true});
console.log(object);
console.log("running");
object.set('Reviews', new Parse.Object(reviewObj));
object.save(null, { useMasterKey: true })
return ("saved relation and updated reviews");
} catch (err){
throw new Error(err.message)
}
});
I assume
reviewObj is the review object, the user recently created.
and in your User class, you got a to-many-relation to Review class, named 'Reviews'.
First I'd suggest to name your user object user instead of object, to make it clearer what you're dealing with and make your code easier to understand.
Since you said, the review object is already successfully saved on server side, I'd recommend to transmit only the reviewId in your request.
To add an object to a relation, you first need to get the relation property of your user object:
const reviewsRelation = user.relation('Reviews');
Then to add the object to the relation just call:
reviewsRelation.add(user);
In total, your snippet should look like this:
Parse.Cloud.define("addReview", async (request, response) => {
let { userObjId, reviewObjId } = request.params;
const userQuery = new Parse.Query(Parse.User);
const reviewQuery = new Parse.Query(Parse.Review);
//get the user object to post review of
try{
let user = await userQuery.get(userObjId, {useMasterKey: true});
let review = await reviewQuery.get(reviewObjId, {useMasterKey: true});
console.log(user);
console.log("running");
const reviewsRelation = user.relation('Reviews');
reviewsRelation.add(review);
user.save(null, { useMasterKey: true })
return ("saved relation and updated reviews");
} catch (err){
throw new Error(err.message)
}
});

Parse Cloud Right Query to retrieve Friendslist and not (like Instagram)

I have the class "Users" the default of Parse Dashboad. I have also the class "Friends", where I store the friendships between users like this:
There are two columns, "toUser" and "fromUser", which are showing who of the two users sent the friend request. These two columns are Pointers(Pointer<_User>) to the Users Class.
My concept is to retrieve two lists:
1. Friendlist for the logged in user
2. Userlist of users (who are not friends with the logged in user)
What would be the appropriate queries for that?
The first one logically should not scan all the class Users because it may slow down the perfomance of the app which is using Ionic 3 and Angular 4
Thank you
you don't need another class to do so all you need is a relation column in the default user class
to add new friend you just need the following code
var friendtoadd
//this is the friend object from parse user class you want to add you can get it by anyway you want
var currentUser = Parse.User.current();
var relation = currentUser.relation("friends");
relation.add(friendtoadd)
currentUser.save();
to retrieve array of friends of logged in user you can use the following code
var currentUser = Parse.User.current();
var friendRelation = currentUser.relation('friends');
var env = this
friendRelation.query().find({
success: function(users) {
for (var i = 0; i< users.length; i++) {
var object = users[i];
env.friends.push(object)
console.log(env.friends)
}
}
});
// you should define friends = []; in the class
if I understood your question right you want to find the friend requests you sent, or the ones you received. because I don't see where you made a relation between the user and his friends.
this is the code if you want to do this using cloud code:
First I validated the parameters of the friendRequest being saved :
Parse.Cloud.beforeSave("Friends", function(request, response){
var friendRequest = request.object;
if (!friendRequest.has("toUser") || !friendRequest.has("fromUser")) {
response.error("Invalid parameters");
return;
}
response.success();
});
then I created two cloud functions, one for retrieving the sentRequests:
Parse.Cloud.define("getSentRequests", function(request, response){
var query = new Parse.Query("Friends");
query.equalTo("fromUser", request.user);
if (!request.master) {
query.find({ sessionToken: request.user.getSessionToken() }).then(function(friends){
response.success(friends);
});
}else{
query.find({useMasterKey:true}).then(function(friends){
response.success(friends);
});
}
});
and you can call this either from a logged in user or using the masterKey if you want, and the other query is for the recievedRequests:
Parse.Cloud.define("getRecievedRequests", function(request, response){
var query = new Parse.Query("Friends");
query.equalTo("toUser", request.user);
if (!request.master) {
query.find({ sessionToken: request.user.getSessionToken() }).then(function(users){
response.success(users);
});
}else{
query.find({useMasterKey:true}).then(function(users){
response.success(users);
});
}
});

Creating new users without resetting current user session

In my Parse web app, I have a user management page that is accessible to administrators. This page allows admins to create new user accounts.
To create the accounts, I use Parse.User.signUp().
It works great, but it has the nasty side effect of resetting the current user session, which logs out the admin who created the new user account.
This is actually the documented behaviour of User.signUp():
This will create a new Parse.User on the server, and also persist the session in localStorage so that you can access the user using #current.
But I want to create new users without changing the current user. How do I do this?
Before creating the new user account with User.signUp, save the current user's sessionToken. Then, once the new user has been created, restore the session with User.become:
var sessionToken = Parse.User.current().getSessionToken();
Parse.User.signUp(username, password).then(function(newUser) {
Parse.User.become(sessionToken);
});
I can create a 'Parse Cloud' method that allows you use the 'Master Key' and call it from the SDK using the Cloud.
Parse.Cloud.define('salvarUsuario', function(request, response) {
var nomeCompleto = request.params.nomeCompleto;
var Email = request.params.Email;
var Username = request.params.Username;
var cpf = request.params.cpf;
var Password = request.params.Password;
var funcionarioBool = request.params.funcionarioBool;
var ativo = request.params.ativo;
var primeiroAcesso = request.params.primeiroAcesso;
var tipoAcesso = request.params.tipoAcesso;
var medicoBool = request.params.medicoBool;
//Parâmetros de controle
var fotoSelecionada = request.params.fotoSelecionada;
var usuario = new Parse.User();
usuario.set("nomeCompleto", nomeCompleto);
usuario.set("email", Email);
usuario.set("username", Username);
usuario.set("cpf", cpf);
usuario.set("password", Password);
usuario.set("funcionarioBool", funcionarioBool);
usuario.set("ativo", ativo);
usuario.set("primeiroAcesso", primeiroAcesso);
usuario.set("tipoAcesso", tipoAcesso);
usuario.set("medicoBool", medicoBool);
if(medicoBool) {
var medicoId = request.params.medico;
var Medico = Parse.Object.extend("Medico");
var medicoPointer = Medico.createWithoutData(medicoId);
usuario.set("medico", medicoPointer);
}
if(fotoSelecionada) {
var bytes = request.params.bytesFoto;
var file = new Parse.File("foto.png", bytes, "image/png");
usuario.set("foto", file);
}
usuario.save(null, {
useMasterKey: true,
success: function(salvoUsuario){
// The user was saved correctly
response.success("1");
},
error: function(error){
response.error("Erro ao criar novo usuário");
}
});
});

Find objects not in a relation

I have a custom object Team in Parse with a relation field for the default User object. What I would like to do is retrieve all User objects which are not related to any Team object. Can anyone point me in the right direction on how to do this using the JavaScript SDK? I've been going over the documentation for the Query object but I can't find anything.
Perhaps another type of relation, or placing the relation at another place is a better solution. What I want to accomplish is the following: Each user is allowed to be in one team and one team only. In addition I need to be able to query the following information from Parse:
I want to retrieve the User objects of all the users assigned to a team
I want to retrieve the User objects of all the users who are not assigned to any team
I have tried using a join table with both the user and team object ids. Then I tried to following query to get all users not assigned to a team:
var teammember = Parse.Object.extend('TeamMember'),
query = new Parse.Query("User");
var innerQuery = new Parse.Query("TeamMember");
query.doesNotMatchQuery('user', innerQuery);
query.find({
success: function(results) {
response.success(results);
},
error : function(error) {
response.error(error);
}
})
But this just gets me the following response: error: "{"code":102,"message":"bad type for $notInQuery"}".
I like the Relation type as I can add or remove multiple members at once with a single call to the REST API. I also have no problems retrieving the information on team members when using the Relation type to connect the users to the teams. It is just getting the users which are not assigned to any team that is giving me problems.
It doesn't sound like you need a relation at all. Instead, add a Pointer column to User that points to Team. It ensures that a User can only belong to one team, and your other requirements can be captured as follows.
// All users assigned to a team
query = new Parse.Query('User');
query.exists('team');
// All users assigned to a specific team
query = new Parse.Query('User');
query.equalTo('team', specificTeam);
// All unassigned users
query = new Parse.Query('User');
query.doesNotExist('team');
Update: If you need to support multiple teams per User in the future, then I would suggest creating a Parse table called Membership with two columns: a Pointer to User and a Pointer to Team. This essentially gives you more control than relying on Parse relations, but it gets a little more complicated.
_ = require('underscore'); // Or lodash
// All users assigned to a team
query = new Parse.Query('Membership');
query.find().then(function (results) {
// http://underscorejs.org/#uniq
users = _.uniq(results, false, function (user) { return user.id; });
});
// All users assigned to a specific team
query = new Parse.Query('Membership');
query.equalTo('team', specificTeam);
// All unassigned users
var assignedUsers = []
var unassignedUsers = []
memberQuery = new Parse.Query('Membership');
userQuery = new Parse.Query('User');
memberQuery.find().then(function (memberResults) {
// http://underscorejs.org/#map
var ids = _.map(memberResults, function (user) { return user.id; });
// http://underscore.js.org/#uniq
assignedUsers = _.uniq(ids);
userQuery.find();
}).then(function (userResults) {
var users = _.map(userResults, function (user) { return user.id; });
// http://underscorejs.org/#difference
unassignedUsers = _.difference(users, assignedUsers);
});
To add and remove Users to/from Teams, you would create Membership objects and save API calls with Parse.Object.saveAll() and Parse.Object.destroyAll().
I ran into trouble with the answer provided by Seth. When retrieving the users not assigned to a team the difference between the two arrays would be incorrect. I am assuming this is due to the assignedUsers having object of type Membership and userResults being of type User. This would make it impossible for underscore to make a proper match.
I would up using this as my Cloud Code:
Parse.Cloud.define("getTeamlessUsers", function(request, response) {
var _ = require("underscore"),
assignedUsers = [],
companyUsers = [],
memberQuery = new Parse.Query("TeamMembers"),
userQuery = new Parse.Query("User"),
index,
ubound;
memberQuery.find().then(function(memberResults) {
// Make sure each User ID will appear just once
memberResults = _.unique(memberResults, false, function(item) { return item.get('user').id; });
// Loop over the unique team members and push the User ID into the array
for (index = 0, ubound = memberResults.length; index < ubound; index++) {
var user = memberResults[index].get("user");
assignedUsers.push(user.id);
}
// Get al the users
return userQuery.find();
}).then(function(userResults) {
// Loop over all the users and push the ID into the array
for (index = 0, ubound = userResults.length; index < ubound; index++) {
companyUsers.push(userResults[index].id);
}
// Create an array of user IDs which are not present in the assignedUsers array
var result = _.difference(companyUsers, assignedUsers);
// Return the IDs of user not assigned to any team
response.success(result);
}).fail(function(error) {
response.error(error);
});
});

Modifying Parse.User object before FIRST save

I'm working on an app and I need some changes to be made on new users registering during certain periods.
I've added a variable which I will change manually, and a check if that value is true or false.
This is my current code:
Parse.Cloud.beforeSave(Parse.User, function(request, status)
{
console.log("********************************************");
Parse.Cloud.useMasterKey();
var special = true;
if(special)
{
request.object.set("points", 1000);
request.object.set("role/objectid", "PZHTquGti0");
}else{
request.object.set("points", 0);
request.object.set("role/objectid", "TQyjIY59oL");
}
console.log("********************************************");
status.success("Job finished successfully!");
}); // end of Parse.define
This code errors out with "Uncaught Error: change must be passed a Parse.Object" and I've been looking through the documentation to find out how to change a value of a subclass of the User object, but have found none.
Also, this code will also run when updating a user, which I don't want it to do.
Any help is highly appreciated!
First of all for running the code on first save (insert), you can use request.object.isNew()
As the role column is a pointer, you should set an object & not the id string directly.
So create a new dummy object and assign the id to it.
Parse.Cloud.beforeSave(Parse.User, function(request, status)
{
console.log("********************************************");
Parse.Cloud.useMasterKey();
if(request.object.isNew()){ // Object Insert
var Role = Parse.Object.extend("_Role");
var role = new Role();
var special = true;
if(special)
{
request.object.set("points", 1000);
role.id = "PZHTquGti0";
request.object.set("role", role);
}else{
request.object.set("points", 0);
role.id = "TQyjIY59oL";
request.object.set("role", role);
}
}
else{ // Object Update
//Do nothing
}
console.log("********************************************");
status.success("Job finished successfully!");
}); // end of Parse.define

Resources