I've seen several questions about placing specific security rules in Firebase. I've figured out how to do this in those cases. Now I'm saving information by authData.uid and getting this info....
[Firebase] Using an unspecified index. Consider adding ".indexOn": "4eb8920a-e407-4488-bce4-c6f64f7b0891" at /UserVideo to your security rules for better performance
How can I add such security rules for unique keys and how?? Here is my query...
let videoRef = self.ref.childByAppendingPath("UserVideo")
videoRef.queryOrderedByChild("\(currentUser)").queryLimitedToFirst(1)
.observeEventType(.ChildAdded, withBlock: { snapshot in
if snapshot != nil
Any insight is welcome!!
* UPDATE *
Yeah. I forgot to add that currentUser is...
let currentUser = ref.authData.uid
And the query actually gives back relevant information. It just also includes the specified warning. Will try the second answer a few times and update this post if it works. If anyone else has any ideas please post.
See this: Security & Rules Quickstart
Security and Firebase Rules are used to determine who has read and write access to your database as well to ensure the structure of that data. They are found in the Security tab of your App Dashboard. They come in three flavors: .write, .read, and .validate.
IN answer to your question, add security rules for unique keys by using the validate and write together.
An example would be:
{
"rules": {
"foo": {
// /foo is readable by the world
".read": true,
// /foo is writable by the world
".write": true,
// data written to /foo must be a string less than 100 characters
".validate": "newData.isString() && newData.val().length < 100"
}
}
}
Does this help?
Related
I have a function that successfully uploads an image to my cloud storage bucket.
With another function I want to get the URL of the image to show it on my page (With <img.../>)
getImageUrl(id: string) {
return this.storageRef.child('X/' + id ).getDownloadURL();
But when I do this.. I get an 'invalid' URL and when I copy the URL and go to it, I get the following message:
{
"error": {
"code": 403,
"message": "Permission denied. Could not perform this operation"
}
}
I read somewhere that this might be because there is no token attached in the URL, but how can I enable this?
The last couple of days I have been trying to understand Firebase Storage rules and I don't know why but when I separate rules for writing and for reading like this for example:
allow write: if request.auth != null && request.resource.size < 3 * 1024 * 1024;
allow read: if true;
the code works great and I can write and read using getDownloadURL(), but when I use them together like this:
allow read, write: if request.auth != null && request.resource.size < 3 * 1024 * 1024;
I got the same error as you:
{
"error": {
"code": 403,
"message": "Permission denied. Could not perform this operation"
}
}
I can write when using them together, but when I try to read the file using getDownloadURL(), the issue appears. Maybe you could try separating the rules as I mention and see if it works. I hope it solves your problem. Also, don't forget that the rules are live after 5 minutes from the moment you set them. Good luck.
You have to set Storage Security Rules, read more about this here
Just faced similar issue with my project, if anyone is still struggling with it, you have to provide proper rules for your Cloud Storage from the Firebase console.
Checkout this link to get full detail of the rools.
If you are uploading any object on your storage, you will require to add write rule under Firebase console > Storage > Rules.
I've faced the same issues with access to my storage files in my iOS project. I don't have any custom security rules. Just what's default in configuration. Breaking rules to new lines helped!
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow write: if request.auth != null;
allow read;
}
}
}
I don't know if this is some firebase bug but it helped:)
just use this rule for image
service firebase.storage {
match /b/{bucket}/o {
match /images/{imageId} {
// Only allow uploads of any image file that's less than 5MB
allow write: if request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
}
}
Separating read and write in different lines makes the issue go away, plus in your url you can just append ?alt=media to render it on screen.
allow write: if true; //your conditino
allow read: if true;
You need to use the getDownloadURL method. This will allow you to obtain the URL needed to download the image or for reference in your own html.
See the reference documentation below:
https://firebase.google.com/docs/storage/web/download-files#download_data_via_url
For my case, I accidentally delete the files before so it needs to show the File not found message but don't know why it was showing the Permission denied message.
Edit:
Although this was a temporary fix but it comes with security vulnerability (refer to #Joao Gavazzi's comment).
I was able to solve this by changing the FirebaseStorage security rules from default to:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
I need some help writing a Firestore security rule.
I have a subcollection called posts like the following example path:
/users/XrMD3azk4Jess5KNTSICv4RYEj02/posts/what-architecture-style-is-this_r3nosJHQIt
I am building a "mystery blog" app where users can submit mysteries (aka, "posts") and other users can provide answers (aka, "comments") to the mystery post. If the mystery post author feels that the provided comment solves the mystery, then the post author can go ahead and click a button labeled "Select as answer".
Example screenshot:
The answer is saved to a subcollection called answers in the user's record (the user that provided the answer, aka "doss"). This is an example path with screenshot:
This works fine.
However, I obviously only want the post author to be allowed to press the "select as answer" button...and I will write the front-end logic for that so that button only appears for the post owner. But, how can I correctly write the back-end firestore rule to prevent a non-post owner user from maliciously trying to edit/select an answer?
I assume that I need to check the post author's id (aka, uid) and see if it matches the signed in user's uid.
This is what I tried, but I get permission errors here:
match /users/{uid}/answers/{docId} {
allow read;
allow write: if request.auth != null; && get(/databases/$(database)/documents/users/$(request.auth.uid)/posts/$(id)).data.uid == uid
}
Here's an example of the post document that contains the author's uid and the post id:
Thanks for any help or pointers!
So I went a different path just to make this a bit easier on the security rule logic and maintenance.
I simply added a postUid property when creating the answer doc:
async selectAsAnswer(comment) {
const answerDoc = this.$fire.firestore
.collection(`users/${this.comment.uid}/answers`)
.doc()
await answerDoc.set({
id: answerDoc.id,
commentId: comment.id,
postId: comment.postId,
postUid: this.post.uid,
createdAt: this.$fireModule.firestore.FieldValue.serverTimestamp(),
commentUid: comment.uid
})
}
Then, in the security rule for this answer document I checked for the postUid property match:
match /users/{uid}/answers/{docId} {
allow read;
allow create, update: if signedInAndVerified() && request.resource.data.postUid == request.auth.uid;
}
This seems to work fine and it just makes the overall DX a bit easier.
What I am trying to achieve
Protect a resource in Keycloak with policy like:
if (resource.status == 'draft') $evaluation.grant();
else $evaluation.deny();
Going by their official documents and mailing list responses, it seems attribute based access control is possible, however, I could not find a way of getting it to work.
What I have tried
Using Authorization Services: I was unable to figure out where and how I can inject the attributes from the resource instance.
Using Authorization Context: I was hoping to get the policies associated with a resource and a scope so that I could evaluate them my self.
So far, I have managed to get no where with both approaches. To be honest, I have been overwhelmed by the terminology used in the Authorization services.
Question
How can I use attributes of a resource instance while defining a policy in keycloak?
I solved this problem in Keycloak 4.3 by creating a JavaScript policy because Attribute policies don't exist (yet). Here is an example of the code I got working (note that the attribute values are a list, so you have to compare against the first item in the list):
var permission = $evaluation.getPermission();
var resource = permission.getResource();
var attributes = resource.getAttributes();
if (attributes.status !== null && attributes.status[0] == "draft") {
$evaluation.grant();
} else {
$evaluation.deny();
}
Currently there is no way to do what you are looking to do. ResourceRepresentation class only has (id, name, uri, type, iconUri, owner) fields. So you can use owner to determine ownership per Keycloak example. I've seen a thread that talks about adding additional resource attributes, but haven't seen a Keycloak JIRA for it.
Perhaps you could use Contextual Attributes in some way by setting what you need at runtime and writing a Policy around it.
var context = $evaluation.getContext();
var attributes = context.getAttributes();
var fooValue = attributes.getValue("fooAttribute");
if (fooValue.equals("something"))
{
$evaluation.grant();
}
so I already finished all of the actual app for this. I just need to setup the backend. I figured Firebase was the best solution since Parse is no longer a thing. What I wanted was:
Users with profiles - These profiles can be viewed by added friends but only edited (written) to by the actual profile owner.
So I read through the Firebase Docs and still cannot really figure out how to do this. They only have 1 Swift application example that does not do anything similar and the one Obj C twitter one, will not even build. All of their docs still have println for Swift which just makes me think it is not updated frequently.
Does anyone have any good examples / tutorials of this? I keep trying to search for things but nothing is as similar enough to what I want. I am more looking on how to setup the db for each user and access it rather actually using Firebase in Swift.
As I wrote in my comment to your question, this answer is based on what we do in a real social app Impether using Swift + Firebase.
Data structure
Let's assume that you want to store the following information for a single user:
email
username
name
followers - number of people who follow a particular user
following - number of people who a particular user follows
avatar_url - url of their avatar
bio - some additional text
Since in Firebase everything is stored a JSON objects, you can store the above structure under node with path like users/$userId, where $userId is Firebase User UID which is created for each registered user if you use simple email/password Firebase authorization.
Firebase email/password authorization is described in their docs:
https://www.firebase.com/docs/ios/guide/user-auth.html
https://www.firebase.com/docs/ios/guide/login/password.html
Notice that there are both Obj-C and Swift snippets. I find Firebase documentation really great as it helped me a lot when I was building our app.
For the purpose of this answer let's assume that we have user with username jack and Firebase User UID equal to jack_uid (in reality this will be a string generated by Firebase).
Then an example data for this user will be store under a path users/jack_uid and can look like this:
{
"email" : "jack#example.com",
"username" : "jack",
"name" : "Jack",
"followers" : 8,
"following" : 11,
"avatar_url" : "http://yourstoragesystem.com/avatars/jack.jpg",
"bio" : "Blogger, YouTuber",
}
Firebase email/password authorization works really well, but let's be honest, if user wants to sign in into the app, it's a lot better for him to use his username than his email he gave while he registering his account.
In order to do that, we decided to store a mapping from usernames to user ids. The idea is that if user inputs his username and password in a login form, we use that mapping to retrieve his user id and then we try to sign him in using his user id and provided password.
The mapping can be stored for example under a path username_to_uid and looks like this:
{
"sample_username_1": "firebase_generated_userid_1",
"sample_username_2": "firebase_generated_userid_2",
...
"jack": "jack_uid",
"sample_username_123": "firebase_generated_userid_123"
}
Then creating a profile may looks like this and it's done as soon as registration of a new account was successful (this snippet is very close to the exact code we use in the production):
func createProfile(uid: String, email: String,
username: String, avatarUrl: String,
successBlock: () -> Void, errorBlock: () -> Void) {
//path to user data node
let userDataPath = "/users/\(uid)"
//path to user's username to uid mapping
let usernameToUidDataPath = "/username_to_uid/\(username)"
//you want to have JSON object representing user data
//and we do use our User Swift structures to do that
//but you can just create a raw JSON object here.
//name, avatarUrl, bio, followers and following are
//initialized with default values
let user = User(uid: uid, username: username, name: "",
avatarUrl: avatarUrl, bio: "",
followers: 0, following: 0)
//this produces a JSON object from User instance
var userData = user.serialize()
//we add email to JSON data, because we don't store
//it directly in our objects
userData["email"] = email
//we use fanoutObject to update both user data
//and username to uid mapping at the same time
//this is very convinient, because either both
//write are successful or in case of any error,
//nothing is written, so you avoid inconsistencies
//in you database. You can read more about that technique
//here: https://www.firebase.com/blog/2015-10-07-how-to-keep-your-data-consistent.html
var fanoutObject = [String:AnyObject]()
fanoutObject[userDataPath] = userData
fanoutObject[usernameToUidDataPath] = uid
let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
ref.updateChildValues(fanoutObject, withCompletionBlock: {
err, snap in
if err == nil {
//call success call back if there were no errors
successBlock()
} else {
//handle error here
errorBlock()
}
})
}
In addition to this you possibly want to store for each user a list of his followers and a separate list of users he follows. This can be done just by storing user ids at a path like followers/jack_uid, for example it can look like this:
{
"firebase_generated_userid_4": true,
"firebase_generated_userid_14": true
}
This is the way we store sets of values in our app. It very convenient, because it is really user to update it and check if some value is there.
In order to count the number of followers, we put this counter into user's data directly. This makes reading the counter very efficient. However, updating this counter requires using transactional writes and the idea is almost exactly the same as in my answer here: Upvote/Downvote system within Swift via Firebase
Read/write permissions
A part of your question is how to handle permissions to data you store. The good news is that Firebase is exceptionally good here. If you go to your Firebase dashboard there is a tab named Security&Rules and this is the place where you control permissions to your data.
What's great about Firebase rules is that they are declarative, which makes them very easy to use and maintain. However, writing rules in pure JSON is not the best idea since it's quite hard to control them when you want to combine some atomic rules into a bigger rule or your app simple grows and there are more and more different data you store in your Firebase database. Fortunately, Firebase team wrote Bolt, which is a language in which you can write all rules you need very easily.
First of all I recommend to read Firebase docs about Security, especially how does permission to a node influences permission for its children. Then, you can take a look at Bolt here:
https://www.firebase.com/docs/security/bolt/guide.html
https://www.firebase.com/blog/2015-11-09-introducing-the-bolt-compiler.html
https://github.com/firebase/bolt/blob/master/docs/guide.md
For example, we use rules for managing users data similar to this:
//global helpers
isCurrentUser(userId) {
auth != null && auth.uid == userId;
}
isLogged() {
auth != null;
}
//custom types, you can extend them
//if you want to
type UserId extends String;
type Username extends String;
type AvatarUrl extends String;
type Email extends String;
type User {
avatar_url: AvatarUrl,
bio: String,
email: Email,
followers: Number,
following: Number,
name: String,
username: Username,
}
//user data rules
path /users/{$userId} is User {
write() { isCurrentUser($userId) }
read() { isLogged() }
}
//user's followers rules
//rules for users a particular
//user follows are similar
path /followers/{$userId} {
read() { isLogged() }
}
path /followers/{$userId}/{$followerId} is Boolean {
create() { isCurrentUser($followerId) && this == true }
delete() { isCurrentUser($followerId) }
}
//username to uid rules
path /username_to_uid {
read() { true }
}
path /username_to_uid/{$username} is UserId {
create() { isCurrentUser(this) }
}
The bottom line is that you write rules you want using Bolt, then you compile them into JSON using Bolt compiler and then you deploy them into your Firebase, using command line tools or by pasting them into dashboard, but command line is way more efficient. A nice additional feature is that you can test your rules by using tools in Simulator tab in your dashboard.
Summary
For me Firebase is a great tool for implementing a system you want. However, I recommend to start with simple features and learn how to use Firebase in the first place. Implementing social app with functionality like for example Instagram is quite a big challenge, especially if you want to do it right :) It's very tempting to put all functionality there very quickly and Firebase makes it relatively easy to do, but I recommend to be patient here.
In addition, take your time and invest in writing tools. For example, we have two separated Firebase databases, one for production and second for testing, which is really important if you want to write unit and UI tests efficiently.
Also, I recommend building permission rules from the beginning. Adding them later may be tempting, but also quite overwhelming.
Last but not least, follow Firebase blog. They post regularly and you can be up to date with their latest features and updates - this is how I learnt how to use concurrent writes using fanout technique.
We have two document 'types': Post and User:
Typical post:
{
"_id": "3847345345",
"Schema": "Post",
"Text": "Hello World! This is a post!",
"IsFeatured": true,
"UserID": "12345345345234234"
}
Typical user:
{
"_id": "12345345345234234",
"Schema": "User",
"Username": "georgepowell"
"PostIds": ["3847345345","5135345345","9987453236", ... ]
}
On a web page that displays a Post, the Username for that post (plus whatever other changable information about that user) is displayed alongside the post. Similar to SO:
This is a typical example of a situation where an SQL JOIN would be perfect, but of course CouchDB doesn't support anything like that. Instead we could make a view that indexes both User documents and Post documents on a Post's _id. Like this:
function(doc) {
if (doc.Schema = 'Post') {
emit([doc._id, 0], null);
} else if (doc.Schema = 'User') {
foreach (string id in doc.PostIds) // not javascript I know. shhh
emit([id, 1], null);
}
}
which works well, as we can efficiently retrieve all the information we need given a single Post's _id.
However, if I want to create a view that lists all the posts where IsFeatured == true along with all the user data, I get stuck!
function(doc) {
if (doc.Schema = 'Post' && doc.IsFeatured) {
emit([doc._id, 0], null);
} else if (doc.Schema = 'User') {
foreach (string id in doc.PostIds)
emit([id, 1], null); // I can't check if the post is featured!
}
}
Have I reached the limit of CouchDB for relational data? or is this kind of indexing possible in CouchDB?
Since it is a different technology there are trade-offs. And sometimes although things look like they will take more resources (an extra request) in the short-run it can be inconsequential, and in the long-run may give significant scalability, if you need that sort of thing.
CouchDB can handle a lot of different "databases" at the same time, which you can think of as different model spaces. So with the same running instance of CouchDB you could have /users and /posts. This requires absolutely no additional work on the part of configuration or performance of CouchDB.
This can make your map code more straight forward as you then don't need to have the 'Schema' field and be incorporating it into every map function.
Also, you can (and should) have multiple different map/reduce pairs in a given design view. This is important because if you have two different document "Schema"s emit(doc.id, doc.val) how can you tell which is which for reduce purposes.
A more CouchDB idiomatic way to look at your data would be that you don't save the post_ids on the user. Just the UserID on the Posts, then have a map something like the following for Posts:
(doc) ->
emit([doc.user_id, doc.isFeatured], null);
emit([doc.isFeatured, doc.createdAt], doc.user_id);
Then a request to the view API with arguments like ?start_key=['12345345345234234']&end_key=['12345345345234234',{}] would get all their posts.
Where one with ?key=['12345345345234234', 1] would just get their featured posts.
The second emit also gives you ability to quickly get all of the posts that are featured across the whole system sorted by date -- with who made them if you want that data, without getting the whole of the posts sent down the pipe.