how to implement Parse CLP, ACP Role & Row level security, CRUD and cloud functions in a Flutter App

Amit Shukla
6 min readJul 18, 2024

--

Service Delivery Management App

welcome back friends,

In our previous blog, we presented an expedited demonstration of the Ride or Service Delivery Management application, which utilizes the Flutter platform coupled with the Parse back-end service by Back4App.

You can access the complete source code here and click here to access next blog.

In case you prefer video blogs, here is YouTube video tutorial.

setup CLP, ACP, Roles and users in Back4App
parse back4app cloud functions

If you are finding this blog for the first time, please visit previous blogs for a quick demo, about how to setup your project, build first hand UI pages. Implement Dark/Light Mode functionality, implement multilingual functionality, data model and dynamic data validators and implemented user authentication pages etc.

In this blog, we will start building CLP, ACP and cloud functions to secure our back-end.

Please see that Back4App uses Parse Framework, so most of the security design pattern discussed below, apply to any other document DB based approach.

Parse classes

Parse framework provides 3 default classes. Users, Roles and Sessions.

User class can only be modified by that user but is accessible to others as read-only object.

Role class act as bonding between Users and other roles into a new role.

Session class stores current user sessions information. It’s secured and allows user to update his own content only.

Class level permissions — CLPs: allows admin to control CRUD operations of class. Think of CLP as table level design, and who (user/role) can or can’t CRUD on entire class and it’s object.

Access Control Lists — ACLs: allows admin to manage CRUD access on each object of a given class. Think of ACL as a row level security mechanism which maps roles/users to CRUD object individually.

Data Security Flow

implement CLPs

First, let’s edit class level permission by changing from `Simple` to `Advanced`.

once `Advanced` option is enabled, it shows different CRUD security options.

implement ACLs

Access control Lists allows you to control access to each object of a given class, think it like, you are securing each and every row in a given table.

implement row level security

First step in implementing row level security is to lock out everything in your database. No one should be able to add fields, read, write, count of find anything in any of your class.

Then come up with a role structure and secure each class (including default User and Roles).

For example, in our app, we need 5 roles

- admin — will only access and manage users and roles.

- customer — can access only their own settings, bids and contracts.

- driver — can access only their own settings, orders and contracts.

- company (driver’s company) — can access only their driver(s) settings, orders and contracts.

- employee (HR) — can access contracts, bids etc.

creating admin and other roles

later, we will create a CRUD page for admin to dynamically update this through UI.

disable client class creation

this option will prevent any external user who can download APP keys from UI, create classes. This is very important step, as anyone with little knowledge can access your API key and use in CURL to access/download your entire `User` class documents or create new `classes` in your database.

secure user class

Let’s revisit ACLs topic, one more time.

We will first work on user class at this time. Open User class and update it CLP like this.

Let’s start with blocking everything in your database. We will lock down every single table/class to no read/write permission.

create and update `SETTINGS class` CLPs

once you create your `SETTINGS` class, which stores user settings, make sure you add two extra fields.

User (which is actually a POINT to user class) and UID (will store user id into this field).

It does not mean, that we will not use ACLs anywhere, ACLs still still be used in sign-up, login, logout, and reset password methods. for every other database transaction, we can pretty much skip parse server SDK. We will use cloud functions instead.

CRUD API — cloud functions

now since our data is locked, we will handle ACLs at transaction level. This kind of means, that we’ll use ACL only for User class which is access through login/logout and signup pages. So we will keep parse server SDK for those functions only and for all data related pages, we will write parse cloud functions to access and update data.

that means, we will make sure, each and every transaction send from UI, is secured and setup in such a way, that it’s only accessible through authorized users.

We will use Cloud functions to do that.

Parse.Cloud.define('setUsersAcls', async(request) => {
let currentUser = request.user;
currentUser.setACL(new Parse.ACL(currentUser));
return await currentUser.save(null, { useMasterKey: true });
});


Parse.Cloud.define("getUserSettingsDoc", async(request) => {
let currentUser = request.user;
let query = new Parse.Query("Settings");
query.equalTo("user", currentUser);
query.limit(1);
});

Parse.Cloud.define("setUserSettingsDoc", async(request) => {
let currentUser = request.user;
let userName = request.params.userName;
let userType = request.params.userType;
let name = request.params.name;
let email = request.params.email;
let phone = request.params.phone;
let address = request.params.address;
let ToDo = Parse.Object.extend("Settings");
let todo = new ToDo();
todo.set("user", currentUser);
todo.set("uid", currentUser.id);
todo.set("userName", userName);
todo.set("userType", userType);
todo.set("name", name);
todo.set("email", email);
todo.set("phone", phone);
todo.set("address", address);
return await todo.save(null, { useMasterKey: true });
});

Parse.Cloud.define("updateUserSettingsDoc", async(request) => {
let currentUser = request.user;
let todoId = request.params.objectId;
let userName = request.params.userName;
let userType = request.params.userType;
let name = request.params.name;
let email = request.params.email;
let phone = request.params.phone;
let address = request.params.address;

let query = new Parse.Query("Settings");
query.equalTo("user", currentUser);
query.equalTo("objectId", todoId);
let todo = await query.first({ useMasterKey: true });
if(Object.keys(todo).length === 0) throw new Error('No results found!');
todo.set("user", currentUser);
todo.set("uid", currentUser.id);
todo.set("userName", userName);
todo.set("userType", userType);
todo.set("name", name);
todo.set("email", email);
todo.set("phone", phone);
todo.set("address", address);
// return await todo.save(null, { useMasterKey: true });
try {
await todo.save(null, { useMasterKey: true});
return todo;
} catch (error){
return("getNewStore - Error - " + error.code + " " + error.message);
}
});

Please do not forget to like and bookmark this article and subscribe to my YouTube and X accounts for more content.

--

--

Amit Shukla
Amit Shukla

Written by Amit Shukla

Build and Share Quality code for Desktop and Mobile App Software development using Web, AI, Machine Learning, Deep Learning algorithms. Learn, Share & Grow.

Responses (1)