Firestore rules, what is a collection and a document? - firebase-security

I mean I know what those are but I am a bit confuse about the security rules, for example:
service cloud.firestore {
match /databases/{database}/documents {
// This is probably a mistake
match /spaceships { // <= what is this a collection or a document?
allow read;
// In spite of the above line, a user can't read any document within the
// spaceship collection.
}
}
}
Firebase documentation says:
Rules for collections don't apply to documents within that collection. It's unusual (and probably an error) to have a security rule that is written at the collection level instead of the document level.
That means that this match /spaceships {... is a collection right?
But later on we have this:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**}{ // <= what is this a document or a collection?
allow read, write: if true;
}
}
}
I don't understand is this match /{document=**}{... a document? or a collection? I mean is at the collection level.

Paths in Firestore are alternating collections and documents: /collection/document/subcollection/subdocument
For example:
// Matches kaylee, the mechanic on serenity
/spaceships/serenity/crew/kaylee/...
When using security rules, you can specify wildcards:
// This will match any spaceship, and any crewmember
/spaceships/{spaceshipId}/crew/{crewmemberId}/...
Now imagine that you have another subcollection under spaceships:
/spaceships/{spaceshipId}/stowaways/{stowawayId}/...
If you want to write rules against multiple subcollections, you need to either:
// You could use multiple single wildcards
/spaceships/{spaceshipId}/{crewOrStowaway}/{crewOrStowawayId}/...
// Or you could use a multi-segment wildcard
/spaceships/{spaceshipId}/{allShipInformation=**}
This returns allShipInformation as a path, which will match all documents and collections at and below that path. Note that it's one or more path segment, rather than zero or more.
You can read more about this in the docs

In your first example /spaceships is at the collection level. As was stated in the quote you referenced, placing a rule here is not helpful as it won't be applied to any of the documents in the collection.
In your second example /{document=**} is at the collection level but is using a recursive wildcard. In short, what this does is apply the rule to the documents within this collection and to any documents within any subcollections of this collection.
This allows you to write:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**}{
allow read, write: if true;
}
}
}
Instead of:
service cloud.firestore {
match /databases/{database}/documents {
match /spaceships/{shipId} {
allow read, write: if true;
}
match /spaceships/{shipId}/crew/{crewMemberId} {
allow read, write: if true;
}
}
}

Related

How can I write a "If..then" condition in Axiomatics

The decisioning is to permit if the user has required roles. The required roles and the user's current permissions are in JSON format.
Required permissions: (Saved as a attribute)
{
"data": {
"service1": {
"service1.1": true
},
"service2": {
"service2.1: false,
"service2.2": true,
"service2.3": false
}
}
}
User's current permissions:
{
"data": {
"service1": {
"service1.1": true
},
"service2": {
"service2.1: false,
"service2.2": false,
"service2.3": true
}
}
}
To make the decision, we need to check if the user has the services as true similar to required Permissions. In the above example, the user has data.service1.service1.1 as true and data.service2.service2.3 as true where the required roles being data.service1.service1.1 as true and data.service2.service2.2 as true, in this case we deny.
I wrote separate rules to check for each and every service, but that would only be a check of combination of services.
rule service1.1{
permit
condition
(allOf(function[booleanEqual], true, requiredRoles.data.service1.service1.1))
&&
(allOf(function[booleanEqual], true, requiredRoles.data.service1.service1.1))
on permit {
advice reasonForPermit{
reasonAttribute= "Contains Valid services"
}
}
}
Would someone please help on how to write a if.. then check in alfa?
There is no if..then construct in ALFA (or XACML). You use combining algorithms instead. In particular, onPermitApplySecond is the policy combining algorithm that resembles an if..then construct the most.
However, there is usually a simpler way to express what you want if you can make reasonably assumptions on your attribute data. In your example, for instance, if it's always guaranteed that both the required and current permissions contain exactly one boolean value for each available service, then you could write:
rule {
target
clause requiredRoles_service1_1 == false or permitted_service1_1 == true
clause requiredRoles_service2_1 == false or permitted_service2_1 == true
...
permit
}
Remember that in a target, clauses are AND'ed together. This rule then checks that, for every service, the role is either not required or is given in the current permissions.
If instead it may happen than any of those attributes is not present (i.e. there are no values for the attribute), then you have to guard against that case. You can do that using a condition like the following one, but there are other ways too:
rule {
permit
condition
(not(booleanIsIn(true, requiredRoles_service1_1)) || booleanIsIn(true, permitted_service1_1))
&&
(not(booleanIsIn(true, requiredRoles_service2_1)) || booleanIsIn(true, permitted_service2_1))
&&
...
}
All in all, there are usually simpler ways to express a policy if you can massage attribute data into other forms. Having a pair of attributes per service, like in the examples above, may not be necessary.
If you could gather all required roles and current permissions in one attribute each, then the policy can be expressed much more compactly. Let's say you have two attributes, requiredRoles and permittedRoles whose values list the service roles required and permitted for a given user, respectively. In your example, this would mean that requiredRoles has value, say, ["service1.1", "service2.2"] and permittedRoles has value ["service1.1", "service2.3"]. Then you can write a rule like this:
rule {
permit
condition stringSubSet(requiredRoles, permittedRoles)
}
I was able to do this by creating separate attributes for each service and wrote a rule with the target clause with the service from required roles and the condition will be if the service in the permitted role is true. I combined all the rules as below in the policy using permitunlessDeny algorithm
rule rule1 {
target clause requiredRoles.service1_1 == true
deny
condition
not(permittedRoles.service1_1 == true)
on permit {
advice reasonForPermit {
reasonAttribute= "User has valid services"
}
}
}
Thank you for the suggestion Pablo.

