I'm implementing a security rule to check the data structure for all data of all collections.
for that i created a "data model" collection and want to check if the data to be stored has the same "structure".
After that the idea is to compare the incoming data with it:
function checkDataModel (collectionId, docKeys){
let standardDocKeys = get(/databases/$(database)/documents/data-models/$(collectionId)).data.keys();
let isValid = standardDocKeys.hasAll(docKeys);
return isValid
}
the docKeys param is an array with all the keys in the document to be stored.
After that I would call the function and expect a boolean result.
The problem is that I'm getting those red arrows under the function name and the get method:
red arrows
I think it's some syntax error, but i really could not find the proper way to write it.
the following code from firebase docs is the best reference I could find:
function isAdmin(userId) {
return exists(/databases/$(database)/documents/admins/$(userId));
}
function isAuthorOrAdmin(userId, article) {
let isAuthor = article.author == userId;
// `||` is short-circuiting; isAdmin called only if isAuthor == false.
return isAuthor || isAdmin(userId);
}
Related
I'm trying to add a rule that automatically merges two users if the user already exist with the same email and just keep one of them with new user newest data.
match /users/{userId} {
allow create: if request.resource.data.email != null;
allow update: if request.resource.data.email != null && request.auth.uid == userId;
function isDuplicateEmail() {
return get(/databases/$(database)/documents/users/$(request.resource.data.email)).exists;
}
function mergeUsers(userId) {
// Get the data of the new user
let newUser = get(/databases/$(database)/documents/users/$(userId)).data;
// Get the data of the existing user
let existingUser = get(/databases/$(database)/documents/users/$(newUser.email)).data;
// Merge the data from the two users
let mergedData = {...existingUser, ...newUser};
// Update the data of the existing user
return update(/databases/$(database)/documents/users/$(newUser.email), mergedData);
}
allow create: if !isDuplicateEmail()
allow create: if isDuplicateEmail() && mergeUsers(userId);
}
But I'm seeing an error in the rule editor: "Unexpected "}". Line 40:
let mergedData = {...existingUser, ...newUser};
What I'm missing?
Thanks.
The security rules expression language does not support the ... spread operator like JavaScript. In fact, it is not JavaScript at all - it just looks a bit like JS. You might want to read about its syntax in the documentation.
On top of that, there is no function called update. You can't modify data in security rules at all. You can only check to see if the incoming access should be allowed or denied. If you want to modify document data, you will have to write application or backend code for that.
The } is closing the match statement before the allow create statement that uses the mergeUsers() function. Try:
match /users/{userId} {
allow create: if request.resource.data.email != null;
allow update: if request.resource.data.email != null && request.auth.uid == userId;
function isDuplicateEmail() {
return get(/databases/$(database)/documents/users/$(request.resource.data.email)).exists;
}
function mergeUsers(userId) {
// Get the data of the new user
let newUser = get(/databases/$(database)/documents/users/$(userId)).data;
// Get the data of the existing user
let existingUser = get(/databases/$(database)/documents/users/$(newUser.email)).data;
// Merge the data from the two users
let mergedData = {...existingUser, ...newUser};
// Update the data of the existing user
return update(/databases/$(database)/documents/users/$(newUser.email), mergedData);
}
allow create: if !isDuplicateEmail()
allow create: if isDuplicateEmail() && mergeUsers(userId);
}
Also, if you going to use the update function, you also need to include a rule allowing the update to happen.
In Firestore security rules we can check the types of optional strings and lists as follows:
function reviewFieldsAreValidTypes(docData) {
return docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
What would one use as the default value for optional timestamps?
docData.get('dateModified', ???) is timestamp;
And for an optional map?
docData.get('translated', ???) is map;
We just need to set a default value of the correct type. We can use the request object:
function reviewFieldsAreValidTypes(docData) {
return docData.get('photo_url', '') is string &&
docData.get('tags', []) is list &&
docData.get('dateModified', request.time) is timestamp &&
docData.get('translated', request.resource.data) is map;
}
There's no direct solution for your question, but you have an option to use a condition like the posted code below:
// resource.data.keys => check if the key exists.
if !("dateModified" in resource.data.keys()) || (resource.data.dateModified is timestamp)
if !("translated" in resource.data.keys()) || (resource.data.translated is map)
Just a note, keep in mind that rules are not filters when writing queries to retrieve documents.
I am trying to sort a list of documents based on whether another user has seen it or not.
My database structure is the following:
following (Document Field){
Dy2k9f2m7uXnnBBPHssPxJucCrK2 : true (Map)
HOIXdQkoerRBgYCGHFRylQD2VKi1 : true (Map)
}
I tried running this command
print(
user.data()['following'].toString(),
);
and recieved the output
{HOIXdQkoerRBgYCGHFRylQD2VKi1: true, Dy2k9f2m7uXnnBBPHssPxJucCrK2:
true}
But I want to know whether a specified user id say HOIXdQkoerRBgYCGHFRylQD2VKi1 has the value true or false.
How do I go about it? Any help is greatly appreciated.
Firestore map type fields accessed with user.data()['following'] return a Map<String, dynamic>, so you can treat it like any other Map in dart. If you want to assume it contains bool values:
Map<String, bool> following = user.data()['following'];
bool seen = following['the-user-id-to-check'];
As Doug suggested, The issue here is that the value is probably dynamic.
You can cast it to a bool variable :
bool see = false;
String userKey = "Dy2k9f2m7uXnnBBPHssPxJucCrK2"; // <-- User Key
print(
see = user.data()['following'][userKey] as bool, // <-- cast as bool
);
This Meteor code is working fine, but I would like to ask if it is the way Meteor does things or it is a un predictable side effect that may change under some condition later.
The things is that when I do
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
Meteor also inserts the correct userId (using 2 different browsers logged in as 2 different users) which I did not explicitly included in the document.
The above line of code is inside a server side function which is called from Meteor method.
here is the relevant information;
//lib/collection.js
DisplayCol = new Mongo.Collection('displayCol');
//server.js
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
In the docs of Collection hooks > Additional notes > second bulleted paragraph says:
userId is available to find and findOne queries that were invoked within a publish function.
But this is a collection.insert. So should I explicitly include the userId in the document or let the collection hook do its hidden magic? Thanks
No, there is no hidden magic in that code, your before hook is inserting the userId field in the document.
When you do an insert like this,
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
the doc that your are inserting is { action: 'task1', element: 'p', value: value_variable }
Because, you have this hook,
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
it changes the doc before inserting into collection. So the above hook will change your doc to {action: 'task1', element: 'p', value: value_variable, userId: 'actual-user-id' }
This is the expected behaviour.
Regarding your other point in the question,
userId is available to find and findOne queries that were invoked
within a publish function.
Previously userId parameter in the find and findOne returns null, so user needs to pass userId as a parameter as mentioned in this comment. Additional notes mentions that the hack is not required any more. It has nothing to do with inserting userId field into the collection document.
To have a quick test, remove the DisplayCol.before.insert hook above, you will not see userId field in the newly inserted documents.
UPDATE
Just to clarify your doubt further, from the 4th point in the docs that you provided
It is quite normal for userId to sometimes be unavailable to hook
callbacks in some circumstances. For example, if an update is fired
from the server with no user context, the server certainly won't be
able to provide any particular userId.
which means that if the document is inserted or updated on the server, there will be no user associated with the server, in that case, userId will return null.
Also you can check the source code yourself here. Check the CollectionHooks.getUserId method, it uses Meteor.userId() to get the userId.
CollectionHooks.getUserId = function getUserId() {
var userId;
if (Meteor.isClient) {
Tracker.nonreactive(function () {
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
});
}
if (Meteor.isServer) {
try {
// Will throw an error unless within method call.
// Attempt to recover gracefully by catching:
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
} catch (e) {}
if (!userId) {
// Get the userId if we are in a publish function.
userId = publishUserId.get();
}
}
return userId;
};
I have no issues when using implicit updates (angelFire). However I need for some of my data use explicit updating. So I implemented angelFireCollection on the exact same ref I was using previously but despite the console.log explicitly saying that the read was granted and trying it with both with the onloadcallback and without, I don't get data directly into my assigned variable AND once the callback fires I get a strange looking object that DOES contain the data but not in the form I expect. My scope variable ends up with an empty collection. Never gets populated. Here is the code:
var streamController = function ($rootScope, $scope, $log, $location, angularFireCollection, profileService) {
//Wait for firebaseLogin...
$rootScope.$watch('firebaseAuth', init);
function init() {
if ($rootScope.firebaseAuth == false) {
return
};
var refUsers = new Firebase($rootScope.FBURL+'/users/'+$rootScope.uid);
$scope.profile = angularFireCollection(refUsers, function onload(snapshot) {
console.log(snapshot)
});
};
};
myApp.gwWebApp.controller('StreamController', ['$rootScope', '$scope', '$log', '$location', 'angularFireCollection', 'profileService',
streamController]);
}());
Here is what the console.log looks like ( ie; what snapshot looks like ):
>snapshot
T {z: R, bc: J, V: function, val: function, xd: function…}
Here is the earlier message before the snapshot was returned:
Firebase Login Succeeded! fbLoginController.js:16
FIREBASE: Attempt to read /users/529ccc5d1946a93656320b0a with auth={"username":"xxxxxxx#me.com","id":"529ccc5d1946a93656320b0a"} firebase.js:76
FIREBASE: /: "auth.username == 'admin'" firebase.js:76
FIREBASE: => false firebase.js:76
FIREBASE: /users firebase.js:76
FIREBASE: /users/529ccc5d1946a93656320b0a: "auth.id == $user" firebase.js:76
FIREBASE: => true firebase.js:76
FIREBASE:
FIREBASE: Read was allowed.
and finally the desired binding that ends up with an empty array: again from the console:
$scope.profile
[]
Anyone know what I could possibly be doing wrong?? This is like 5 lines of code. Frustrating.
I have put stops in angelFireCollection factory function and can see that the data is getting added to the collection in the callbacks inside that function but my binded variable never gets updated.
UPDATE
Ok experimenting with a plnkr. It seems that angularFireCollection EXPECTS your returning a LIST of items. The snapshot returns properly if you inspect snapshot.val() it will be whatever object structure was stored in firebase. IF you use angularFireCollection it does indeed bind to the variable HOWEVER it turns a non-list object into a garbled mess and you can not access the object user the normal dot operator. This is either a bug or it is a severe limitation of angularFireCollection which will cause me to revaluate how easily I can use firebase as the backend. I can't share my plnkr because it is accessing non-public data but tomorrow if i have time I will create a public firebase with an object store and demonstrate.
Ok. So it appears that indeed angularFireCollection is MEANT to be array based. Which is fine. It would be VERY helpful if the angularFire documentation was updated to make that clear. As such it is not an implicit vs explicit update technique.
For an explicit non-array based approach I have come up with the following code. Had I not been mislead by the documentation I would have gone down this path originally.
var MainCtrl = function($scope, angularFire) {
$scope.test = {};
var _url = 'https://golfwire.firebaseio.com/tmp';
var _ref = new Firebase(_url);
var promise = angularFire(_ref, $scope, 'implicit');
promise.then ( function(data){
$scope.explicit=angular.copy($scope.implicit );
});
}
You then work locally with the 'explicit' copy and when ready just update the 'implicit' by assigning: $scope.implicit = $scope.explicit.
Here is a plnkr: http://plnkr.co/edit/bLJrL1