Data ACLs - Limit access to data
Last updated
Last updated
Data ACLs give more granular control over users' access to data objects, therefore making apps more secure. This is achieved by specifying read
and/or write
permissions on data buckets and their relationships when querying DB
or OnlineDB
.
ACLs vs sync rules
ACLs specify users' access to data objects, whereas sync rules specify which data syncs to users' devices. This document describes ACLs. You can learn more about sync rules here.
Data ACLs on Web
For apps on web it is strongly recommended to implement data ACLs to limit data a single compromised web user could have access to.
Data ACLs are specified on data buckets. It is important that you are familiar with these before moving on to the next sections. Read up about them here:
Data ACLs read
and write
attributes that can be specified for the data buckets global-bucket
, bucket
, and the model
and has-many
tags within these buckets.
Note: These are only “allow” rules - no “deny” rules are supported. This means that if any rule matches, the operation is allowed.
read="any|none|online|offline"
Type | Description |
---|---|
Attachments require online access
Downloading attachments requires online access, since they are not stored on a device. The recommended rule for attachments is read="any"
.
write="any|none|create|update|delete"
You can specify one, or a comma-separated combination of the above. E.g. write="none"
or write="create,update"
Object-specific bucket access rules include their root objects by default. To overwrite this behavior, read
and write
attributes can be specified for the root object, by adding the a root
tag inside a bucket
, e.g.:
If not specified (the tag is not present, or attributes on the tag are not present), root object access permissions default to:
<root read="any" write="update,delete" />
Root permissions have some important details:
Only a single root
tag is allowed per bucket.
When the via
path to the bucket root is a belongs-to
relationship, write="create"
is not a valid permission. Additionally, the write="any"
permission also does not allow creates on root objects in this case (but it does allow updates and deletes). The reason for this is that the relationship from the user to the root object would need to be set before creating the root object:
This does not apply to bucket roots where the via
path to the root is a has-many
relationship. In this case, write="create"
and write="any"
are valid permissions and writes are supported if a single operation creates the root object and assigns it to the user/bucket. To illustrate with an example:
By specifying write="none"
on this global bucket, its data is synced to all users and they have read-only access.
In this example, only admins should be able to update certain data, while all other users have read-only access. Here we define the read="none"
rule on the data bucket that provides write access for admins, since another data bucket already allows synching and unrestricted read access to these objects for all users (including admins).
In this example, a user
belongs-to
a region
, and the region has-many
clients
. Users have unrestricted access to their region's clients, but cannot update their own region (and thereby access other region's clients). However, they can create new regions.
Sometimes it may be necessary to "lock objects" after they have reached a certain state. In this example, we want to prevent technicians from updating jobs once they have been marked as completed.
Here we override the access rules defined for the overall bucket (the default: sync, read and write access), by defining various access rules on the bucket's root and relationships. The individual access rules are described below.
Large number of OnlineDB queries and individual object lookups in your app can negatively affect app performance, since individual requests are slower to perform with data rules.
Tips to improve app performance with data rules:
Use .include(relationship)
when related objects need to be looked up in a query
When doing individual object lookups, use .where('id in ?',ids)
to batch the requests
When doing OnlineDB writes, use Batch
OnlineDB
access is limited to synced data by defaultNote: When migrating your app from sync rules to data rules, additional data rules will automatically be added to your data rules to support existing OnlineDB
calls in your app given this implication. See more details here.
The default behavior of data rules is that OnlineDB
access is limited to data synced to the device (in other words, as previously specified in the app's sync rules).
With sync rules, it was possible to query any model via OnlineDB
, regardless of the model being included in sync_rules.xml
. With data rules, only the data specified in data_rules.xml
is accessible with OnlineDB
.
Let's look at an example. Take the following sync rules definition:
Since sync rules only specify which data is synced to a device, it would be possible to query another model not included in the sync rules using OnlineDB
:
With data rules, given the initial sync rules definition, the above OnlineDB
query would not return any results. Read or write access to site
via OnlineDB
would need to be explicitly defined within data rules to support the above query:
Note: When migrating your app from sync rules to data rules, additional data rules will automatically be added to your data rules to support existing object writes throughout your app given this implication. See more details here.
With data rules, object writes are only performed if the user has access to the object before and after an update. Of course, for create
operations it’s only the after state that is validated.
This means that where updates to objects need to occur by a user, those objects still need to be accessible to the user after the update.
Let's look at a practical example.
Take the following bucket defined in data_rules.xml
:
And consider the following in-app logic that marks a job as complete
.
Once a user marks a job
as complete, the job would no longer be accessible to the user, as only incomplete jobs are synced to the device. With sync rules, this kind of update is supported. With data rules, however, since the user would no longer have access to the job after this update (of setting the complete
boolean to true
).
Therefore, it is important to review all conditional buckets and models, and ensure that writes are supported.
Taking the above example, the following additional rule in data_rules.xml
would enable updates to a job
:
Additional buckets when migrating to data rules
When migrating to data rules, these rules to support writes to objects that are only conditionally synced, are automatically created. Read more about this here.
Since data rules control which data is available to users it is recommended you test data users should be able to access, and data they shouldn’t be able to access before deploying to active users. For this, you can use the debug developer console, and specifically run OnlineDB
calls and queries. The below section on what happens when access is denied, should also help with these tests.
When data rules prevent access to data for a user/device, they can expect the following to happen:
OnlineDB
save()
operations will throw an "Access denied" error.
OnlineDB
destroy()
operations will appear to work, but the data will not be touched.
OnlineDB
queries will exclude the results.
DB
save()
operations will show an "Access denied" error in the developer console (in JourneyApps Runtime version 4.85.0 and greater).
Data rule reprocessing follows the same principles as sync rule reprocessing.
Sync rule reprocessing is only applicable if updates have been made to rules that affect what data is synced. Therefore, there are several cases where updates to data rules would not require reprocessing. This includes migrating to data rules, and updating/adding/removing rules that don’t have read="any"
or read="offline"
set. These data rules take effect immediately after deploying an update.
Type | Description |
---|---|