Only allow certain fields (Firestore Rules) - javascript

Im trying to create a rule that only allows certain fields to be updated within my firestore rules, so I thought about doing a function like so:
function checkFieldsSent() {
for(field in request.resource.data) {
if (!(field in ['userRole', 'userName', 'messageType', 'userMessage', 'userHonourLevel', 'userAvatar', 'userChatPosted', 'userLevel', 'userChatMessage'])) { return false }
}
return true
}
of course as you can imagine, firestore moans about the use of "FOR" and "Return" within that function.
Does anyone know a solution? So if the request comes through with fields that ARE NOT ALLOWED to be updated it disallows the update.
Kind Regards,
Josh

Loops are not allowed in the rules. But there is a new data type available since February that gives a nice solution: Map.Diff.
function checkFieldsSent() {
let forbiddenfields = ['userRole', 'userName', 'messageType', 'userMessage', 'userHonourLevel', 'userAvatar', 'userChatPosted', 'userLevel', 'userChatMessage'];
return !request.resource.data.diff(resource.data).affectedKeys().hasAny(forbiddenfields);
}
More details here

Related

Firestore - Reference Data Type - can I use it in the rules (exists or get functions)? - can I use it in the JS client?

I read the documentation and there is nothing there to understand what you can do with the reference data type.
This code comes in the documentation for the Security Rules:
match /messages/{messageId} {
allow read: if exists(/databases/$(database)/documents/rooms/$(roomId)/users/$(request.auth.uid));
}
Instead of the previous code, I am wondering if you can do something like this:
match /messages/{messageId} {
allow read: if exists(resource.data.user_ref);
}
or
match /messages/{messageId} {
allow read: if get(resource.data.user_ref).data.name == 'fran';
}
My feelings are that is not possible to do that according to the documentation for get and exists functions The path provided must begin with /databases/$(database)/documents. because I can see that the reference stored in the DB is like this:
firestore.googleapis.com/project/x/database/(default)/documents/messages/messageId
And I'd like to know what can you do in the JS client, for example, something like this:
var docRef = db.collection("messages/message1");
docRef.get().then(function(doc) {
var userDocRef = db.collection(doc.data().user_ref);
userDocRef.get().then(function(userDoc) {
console.log("User data:", userDoc.data());
});
});
I'll really appreciate if somebody can explain this data type in more detail with examples.
Thanks in advance.

Need Help to implement Tincan Javascript API

I'm working on tincan JavaScript API. The issue my data format is total change and TinCan have specified a why to pass data along with call. Help me to adjust my data in TinCan Api format. Here is sample data one of my call.
var data = {
"groupId": "groupId",
"groupName": "gNameEncrypt",
"tutorNames": "tutorNames",
"actorNames": "actorNames",
"otherNames": "otherNames"
};
Current what i do i simply decode this data and send it like this.
var actionList = new TinCan(
{
recordStores: [{
endpoint: "http://example.com",
username: username,
password: password,
allowFail: false
}]
});
var action = new TinCan.Agent({
"name": "insert"
});
actionList.getStatements({
'params': {
'agent': action,
'verb': {
'id': $.base64.encode(data)
}
},
'callback': function (err, data) {
console.info(data.more);
var urlref = "http://<?php echo $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . $uriParts[0] . "?" ?>t=" + data.more.TutorToken;
window.location.href = urlref;
}
});
crypt.finish();
});
There are really two parts here:
need to get data into an xAPI (formerly Tin Can) format, and
the code itself.
In depth,
I think you need to take another look at how xAPI is used in general. Data is stored a JSON "Statement" object that has 3 required properties and various other optional ones. These properties often contain complex objects that are very extensible. It is hard to tell from what you've shown what you are really trying to capture and what the best approach would be. I suggest reading some material about the xAPI statement format. http://experienceapi.com/statements-101/ is a good starting point, and to get at least some coverage of all the possibilities continue with http://experienceapi.com/statements/ .
The code you've listed is attempting to get already stored statements based on two parameters rather than trying to store a statement. The two parameters being "agent" and "verb". In this case We can't tell what the verb is supposed to be since we don't know what data contains, I suspect this isn't going to make sense as a verb which is intended to be the action of a statement. Having said that the fact that the "actor" has a value of action is questionable, as that really sounds more like what a "verb" should contain. Getting the statements right as part of #1 should make obvious how you would retrieve those statements. As far as storing those statements, if you're using the TinCan interface object you would need to use the sendStatement method of that object. But this interface is no longer recommended, the recommended practice is to construct a TinCan.LRS object and interact directly with it, in which case you'd be using the saveStatement method.
I would recommend looking at the "Basic Usage" section of the project home page here: http://rusticisoftware.github.io/TinCanJS/ for more specifics look at the API doc: http://rusticisoftware.github.io/TinCanJS/doc/api/latest/

