I have the following rules which should allow me to read a document, but I get insufficient permission error. Any advice?
The current user has a document under a collection named users with a field named role that has the value admin
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/tickets/{ticketId} {
allow update, delete, create: if false
allow read: if get(/users/$(request.auth.uid)).data.role == "admin"
}
match /users/{userId} {
allow delete:
if false
allow read, write:
if request.auth != null && request.auth.uid == userId
allow update:
if resource.data.points == request.resource.data.points && request.auth != null && request.auth.uid == userId
}
iOS code fetching data
func fetchTickets(contestId: String) -> SignalProducer<[Ticket], FetchError> {
Firestore.firestore()
.collection("users/*/tickets")
.whereField("contest.id", isEqualTo: contestId)
.getDocuments()
}
users collection
{
"role": "admin"
}
users.{userId}.tickets collection
{
"contest": {
"id": ""asdasd
}
}
First of all, wildcards are not supported in Firestore queries. You will need to either identify a single user's tickets and query only that one subcollection. Or, you will need to perform a collection group query on tickets to query all subcollections named tickets. If you use a collection group query, you will need completely different rules to support that.
Second of all, security rules are not filters. Be sure to read that documentation carefully. You can't have a rule filter out some documents based on the get() of another document. This simply does not scale the way that Firestore requires. The client must be able to formulate the filter in the query, and that requires that the data to filter with must be in the documents in the collection being queried (they can't be in other documents).
Related
I'm a bit confused about firebase rules. This is my realtime database. Each node inside "1" is created using the firebase unique id of the user. And in the user's node there is a list of objects.
The objective is for the user to be able to create this node if it doesn't exist, and allow the user to read/write only inside this node.
I tried this but it doesn't work. I get permission error.
{
"rules": {
"1": {
"$key": {
".read": "auth != null && auth.uid == $key",
".write": "auth != null && auth.uid == $key"
}
}
}
}
Note: In the future there will be other parent nodes ("2","3" etc) So it is important to keep the "1". Also in case it matters I am using firebase anonymous sign in.
I appreciate the help.
UPDATE:
I retrieve the installationId like this:
Task<String> getIdTask = FirebaseInstallations.getInstance().getId()
and access the database like this:
FirebaseDatabase.getInstance()
.getReference()
.child("1")
.child(installationId)
Trying to access the database using above code gives this:
Listen at /1/cKYZwWrlRmSof79rtfuX82 failed: DatabaseError: Permission denied
SOLUTION:
I just realized the magnitude of my mistake. To retrieve the userId I was using
FirebaseInstallations.getInstance().getId()
instead of this which is what firebase sees as userId:
FirebaseAuth.getInstance().getCurrentUser().getUid();
Using the later one solved the issue.
Unless you'd add another node, that actually links the UID with your user ID, how shall it know about it? I'd suggest to reconsider the structure and get rid of that superfluous node altogether; just use the UID. It's not that it wouldn't be possible, to lookup values by UID ... but it might be an unfortunate database design, which ignores the given environment.
How can I get UserId and ScreenName of the users that a given user is FOLLOWING (not followers) in LinqToTwitter?
??
The Twitter API uses the terms follower to mean people who follow a user and friends to mean people that a user follows and LINQ to Twitter continues that approach. So, you would use a Friendship/FriendshipType.FriendsList query, like this:
static async Task FriendsListAsync(TwitterContext twitterCtx)
{
Friendship friendship;
long cursor = -1;
do
{
friendship =
await
(from friend in twitterCtx.Friendship
where friend.Type == FriendshipType.FriendsList &&
friend.ScreenName == "JoeMayo" &&
friend.Cursor == cursor &&
friend.Count == 200
select friend)
.SingleOrDefaultAsync();
if (friendship != null &&
friendship.Users != null &&
friendship.CursorMovement != null)
{
cursor = friendship.CursorMovement.Next;
friendship.Users.ForEach(friend =>
Console.WriteLine(
"ID: {0} Name: {1}",
friend.UserIDResponse, friend.ScreenNameResponse));
}
} while (cursor != 0);
}
This example pages through the results in a do/while loop. Notice that the cursor is set to -1, which starts off the query without a Twitter API cursor. Each query assigns the cursor, which gets the next page of users. In the if block, the first statement reads the friendship.CursorMovement.Next to the get cursor for the next page of users. When the next cursor is 0, you've read all of the followers.
After the query executes, the Users property has a List<User> where you can get user information. This demo prints each member of the list.
One of the things you might run into with large friend lists is that Twitter will return an error for exceeding the rate limit. You'll be able to catch this, in a try/catch block, by catching TwitterQueryException and examining properties for the Rate Limit Exceeded. To minimize rate limit propblems, set count to 200, the max. Otherwise count defaults to 20.
You can download samples and view documentation for this on the LINQ to Twitter Web site.
I am getting confused about Firebase Security. Here why...
For example; I have the following database:
clients: {
$key: {
//client data
}
}
trainer: {
$key: {
//trainer data
}
}
I need a client to be able to see their own information. I need a trainer to be able to see the information of all their clients, but not other trainers.
So client A, B and C can see their personal, private data. But Trainer A can only see client A and B's details (he doesn't train C).
The problem I run into is that you can't seem to request for example all clients, but only return the ones that checkout with security rules. As the docs state, if one in the list returns false, the whole bunch does.
How can I create the correct structure and security rules?
You should impliment something like adding clients to trainer as friend. Create a node sharedwith which contains trainer as child and trainer will contains A & B clients key and their data .
Similar to examples in the Firebase docs (https://firebase.google.com/docs/database/security/user-security), this might work for you.
I think at a minimum your structure could benefit from having a direct key tie between client and trainer. Something like
clients: {
clientA: {
trainerKey: "trainerA"
}
}
trainers: {
trainerA: {
clients: { clientA: true, clientB: true }
}
}
Security Rules - edited to include user
"clients": {
".read": "auth !== null && (root.child('trainers/' + $trainerKey + '/clients').child($uid).exists() || auth.uid == $uid")
}
This a) checks that a user is authenticated and b) looks to see if the client is in the list of clients for the trainer OR whether this is the client.
This is untested, but hopefully gets you where you're trying to go. I'm also making the assumption that your clients IDs are the same as their auth ID.
I try to prevent the creation of already existing usernames.
This is my code where I upload usernames to the database:
ref?.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("username").setValue(self.createUserName.text)
And this is the code where I try to get if the username already exists or not
ref?.child("users")
.queryOrdered(byChild: "username")
.queryEqual(toValue: self.createUserName.text?.uppercased())
.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("😍")
}
}) { error in
print("👾")
}
The database looks like this
Database_App {
users {
-3bSRmS4PHXUwsr7XbMBwgPozNfK2 {
username: "sebas.varela"
}
}
}
And appear this line in the console:
Consider adding ".indexOn": "username" at /users to your security rules for better performance
The problem is that I always get 😍. What is the problem with this?
You can only query for values that are at a directly under the reference you query at. For your case that would be with a data model like:
Database_App {
users {
-3bSRmS4PHXUwsr7XbMBwgPozNfK2: "sebas.varela"
}
}
This will work in your code, but is hard to get secure and performant. The more common approach is to work with a extra node where you map user names to uids:
Database_App {
userNames {
"sebas,varela": "-3bSRmS4PHXUwsr7XbMBwgPozNfK2"
}
}
In this case the node essentially allows a user to claim their name. The advantage of this system is that the keys are automatically guaranteed to be unique on the server, no client-side code needed for that part.
You will want to:
add security rules that ensure a user can only claim a username that hasn't been claimed yet
also in these security rules allow a user to release their claim on a username
add client-side code to handle conflicts in a nicer way than the default "permission denied" error you'll get from the server
In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)