firestore rules nested objects - authorization

Im trying to set up rules for a Firestore Database.
Im having some trouble setting up rules for nested objects.
The database structure looks like this:
Users ( collection of User objects )
-----userDocument ( the name of the document matches the auth-users uid )
-----------------users ( subcollection of users )
-------------------------userdocument ( the name of the document matches the auth-users uid )
note: the last userDocument does not contain any references.
He has his own document in the main Users collection.
I want every user to have read/write access to every user in the Users collection, who has a matching ID of the users in his subcollection of users.
Furthermore, any user should be able to create a new user on the database, as long as they are authenticated with firebase Auth.
I have tried following solutions, it doesnt work:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}{
allow read, write: if exists(/databases/$(database)/documents/users/userId/users/$(request.auth.uid)) || userId == request.auth.uid;
}
}
}
What i need is:
A way to get all document names from the logged in userĀ“s subcollection of users
A way to grant access to ONLY these users
The user has 1 user in his subcollection, so the user should have access to read/write his own user, and qb2pa1TWXHZr0NZUREealgWrOYb2.

I found a solution that works, i hope this helps someone in the future.
Everything is fully tested, commented and working.
service cloud.firestore {
//This is the "root" of the database. From here we can match into our collections.
match /databases/{database}/documents {
//matching the collection "users", the wildcard "userId" is used for the user we will be working with.
match /users/{userId}
{
//Everyone is allowed to write, if they are logged in.
allow write: if request.auth.uid != null;
//A user is allowed to read, update and delete his own account.
allow read, update, delete: if request.auth.uid == userId;
//A user is allowed to read a user, if the user matching "userId" exists in the logged in users own subcollection of users.
allow read: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/users/$(userId));
//Matching the subcollection "users", still in the user matching userId.
match /{users=**}{
//A user is allowed to read, write, update, delete in the subcollection on his own account.
allow read, write, update, delete: if request.auth.uid == userId;
//A user is allowed to read, write, update, delete in the subcollection,
//if the user matching "userId" exists in the logged in users own subcollection of users.
allow read, write, update, delete: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/users/$(userId));
}
}
//matching the collection "duties", the wildcard "dutyId" is used for the duty we will be working with.
match /duties/{dutyId}{
//Everyone is allowed to write, if they are logged in.
allow read, write: if request.auth.uid != null;
// A user is allowed to read, write and update if the string in the field "ownerId" in the duty matching "dutyId" == the users Uid.
allow read, update: if resource.data.ownerId == request.auth.uid;
//A user is allowed, if the user matching "ownerId" exists in the logged in users subcollection of users.
allow read, update, delete: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/users/$(resource.data.ownerId));
}
}
}

Related

How to allow firebase user to only access documents that they created

