Firestore Database Rules for User - firebase-security

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.

Related

Accessing a deeper level document if email is found along the way in cloud firestore

I'm a teacher who has built a webapp for my school to store assessment data basically. The database has this structure:
matrix -> mStudents -> mStudents--active -> *STUDENT* -> courseList -> *COURSE* -> assessments -> *ASSESSMENT*
there are many STUDENT documents, and each have a few COURSE documents, and each have a few ASSESSMENT documents - this is where the grade data is. Initially, I allowed teachers to write freely, but I want to up the security level a bit and allow teachers to only write in COURSE documents (and deeper than that) that have their email listed in them. Here is what I've tried:
Attempt #1
match /matrix/mStudents/mStudents--active/{studentDoc=**} {
allow write: if request.auth.token.email == resource.data.teacher_email;
}
This would work(?) if all the ASSESSMENT documents also had teacher_email in them, but I would much rather prefer a cleaner solution.
Attempt #2
match /matrix/mStudents/mStudents--active/{student}/courseList/{course=**} {
allow write: if request.auth.token.email == get(/databases/$(database)/documents/matrix/mStudents/mStudents--active/$(course)).data.teacher_email;
}
I feel like this should work but alas no luck here. Maybe the recursive wildcard can't be used as a variable?
This is basically my question then: is it possible to access a deeper document by retrieving user credentials along the way, or should I just try to use attempt #1?
In your failed attempt you are missing the student collection within the get
Try this:
match /matrix/mStudents/mStudents--active/{student}/courseList/{course} {
allow write: if request.auth.token.email == resource.data.teacher_email;
}
match /matrix/mStudents/mStudents--active/{student}/courseList/{course}/assessments/{assessment} {
allow write: if request.auth.token.email == get(/databases/$(database)/documents/matrix/mStudents/mStudents--active/$(student)/courseList/$(course)).data.teacher_email;
}

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 filter fields in documents with security rules

I am experimenting with Cloud Firestore security rules. Is it possible to filter document fields?
For example if you have a document
{
name: "John Doe",
email: "doe#example.com"
}
then some users aren't allowed to get the document with the email address. Their application requests the document with
firebase.firestore.doc('users/doe-uid')
and gets this document
{
name: "John Doe",
}
If yes, how?
I think it should be possible because the Cloud Firestore Security Rules Reference says in the first sentence (emphasis is mine):
Cloud Firestore Security Rules are used to determine who has read and write access to collections and documents stored in Cloud Firestore, as well as how documents are structured and what fields and values they contain.
However I couldn't find anything in the reference telling me how to filter out fields.
Firestore rules are not filters, they're a server-side validation of document queries, meaning that you access (or not) the whole document, not particular fields.
The piece of documentation you mentionned means that you can do data validation on fields.
Here is a basic example of rules validating data on a write query (via request.resource.data) :
match /users/{userId} {
allow write: if request.resource.data.age is int;
}
Here is another basic example that uses an existing field to validate a read query (via resource.data) :
match /articles/{articleId} {
allow read: if resource.data.isPublished == true;
}
To filter out fields, you have do it client side, after the query.
Now If you want to secure access to certain fields, you have to create another collection (look into subcollections) with a different set of rules, and make another query that will match these rules.

Firebase - Security Rule Restricting Write to Node Owner/Creator

In the screenshot below, my details branch/node contains lots of details named with a random-id. As shown in the example, 8641260c-900... is a detail record and there will be several others like these.
I would like to know whether my .write rule is correct or not? I wanted to enable restriction so that current auth.id exactly match the existing record's user field.
I would also wanted to restrict deletion of the record (via .remove).
Can I simply add && !data.exists() || newData.exists() to the .write rule?
Thanks in advance.
I would like to know whether my .write rule is correct or not?
First of all, giving ".read":true and ".write":true to the root will override all child node rules to true. Therefore any rules specified to child nodes will become redundant.
I wanted to enable restriction so that current auth.id exactly match the existing record's user field.
{"rules":{
"existing_record":{
"user":{
".write":"newData.isString() && auth.uid == newData.val()"
}
}
}}
I would also wanted to restrict deletion of the record (via .remove).
newData.exists() will prevent deletion of a node. newData represents how data will look like after the operation took place. Therefore, by ensuring that newData exists after the operation, deletion of a node is prohibited.

How to check the user's CRUD permissions for an object in Salesforce?

According to a requirement, i have to change the owner of an account if the user does not have read access to a third object.
I need a functionality similar to the isAccessible() method of Describe Field Result, but it is only available for the current logged in user.
Is there any other way to check the user's CRUD permissions for an object in Apex code?
I wrote an article about this on my blog. There is a feature that was just released in version 24.0 of the API (Spring Release) that will let you do just this on a record by record basis for the current user.
Here is the link to that blog entry that goes into details: How to tell if a user has access to a record
Don't confuse record level access with CRUD - the latter is the ability for a user to Create, Read, Update or Delete an object in general, regardless of sharing rules etc. that might affect the user's access to a particular record.
To check whether a user can create (e.g. Contacts) in general, just use
Schema.sObjectType.Contact.isCreateable()
(returns true or false)
From the documentation. it sounds like you want to use execute anonymously.
Apex generally runs in system context; that is, the current user's permissions, field-level security, and sharing rules aren’t taken into account during code execution.​ The only exceptions to this rule are Apex code that is executed with the executeAnonymous call. executeAnonymous always executes using the full permissions of the current user. For more information on executeAnonymous, see Anonymous Blocks.
Although Apex doesn't enforce object-level and field-level permissions by default, you can enforce these permissions in your code by explicitly calling the sObject describe result methods (of Schema.DescribeSObjectResult) and the field describe result methods (of Schema.DescribeFieldResult) that check the current user's access permission levels. In this way, you can verify if the current user has the necessary permissions, and only if he or she has sufficient permissions, you can then perform a specific DML operation or a query.
For example, you can call the isAccessible, isCreateable, or isUpdateable methods of Schema.DescribeSObjectResult to verify whether the current user has read, create, or update access to an sObject, respectively. Similarly, Schema.DescribeFieldResult exposes these access control methods that you can call to check the current user's read, create, or update access for a field. In addition, you can call the isDeletable method provided by Schema.DescribeSObjectResult to check if the current user has permission to delete a specific sObject.
http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_classes_perms_enforcing.htm#kanchor431
Have you tried the runAs() method?
Something like (not verified):
User u = [SELECT Id FROM User WHERE Name='John Doe'];
System.runAs(u) {
if (Schema.sObjectType.Contact.fields.Email.isAccessible()) {
// do something
}
}
The DescribeSObjectResult class has methods for checking CRUD.
E.g. this allows you to test whether or not the current user can update the account object in general.
Schema.DescribeSObjectResult drSObj = Schema.sObjectType.Account;
Boolean thisUserMayUpdate = drSObj.isUpdateable();
#John De Santiago: your article covers record level access rather than object CRUD (= object level access)
Very old post. Since then SF add option to query object permission:
Select SobjectType ,ParentId, PermissionsEdit, PermissionsRead
From ObjectPermissions
Order by ParentID, SobjectType ASC
Basically you will need to get the profile and permissionset of the user that you want to check and the relevant object. So it will be something like:
Select SobjectType ,ParentId, PermissionsEdit, PermissionsRead
From ObjectPermissions
where parentId IN :UserProfileIdAndPermission
AND sObjectType=:objectType
Order by ParentID, SobjectType ASC