Firebase security rules for sub collections - firebase

I have the following structure:
+ properties: (collection)
- address
status
type
ownerId
renterId
+ offers (collection)
- id
amount
date
- id
amount
date
+ features (collection)
- id
name
- id
name
I would like to allow read access to the properties for owner (using ownerId), renter (using renterId) and admin.
Doing this doesn't seem to work:
match /properties/{property} {
allow read, write: if get(/databases/$(database)/documents/properties/$(property)).data.renter == request.auth.uid
|| isOwnerSeller(get(/databases/$(database)/documents/properties/$(property)))
|| isAAdmin();
}
What am I missing?
Can I also target the offer only?

service firebase.storage {
// Allow the requestor to read or delete any resource on a path under the
// user directory.
match /users/{userId}/{anyUserFile=**} {
allow read, delete: if request.auth.uid == userId;
}
// Allow the requestor to create or update their own images.
// When 'request.method' == 'delete' this rule and the one matching
// any path under the user directory would both match and the `delete`
// would be permitted.
match /users/{userId}/images/{imageId} {
// Whether to permit the request depends on the logical OR of all
// matched rules. This means that even if this rule did not explicitly
// allow the 'delete' the earlier rule would have.
allow write: if request.auth.uid == userId && imageId.matches('*.png');
}
}
According the documentation you can set rules with also this way. Follow this example and you should be able to apply your desired rules.

Related

Access userId without having it as a field

I'm writing Firestore security rules for my project. I want to allow users to edit information in their own user page, but not in anyone else's. Right now I don't save userId as a field in each user, only as the reference to the user document. I know how to access fields in each user, but not the reference to them. See picture:
match /Users/{document} {
allow update: if request.auth.uid == userId; //how do I reach the userId without having it as a field
}
I do not want to add userId as a field in each user, there must be an easy way of accessing the path.
As mentioned in the Firestore docs you get the document id from the match query.
In your case this would be document from match /Users/{document}. You could also rename this query to match /Users/{userId} to make it work.
Check the second example in the documentation on using authentication information in security rules:
Another common pattern is to make sure users can only read and write their own data:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
So in your case that'd be if request.auth.uid == document.

Firebase Security Rule with attribute in database

I need security rules for my firebase-app.
My Data looks like this
{
owner = "djskjfskdjf",
data = "some data"
}
Collection Path is /Data/
I want that users that are authenticated users can read and create documents in this collection. To edit or delete documents, I want that the uid in the field owner is the same as the request.auth.id.
According to firebase documentation this should work:
service cloud.firestore {
match /databases/{database}/documents {
match /Data/{document=**} {
allow read, create: if request.auth.uid != null;
allow delete, write: if request.auth.uid == request.resource.data.owner;
}
}
}
But when I try to update a field in simulation it gives me the error:
Error: simulator.rules line [5], column [51]. Property resource is undefined on object.
I hope you can help me with this problem.
If your request isn't sending the all the required fields you're checking in rules, then request.resource.data.owner wont work (like if you are just sending data but not owner with the request).
You should use the below style to match an existing object in the database. It won't matter if the request omits the owner info.
allow delete: if resource.data.owner == request.auth.uid;

Firebase firestore security rules sharing data

I have this data structure in firestore where I'm trying to link user to profile then to event. A profile can be shared by multiple users and should be able to access events for that profile.
user
- id
- email
- name
- profilePicUrl
profile
- id
- name
- dateOfBirth
- owners: [ "user1","user2" ]
- etc.
event
- id
- profileId
- name
- startDate
- endDate
I currently have:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{id} {
allow read, write: if request.auth.uid == id;
}
match /profiles/{id} {
allow read, write: if ("owners" in resource.data && resource.data.owners != null && request.auth.uid in resource.data.owners);
}
match /events/{id} {
allow read, write: if hasAccess(userId, resource) == true;
}
}
}
function hasAccess(userId, resource) {
// Not sure what to put here but basically need
// to get profiles where user is owner
// and get events for these profiles
}
But not sure what to put in the hasAccess function. Appreciate if someone can guide me.
UPDATE 2019/10/11
Somehow I got this to work by using the following rule:
match /events/{id} {
allow read, write: if (exists(/databases/$(database)/documents/profiles/$(resource.data.profileId)) &&
"owners" in get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data &&
get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data.owners != null &&
request.auth.uid in get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data.owners);
}
UPDATE 2019/10/14
I have some permission issues with the write so I had to revise it as shown below:
match /events/{id} {
allow read: if ( exists(/databases/$(database)/documents/profiles/$(resource.data.profileId))
&& "owners" in get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data
&& get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data.owners != null
&& request.auth.uid in get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data.owners);
allow write: if ( request.auth.uid in get(/databases/$(database)/documents/profiles/$(resource.data.profileId)).data.owners );
}
What you're trying to do is actually not possible with security rules given the existing structure of your data. This is due to the fact that security rules can't perform queries against collections. The only thing you can do is get() a specific document using its known path in order to read its fields, which isn't going to help you link up documents where you can't build that path.
What you can do instead is duplicate the data required for the rule into each document that needs to be protected. This means each event document needs to have a copy of each list of owners as a field. Yes, that is going to be more hassle to keep all the events up to date if the list of owners for an event has to change.

Firestore Security Rules: Allow User To Create Doc Only If New Doc ID is same as User ID

