Security & Privacy
Aprimore a Segurança do Aplicativo com Parse: Técnicas Eficazes
23 min
como fazer um aplicativo seguro usando parse introdução ahoy comunidade back4app! este é um tutorial de convidado de joren winge em startup soul http //startup soul com/ ajudamos startups a construir e lançar seus produtos rapidamente nossos amigos @ back4app nos pediram para mostrar como construir um aplicativo seguro em cima do back4app neste post, vamos guiá lo pelos passos para fazer um aplicativo to do seguro no back4app a segurança é importante se o seu aplicativo decolar, você precisará garantir que os dados do seu aplicativo estejam seguros e que seu sistema não possa ser invadido recursos de segurança no parse vamos primeiro falar sobre o primeiro nível de segurança, acls (lista de controle de acesso) acls são basicamente apenas regras que você define ao criar um objeto digamos que você crie um item de tarefa, no momento da criação você pode dizer quem pode ler o item e quem pode escrevê lo você pode atribuir certos usuários para poder ler esse item ou escrever nesse item ou pode definir qualquer um como público, o que permite o acesso a qualquer pessoa mas as acls nem sempre funcionam existem casos em que você pode precisar ter uma lógica mais sofisticada em vez de uma simples acl às vezes, você também pode se colocar em uma situação em que pode precisar dar a alguém acesso a um objeto com base em condições em vez de uma base absoluta como com as acls então, vamos pular o uso de acls elas são muito rígidas e, ao mesmo tempo, permitem acesso demais aos dados então, estou prestes a lhe dar o segredo para construir um aplicativo seguro no back4app e no parse server você está pronto? permissões de nível de classe! sim, vamos definir permissões de nível de classe para cada tabela em nosso banco de dados e o nível de permissão que vamos definir é nenhuma permissão vamos bloquear cada tabela para que nenhum acesso de leitura ou escrita seja permitido a ninguém! parece extremo, eu sei, mas este é o primeiro passo para criar um aplicativo seguro a única permissão que permitiremos é na tabela de usuários, que será para a criação de novos objetos de usuário e para que o usuário visualize seus próprios dados, o que é necessário para atualizar o usuário atual vamos proteger o usuário de poder visualizar os dados de outros usuários usando acls esta é a única vez que usaremos acls, então eu acho que elas não são totalmente inúteis elas são boas de ter, mas não confie nelas para fazer tudo mas como acessaremos os dados, você pergunta? boa pergunta, fico feliz que você esteja pensando nisso! bem, o segredo para permitir que os clientes acessem os dados de maneira controlada é fazer com que cada interação entre o cliente e o banco de dados seja filtrada através de uma função de código em nuvem sim, sempre que você fizer algo com seu aplicativo, agora será através de uma função de código em nuvem personalizada nada de pfqueries baseadas no cliente você basicamente pula o uso de todo o sdk parse baseado no cliente, exceto para funções de cadastro, funções de login, funções de esqueci a senha e funções de logout para essas, ainda usaremos os sdks nativos do cliente é apenas mais fácil você já escreveu código em nuvem antes? não, você diz? bem, é bem fácil é apenas javascript e usa o sdk javascript do parse, mas internamente no servidor do seu próprio aplicativo na verdade, como o parse server é baseado em node js, é bem semelhante a escrever rotas com express, mas ainda mais fácil, já que sua linguagem de consulta já está instalada e as funções de código em nuvem são muito mais fáceis de escrever do que um aplicativo inteiro em node js express então aqui está o que faremos usaremos um aplicativo de tarefas para ios que eu já criei não vamos nos preocupar em mostrar como eu o criei em vez disso, vamos nos concentrar em escrever código em nuvem e proteger o banco de dados o aplicativo de tarefas será um aplicativo seguro onde você só pode acessar suas próprias tarefas e você só pode escrever suas próprias tarefas os dados estarão seguros no servidor, protegidos de clientes maliciosos eu também vou te mostrar como escrever um trabalho em segundo plano seguro no parse basicamente, a mesma coisa que um trabalho cron para que você possa ter serviços automatizados manipulando seus dados em uma programação parece complicado, mas não é apenas imagine pequenos robôs de servidor que fazem o que você quiser em uma programação automatizada parece legal, certo? ok, então vamos lá!!!!!! vamos configurar o aplicativo todo seguro do back4app 1\) crie um app no back4app crie um novo app no back4app chame o app de ‘secure todo app’ nota siga o tutorial de novo app parse para aprender como criar um app no back4app vá para a página de configurações principais do app e clique em editar detalhes do app desmarque a caixa chamada ‘permitir criação de classe do cliente’ para desativar a criação de classe do cliente e clique em salvar queremos limitar o que o cliente pode fazer como regra 2\) defina permissões de segurança em nível de classe para a classe de usuário em seguida, vamos definir as permissões para a classe usuário acesse o painel do banco de dados back4app e clique na classe usuário em seguida, clique na aba segurança, depois clique no ícone de engrenagem no canto superior direito você deve ver um menu que diz simples/avançado mova o controle deslizante para avançado você deve então ver as permissões de nível de classe completas para esta classe desative a caixa de seleção encontrar desative a caixa de seleção atualizar e excluir finalmente, desative a caixa de seleção adicionar campo então clique em salvar suas configurações de segurança devem parecer assim 3\) crie a classe todo clique em criar uma classe e chame a de todo defina o tipo de classe como personalizado 4\) defina as permissões de segurança de nível de classe para a classe todo em seguida, vamos definir as permissões para a classe todo acesse o painel do banco de dados back4app e clique na classe todo em seguida, clique na aba segurança, depois clique no ícone de engrenagem no canto superior direito você deve ver um menu que diz simples/avançado mova o controle deslizante para avançado você deve então ver as permissões de nível de classe completas para esta classe desative tudo e clique em salvar suas configurações de segurança devem parecer assim 5\) vamos adicionar algumas colunas à classe todo primeiro, vamos juntar a classe todo com a classe user faremos isso adicionando 2 colunas a primeira coluna será chamada ‘user’ e será um ponteiro de volta para a classe de usuário em seguida, vamos criar uma coluna para o id do objeto do usuário que a criou será do tipo string e será chamada ‘userobjectid’ em seguida, vamos criar uma coluna para armazenar nossas informações reais de todo também será uma string e será chamada ‘tododescription’ vamos criar um booleano para manter o estado do todo vamos chamá lo de ‘finished’ finalmente, vamos adicionar mais uma coluna para armazenar a data em que você terminou seu todo vamos chamá la de ‘finisheddate’ e defini la como um tipo de data sua classe todo deve se parecer com isso 6\) vamos revisar o cliente o cliente é um aplicativo de tarefas bem básico ele usa as funções internas do parse para fazer login, criar um novo usuário e redefinir sua senha além disso, tudo é baseado em código na nuvem e seguro as acls do usuário também são definidas assim que eles fazem login ou se inscrevem, apenas para ter 100% de certeza de que o sistema é seguro vamos começar escrevendo a função de código na nuvem para definir as acls do usuário ao fazer login ou se inscrever a qualquer momento, você pode acessar o projeto completo de ios construído com este tutorial neste repositório do github você também pode acessar o arquivo de código em nuvem main js construído para este tutorial neste repositório do github 1 no cliente, vá para todocontroller swift e procure pela função setusersaclsnow esta função é chamada quando você faz login ou visualiza o loggedinviewcontroller swift a função verifica se você está logado e, se estiver, chama a função em nuvem para configurar os acls pessoais do seu usuário 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 agora vamos escrever a função de código em nuvem 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 em nuvem utiliza duas características principais para tornar seu aplicativo seguro, request user e masterkey request user permite que você acesse o usuário que está fazendo a chamada do código em nuvem e permite que você limite o acesso para esse usuário neste caso, estamos usando para definir os acl's do usuário para limitar o acesso de leitura apenas ao usuário atual dessa forma, apenas o usuário pode ler suas próprias informações as permissões de nível de classe impedem o acesso de gravação mesmo para o usuário atual dessa forma, os usuários não podem modificar suas próprias informações eles podem apenas alterar coisas sobre seu próprio usuário através do código em nuvem é possível importar informações falsas quando o usuário se inscreve pela primeira vez, mas eu recomendaria escrever uma função de código em nuvem para verificar as informações do usuário após um novo usuário ser criado a função integrada do parse para criar um novo usuário é realmente útil, então eu acho que é um bom compromisso, mas você sempre pode definir os valores padrão para o usuário via código em nuvem logo após a inscrição existem muitas salvaguardas que você também pode escrever no código em nuvem e fazê las rodar automaticamente e continuamente usando trabalhos em segundo plano para detectar qualquer informação maliciosa do usuário que foi importada quando o usuário foi criado pela primeira vez se você quiser ser realmente seguro, pode armazenar qualquer informação sensível, como status de associação ou informações de pagamento, em uma tabela separada da tabela de usuários dessa forma, o usuário não pode falsificar nenhuma informação sensível na criação do usuário 4 em seguida, vamos criar um todo no cliente, vá para todocontroller swift e procure pela função savetodo esta função é chamada quando você cria um novo todo a função recebe uma string que descreve o todo e o salva no banco de dados 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 agora vamos escrever a função de código em nuvem para salvar o todo no banco de dados 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 função de código em nuvem cria um objeto todo e define o usuário atual como o proprietário do objeto isso é importante para que apenas o usuário que o criou possa encontrá lo ou modificá lo ao não permitir que todos sejam criados no cliente, estamos forçando o objeto todo a se conformar aos nossos padrões e garantindo que os todos sejam de propriedade do usuário que os criou 7 em seguida, vamos ver como recuperar os todos que você criou do servidor no cliente, vá para todocontroller swift e procure pela função gettodosfordate esta função é chamada quando você recupera seus todos a função recebe uma data como parâmetro e a utiliza para recuperar uma lista de todos que foram criados por você antes dessa data em ordem decrescente usar uma data é uma ótima maneira de escrever uma consulta de carregamento preguiçoso que não usa skips skip pode falhar às vezes em um grande conjunto de dados 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 agora vamos escrever a função de código em nuvem para recuperar todos do banco de dados com base em uma data de início consultamos os todos que são criados antes da data do parâmetro, então usamos ‘query lessthan’ porque as datas são basicamente números que aumentam quanto mais longe no futuro você está eu também incluí um código complicado aqui digamos que estamos incluindo o objeto do usuário que criou o todo, mas não queremos compartilhar informações sensíveis sobre esse usuário com outros usuários, precisamos removê las da resposta json então temos um loop for onde tiramos o objeto do usuário do todo, removemos o email e o nome de usuário do json e depois o colocamos de volta no todo isso é útil para remover dados sensíveis de uma chamada de api em situações onde você não pode controlar quais campos retorna como um objeto de usuário incluído neste caso, realmente não precisamos disso porque esta função só retornará todos que você criou fazemos isso usando currentuser novamente para consultar apenas os todos criados pelo currentuser que estava anexado à solicitação os resultados são retornados em ordem decrescente para que os últimos todos apareçam primeiro quando você precisa carregar preguiçosamente outro lote de todos, você pega a data createdat do último todo e a usa como o parâmetro de data para a próxima solicitação 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 agora que temos os todos, podemos vê los no aplicativo e marcá los como concluídos se quisermos vamos cobrir isso a seguir 10 para marcar um todo como concluído, basta clicar no botão ‘marcar como concluído’ em qualquer um dos todos que você criou isso acionará um método no todocontroller swift chamado ‘marktodosascompletedfor’ que recebe o todo que você selecionou como parâmetro ele envia o todo objectid para o servidor como um parâmetro e, em seguida, retorna o todo atualizado 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\ agora vamos escrever o código em nuvem para atualizar esta tarefa ele procura a tarefa a ser atualizada com base no objectid, mas também usa o currentuser para garantir que a tarefa associada ao objectid foi criada pelo usuário que está fazendo a consulta isso garante que você só pode visualizar tarefas que criou e, portanto, é seguro incluímos um limite de 1 resultado para garantir que o servidor não continue a buscar após encontrar a tarefa há outro método para encontrar um objeto com base em um objectid, mas não gosto de usá lo, pois pode retornar resultados estranhos se não encontrar o objeto associado ao objectid também estamos definindo a 'finisheddate' com a data atual quando o objeto foi atualizado ao ter a finisheddate definida apenas por esta função, garantimos que a finisheddate é segura e não pode ser falsificada ou alterada também usamos 'query equalto("finished", false)' para garantir que apenas uma tarefa não finalizada pode ser marcada como finalizada e ter a finisheddate definida isso significa que, uma vez que uma tarefa foi marcada como finalizada, ela nunca pode ser marcada como finalizada novamente em uma data 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\) conclusão! e é isso você construiu um aplicativo de tarefas seguro novamente, a chave para fazer um aplicativo seguro no servidor parse é desativar todas as permissões de nível de classe para todas as classes, exceto para a classe usuário na classe usuário, você desativa todas as permissões, exceto criar e obter também certifique se de definir todos os acls do usuário para que o usuário possa apenas obter seus próprios dados então, todas as suas interações passam pelo código em nuvem e são filtradas usando request user, também conhecido como currentuser então é isso, agora você pode construir sistemas seguros em cima do servidor parse e back4app mas espere, você diz? e quanto a trabalhos em segundo plano e consultas ao vivo? bem, você tem um bom ponto, então eu vou cobrir isso em duas seções bônus a seguir 8\) seções bônus 1\ trabalhos em segundo plano às vezes você precisa criar um trabalho em segundo plano para rodar a cada hora, ou a cada dia ou a cada semana se você estiver rodando com todas as permissões de nível de classe desativadas, seu trabalho em segundo plano não poderá consultar o banco de dados, a menos que esteja configurado corretamente isso é meio complicado de fazer, então eu quero incluir um exemplo disso aqui neste caso, criaremos um trabalho em segundo plano que verifica o banco de dados em busca de tarefas não finalizadas que têm mais de 1 ano e, em seguida, as marca automaticamente como finalizadas o truque aqui é usar 'usemasterkey' corretamente ele deve ser adicionado à consulta antes da promessa then basta seguir este modelo e você deve ser capaz de escrever trabalhos em segundo plano seguros facilmente você sempre começa escrevendo uma consulta que deseja iterar sobre todo o banco de dados e, em seguida, certifique se de incluir status error se houver um erro e termine com um status success para garantir que ele seja concluído você pode assistir aos logs no back4app para ver o trabalho em segundo plano funcionando enquanto você o executa 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 ao vivo às vezes você precisa usar o recurso de consulta ao vivo do parse para algo como um aplicativo de chat ao vivo você vai querer usar a consulta ao vivo para ver quando novas mensagens para o seu usuário são criadas a consulta ao vivo é basicamente a maneira do parse de usar sockets para obter atualizações em tempo real é bastante útil, mas não funcionará com uma classe cujas permissões de find foram desativadas portanto, neste caso, vamos reativar as permissões de find para a classe mensagem e, em vez disso, vamos atribuir acls para essa mensagem diretamente a acl deve ser configurada para que apenas o destinatário possa usar um find para obter a mensagem do servidor então você executa sua pf live query no seu cliente procurando mensagens para o seu usuário e funcionará perfeitamente se você estiver lidando com mensagens em grupo, é um pouco diferente você pode atribuir várias pessoas à acl, mas realmente não escala em vez disso, há uma maneira melhor você define a acl para ser baseada em um papel parse role e então qualquer usuário que você queira que tenha acesso a essa mensagem, você apenas o atribui a esse parse role se você quiser impedi los de ler as mensagens para aquele grupo, você os remove desse papel isso é muito mais fácil do que removê los da acl de cada mensagem individual e escala para grupos super grandes esta é a maneira correta de fazer isso não vou deixar um exemplo de código para isso, pois é muito complexo para este tutorial, mas talvez eu explique como fazer isso no meu próximo obrigado por ler este tutorial sobre segurança com parse e back4app se você tiver perguntas, fique à vontade para me contatar e ficarei feliz em respondê las obrigado! joren