Regex is not matching - Firestore

I'm checking if any of the user's roles are part of a document's allowed roles. The following should match since the user is an admin+editor, and the post allows reading by publisher+editor:
service cloud.firestore {
match /databases/{database}/documents {
match /group/{groupId} {
match /posts/{postId} {
allow read: if 'admin,editor'.matches('((,|^)(publisher|editor)(,|$))');
}
}
}
}
Here's it working in a regex tester: https://regex101.com/r/bDXMg3/2/
But this fails to match, any ideas?
Our docs could be clearer on this, but it looks like the whole string needs to match. Give (.*,|^)(publisher|editor)(,.*|$) a try.

Return multiple validation failures from a single Custom() rule?

Using FluentValidation and a Custom() rule, I want to be able to validate a collection of child objects, and return a ValidationFailure for each child object that is invalid.
I can't use a collection validator because the child object doesn't contain the right information to execute the rule - it must run in the context of the parent.
However the Custom() API limits me to returning a single ValidationFailure or nothing at all.
Is there a pattern I can use that allows a single rule to generate multiple errors?
I found a good solution - use AddRule() with a DelegateValidator.
public MyValidator : AbstractValidator<MyClass>
{
public MyValidator()
{
AddRule(new DelegateValidator<MyClass>(MyRule));
}
private IEnumerable<ValidationFailure> MyRule(
MyClass instance,
ValidationContext<MyClass> context)
{
var result = new List<ValidationFailure>();
// add as many failures to the list as you want:
var message = "This is not a valid message";
result.Add(new ValidationFailure(nameof(MyClass.SomeProperty), message));
return result;
}
}

How to add subscripts to my custom Class in Perl 6?