Loading Breeze Navigation Properties offline

I am using the latest versions of Angular, breeze, EF.
I am constructing a complex object on a client called a Quote which is added to a job. This has a QuoteMeasure added to it. One of the properties of QuoteMeasure is a navigation property called measure:
var quote = em.createEntity("Quote", { id: breeze.core.getUuid() }),
quoteMeasure,
measure;
measure = _getMeasureFromLookups(4);
quoteMeasure = em.createEntity("QuoteMeasure", { id: breeze.core.getUuid(), quoteId: quote.id });
I have tried the following which executes a query to the server
quoteMeasure.measureId = measure.id;
quoteMeasure.entityAspect.loadNavigationProperty("measure").then(function () {
console.log(quoteMeasure.measure);
});
quote.quoteMeasures.push(quoteMeasure);
job.quotes.push(quote);
to url /Breeze/Data/Measure?$filter=Id%20eq%204&
which does not exist. I would ideally like to set the navigation property manually as it is static data and previously obtained from a breeze query lookups on the server:
[HttpGet]
public object Lookups()
{
var measures = UnitOfWork.MeasureRepository.Get(null, q => q.OrderBy(m => m.Ordinal)).ToList();
return new { measures = measures };
}
This is what the function _getMeasureFromLookups does, it looks up the previously stored measure. I would like to do assign it this way:
quoteMeasure.measure = measure;
But I get the following meaningless error on the client:
Error: A is undefined M#//llhst/X/Scripts/breeze.min.js:1 d/f.set#//llhst/X/Scripts/breeze.min.js:5 _createNewQuote#//llhst/X/Scripts/app/services/jobService.js:76
This I assume is because a full tree of objects has been downloaded via the lookup rather than an individual measure entity. In http://www.breezejs.com/documentation/navigation-properties there is a section on 'Omitting navigation properties' but then it neglects to tell you how to do this.
So my question is what is best practise for loading navigation property data offline? How can I modify the sample above so that it works?
If I understand your requirement correctly, you should be able to construct your quote and quoteMeasure entities as follows:
var quote = em.createEntity("Quote", { id: breeze.core.getUuid() });
//the assignment quoteId: quote.id is the same as quote.quoteMeasures.push(quoteMeasure)
//you don't need to add it again to the collection
var quoteMeasure = em.createEntity("QuoteMeasure", { id: breeze.core.getUuid(), quoteId: quote.id });
var measure = _getMeasureFromLookups(4);
quoteMeasure.measure = measure;
//or
//quoteMeasure.measureId = measure.id
//your _getMeasureFromLookups should look something like this
function _getMeasureFromLookups(measureId) {
//getEntityByKey will look up Measure from client cache
return em.getEntityByKey('Measure', measureId);
}
Calling loadNavigationProperty will initiate a query to the server.
The 'Omitting navigation properties' section actually tells you how you can omit the principal side of the association. So for example, to apply it to your EF model, if you don't want a Quote to be able to navigate to all QuoteMeasures, you can do the following:
//EF Model on Server
public class Quote {
//Simply remove or comment this collection navigation property
//public virtual ICollection<QuoteMeasure> QuoteMeasures { get; set; }
}
Hope this helps.
Seems the problem was the ommission of these statements:
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
Not having this caused the preloading of not only the navigation properties but all of theirs as well which caused the obscure error I noted above. No other modifications were required to get the code working.
EntityAspect.loadNavigationProperty() always makes a request to the server. If you want properties be loaded without making a separate request, do Eager loading with EF.
If you have several properties which are null when breeze fetches them, and you don't want to make several loadNavigationProperty calls, use EntityQuery.expand() method. You can list any properties you need to be loaded

Couchbase Java API and javascript view not returning value for a specific Key