This, to me, is the most basic authentication scheme for user-generated content, given a collection called "posts":
Allow any authenticated user to insert into "posts" collection
Allow the user who inserted the document into collection "posts", to read, update, and destroy the document, and deny all others
Allow the user to list all documents in collection "posts" if they are the one who created the documents originally
All examples I've found so far seem to rely on the document ID being the same as the user's id, which would only work for user's "profile" data (again, all the examples seem to be for this single limited scenario).
It doesn't seem that there is any sort of metadata for who the authenticated user was when a document was created, so it seems i must store the ID on the doc myself, but I haven't been able to get past this point and create a working example. Also, this opens up the opportunity for user's to create documents as other users, since the user ID is set by the client.
I feel like I am missing something fundamental here since this has to be the most basic scenario but have not yet found any concise examples for doing this.
This answer is from this github gist. Basically, in the document collection posts there is a field called uid and it checks if it matches the users uid.
// Checks auth uid equals database node uid
// In other words, the User can only access their own data
{
"rules": {
"posts": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
-- Edit --
DSL rules
match /Posts/{document=**}{
allow read : if uid == request.auth.uid;
allow write: if uid == request.auth.uid;
}

how to make better doc/user -specific write permissions in couchDB?

I am playing around with CouchDB and PouchDB for a project where users have their own database and can add each other to view, or edit their docs.
Goal is to have different levels of accessibility: Depending on the docs themselves other users, who are not the docs.owner will have limited writing/updating permissions. The databases owner/admin grants those privileges.
I am not sure how to properly implement that.
At the moment my solution for this is to let the DBs owner "befriend" other users and add them as members to db/_security, while limiting the writing rights with a _design document like described in: https://github.com/pouchdb-community/pouchdb-authentication/blob/master/docs/recipes.md
But I need a mixture of user specific and DBs specific permissions. So my strategy is to also let the user/owner add special roles besides the default "members" and "admins" to db/_security.
Example:
A user paula owns the DB paulas_DB and wants to grant user jan the right to change the property "location" of every document.
So Paula adds jan to members.names in _security and adds a new list to _security called "movers":
curl -X PUT $HOST/paulas_DB/_security -d '{"members":{"names":["admin","paula","jan"],"roles":[]},"admins":{"names":["admin","paula"]},"movers":["jan"]}'
the docs in paulas_DB are structured like this:
{
"_id": "y",
"_rev": "7-x",
"owner": "paula",
"location": "somewhere",
"name":"thing"
}
now there there is a design document in place in her database, checking that anyone who wants to change the document in general is at least a member AND then checking if they want to change location like this:
function (newDoc, oldDoc, userCtx, secObj) {
// only admins owners or friends of the owner (aka users in the _security members.names list) can edit
if (userCtx.roles.indexOf('_admin') === -1 && oldDoc.owner !== userCtx.name && secObj.members.names.indexOf(userCtx.name) === -1)
{
// next step: add special fields to be either editable or not
throw({forbidden : "sorry. you are not the owner of this document"});
}
// only owners users who are also listed within _security.movers can change the location
if (oldDoc.location !== newDoc.location && oldDoc.owner !== userCtx.name && secObj.movers.indexOf(userCtx.name) === -1)
{
throw({forbidden : "you are not allowed to change the location of an item, dummie!"})
}
}
This method seems to work and was fairly straight forward, but something feels off to add non-standard properties into _security.
Is there another, propper way to do the same thing? Or is that an acceptable design for a document/user specific permission system?

Firestore Database Rules for User

I'm following a tutorial about firestore but I don't understand firestore rules very well. I'm trying to allow anyone to be able to create in the standard
users/uid/
path but only allow updates if the requester is trying to update
users/theirUserId/
I saw this in the documentation, but it didn't seem to work for me:
allow write: if request.auth.uid == resource.data.author_id;
Can anyone explain the functionality of the above line and/or offer suggestions as to how I can achieve this?
Additionally, is there any way to specify rules for a specific piece of data within a document?
It looks like that your document doesn't contain a author_id field.
The Firebase documentation Writing Conditions for Security Rules use this example:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches the 'author_id' field
// of the document
match /users/{user} {
allow read, write: if request.auth.uid == resource.data.author_id;
}
}
}
It means that a random user will be able to read and write in the users collections only if their authentication ID equals the author_id field of a specific document.
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
For your second question, I recommend you to have a look on the documentation about resource variable (link in the quote above). It is the same logic as your author_id question.
You can split allow write in to three create, update, delete for specific cases.
In your case
allow create: if request.auth.uid != null;
allow update: if request.auth.uid == resource.data.author_id;
which says any authenticated users can create and only update their on document. and created user must have a field author_id which is their user id.

Firebase simple blog (confused with security rules)