I am new to Perl 6. I have the following code in my Atom Editor, but I still don't understand how this works. I copied the following code, as the docs.raku.org said, but it seems that it does not work. So I changed the code to this:
use v6;
class HTTPHeader { ... }
class HTTPHeader does Associative {
has %!fields handles <self.AT-KEY self.EXISTS-KEY self.DELETE-KEY self.push
list kv keys values>;
method Str { say self.hash.fmt; }
multi method EXISTS-KEY ($key) { %!fields{normalize-key $key}:exists }
multi method DELETE-KEY ($key) { %!fields{normalize-key $key}:delete }
multi method push (*#_) { %!fields.push: #_ }
sub normalize-key ($key) { $key.subst(/\w+/, *.tc, :g) }
method AT-KEY (::?CLASS:D: $key) is rw {
my $element := %!fields{normalize-key $key};
Proxy.new(
FETCH => method () { $element },
STORE => method ($value) {
$element = do given $value».split(/',' \s+/).flat {
when 1 { .[0] } # a single value is stored as a string
default { .Array } # multiple values are stored as an array
}
}
);
}
}
my $header = HTTPHeader.new;
say $header.WHAT; #-> (HTTPHeader)
"".say;
$header<Accept> = "text/plain";
$header{'Accept-' X~ <Charset Encoding Language>} = <utf-8 gzip en>;
$header.push('Accept-Language' => "fr"); # like .push on a Hash
say $header.hash.fmt;
"".say;
say $header<Accept-Language>.values;
say $header<Accept-Charset>;
the output is:
(HTTPHeader)
Accept text/plain
Accept-Charset utf-8
Accept-Encoding gzip
Accept-Language en fr
(en fr)
utf-8
I konw it works, but the document in docs.raku.org is a little different to this, which doesn't have "self" before the AT-KEY method in the 7th line. Is there any examples that more detail about this?
Is there any examples that more detail about this?
Stack overflow is not really the place to request more detail on a published example. This is the perl6 doco on the community itself - I would suggest that the most appropriate place if you have further queries is the Perl6 users mailing list or, failing that, the IRC channel, perhaps.
Now that you've posted it though, I'm hesitant to let the question go unaddressed so, here are a couple of things to consider;
Firstly - the example you raised is about implementing associative subscripting on a custom (ie user defined) class - it's not typical territory for a self-described newbie. I think you would be better off looking at and implementing the examples at Perl6 intro by Naoum Hankache whose site has been very well received.
Option 1 - Easy implementation via delegation
Secondly, it's critical to understand that the example is showing three options for implementing associative subscripting; the first and simplest uses delegation to a private hash attribute. Perl6 implements associative and positional subscripts (for built-in types) by calling well-defined methods on the object implementing the collection type. By adding the handles trait on the end of the definition of the %!fields attribute, you're simply passing on these method calls to %!fields which - being a hash - will know how to handle them.
Option 2 - Flexible keys
To quote the example:
However, HTTP header field names are supposed to be case-insensitive (and preferred in camel-case). We can accommodate this by taking the *-KEY and push methods out of the handles list, and implementing them separately...
Delegating all key-handling methods to the internal hash means you get hash-like interpretation of your keys - meaning they will be case-sensitive as hash keys are case-sensitive. To avoid that, you take all key-related methods out of the handles clause and implement them yourself. In the example, keys are ran through the "normalizer" before being used as indexes into %!fields making them case-insensitive.
Option 3 - Flexible values
The final part of the example shows how you can control the interpretation of values as they go into the hash-like container. Up to this point, values supplied by assigning to an instance of this custom container had to either be a string or an array of strings. The extra control is achieved by removing the AT_KEY method defined in option 2 and replacing it with a method that supplies a Proxy Object. The proxy object's STORE method will be called if you're assigning to the container and that method scans the supplied string value(s) for ", " (note: the space is compolsory) and if found, will accept the string value as a specification of several string values. At least, that's what I think it does.
So, the example has a lot more packed into it than it looks. You ran into trouble - as Brad pointed out in the comments - because you sort-of mashed option 1 togeather with option 3 when you coppied the example.

Doctrine ODM: Cannot prime->(true) getSingleResult(); throws cursor error

I have a document that has a ReferenceMany attribute to another document. The reference is setup fine, and the data is returned from the query fine, but each document in the arraycollection is returned as a proxy. On this site, I saw it was mentioned I should add ->prime(true) in order to return the actual referenced documents.
When that ArrayCollection of documents is returned, I am running a loop of ids I have submitted to the server to remove them from the referenced collection. The removeElement method is not working b/c the returned documents are proxies, and I am comparing an actual document vs. those proxies. So basically I am trying to:
Look up a single document
Force all documents in the ReferenceMany attribute to be actual documents and not Proxy documents
Loop through my array of id's and load each document
Send the document to the removeElement method
On the first getSingleResult query method below, I am getting an error cannot modify cursor after beginning iteration. I saw a thread on this site mention you should prime the results in order to get actual documents back instead of proxies, and in his example, he used getSingleResult.
$q = $this->dm->createQueryBuilder('\FH\Document\Person')->field('target')->prime(true)->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
foreach($data->loc_id as $loc) {
$location = $this->dm->createQueryBuilder('\FH\Document\Location')->field('id')->equals(new \MongoId($loc))->getQuery()->getSingleResult();
$person->removeTarget($location);
}
....
....
....
public function removeTarget($document)
{
$this->target->removeElement($document);
return $this;
}
If I remove ->prime(true) from the first query, it doesn't throw an error, yet it doesn't actually remove any elements even though I breakpoint on the method, compare the two documents, and the data is exactly the same, except in $this->target they are Location Proxy documents, and the loaded one is an actual Location Document.
Can I prime the single result somehow so I can use the ArrayCollection methods properly, or do I need to just do some for loop and compare ids?
UPDATE
So here is an update showing the problem I am having. While the solution below would work just using the MongoId(s), when I submit an actual Document class, it never actually removes the document. The ArrayCollection comes back from Doctrine as a PersistentCollection. Each element in $this->coll is of this Document type:
DocumentProxy\__CG__\FH\Document\Location
And the actual Document is this:
FH\Document\Location
The removeElement method does an array_search like this:
public function removeElement($element)
{
$key = array_search($element, $this->_elements, true);
if ($key !== false) {
unset($this->_elements[$key]);
return true;
}
return false;
}
So because the object types are not exactly the same, even though the proxy object should be inheriting from the actual Document I created, $key always returns 0 (false), so the element is not removed. Everything between the two documents are exactly the same, except the object type.
Like I said, I guess I can do it by MongoId, but why isn't it working by submitting the entire object?
Don't worry about the prime(true) stuff for just now. All that does is tell doctrine to pull the referenced data now, so it doesn't have to make multiple calls to the database when you iterate over the cursor.
What I would do is change your removeTarget method to do the following.
$this->dm->createQueryBuilder('\FH\Document\Person')->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
$person->removeTargets($data->loc_id);
Person.php
public function removeTargets($targets)
{
foreach ($targets as $target) {
$this->removeTarget($target);
}
}
public function removeTarget($target)
{
if ($target instanceof \FH\Document\Location) {
return $this->targets->removeElement($target);
}
foreach ($this->targets as $t) {
if ($t->getId() == $target) {
return $this->targets->removeElement($t);
}
}
return $this;
}
This would mean you don't have to perform the second query manually as doctrine will know it needs to pull the data on that reference when you iterate over it. Then you can make this operation quicker by using the prime(true) call to make it pull the information it needs in one call rather than doing it dynamically when you request the object.