Using OR condition in OPA rego - open-policy-agent

I want to use an OR operation to combine the following conditions:
the count of my arr is not equal to 0
my email does not contain "test.com"
Currently I am using the built-in function any():
any([count(arr) != 0, not contains(email, "test.com")])
However my rule is producing an error.
How can I achieve and improve this in one line?

More generally, Rego does not allow OR-ing statements in the same function. Using any() works well for simple cases, but can become unwieldy for complex ones so it is considered an antipattern.
Instead, Rego uses incremental rules, where every statement within each Rule is AND-ed together, and rules of the same name are OR-ed together.
Consider the following deny rule. In short, it says that we will deny the request if at least one of:
The user is not an admin, or
Today is Sunday.
deny {
input.role != "admin"
}
deny {
time.weekday(time.now_ns()) == "Sunday"
}
This would only allow requests to the admin role on days other than Sunday. If instead we said:
deny {
input.role != "admin"
time.weekday(time.now_ns()) == "Sunday"
}
We would then only deny requests from non-admin roles on Sunday. Requests from admin would always be allowed.

The not keyword is a bit of a special case in that it can't be used inside of many contexts such as the array of your example. I believe this could be improved in OPA, but until then you can trivially avoid it for at most cases, like the one you provided:
any([count(arr) != 0, contains(email, "test.com") == false])

Related

Firebase Real Time Database rules for update

I can't understand how to write a rule for an update.
Here is the flow:
First-time users create new, "Conversation" with the following properties at initial states:
TotalMessages: 0
UsersOnline: 0
Date: Some date
Rule I wrote for this:
"$message": {
".validate": "newData.hasChildren(['TotalMessages', 'UsersOnline', 'Date',])"
}
Then when new users join conversation, I need to update the "TotalMessages" value on the client:
update(this.topicPropsRef, {
TotalMessages: increment(+1),
})
I can't understand how to write a rule for an update. According to the rule above, each request should have all these three children, while I need only one for an update.
At Firestore, I could write a separate rule just for an update, but it is only read&write rules in the real-time database.

How to flag and report user in firebase firestore database?

I have application which have multiple users, one of the major thing left is to block and report users in firebase.
I am trying to look for the solution for the same by googling for it, but till now not any particular success.
I would like to know how I can achieve that. Please guide me for that,
and how the firestore security rules should be to achieve the same?
The typical approach is to have a collection that contains the blocked users, with one document for each blocked user and with the ID of that document being the UID of that user.
With that structure in place, your security rules can check for the existence of such a document and then block the user.
There's a great example of this in the blog post 7 tips on Firebase security rules and the Admin SDK (it's tip 7). The rules from there:
service cloud.firestore {
match /databases/{database}/documents {
function isBlackListed() {
return exists(/databases/$(database)/documents/blacklist/$(request.auth.uid))
}
// Collections are closed for reads and writes by default. This match block
// is included for clarity.
match /blacklist/{entry} {
allow read: if false;
allow write: if false;
}
match /posts/{postId} {
allow write: if !isBlackListed()
}
}
}

Firebase one to one chat security rules

From numerous other posts, it seems that the recommendation for creating the structure for one to one chat seems to be to combine two user IDs, taking into account their natural ordering. For example:
root{
messages:{
user1_user2:{
//messages here
},
user1_user3:{
//messages here
}
}
}
Seems fine and is much more efficient than creating a personal message topic for each user. So my question now is what would be the best way to secure this? Right now I have the following:
"messages":{
"$channel":{
".read": "$channel.beginsWith(auth.uid) || $channel.endsWith(auth.uid)"
}
}
Is this sufficient? Can't shake the thought that it's possible for a different but longer user id that starts or ends the same way will get past this check

Building a search criteria thats tricky?

I am trying to do a search on my database IndividualRecords by first building a search criteria but its syntax is getting a little tricky for some values. Its easy to set a criteria for an exact field like if the firstName field has 'John' in it I would put this predicate in my criteria:
IndividualRecord.withCriteria {
if (predicates.firstName != null) {
eq 'firstName', predicates.firstName
}
}
But if they also add that they want to search for US citizens, I can't simply do,
if (predicates.UScitizenship) {
eq 'citizenship', predicates.citizenship
}
because I want to look for records based on citizenship 'US', 'Us', 'uS', and 'us'(case insensitivity must be taken in to account) so how would I get around this?
and then here is where the real fun starts. Say I want to find only foreign citizens. I do have a low level mongodb api method that tells me if the citizenship is a valid one by returning true if it finds it in the database of country codes that I have so I guess I could build another predicate something like pseudocode:
if (predicates.foreign) {
all such people whose !citizenship.caseIgnoreEquals('US') && matchCountry(it.citizenship)
}
meaning that all such people whose citizenship isn't 'US' and matches the list of country codes I have where matchCountry(String countryCode) is my low level api method for verifying a country code and will return true if its a valid country code.
I am finding it hard to define such complicated predicates' syntax and that is where I need some help. Thanks.
There are two issues at hand here.
First, case sensitivity and insensitivy can be addressed by using ilike instead of equals. So for example:
if (predicates.firstName != null) {
ilike 'firstName', predicates.firstName
}
Secondly, you may want to look at named queries to encapsulate some of your query definitions. This way you can include/exclude them as you see fit. For example:
if (predicates.foreign) {
foreignPersons(predicates) // call to named query which contains logic
}
Using this you should be able to construct very complex queries which are built upon smaller definitions and in turn make them more usable and reusable.

