Complex firestore rules - google-cloud-firestore

I have a top level collection: "organizations", in that collections doc's there is an employees map like this:
employees: {
uid1: {
displayName: John Do
[...]
}
uid2 {
[...]
}
}
I have an other top collection: "customers" with an organization map like this:
organizations: {
organizationId1: some string,
organizationId2: some other string,
[...]
}
where:
uid is the user id from firebase auth
organizationId is the document id of an organization doc.
user can be in multiple organizations, and customers can be share between multiple organizations as well.
I want to restain acces to customer doc, at user who are employee of at least one organization listed in the customer doc.
Has there is no way to loop in firestore.rules
I think the answer may be mapDiff, and custom claims.
user custom claims:
organizations:[organizationId1, organizationId2, ...]
But i have some difficulty to understand the documentation:
https://firebase.google.com/docs/reference/rules/rules.MapDiff
Is there a way to achive that ?

Maybe I didn't understand it correctly, but you can try something like this:
allow read, write: if employeeOrganization in [organization1, organization2...]
https://firebase.google.com/docs/reference/rules/rules.List

I finaly find the ways to set rules for that:
match /customer/{cId} {
allow create: if request.auth != null;
allow read, update: if (request.auth.token.organisations.keys().hasAny(resource.data.organizationIds.keys()));
allow delete: if false;
}
My custom claims are like:
(there is only 1 to 5 organisations so it's not heavy to transmit)
organizations:{
organizationId1: "role",
organizationId2: "admin",
...
}
My file customer docs as a map like this:
organizationIds{
organization1Id: "some AES key used for security purpose",
organization358Id: "some AES key used for security purpose"
}
It work nice and using custom claims save countless read per day.

Related

Firestore rules and data structure

I have a question regarding data structure and rules ... I have content on which users can vote. Something like this:
Firestore object:
{
name: "Cat",
description: "A cat named Cat",
votes: 56
}
Now ... I want authenticated users to be able to have update access to the votes, but not to any other values of the object and of course read rights since the content has to be displayed.
I did this because I wanted to avoid additional queries when displaying the content.
Should I create another collection "votes" maybe where the votes are kept and for each document make an additional request to get them?
In rules, you have access to the state of the data both before and after the writes - so you can test specific fields to be sure they have not changed:
function existing() {
return resource.data;
}
function resulting() {
return request.resource.data;
}
function matchField(fieldName) {
return existing()[fieldName] == resulting()[fieldName];
}
....
allow update: if matchField("name") && matchField("description")
....
The functions just make the rule easier to read.

Firestore rules - use exists with where

In my project I have firestore collection of users and classes. Each user can be part of one or more classes. Classes document has property members which is an array including all users uids in that class.
For instance:
users documents:
doc.id: USER1UID
{ name: 'user1', email: 'user1#user1.com', phone: '+123 456 789 001' }
doc.id: USER2UID
{ name: 'user2', email: 'user2#user2.com', phone: '+123 456 789 002' }
doc.id: USER3UID
{ name: 'user3', email: 'user3#user3.com', phone: '+123 654 789 003' }
classes documents:
doc.id: ABCDEF
{ name="class1", members: ['USER1UID', 'USER2UID'] }
doc.id: GHIJKL
{ name="class2", members: ['USER1UID', 'USER3UID'] }
doc.id: MNOPQR
{ name="class3", members: ['USER3UID'] }
I need to write a rule that will allow user to read details about another user ONLY if they are in the same class. Every user can also read own profile.
In this case user1 can read details of user2 and user3. (they are together in class1 and class2).
User2 can read details only of user1 (they are together in class1).
User3 can read details only of user1 (they are together in class2).
I need something like:
match /users/{userId} {
allow read:
//user is logged in
if request.auth != null
&& (
//user can read own profile
request.auth.id == $(userId)
//there is a class where are both (requesting and requested) users
|| exists( (/databases/$(database)/documents/classes/).where(request.auth.id in members).where($(userId) in members)
)
}
What you're trying to do is not possible with your database schema, because security rules don't allow you to perform queries. You may only request one document at a time using its known path, maximum of 10 documents per rule execution.
What you can do instead is manage each users document to contain a list of all other users who have a class in common with that user. But you will have to write some code to keep that up to date as the roster of the classes might change over time. This might be a good use for Cloud Functions.

Security rules Firestore

I'm trying to secure my data in Firestore. I have read the documentation and watch some videos but I still have some difficulties getting it right.
What I have built is a project app. With a data structure like this:
"School": {
school1:
school2: {
"Users": {
userId: {
"SchoolName": "school2"
}
}
"Projects": {
projectId: {
}
}
}
}
Only authenticated users can read and write to the whole database and only users in the same school can read and write data to that school. For example, only users in school2 can add a project to school2.
I tried something like this but it didn't work
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
match /School/{schoolName} {
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{userId}).data.SchoolName[(schoolName)]
}
}
}
Can someone please show me how to do this and maybe some good explanation on how to think about security rules. Thank you very much in advance!
you made just one mistake, replace this line:
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{userId}).data.SchoolName[(schoolName)]
with this :
allow read, write: if get(/databases/{database}/documents/School/$(schoolName)/Users/{request.auth.uid}).data.SchoolName == "school2"

How to get info about specific user using github graphQL?

How can I get info about specific user or specific repo using github GraphQL? I've try this query:
{
search (query: "torvalds", type: USER, first: 1){
edges {
node {
}
}
}
}
but autocomplete for node show only __typename which return string "User".
DEMO
search returns a SearchResultItem, which is an interface. In order to access fields on it, you need to use a fragment on a concrete type like so:
{
search (query: "torvalds", type: USER, first: 1){
edges {
node {
... on User {
login
}
}
}
}
}
I made a short video tour of GitHub's GraphQL API which you might find useful: https://youtu.be/6xO87LlijoQ
EDIT: If you're just looking for a user or org and know the exact name, #stubailo's answer is actually better. You'll still need to use a fragment for most fields, but you'll get just one result of type RepositoryOwner.
The best way to get information about a specific user is to use the repositoryOwner query, like so:
{
repositoryOwner(login: "stubailo") {
login
... on User {
bio
}
}
}
Search is good too, but if you know the name of a user or organization, it's more direct to use the query above.

Rest API get resource id by field

What is a correct rest way of getting a resource ID by a field, for example a name. Take a look at the following operations:
GET /users/mike-thomas
GET /users/rick-astley
I don't want to use these operations at my API end, instead I want to write an API operation that will get me the ID when submitting a field (name in the case of users) for example:
GET /users/id-by-field
Submitted data:
{
"fullName": "Mike Thomas"
}
Return data:
{
"data": {
"id": "123456789012345678901234"
}
}
What you want is known as an algorithmic URL where the parameters for the algorithm are passed as URL parameters:
GET /users?name="Mike Thomas"
Advantages are that you are using the "root" resource (users) and the search parameters are easily extended without having to change anything in the routing. For example:
GET /users?text="Mike"&year=1962&gender=M
where text would be searched for in more than just the name.
The resultant data would be a list of users and could return more than the identification of those users. Unless fullName uniquely identifies users, that is what you need to allow for anyway. And of course the list could contain a single user if the parameters uniquely identified that user.
{
users: [
{
id: "123456789012345678901234",
fullName: "Mike Thomas",
dateJoined: 19620228
}
, {
id: "234567890123456789012345"
fullName: "Rick Astley",
dateJoined: 19620227
}
]
}