I'm trying to create a simple todo or blog system based on React + ReactFire.
And after a hour of reading firebase tutorial confused about configuring firebase security rules.
Code for saving element :
this.props.itemsStore.push({
text : this.state.text,
done : false,
user : this.props.user.uid
})
Everything ok, but how i can get all records what owns only but authorized user?
This rules doesn't works :
"rules": {
"items" : {
".write" : "auth !== null",
"$item" : {
".read": "data.child('user').val() == auth.uid"
}
}
}
Seems to there no way to get all records only for one user, with security rules, instead of this, i should use something like filter. But again, i don't know how to filter elements in ReactFire, and in manuals no information.
As example how does it work in Parse http://i.stack.imgur.com/l9iXM.png
The Firebase security model has two common pitfalls:
permissions cascade: once you've granted a read or write permission on a specific level, you cannot take this permission away at a lower level
rules are not filters: (this is essentially a consequence of the previous pitfall) you cannot use security rules to return a different subset of children for specific users. Either a user has access to a node, or they don't have access to it.
You seem to be falling for that second pitfall. While the user can access each specific message that they are the user for, they cannot query the higher-level items node since they don't have read access to it.
If you want to secure a list of messages/todos for a specific user, you will need to store that data for that specific user.
items_per_user
$uid
$itemid: true
This is quite common in NoSQL database and is often called denormalizing. See this article called "denormalization is normal" on the Firebase web site. It's a bit outdated as far as the Firebase API goes, but the architectural principles on denormalizing still apply.
To then show the items for a user, you'd do:
ref.child('items_per_user')
.child(ref.getAuth().uid)
.on('child_added', function(snapshot) {
ref.child('items')
.child(itemId.key())
.once('value', function(itemSnapshot) {
console.log(itemSnapshot.val());
});
})
Many developer new to Firebase think that the inner loop will be too slow to load their data. But Firebase is very efficient when it comes to handling multiple requests, since it only opens a connection once per client and pipelines all the requests in the inner loop.
Keep in mind, Rules are not filters. They allow access to nodes based on criteria.
Here's an example simple structure where users 0 and 1 have stored text data within their node.
Data Structure
ToDo
a_user_id_0
text: "some text"
done: yes
a_user_id_1
text: "another text"
done: no
Rules
In this example rule, users can only read/write from nodes that belong to them within the ToDo node, so the path $user_id would be equal to their auth.id. It assumes the users has authenticated as well.
"ToDo": {
"$user_id": {
".read": "auth != null && $user_id == auth.uid",
".write": "auth != null && $user_id == auth.uid"
}
}
If user_0 was auth'd and attempted to read/write data from a_user_id_1 node, it would fail.

Combination of roles in Episerver

I have a site where our customers log in to see their own data. Each customer must only see their own data (of course), and different users will have access to different pages within one customer. In addition - the editors must see all data.
I want to set up the access rights based on roles to determine which customer that the user is member of, and what pages the user can access.
Groups:
Customer1Role
Customer2Role
TicketViewerRole
ChangeRequestRole
Users:
Cust1_LowLevelUser. Roles: Customer1Role, TicketViewerRole
Cust1_HighLevelUser Roles: Customer1Role, TicketViewRole, ChangeRequestRole
Cust2_LowLevelUser. Roles: Customer2Role, TicketViewerRole
Cust2_HighLevelUser Roles: Customer2Role, TicketViewRole, ChangeRequestRole
Page structure
We have created a page tree where each customer has its own "root page" with access only to their respective role. Below that node we create instances of the data specific pages, which have their access rights based on user roles as well as the customer role.
Customer1 (Customer1Role)
|--TicketsForCust1 (Customer1Role, TicketViewerRole)
|--ChangeRequestsForCust1 (Customer1Role, ChangeRequestRole)
Customer2 (Customer2Role)
|--TicketsForCust2 (Customer2Role, TicketViewerRole)
|--ChangeRequestsForCust2 (Customer2Role, ChangeRequestRole)
Burning question:
How do we prevent user Cust2_HighLevelUser from seeing ChangeRequestsForCust1?
EPiServer only checks if any role is sufficient for granting access, and since the user belongs to ChangeRequestRole, they will be granted access, regardless of the customer specific role. Is it possible to make EPiServer check BOTH the customer role, and the page role?
Or do I have to look at this from another view? Please let me know if you have run into this and solved it in another way.
Sorry, long post, but hopefully I get my point across.
There is no Deny flag in the access rights model so you need to code it yourself with that role structure.
Add code to your template base class that denies access and for the PageTree control you can do something like this:
protected void NavSubPageTreeFilter(object sender, EPiServer.Filters.FilterEventArgs e)
{
for (int i = e.Pages.Count - 1; i > -1; i--)
{
PageData pd = e.Pages[i];
if (yourUser.IsInRole("blabla") && ... etc)
{
e.Pages.RemoveAt(i);
}
}
}