Firebase Commit/Rollback for complex writes

I'm writing a financial app with Firebase and for an receipt to be submitted, a number of other objects also need to be updated. For the data to be valid, all data updates need to be completed successfully. If there's an error in one of the writes, all updates must be rolled back.
For example:
If the user submits a receipt, the receipt object must be updated as well as an invoice object as well as other general ledger objects.
If the update started but the user lost internet connection half way through, all changes should be rolled back.
What's the best way to achieve this in Firebase?
First, let's chat for a minute about why someone might want to do commit/rollback on multiple data paths...
Do you need this?
Generally, you do not need this if:
you are not writing with high concurrency (hundreds of write opes per minute to the SAME record by DIFFERENT users)
your dependencies are straightforward (B depends on A, and C depends on A, but A does not depend on B or C)
your data can be merged into a single path
Developers are a bit too worried about orphaned records appearing in their data.
The chance of a web socket failing between one write and the other is probably trivial and somewhere on the order of collisions between
timestamp based IDs. That’s not to say it’s impossible, but it's generally low consequency, highly unlikely, and shouldn’t be your primary concern.
Also, orphans are extremely easy to clean up with a script or even just by typing a few lines of code into the JS console. So again,
they tend to be very low consequence.
What can you do instead of this?
Put all the data that must be written atomically into a single path. Then you can write it as a single set or a transaction if necessary.
Or in the case where one record is the primary and the others depend on this, simply write the primary first, then write the others in the callback. Add security rules to enforce this, so that the primary record always exists before the others are allowed to write.
If you are denormalizing data simply to make it easy and fast to iterate (e.g. to obtain a list of names for users), then simply index that data in a separate path.
Then you can have the complete data record in a single path and the names, emails, etc in a fast, query/sort-friendly list.
When is this useful?
This is an appropriate tool to use if you have a denormalized set of records that:
cannot be merged practically into one path in a practical way
have complex dependencies (A depends on C, and C depends on B, and B depends on A)
records are written with high concurrency (i.e. possibly hundreds of write ops per minute to the SAME record by DIFFERENT users)
How do you do this?
The idea is to use update counters to ensure all paths stay at the same revision.
1) Create an update counter which is incremented using transactions:
function updateCounter(counterRef, next) {
counterRef.transaction(function(current_value) {
return (current_value||0)+1;
}, function(err, committed, ss) {
if( err ) console.error(err)
else if( committed ) next(ss.val());
}, false);
}
2) Give it some security rules
"counters": {
"$counter": {
".read": true,
".write": "newData.isNumber() && ( (!data.exists() && newData.val() === 1) || newData.val() === data.val() + 1 )"
}
},
3) Give your records security rules to enforce the update_counter
"$atomic_path": {
".read": true,
// .validate allows these records to be deleted, use .write to prevent deletions
".validate": "newData.hasChildren(['update_counter', 'update_key']) && root.child('counters/'+newData.child('update_key').val()).val() === newData.child('update_counter').val()",
"update_counter": {
".validate": "newData.isNumber()"
},
"update_key": {
".validate": "newData.isString()"
}
}
4) Write the data with the update_counter
Since you have security rules in place, records can only successfully write if the counter does not move. If it does move, then the records have been overwritten by a concurrent change, so they no longer matter (they are no longer the latest and greatest).
var fb = new Firebase(URL);
updateCounter(function(newCounter) {
var data = { foo: 'bar', update_counter: newCounter, update_key: 'myKey' };
fb.child('pathA').set(data);
fb.child('pathB').set(/* some other data */);
// depending on your use case, you may want transactions here
// to check data state before write, but they aren't strictly necessary
});
5) Rollbacks
Rollbacks are a bit more involved, but can be built off this principle:
store the old values before calling set
monitor each set op for failures
set back to old values on any committed changes, but keep the new counter
A pre-built library
I wrote up a lib today that does this and stuffed it on GitHub. Feel free to use it, but please be sure you aren't making your life complicated by reading "Do you need this?" above.

Resources