Security & Privacy
Guía de seguridad para aplicaciones Parse con Back4App
23 min
cómo hacer una aplicación segura usando parse introducción ¡hola comunidad de back4app! este es un tutorial de invitado de joren winge en startup soul http //startup soul com/ ayudamos a las startups a construir y lanzar sus productos rápidamente nuestros amigos de back4app nos pidieron que te mostremos cómo construir una aplicación segura sobre back4app en esta publicación, te guiaremos a través de los pasos para hacer una aplicación de tareas segura en back4app la seguridad es importante si tu aplicación despega, necesitarás asegurarte de que los datos de tu aplicación estén seguros y que tu sistema no pueda ser hackeado características de seguridad en parse primero hablemos sobre el primer nivel de seguridad, acls (lista de control de acceso) las acls son básicamente solo reglas que estableces cuando creas un objeto digamos que creas un elemento de tarea, en el momento de la creación puedes decir quién puede leer el elemento y quién puede escribir en él puedes asignar ciertos usuarios para que puedan leer ese elemento o escribir en ese elemento o puedes establecer cualquiera de los dos como público, lo que permite el acceso a cualquiera pero las acls no siempre funcionan hay casos en los que podrías necesitar tener una lógica más sofisticada en lugar de una simple acl a veces también puedes pintarte en una esquina donde puedes necesitar dar acceso a alguien a un objeto de manera condicional en lugar de una base absoluta como con las acls así que vamos a omitir el uso de acls son demasiado rígidas y al mismo tiempo permiten demasiado acceso a los datos así que estoy a punto de darte el secreto para construir una aplicación segura en back4app y parse server ¿estás listo? ¡permisos a nivel de clase! sí, estableceremos permisos a nivel de clase para cada tabla en nuestra base de datos y el nivel de permiso que estableceremos es ningún permiso en absoluto cerraremos cada una de las tablas para que no se permita el acceso de lectura o escritura a nadie suena extremo, lo sé, pero este es el primer paso para crear una aplicación segura el único permiso que permitiremos es en la tabla de usuarios, que será para la creación de nuevos objetos de usuario y para que el usuario vea sus propios datos, lo cual es necesario para actualizar al usuario actual aseguraremos al usuario de poder ver los datos de otros usuarios utilizando acls esta es la única vez que usaremos acls, así que supongo que no son totalmente inútiles son buenos de tener, pero no confíes en ellos para hacer todo pero, ¿cómo accederemos a los datos, preguntas? buena pregunta, ¡me alegra que estés pensando en ello! bueno, el secreto para permitir que los clientes accedan a los datos de manera controlada es hacer que cada interacción entre el cliente y la base de datos esté filtrada a través de una función de código en la nube sí, cada vez que hagas algo con tu aplicación, ahora será a través de una función de código en la nube personalizada no más pfqueries basadas en el cliente prácticamente omites el uso de todo el sdk de parse basado en el cliente, excepto para las funciones de registro, funciones de inicio de sesión, funciones de olvidé mi contraseña y funciones de cierre de sesión para estas, aún utilizaremos los sdk nativos del cliente es simplemente más fácil ¿alguna vez has escrito código en la nube antes? no, ¿dices? bueno, es bastante fácil es solo javascript y utiliza el sdk de javascript de parse, pero internamente en el servidor de tu propia aplicación de hecho, dado que parse server se basa en node js, es bastante similar a escribir rutas con express, pero aún más fácil ya que tu lenguaje de consulta ya está instalado y las funciones de código en la nube son mucho más fáciles de escribir que una aplicación completa de node js express así que aquí está lo que haremos usaremos una aplicación de tareas para ios que ya he creado no nos molestaremos en mostrarte cómo la creé en cambio, nos centraremos en escribir código en la nube y asegurar la base de datos la aplicación de tareas será una aplicación segura donde solo podrás acceder a tus propias tareas y solo podrás escribir tus propias tareas los datos estarán seguros en el servidor, a salvo de clientes maliciosos también te mostraré cómo escribir un trabajo en segundo plano seguro de parse básicamente, lo mismo que un trabajo cron para que puedas tener servicios automatizados que manipulen tus datos según un horario suena complicado, pero no lo es solo imagina pequeños robots de servidor que hacen lo que quieras en un horario automatizado suena genial, ¿verdad? ok, ¡así que aquí vamos!!!!!! configuramos la aplicación todo segura de back4app 1\) crea una aplicación en back4app crea una nueva aplicación en back4app llama a la aplicación ‘aplicación todo segura’ nota sigue el tutorial de nueva aplicación parse para aprender cómo crear una aplicación en back4app ve a la página de configuración principal de la aplicación y luego haz clic en editar detalles de la aplicación desactiva la casilla llamada ‘permitir creación de clases de cliente’ para deshabilitar la creación de clases de cliente y haz clic en guardar queremos limitar lo que el cliente puede hacer como regla 2\) establecer permisos de seguridad a nivel de clase para la clase usuario a continuación, configuraremos los permisos para la clase usuario ve al panel de control de la base de datos de back4app y haz clic en la clase usuario luego haz clic en la pestaña seguridad, después haz clic en el ícono de engranaje en la esquina superior derecha deberías ver un menú que dice simple/avanzado cambia el control deslizante a avanzado luego deberías ver los permisos de nivel de clase completos para esta clase desactiva la casilla de verificación buscar desactiva la casilla de verificación actualizar y eliminar finalmente, desactiva la casilla de verificación agregar campo luego presiona guardar tus configuraciones de seguridad deberían verse así 3\) crea la clase todo haz clic en crear una clase y llámala todo establece el tipo de clase como personalizado 4\) establece los permisos de seguridad a nivel de clase para la clase todo a continuación, configuraremos los permisos para la clase todo ve al panel de control de la base de datos de back4app y haz clic en la clase todo luego haz clic en la pestaña seguridad, después haz clic en el ícono de engranaje en la esquina superior derecha deberías ver un menú que dice simple/avanzado cambia el control deslizante a avanzado luego deberías ver los permisos de nivel de clase completos para esta clase desactiva todo y luego presiona guardar tus configuraciones de seguridad deberían verse así 5\) agreguemos algunas columnas a la clase todo primero, unamos la clase todo a la clase usuario haremos eso añadiendo 2 columnas la primera columna se llamará ‘usuario’ y será un puntero de vuelta a la clase usuario a continuación, creemos una columna para el id del objeto del usuario que lo creó será de tipo cadena y se llamará ‘userobjectid’ a continuación, creemos una columna para contener nuestra información real de todo también será una cadena y se llamará ‘tododescription’ creemos un booleano para mantener el estado del todo llamémoslo ‘terminado’ finalmente, añadamos una columna más para mantener la fecha en que terminaste tu todo llamémosla ‘finisheddate’ y establezcámosla como tipo fecha tu clase todo debería verse así 6\) revisemos el cliente el cliente es una aplicación de tareas bastante básica utiliza las funciones integradas de parse para iniciar sesión, crear un nuevo usuario y restablecer tu contraseña aparte de eso, todo está basado en código en la nube y es seguro los acl del usuario también se establecen tan pronto como inician sesión o se registran, solo para estar 100% seguros de que el sistema es seguro comencemos escribiendo la función de código en la nube para establecer el acl del usuario al iniciar sesión o registrarse en cualquier momento, puedes acceder al proyecto completo de ios construido con este tutorial en este repositorio de github también puedes acceder al archivo de código en la nube main js construido para este tutorial en este repositorio de github 1 en el cliente ve a todocontroller swift y busca la función setusersaclsnow esta función se llama cuando inicias sesión o ves loggedinviewcontroller swift la función verifica si has iniciado sesión y, si lo has hecho, llama a la función en la nube para configurar tus acls de usuario personal todocontroller swift 1 func setusersaclsnow(){ 2 if pfuser current() != nil{ 3 let cloudparams \[anyhashable\ string] = \["test" "test"] 4 pfcloud callfunction(inbackground setusersacls, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 //print(error debugdescription) 8 if let descrip = error? localizeddescription{ 9 print(descrip) 10 } 11 }else{ 12 print(result as! string) 13 } 14 }) 15 } 16 } 2 ahora escribamos la función de código en la nube parse server 3 x 1 parse cloud define('setusersacls', async(request) => { 2 let currentuser = request user; 3 currentuser setacl(new parse acl(currentuser)); 4 return await currentuser save(null, { usemasterkey true }); 5 }); parse server 2 x 1 parse cloud define('setusersacls', function (request, response) { 2 var currentuser = request user; 3 currentuser setacl(new parse acl(currentuser)); 4 currentuser save(null, { 5 usemasterkey true, 6 success function (object) { 7 response success("acls updated"); 8 }, 9 error function (object, error) { 10 response error("got an error " + error code + " " + error description); 11 } 12 }); 13 }); 3 este código en la nube utiliza dos características clave para hacer que tu aplicación sea segura, request user y masterkey request user te permite acceder al usuario que está haciendo la llamada al código en la nube y te permite limitar el acceso para ese usuario en este caso, lo estamos utilizando para establecer los acl del usuario y limitar el acceso de lectura solo al usuario actual de esta manera, solo el usuario puede leer su propia información los permisos a nivel de clase previenen el acceso de escritura incluso para el usuario actual de esta forma, los usuarios no pueden modificar su propia información solo pueden cambiar cosas sobre su propio usuario a través del código en la nube es posible importar información falsa cuando el usuario se registra por primera vez, pero recomendaría escribir una función de código en la nube para verificar la información del usuario después de que se crea un nuevo usuario la función incorporada de parse para crear un nuevo usuario es realmente útil, así que creo que es un intercambio decente, pero siempre puedes establecer los valores predeterminados para el usuario a través del código en la nube justo después de que se registren hay muchas medidas de seguridad que también puedes escribir en el código en la nube y hacer que se ejecuten automáticamente y de manera continua utilizando trabajos en segundo plano para detectar cualquier información de usuario maliciosa que se importó cuando se creó el usuario por primera vez si quieres ser realmente seguro, puedes almacenar cualquier información sensible como el estado de membresía o la información de pago en una tabla separada de la tabla de usuarios de esa manera, el usuario no puede falsificar ninguna información sensible en la creación del usuario 4 a continuación, veamos cómo crear un todo en el cliente, ve a todocontroller swift y busca la función savetodo esta función se llama cuando creas un nuevo todo la función toma una cadena que describe el todo y lo guarda en la base de datos todocontroller swift 1 func savetodo(todostring\ string, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["todostring"\ todostring] 4 pfcloud callfunction(inbackground createtodosforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 5 ahora escribamos la función de código en la nube para guardar el todo en la base de datos parse server 3 x 1 parse cloud define("createtodosforuser", async(request) => { 2 let currentuser = request user; 3 let todostring = request params todostring; 4 let todo = parse object extend("todo"); 5 let todo = new todo(); 6 todo set("user", currentuser); 7 todo set("userobjectid", currentuser id); 8 todo set("tododescription", todostring); 9 todo set("finished", false); 10 return await todo save(null, { usemasterkey true }); 11 }); parse server 2 x 1 parse cloud define("createtodosforuser", function(request, response) { 2 var currentuser = request user; 3 var todostring = request params todostring; 4 var todo = parse object extend("todo"); 5 var todo = new todo(); 6 todo set("user", currentuser); 7 todo set("userobjectid", currentuser id); 8 todo set("tododescription", todostring); 9 todo set("finished", false); 10 todo save(null, { 11 usemasterkey true, 12 success function (object) { 13 response success(\[todo]); 14 }, 15 error function (object, error) { 16 response error("got an error " + error code + " " + error description); 17 } 18 }); 19 }); 6 esta función de código en la nube crea un objeto todo y establece al usuario actual como el propietario del objeto esto es importante para que solo el usuario que lo creó pueda encontrarlo o modificarlo al no permitir que los todos se creen en el cliente, estamos obligando al objeto todo a conformarse a nuestros estándares y asegurándonos de que los todos sean propiedad del usuario que los creó 7 a continuación, veamos cómo recuperar los todos que creaste desde el servidor en el cliente, ve a todocontroller swift y busca la función gettodosfordate esta función se llama cuando recuperas tus todos la función toma una fecha como parámetro y la utiliza para recuperar una lista de todos que fueron creados por ti antes de esa fecha en orden descendente usar una fecha es una excelente manera de escribir una consulta de carga diferida que no utiliza saltos saltar puede fallar a veces en un conjunto de datos grande todocontroller swift 1 func savetodo(todostring\ string, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["date"\ date] 4 pfcloud callfunction(inbackground gettodosforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 8 ahora escribamos la función de código en la nube para recuperar todos de la base de datos en función de una fecha de inicio consultamos los todos que se crean antes de la fecha del parámetro, por lo que usamos ‘query lessthan’ porque las fechas son básicamente números que aumentan cuanto más lejos en el futuro estás también incluí algo de código complicado aquí supongamos que estamos incluyendo el objeto de usuario que creó el todo, pero no queremos compartir información sensible sobre ese usuario con otros usuarios, necesitamos eliminarlo de la respuesta json así que tenemos un bucle for donde sacamos el objeto de usuario del todo, eliminamos el correo electrónico y el nombre de usuario del json y luego lo volvemos a poner en el todo esto es útil para eliminar datos sensibles de una llamada a la api en situaciones donde no puedes controlar qué campos devuelves, como un objeto de usuario incluido en este caso, realmente no lo necesitamos porque esta función solo devolverá todos que tú creaste hacemos esto usando currentuser nuevamente para consultar solo los todos creados por el currentuser que estaba adjunto a la solicitud los resultados se devuelven en orden descendente para que los últimos todos aparezcan primero cuando necesites cargar de forma diferida otro lote de todos, tomas la fecha createdat del último todo y la usas como el parámetro de fecha para la siguiente solicitud parse server 3 x 1 parse cloud define("gettodosforuser", async(request) => { 2 let currentuser = request user; 3 let date = request params date; 4 let query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query lessthan("createdat", date); 7 query descending("createdat"); 8 query limit(100); 9 query include("user"); 10 let results = await query find({ usemasterkey true }); 11 if(results length === 0) throw new error('no results found!'); 12 13 let resultsarray = \[]; 14 for (let i = 0; i < results length; i++) { 15 let todo = results\[i]; 16 let tempuser = todo get("user"); 17 let jsonuser = tempuser tojson(); 18 delete jsonuser email; 19 delete jsonuser username; 20 21 jsonuser type = "object"; 22 jsonuser classname = " user"; 23 24 let cleanedtodo = todo tojson(); 25 cleanedtodo user = jsonuser; 26 cleanedtodo type = "object"; 27 cleanedtodo classname = "todo"; 28 resultsarray push(cleanedtodo); 29 } 30 return resultsarray; 31 }); parse server 2 x 1 parse cloud define("gettodosforuser", function(request, response) { 2 var currentuser = request user; 3 var date = request params date; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query lessthan("createdat", date); 7 query descending("createdat"); 8 query limit(100); 9 query include("user"); 10 query find({ 11 usemasterkey true, 12 success function (results) { 13 var resultsarray = \[]; 14 for (var i = 0; i < results length; i++) { 15 var todo = results\[i]; 16 var tempuser = todo get("user"); 17 var jsonuser = tempuser tojson(); 18 delete jsonuser email; 19 delete jsonuser username; 20 21 jsonuser type = "object"; 22 jsonuser classname = " user"; 23 24 var cleanedtodo = todo tojson(); 25 cleanedtodo user = jsonuser; 26 cleanedtodo type = "object"; 27 cleanedtodo classname = "todo"; 28 resultsarray push(cleanedtodo); 29 } 30 response success(resultsarray); 31 }, 32 error function (error) { 33 response error(" error " + error code + " " + error message); 34 } 35 }); 36 }); 9 ahora que tenemos los todos, podemos verlos en la aplicación y marcarlos como completados si queremos cubramos eso a continuación 10 para marcar un todo como completado, simplemente presiona el botón ‘marcar como completado’ en cualquiera de los todos que creaste esto activará un método en el todocontroller swift llamado ‘marktodosascompletedfor’ que toma el todo que seleccionaste como parámetro envía el todo objectid al servidor como un parámetro y luego devuelve el todo actualizado como resultado todocontroller swift 1 func marktodosascompletedfor(todo\ todo, completion @escaping ( result bool, message\ string, todoarray \[todo]) >()){ 2 var resulttodoarray \[todo] = \[] 3 let cloudparams \[anyhashable\ any] = \["todoid"\ todo objectid ?? ""] 4 pfcloud callfunction(inbackground marktodoascompletedforuser, withparameters cloudparams, block { 5 (result any?, error error?) > void in 6 if error != nil { 7 if let descrip = error? localizeddescription{ 8 completion(false, descrip, resulttodoarray) 9 } 10 }else{ 11 resulttodoarray = result as! \[todo] 12 completion(true, "success", resulttodoarray) 13 } 14 }) 15 } 11\ ahora escribiremos el código en la nube para actualizar esta tarea busca la tarea a actualizar en función del objectid, pero también utiliza el currentuser para asegurarse de que la tarea asociada con el objectid fue creada por el usuario que realiza la consulta esto asegura que solo puedes ver las tareas que creaste y, por lo tanto, es seguro incluimos un límite de 1 resultado para asegurarnos de que el servidor no continúe buscando después de encontrar la tarea hay otro método para encontrar un objeto basado en un objectid, pero no me gusta usarlo ya que puede devolver resultados extraños si no encuentra el objeto asociado con el objectid también estamos estableciendo la 'finisheddate' con la fecha actual cuando se actualizó el objeto al tener la finisheddate establecida solo por esta función, nos hemos asegurado de que la finisheddate sea segura y no pueda ser falsificada o cambiada también usamos 'query equalto("finished", false)' para asegurarnos de que solo una tarea no terminada pueda ser marcada como terminada y tener la finisheddate establecida eso significa que una vez que una tarea ha sido marcada como terminada, nunca podrá ser marcada como terminada nuevamente en una fecha posterior parse server 3 x 1 parse cloud define("marktodoascompletedforuser", async(request) => { 2 let currentuser = request user; 3 let todoid = request params todoid; 4 let query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 let todo = await query first({ usemasterkey true }); 9 if(object keys(todo) length === 0) throw new error('no results found!'); 10 todo set("finished", true); 11 let date = new date(); 12 todo set("finisheddate", date); 13 try { 14 await todo save(null, { usemasterkey true}); 15 return todo; 16 } catch (error){ 17 return("getnewstore error " + error code + " " + error message); 18 } 19 }); parse server 2 x 1 parse cloud define("marktodoascompletedforuser", function(request, response) { 2 var currentuser = request user; 3 var todoid = request params todoid; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 query limit(1); 9 query find({ 10 usemasterkey true, 11 success function (results) { 12 if (results length > 0) { 13 var todo = results\[0]; 14 todo set("finished", true); 15 var date = new date(); 16 todo set("finisheddate", date); 17 todo save(null, { 18 usemasterkey true, 19 success function (object) { 20 response success(\[todo]); 21 }, 22 error function (object, error) { 23 response error("got an error " + error code + " " + error description); 24 } 25 }); 26 } else { 27 response error("todo not found to update"); 28 } 29 30 }, 31 error function (error) { 32 response error(" error " + error code + " " + error message); 33 } 34 }); 35 }); 7\) ¡conclusión! y eso es todo has construido una aplicación de tareas segura nuevamente, la clave para hacer una aplicación segura en el servidor de parse es deshabilitar todos los permisos a nivel de clase para todas las clases, excepto para la clase usuario en la clase usuario, deshabilitas todos los permisos excepto crear y obtener también asegúrate de establecer todos los acl de los usuarios para que el usuario solo pueda obtener sus propios datos luego, todas tus interacciones pasan a través del código en la nube y se filtran utilizando request user, también conocido como currentuser así que ahí lo tienes, ahora puedes construir sistemas seguros sobre el servidor parse y back4app pero espera, ¿qué dices? ¿qué pasa con los trabajos en segundo plano y las consultas en vivo? bueno, tienes un buen punto, así que lo cubriré en dos secciones adicionales a continuación 8\) secciones adicionales 1\ trabajos en segundo plano a veces necesitas crear un trabajo en segundo plano que se ejecute cada hora, o cada día o cada semana si estás ejecutando con todos tus permisos a nivel de clase desactivados, tu trabajo en segundo plano no podrá consultar la base de datos a menos que esté configurado correctamente esto es un poco complicado de hacer, así que quiero incluir un ejemplo de ello aquí en este caso, crearemos un trabajo en segundo plano que verifica la base de datos en busca de tareas no finalizadas que tengan más de 1 año y luego las marca automáticamente como finalizadas el truco aquí es usar 'usemasterkey' correctamente debe añadirse a la consulta antes de la promesa then simplemente sigue esta plantilla y deberías poder escribir trabajos en segundo plano seguros fácilmente siempre comienzas escribiendo una consulta que deseas iterar sobre toda la base de datos y luego asegúrate de incluir status error si hay un error y terminarlo con status success para asegurarte de que se complete puedes ver los registros en back4app para ver el trabajo en segundo plano funcionando mientras lo ejecutas parse server 3 x 1 parse cloud job("markunfinishedtodosolderthan1yearasfinished", async(request) => { 2 let date = new date(); 3 let intyear = date getfullyear() 1; 4 let query = new parse query("todo"); 5 query equalto("finished", intyear); 6 query lessthan("createdat", date); 7 8 let todo = await query find({ usemasterkey true }); 9 for (let i = 0; i < results length; i++) { 10 let todo = results\[i]; 11 todo set("finished", true); 12 todo set("finisheddate", date); 13 try { 14 await todo save(null, { usemasterkey true}); 15 } catch (error){ 16 console log("getnewstore error " + error code + " " + error message); 17 } 18 } 19 return "migration completed successfully "; 20 }); parse server 2 x 1 parse cloud define("marktodoascompletedforuser", function(request, response) { 2 var currentuser = request user; 3 var todoid = request params todoid; 4 var query = new parse query("todo"); 5 query equalto("user", currentuser); 6 query equalto("objectid", todoid); 7 query equalto("finished", false); 8 query limit(1); 9 query find({ 10 usemasterkey true, 11 success function (results) { 12 if (results length > 0) { 13 var todo = results\[0]; 14 todo set("finished", true); 15 var date = new date(); 16 todo set("finisheddate", date); 17 todo save(null, { 18 usemasterkey true, 19 success function (object) { 20 response success(\[todo]); 21 }, 22 error function (object, error) { 23 response error("got an error " + error code + " " + error description); 24 } 25 }); 26 } else { 27 response error("todo not found to update"); 28 } 29 30 }, 31 error function (error) { 32 response error(" error " + error code + " " + error message); 33 } 34 }); 35 }); 2 consultas en vivo a veces necesitas usar la función de consulta en vivo de parse para algo como una aplicación de chat en vivo querrás usar la consulta en vivo para ver cuándo se crean nuevos mensajes para tu usuario la consulta en vivo es básicamente la forma de parse de usar sockets para obtener actualizaciones en vivo es bastante útil, pero no funcionará con una clase cuyos permisos de find han sido desactivados así que en este caso volveremos a activar los permisos de find para la clase mensaje y en su lugar asignaremos acl directamente para ese mensaje el acl debe configurarse de manera que solo el destinatario pueda usar un find para obtener el mensaje del servidor luego ejecutas tu pf live query en tu cliente buscando mensajes para tu usuario y funcionará a la perfección si estás tratando con mensajes grupales, es un poco diferente puedes asignar a varias personas en el acl, pero realmente no escala en su lugar, hay una mejor manera configuras el acl para que se base en un rol parse role y luego a cualquier usuario que desees que tenga acceso a ese mensaje simplemente lo asignas a ese parse role si deseas evitar que lean los mensajes de ese grupo, los eliminas de ese rol esto es mucho más fácil que eliminarlos de cada acl de mensaje individual y escala para grupos muy grandes esta es la forma correcta de hacerlo no voy a dejar un ejemplo de código para esto ya que es demasiado complejo para este tutorial, pero tal vez explique cómo hacerlo en el siguiente gracias por leer este tutorial sobre seguridad con parse y back4app si tienes preguntas, no dudes en contactarme y estaré encantado de responderlas ¡gracias! joren