When users log in for the first time, I need to also call a function that creates a document in my firestore users collection to store their profile data. Using Web SDK.
(I was previously using a new user triggered event with firebase functions, but it was too slow to wait for a cold function to spin up).
Security Rule Requirements
Needs to ensure that the user can only create a document if the document id is the same as their user id (to prevent the user from creating other docs). Needs to ensure that this doc doesn't already exist.
Attempt - Works In Simulator, Not IRL
These tests pass in the simulator, but not IRL.
// Allow users to create a doc if the doc ID == their user id
allow create: if path("/databases/" + database + "/documents/users/" + request.auth.uid) == request.path;
OR
allow create: if /databases/$(database)/documents/users/$(request.auth.uid) == request.resource['__name__']
Have also tried this (again, works in simulator, but not IRL)
match /users/{userId} {
// Allow users to read their own profile
allow create: if request.auth.uid == userId;
}
Update
I recently had to update my rule set because of some changes to the way firestore rules worked, and changes in how the "getAfter" function works. Specifically, I am now able to use request.resource for data comarisons. Anyways, it appears that I can accomplish my goals with simpler rules now so I thought I'd update this answer and share.
Goals
User can create a document, only if the new document ID matches their user ID.
User cannot declare themselves an "admin", block create / update / write requests if "admin" is a field (unless they are already an admin)
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to create a document for themselves in the users collection
match /users/{document=**} {
allow create: if request.resource.id == request.auth.uid &&
!("admin" in request.resource.data);
}
// Allow users to read, write, update documents that have the same ID as their user id
match /users/{userId} {
// Allow users to read their own profile (doc id same as user id)
allow read: if request.auth.uid == userId;
// Allow users to write / update their own profile as long as no "admin"
// field is trying to be added or created - unless they are already an admin
allow write, update: if request.auth.uid == userId &&
(
!("admin" in request.resource.data) ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true // allow admin to update their own profile
)
// Allow users to read their own feeds
match /feeds/{document=**} {
allow read: if request.auth.uid == userId;
}
}
}
}
Old Answer
So I figured out how to do this in a workaround way. I also had some additional write / update conditions that prevent the user from changing their permission level. This was for some reason, preventing any "creates" from happening. So I had to mirror the same conditions in create, and the write / update rules. For some reason this was necessary.
This new rule structure accomplishes the following
First Section, for create rule
allows the only authenticated users to create documents only in the "users" collection (during the user setup process, a document is created automatically with the same ID as their user id).
does not allow creation of a document containing the "admin" field, which would suggest they are trying to gain admin access.
it seems that validating the id of the document during creation is not possible, hence additional write / update rules below
Second Section - read, update, write
allows users to read / write / update only documents that have the same ID as their user id (user trying to create a document with an ID other than their user id will fail, also prevents the user from spamming creation of tons of docs by manipulating the client-side JS request.)
does not allow users to write / update their profile to include the "admin" field
Rules
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to create documents in the user's collection
match /users/{document=**} {
allow create: if request.auth.uid != null &&
!("admin" in getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data);
}
// Allow users to read, write, update documents that have the same ID as their user id
match /users/{userId} {
// Allow users to read their own profile (doc id same as user id)
allow read: if request.auth.uid == userId;
// Allow users to write / update their own profile as long as no "admin" field is trying to be added or created
allow write, update: if request.auth.uid == userId &&
!("admin" in getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data);
}
}
}
PS
This was not intuitive at all, so if someone has a better workaround, please post it. Also, I'm really hoping that once firestore 1.0 is out, it will bring with it some huge improvements to rules and rule documentation.
A little bit late, but I manage to tweak one of your possible solutions and make it work:
allow create: if path("/databases/(default)/documents/users/" + request.auth.uid) == request.path;
Just had to replace the database variable with (default). Yes, not fancy...
The solution i came up with. My tests showed it's not possible to create other user-docs than the own uid and it prevents normal users to change any admin state.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).isAdmin == true ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true;
}
function signedIn(){
return request.auth.uid != null;
}
match /users/{user} {
// allow updates to own user-doc
allow read, update, delete: if request.auth.uid == user &&
// allow updates to own user-doc if "isAdmin" field is the same as before the update (in case user was already admin)
(request.resource.data.isAdmin == resource.data.isAdmin ||
// or allow updates if "isAdmin" will be set to false
request.resource.data.isAdmin == false ||
// or allow updates if no "isAdmin" field exists after the update
!("isAdmin" in getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data)
);
// allow creation of user-doc with own uid and no others
allow create: if request.auth.uid == user &&
// if no "isAdmin" field is set
!("isAdmin" in getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data);
// give full access to admins
allow read, write: if isAdmin();
}
}
}

firebase - setting specific fields as private

I want to set up certain fields to be private on my user profiles. I have a user documents with name, email, etc but I want to make the gold field read only as I plan to use a cloud function to update this value when a user makes an in app purchase. I've not done in app purchases before so this is the only way I can think of doing it.
I understand I can use wildcard vars in the path when using Firestore security rules, however as far as I'm aware, I can only use wildcard vars in place of the documents and collections.
You are correct that wildards can only be used to identify collections and documents, but not fields. However one option you could have is to create an additional 'private collection' which you could secure with the standard security rules. For example -
users
user1
email
name
gold
user1
goldValue
Then in your security could look something like -
service cloud.firestore {
match /databases/{database}/documents {
match /gold/{userId} {
allow read, update, delete: if request.auth.uid == userId
allow create: if request.auth.uid != null;
}
match /users/{document=**} {
allow read;
allow write: if request.auth.uid != null;
}
}
}

Resources