I am using couchbase API in java
View view = client.getView("dev_1", "view1");
Query query = new Query();
query.setIncludeDocs(true);
query.setKey(this.Key);
ViewResponse res=client.query(view, query);
for(ViewRow row: res)
{
// Print out some infos about the document
a=a+" "+row.getKey()+" : "+row.getValue()+"<br/>";
}
return a;
and the java script view in couchbase
function (doc,meta) {
emit(meta.id,doc);
}
So, when I remove the statement query.setkey(this.Key) it works returns me all the tables, what am I missing here .. How can I change the function to refect only the table name mentioned in the key
Change the map function like this:
function (doc,meta) {
emit(doc.table,null);
}
it is good practice not to emit the entire document like:
emit(doc.table, doc)
NB: This is surprisingly important:
i have tried using setKey("key") so many times from Java projects and setting the key using CouchBase Console 3.0.1's Filter Result dialog, but nothing get returned.
One day, i used setInclusiveEnd and it worked. i checked the setInclusiveEnd checkbox in CouchBase Console 3.0.1's Filter Result dialog and i got json output.
query.setKey("whatEverKey");
query.setInclusiveEnd(true);
i hope this will be helpful to others having the same issue. if anyone finds another way out, please feel free to add a comment about it.
i don't know why their documentation does not specify this.
EXTRA
If your json is derived from an entity class in a Java Project, make sure to include an if statement to test the json field for the entity class name to enclose you emit statement. This will avoid the key being emitted as null:
if(doc._class == "path.to.Entity") {
emit(doc.table, null);
}

JQuery Autocomplete GET & JSON array data security concerns

I am learning JQuery with a MVC3 book. I find that Json data is really easy to use, but it may not be safe.
Consider the scenario, say, I got a CRM with senstive customer infomation. Ajax returns Json array as search results. The search textbox ajax autocomplete also return Json array of senstive keywords from database. etc...They all use GET method.
However, it is said that GET method has vulnerabilities when passing around Json array data:
http://haacked.com/archive/2009/06/25/json-hijacking.aspx
http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
How do you JQuery experts out there go about fixing this issue? Please help.
--- EDIT: ---
#Gren. Awesome. Thank you. Based on your tips, here is what I figured out.
The normal autocomplete returning json array
and a mod one with a json object wrapping the array
Here is the code, assuming we got a global List named txtlst in the controller.cs...
// normal one
public JsonResult AutoCompleteHelper1(string term) {
//if (!Request.IsAjaxRequest()) return null;
var lst = txtlst.Where(s => s.StartsWith(term)).ToList();
var res = lst.Select(x => new { value = x }).ToList();
return Json(res, JsonRequestBehavior.AllowGet);
}
//mod one
public JsonResult AutoCompleteHelper2(string term) {
//if (!Request.IsAjaxRequest()) return null;
var lst = txtlst.Where(s => s.StartsWith(term)).ToList();
var res = lst.Select(x => new { value = x }).ToList();
return Json(new { wrapper= res, name="wrapper" }, JsonRequestBehavior.AllowGet);
}
}
and then in the .cshtml file...
<p>Auto Complete Example</p>
<input type="text" name="q" id="MyInput1" data-autocomplete-source="#Url.Action("AutoCompleteHelper1", "Home")"/>
<input type="text" name="q" id="MyInput2" data-autocomplete-source="#Url.Action("AutoCompleteHelper2", "Home")" />
and then in the .js file...
$(document).ready(function () {
// normal autocomplete
$("#MyInput1").autocomplete({ source: $("#MyInput1").attr("data-autocomplete-source") });
// mod autocomplete with a wrap
$("#MyInput2").autocomplete({
source: function (req, add) {
$.getJSON($("#MyInput2").attr("data-autocomplete-source"), req, function (data) {
var suggestions = [];
$.each(data.wrapper, function (i, o) {
suggestions.push(o.value);
});
add(suggestions);
});
}
});
});
--- EDIT 2: ---
Please ignore those comments that are telling me to use POST. They
are not reading the blog links or do not understand the issue.
The other option is to wrap your JSON Arrays within JSON objects. The article and comments in it answered this question.
Edit:
From the article:
The fact that this is a JSON array is important. It turns out that a script that contains a JSON array is a valid JavaScript script and can thus be executed. A script that just contains a JSON object is not a valid JavaScript file.
If you wrap your json array in an object {"myJsonArray":[{"name":"sensitive"},{"name":"data"}]} the HTML script tag would not be able to execute.
Security of an Ajax/JSONP/JSON call is the same exact thing as the security of an http call since Ajax requests are http requests. Nothing changes in how you handle it. You make sure the user is logged in and can access the information.
If you are worried about data being cached, use Post or set the proper no caching headers with the backend.
EDIT:
Things you can do to prevent JOSN from being read is the infinite loop trick. Stick an infinte loop in front of the call, means your Ajax call will have to strip this before using it.
You can use keys, third party site would not have the keys needed to validate the request.
You can check referrers.

Categories

Resources