Git Product home page Git Product logo

blaze_compiler's Introduction

Status: Archived

This repository has been archived and is no longer maintained.

status: inactive

DEPRECATED - NO LONGER MAINTAINED

If you're still interested in using a Firebase Database Security Rules compiler, check out the experimental Bolt compiler.

Blaze Security Compiler for Firebase

The blaze compiler simplifies building security rules for your Firebase database. It drastically reduces the amount of copy and pasting involved. Blaze compiler security rules are shorter, and the syntax is less fussy.

Getting started

npm install -g blaze_compiler

create a rules.yaml containing the following code

functions:
  - isLoggedIn(): auth.uid !== null

schema: {}

access:
  - location: /
    read:  true
    write: true && isLoggedIn()

now compile it from the commandline with

blaze rules.yaml

A rules.json will be generated which you can upload to Firebase!

You can find more about the functions, simpler rule expressions, the schema definitions, access control or inline tests.

Functions

Common expressions for reuse are defined in the functions list. A function can take arguments (they are functions).

functions:
  - isLoggedIn():          auth.username !== null
  - isUser(username):      auth.username === username

You can then use them anywhere a security expression would be expected, for example, in the access control section:-

access:
  location: /users/$userid/
  write:    isUser($userid)

Simple Security Expressions

Security expressions are the strings that used to go in write/read/validate portions of the old security rules. Blaze expressions have similar semantics but shorter syntax.

Variables renamed

data and newData have been renamed prev and next. root has the same meaning.

Child selection

The expression for selecting a child is now an array-like syntax. What was:

root.child('users')

is now

root['users']

In the common case that you are selecting a child using a single literal, you can select the child as if it were a property. So you can also write the above as:

root.users

Coercion of .val()

In the new syntax, .val() is inserted if the expression is next to an operator or in an array like child selector. You only need to use .val() if you are using a method of a value type like .length, .beginsWith() .contains(...). So

newData.child('counter').val() == data.child('counter').val() + 1

is simplified to just

next.counter == prev.counter + 1

Schema

The schema section describes the layout of the data tree. It is strongly suggested you use schema to describe the layout of your Firebase, however it is possible to describe just the end points with access controls. A compiler warning is emitted if you give access to a path that is not described by the data schema.

Types

A Firebase database schema node is either a leaf type (string, number, boolean) or an object which contains more child schema nodes. The type is specified with "type". Children of objects are specified as a map under "properties"

schema:
  type: object
  properties:
    string_child:  {type: string}
    boolean_child: {type: boolean}
    number_child:  {type: number}
    anything_child: {}

In the above example you could set {string_child: "blah"} at the root of your database but not {string_child: true}

You can leave a schema unspecified with {} or with type: "any".

required

The required keyword states which children must be present. The required keyword is only valid for schema nodes with the object or any types.

schema:
  type: object
  required: [child1, child2]

additionalProperties

By default, objects can have additional children not mentioned. If additionalProperties is set to false, however, only children explicitly mentioned in the properties are allowed. The additionalProperties keyword is only valid for object and non-typed schemas.

schema:
  type: object
  additionalProperties: false
  properties:
    string_child:  {type: string}

would not accept {number_child: 5} in the root, but without additionalProperties it would.

enum

The enum keyword constrains the value of a string types to be one of the predefined array elements.

schema:
  type: string
  enum: [yes, no, maybe]

indexOn

The indexOn keyword adds an index for querying. This can either be specified as a single string or an array, applied to non-typed or object types only.

schema:
  indexOn: name
  $user:
     indexOn: [inbox, outbox]

ranges

The minimum keyword constrains the minimum value of a number type. You set exclusiveMinimum to true, otherwise the minimum is inclusive. Maximum and exclusiveMaximum follow the pattern

schema:
  type: number
  minimum:  0
  maximum: 10
  exclusiveMaximum: true

  examples:
    - 0
    - 9.9
  nonexamples:
    - 10

$wildchild

An object can have many children bound to a path variable denoted with a keyword starting with $. Note that wildchilds are not put in the properties definition. The following shows how to accept many objects as children of "/users/"

schema:
  type: object
  properties:
    users:
      type: object
      $userid: {}

The use of a wildchild prevents all ascendents from being writable.

~$wilderchild

schema:
  type: object
  properties:
    users:
      type: object
      ~$userid: {type: string, constraint: next != null}

Wilderchilds are an unsafe but more flexible wildchild. Use them with caution. Wilderchilds do not lock the parent against writing. Wilderchildren's constraints are respected only when next!=null. This implies wilderchild can be set to null whenever user has write access to them, either by writing to the parent or the wilderchild location directly. In the above example, despite the guard against being set to null in the constraint, the constraint is not evaluated when the wilder child is set to null and thus has no effect. Wilderchilds are useful because their enclosing location can still be written, but be aware of the drawbacks.

Constraints

The semantics of enforcing data integrity is different from the original rules. There is no overriding of constraints, nor separate read/write/validate expressions. There is just one field for expressing data integrity named constraint. All ancestors and descendant constraints must evaluate to true for a write to be allowed.

The following example, fixes the id of a user to equal the key, and makes the account writable only at creation time.

schema:
  type: object
  properties:
    users:
      type: object
      $userid:
        properties:
          id:
            type: string
            constraint: next == $userid

        constraint: (!prev.exists())

You can be sure all constraints above and below evaluate to true for a write to be allowed. The only quirk is related to wildchilds. You can't write anything above a wildchild that includes the wildchild as a descendant. They do inherit their parents constraints though, as do their siblings, so the use of wildchilds never makes the database less constrained accidentally.

Model reuse

Denormalization of data requires replicating a model in multiple places in a schema. JSON Schema allows importing of models across the Internet or within a document through URLs. Currently, blaze only supports in-document reuse.

Model definitions are declared in the keyword definitions object, and references are made using the $ref keyword as follows:

schema:
  definitions:
    stamped_value:
      type: object
      properties:
        modified: {type: number}
      required: [value, modified]
      constraint: next.value == prev.value || next.modified == now

  type: object
  $data: {$ref: "#/definitions/stamped_value"}

In JSON Schema you are able to extend model objects using the allOf modeling construct (example). However, blaze does not currently support this. Let us know if you need it!

Inline testing

Writing a complex schema can be difficult. For example, a typo in a required field could enforce the existence of a child other than the one intended. For that reason blaze provides keywords for inline testing of nested schema at compile time.

examples is a list of JSONs that you expect to be accepted by the JSON schema node.

nonexamples is a list of JSONs that you expect to be rejected by the JSON schema node.

These inline tests are good for documenting intent and providing fast feedback when authoring a new schema. Note that inline tests cannot understand the constraint field, they can only test the schema.

schema:
  type: object
  properties:
    object:  {type: object}
    string:  {type: string }
    boolean: {type: boolean}
    number:  {type: number }
  additionalProperties: false

  examples:
    - {object:  {name: "hello"}} # you can have extra children in objects by default
    - {string:  string}
    - {boolean: true}
    - {number:  4.6}

  nonexamples:
    - {object:  true}
    - {string:  {grandchild: true}}
    - {boolean: "true"}
    - {number:  "4.6"}
    - {extra:   "4.6"} #additionProperties is false, so no unexpected properties allowed

Access Control

The schema portion of the rules YAML file is for specifying the data layout and constraints. Read/write access is described in a separate access control list under "access". For each entry, the scope of the rule is a subtree at, or below, the path indicated in the location field. Read access is granted to that subtree if the read expression evaluates to true, and write access is granted if the write expression evaluates to true.

functions:
  - isLoggedIn():   auth !== null
...
access:
  - location: "/"
    read: isLoggedIn()
  - location: "/users/$userid/"
    write: auth.username === $userid

Only one access control entry needs to evaluate to true for an operation to be permitted.

Example

This is an example that exploits most of the new features. It is a messaging system where users can send messages to each other, by posting to other user's inboxes

functions:            #reusable boolean functions
  - isLoggedIn():      auth.username !== null
  - createOnly():      next.exists() && !prev.exists()
  - deleteOnly():      prev.exists() && !next.exists()
  - createOrDelete():  createOnly() || deleteOnly()

schema:
  definitions:         #create a reusable message model
    message:           #for use in the in and out boxes
      type: object
      properties:
        from:
          type: string
          #enforce the from field is *always* correct on creation,
          #and that only the *box owner* can delete
          constraint:  (auth.username == next     && createOnly()) ||
                       ($userid === auth.username && deleteOnly())

        #you can't delete single field due to parent's required
        to:      {type: string, constraint:  createOrDelete()}
        message: {type: string, constraint:  createOrDelete()}

      required: [from, to, message] # all messages require all the fields to be defined
                                    #(or none if the message does not exist)

      additionalProperties: false   #prevent spurious data being part of a message

      examples: #examples of inline testing
        - {from: "bill", to: "tom", message: "hey Tom!"}
      nonexamples:
        - {to: "tom", message: "hey Tom!"} #not allowed because from is missing

  type: object
  properties:
    users: # the users subtree is a collection of users
      type: object
      $userid: #wildchild expression of many children
        type: object
        properties: #each user has an optional inbox and outbox
          inbox:
            type: object
            $message: {$ref: "#/definitions/message"}

          outbox:
            type: object
            $message: {$ref: "#/definitions/message"}

  additionalProperties: false

access:
  #append only write is granted to anyone's inbox,
  #so users can send messages to strangers
  - location: users/$userid/inbox/
    write:    createOnly() && isLoggedIn()

  #the inbox owner can delete their incoming mail
  - location: users/$userid/inbox/
    write:    deleteOnly() && $userid === auth.username

  #write and delete is given to owners outbox
  - location: users/$userid/outbox/
    write:    true

  #owners can read everything in their inbox and outbox
  - location: users/$userid/
    read:    $userid === auth.username

Changelog

  • 25th Sep 2015:

    • schema padding emits a warning when applied to help visibility
    • performance schema padding efficiency greatly improved
    • performance of optimization routines improved
  • 20th Aug 2015:

    • tailored error message when a wild(er)child is used in a properties section
  • 10th Aug 2015:

    • Allowed repeat examples and non-example, as the error message can be unclearly attached to something unrelated (see repeatExample.yaml)
    • upgrade source-map-repository so blaze_compiler continues to fix issue with io.js
    • Allowed any type to have the required keyword
    • Schema is padded to match with ACL if the ACL is bigger
  • 19th May 2015:

    • Improved optimization use a less verbose object detection notation and spurious parent is an object checks
  • 22nd April 2015:

    • Special cased forgetting to .val() before using an inbuilt string method with an error message
  • 21th April 2015:

    • $ref not importing into (non)example schema fragments properly
  • 10th April 2015:

    • fixed erroneous substitution of parameters into member expressions
  • 12th Jan 2015:

    • improved messaging if blah.child('name') syntax is erroneously used
  • 23rd December 2014:

    • bugfix: function with next or prev were not moved around when in constraints properly (similar to Nov 4th bug)
    • bugfix: minimum and maximum
  • 20th November 2014:

    • support for indexOn
  • 4th November 2014:

    • bugfix: functions in ACL resolved properly
  • 3rd November 2014:

    • bugfix: wilderchild matching fix in ACL
  • 1st November 2014:

    • bugfix: wilderchild overwriting parent constraints bug fixed
    • bugfix: access control constraints localised properly
    • bugfix: regex detection firing erroneously on strings starting with '/' fixed
  • 20th October 2014:

    • optimizations added to reduce code bloat
    • sensitization bug regarding regexes fixed
  • 28th August 2014:ß

    • range constraints for number type added
  • 26th August 2014:

    • wilderchilds introduced, ~$ allows nullable wildchilds whose parents can be written to.
    • sanitized expressions bug fix
  • 18th August 2014:

    • predicates renamed to functions
  • 14th July 2014:

    • improved error reporting
    • updated installation
  • 9th July 2014:

    • support for rules in JSON
  • 30th June 2014:

    • removed trailing /* from access location syntax
    • allowed untyped schema if type is not specified

blaze_compiler's People

Contributors

abeisgoat avatar andressrg avatar asciimike avatar azell avatar samtstern avatar startupandrew avatar tomlarkworthy avatar vikrum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blaze_compiler's Issues

Better error message for missing type when using required

It seems inconsistent that in a schema, type is required but only when using required.

These are the same blocks of code but with different lines commented out.

This works fine:

schema:
  type: object
  properties:
    menu:
      type: object
#  required: [menu]

This also works fine:

schema:
  type: object
  properties:
    menu:
      type: object
  required: [menu]

This gives a horrible error. I believe I copied the base config an example (mentioned at the bottom) and then added required to it:

schema:
#  type: object
  properties:
    menu:
      type: object
  required: [menu]

It gave the following error message:

compile object
error line 1:8
{ properties: 
   { menu: 
      { type: 'object',
        constraint: '((true) && (!next.exists() || next.hasChildren())) && (next == null || (true))' } },
  required: [ 'menu' ],
  constraint: 'true' }
cannot validate schema node with type schema
data was on path: /required
the schema constraint was defined at 
Unknown property (not in schema)
run with -v option for fuller error messages

This is not a very descriptive error message. I mean you can kinda pick it out from the message saying that the type is schema (which isn't one of the valid types in the docs) and some problem with the /required path but it doesn't mean much to a first time user. It would be great if it could be something along the lines of on root schema, type attribute must be set when using required attribute

Also an issue in the docs that mislead me:
In the Constraints section, the example code doesn't have a type under $userid. I copied that code and then added required to it which created this error. I'm not sure if type should be required or not in all cases but definitely needs to be documented that the required attribute needs it.

Can't have multiple multi-line examples

The first multi-line example is fine, but as soon as a second multi-line example is added then there seems to be a parse error. If I collapse the first multi-line example into a single line, then they both work as expected.

Here is the input for the examples I'm trying to run:
examples:

  • {externalUsers : {
    "facebook:10152415019676650" : 7,
    "facebook:10152713523692632" : 8
    }}
    • {usersToMerge: {
      {"werwer": 8},
      {"xcvxcv": 9},
      {"rtyrty": 1}
      }}

Here is the output from the error case (two multi-line examples):

Sarahs-MacBook-Pro:beehive sarahwiley1$ blaze rules.yaml
transpiling rules.yaml
error line 7:14
{ authorLookup:
{ type: 'object',
properties: { publisher: [Object] },
examples: [ {} ] },
blacklist: { '$id': { type: 'string' } },
comments:
{ type: 'object',
indexOn: [ 'pageGuid', 'createdBy', 'processed', 'authorUserId' ],
'$commentId':
{ type: 'object',
properties: [Object],
additionalProperties: false } },
currentUserId: { type: 'number', constraint: 'next === prev+1' },
externalUsers: { type: 'object', '$externalUserId': { type: 'number' } },
newComments:
{ type: 'object',
'$commentId':
{ type: 'object',
required: [Object],
constraint: '(( next.hasChild('rating') && root['comments'][next['replyToCommentId']]['createdBy'] !== next['createdBy'] ) || !next['rating'].exists() ) && ( (!next.hasChild('createdBy') && next.hasChildren(['authorId', 'permalink'])) || (next['createdBy'] === root['externalUsers'][auth.uid] && next['replyToCommentId'].exists()) ) && (next['rating'].exists() || next['content'].exists())',
properties: [Object],
additionalProperties: false } },
users:
{ type: 'object',
indexOn: 'processed',
'$userId':
{ type: 'object',
required: [Object],
properties: [Object],
additionalProperties: false } },
usersToMerge: { type: 'object', '$externalUserId': { type: 'number' } },
settings: { type: 'object' } }
cannot validate blaze file with blaze schema
data was on path: /schema/properties
the schema constraint was defined at
Unknown property (not in schema)
run with -v option for fuller error messages

Multiple child selection stopped working

functions:
  - isWorkspaceRole(userId, workspaceId, role): root['/rels/workspaces/users'][workspaceId][userId]['role'] == role

The above code throws: Bug: 3 Unexpected situation in type system rule regex

This one seem to work fine though:

functions:
  - isWorkspaceRole(userId, workspaceId, role): root['/rels/workspaces/users/' + workspaceId][userId]['role'] == role

Nested functions causing strange text replacement error

I have two functions:

  - isUser(uid): root.authUserIndex[auth.userid] === uid || isShopper()
  - isShopper(): root.users[root.authUserIndex[auth.uid]].isShopper === true

along with the following rule:

   - location: "/users/$userid/"
    read: isUser($userid)
    write: isUser($userid)

it LOOKS like that the isShopper() function is getting replaced into the isUser(uid) function, and since uid is a parameter in the first function, the resulting rule comes out to be:


".read":"false||false||root.child('authUserIndex').child(auth.$userid).val()===$userid||root.child('users').child(root.child('authUserIndex').child(auth.$userid).val()).child('isShopper').val()===true"

(notice the auth.$userid part). I believe this is a bug, and the obvious workaround is to rename my variables (which I did), but just wanted to make you guys aware of a potential issue here.

Set default type

Would be great to set a default type on the schema. I find myself writing type: object a LOT. In Soviet Javascript, everything is an object.

Confusing error for `-` in wildchild name.

rules.yaml:

schema:
  type: object
  additionalProperties: false
  $foo-bar:
    type: string
    constraint: $foo-bar.matches(/regex/)

blaze rules.yaml yields:

Error: Bug: 3 Unexpected situation in type system undefined undefined
at falafel_visitor (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/src/expression.ts:342:167)
at walk (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:60:9)
at /home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:57:17
at Array.forEach (native)
at forEach (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:8:31)
at walk (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:44:9)
at /home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:57:17
at Array.forEach (native)
at forEach (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:8:31)
at walk (/home/jyasskin/opt/nodejs/lib/node_modules/blaze_compiler/node_modules/falafel/index.js:44:9)

Unable to compile with io.js

When using io.js running blaze rules.yaml -v outputs undefined and does not create a rules.json file.

Console output:

# npm run build => blaze rules.yaml -v

$ npm run build

> <my-app-name>@0.0.1 build /<my-app-dir>
> blaze rules.yaml -v

args { _: [ 'rules.yaml' ],
  v: true,
  java: false,
  '$0': '/<my-app-dir>/node_modules/.bin/blaze' }
transpiling rules.yaml
undefined

<my-app-name> is the name of my app removed for privacy.
<my-app-dir> is the directory to my app removed for privacy.

rules.yaml is the same as in the README.md.

Any ideas on what's happening?

Constraint with child() method produces compilation error

I have isolated the problem to the following constraint:

constraint: (!prev.parent().child('registrationDate').exists())

I get a Bug: 4 Unexpected situation in type system rule error when trying to compile the rules.

If I remove the child() method the compilation passes:

constraint: (!prev.parent().exists())

Is there something I don't understand or is this actually a bug?

"Bug: 4 Unexpected situation in type system on 'undefined'" should instead bubble up better error messages.

Hey firebase folks,

Loving blaze - much simpler than writing really complex rules in the json directly.

Did run into one snag though that took a couple days to sort out, and I think could be fixed with simply better error messages.

Here's the details:

If I have a config like so:

functions:
    - isValid(): !prev.foo

schema:
    type: object
    properties:
        bar:
            type: object
            constraint: isValid()
            properties:
                foo: {type: boolean}

Running blaze will get me

Bug: 4 Unexpected situation in type system on 'undefined'
run with -v option for fuller error messages

Which, to me, is pretty much meaningless. Running with -v, produces the even less debuggable

   { raw: '((true&&(!next.exists()||!(next.isString()||next.isNumber()||next.isBoolean()))&&(next==null||true)) && ((true&&(!next[\'bar\'].parent().exists()||!(next[\'bar\'].parent().isString()||next[\'bar\'].parent().isNumber()||next[\'bar\'].parent().isBoolean()))&&(next[\'bar\'].parent()==null||true)&&isValid()&&(!next[\'bar\'].exists()||!(next[\'bar\'].isString()||next[\'bar\'].isNumber()||next[\'bar\'].isBoolean()))&&(next[\'bar\']==null||true)) && (((((true) && (!next[\'bar\'][\'foo\'].parent().parent().exists() || !(next[\'bar\'][\'foo\'].parent().parent().isString()||next[\'bar\'][\'foo\'].parent().parent().isNumber()||next[\'bar\'][\'foo\'].parent().parent().isBoolean()))) && (next[\'bar\'][\'foo\'].parent().parent() == null || (true)))&&(isValid()&&(!next[\'bar\'][\'foo\'].parent().exists()||!(next[\'bar\'][\'foo\'].parent().isString()||next[\'bar\'][\'foo\'].parent().isNumber()||next[\'bar\'][\'foo\'].parent().isBoolean()))&&(next[\'bar\'][\'foo\'].parent()==null||true)))&&(true&&(!next[\'bar\'][\'foo\'].exists()||next[\'bar\'][\'foo\'].isBoolean())))))&&(false)',
     source: null },
  read: { raw: 'false', source: null } }
Error: Bug: 4 Unexpected situation in type system on 'undefined'

However, if I place the problematic method directly on the schema, instead of abstracting it out, like this:

schema:
    type: object
    properties:
        bar:
            type: object
            constraint: !prev.foo
            properties:
                foo: {type: boolean}

I get the much more helpful (-v)

JS-YAML: unknown tag !<!prev.foo> at line 7, column 13:
                properties:
                ^

input:
{"schema":{"type":"object","properties":{"bar":{"type":"object","constraint":null,"properties":{"foo":{"type":"boolean"}}}}}}
Warning:  no access list defined, this Firebase will be inactive
loading type any definition from codebasePath/node_modules/blaze_compiler/src/../schema/metaschema/any.yaml
loading type boolean definition from codebasePath/node_modules/blaze_compiler/src/../schema/metaschema/boolean.yaml
loading type number definition from codebasePath/node_modules/blaze_compiler/src/../schema/metaschema/number.yaml
loading type object definition from codebasePath/node_modules/blaze_compiler/src/../schema/metaschema/object.yaml
loading type string definition from codebasePath/node_modules/blaze_compiler/src/../schema/metaschema/string.yaml
annotate_schema { type: 'boolean' }
compile boolean
addConstraint !next.exists() || next.isBoolean()
annotate_schema { type: 'object',
  constraint: null,
  properties: 
   { foo: 
      { type: 'boolean',
        constraint: '(true) && (!next.exists() || next.isBoolean())' } } }
error line 4:13
{ type: 'object',
  constraint: null,
  properties: 
   { foo: 
      { type: 'boolean',
        constraint: '(true) && (!next.exists() || next.isBoolean())' } } }
Error: cannot validate schema node with type schema
data was on path: /constraint
the schema constraint was defined at /properties/constraint/anyOf
Data does not match any schemas from "anyOf"

Putting aside why !foo doesn't work but foo == false does (wouldn't mind learning why that happens, though), simply bubbling up those errors would make debugging much simpler.

Thanks much!

-Steven

Nested objects write permission is missing

I've moved the rules in Firechat application to Yaml file in order to allow me to modify schema easier, the minimal is below.

functions:
  - isAuth():                   auth != null
  - isServer():                 auth.uid === "publisher"
  - isModerator():              root.moderators.hasChild(auth.uid) === true
  - isRoomPublic():             root['room-metadata'].$roomId.type !== 'private'
  - isRoomAuthorized():         root['room-metadata'].$roomId.autorizedUsers.hasChild(auth.uid) === true
  - canReadRoomMsgs():          isRoomPublic() || isRoomAuthorized()
  - canWriteMessage():          isAuth() && ( prev === null || isModerator() ) && (isRoomPublic() || isRoomAuthorized())

schema:
  type: object
  properties:
    #Conference is a collection of rooms
    room-messages:
      type: object
      properties:
        $roomId:
          type: object
          properties:
            $msgId:
              type: object
              properties:
                userId:     {type: string}
                name:       {type: string}
                message:    {type: string}
                timestamp:  {type: number}
                #Per message list of users who read it
                readBy:
                  type: object
                  properties:
                    $userId: {type: boolean}
              required: [userId, name, message, timestamp]
    moderators: {}


access:
  #Root by default nothing to read or write
  - location: /
    write: false
    read: false

  #Messages deposited into each of the rooms
  - location: room-messages/$roomId/
    read: canReadRoomMsgs()
  - location: room-messages/$roomId/$msgId
    write: canWriteMessage()

  #Misc structures
  - location: moderators/
    read:   isAuth()
    write:  isServer()

Here is something unexpected that I'm seeing after compilation with blaze:

{
  "rules":{
    ".write":"(((false)))",
    ".read":"false",
    "room-messages": {
      ".write":"((false))",
      ".read":"false",
      "$roomId": {
        ".write":"((false))",
        ".read":"((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))",
        "$msgId": {
          ".write":"((((false))))",
          ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))",
          "userId": {
            ".write":"((((newData.parent().val()==null||newData.parent().child('userId').exists()&&newData.parent().child('name').exists()&&newData.parent().child('message').exists()&&newData.parent().child('timestamp').exists())&&(!newData.exists()||newData.isString())&&auth!=null&&(data.parent().val()===null||root.child('moderators').hasChild(auth.uid)===true)&&(root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))))",
            ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))"
          },
          "name": {
            ".write":"((((newData.parent().val()==null||newData.parent().child('userId').exists()&&newData.parent().child('name').exists()&&newData.parent().child('message').exists()&&newData.parent().child('timestamp').exists())&&(!newData.exists()||newData.isString())&&auth!=null&&(data.parent().val()===null||root.child('moderators').hasChild(auth.uid)===true)&&(root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))))",
            ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))"
          },
          "message": {
            ".write":"((((newData.parent().val()==null||newData.parent().child('userId').exists()&&newData.parent().child('name').exists()&&newData.parent().child('message').exists()&&newData.parent().child('timestamp').exists())&&(!newData.exists()||newData.isString())&&auth!=null&&(data.parent().val()===null||root.child('moderators').hasChild(auth.uid)===true)&&(root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))))",
            ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))"
          },
          "timestamp": {
            ".write":"((((newData.parent().val()==null||newData.parent().child('userId').exists()&&newData.parent().child('name').exists()&&newData.parent().child('message').exists()&&newData.parent().child('timestamp').exists())&&(!newData.exists()||newData.isNumber())&&auth!=null&&(data.parent().val()===null||root.child('moderators').hasChild(auth.uid)===true)&&(root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))))",
            ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))"
          },
          "readBy": {
            ".write":"(false)",
            ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))",
            "$userId": {
              ".write":"((((newData.parent().parent().val()==null||newData.parent().parent().child('userId').exists()&&newData.parent().parent().child('name').exists()&&newData.parent().parent().child('message').exists()&&newData.parent().parent().child('timestamp').exists())&&(!newData.exists()||newData.isBoolean())&&auth!=null&&(data.parent().parent().val()===null||root.child('moderators').hasChild(auth.uid)===true)&&(root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true))))",
              ".read":"(((root.child('room-metadata').child($roomId).child('type').val()!=='private'||root.child('room-metadata').child($roomId).child('autorizedUsers').hasChild(auth.uid)===true)))"
            }
          }
        }
      }
    },
    "moderators": {
      ".write":"(((auth.uid==='publisher')))",
      ".read":"((auth!=null))"
    }
  }
}

The specific part is:

  "$roomId": {
    ".write":"((false))",

This does not let me write any new rooms even as "moderator".
I'm noticing that whenever I have nested wildchild schema I get write to be permanently false for the root child but not the leaf nodes. Still Firebase prevents writing such structures.

Inheritance

Is additionalProperties inherited? I can't figure it from the readme. Thanks.

Error messages with line numbers

Often, an error message doesn't contain a line number or any other clue as to where to find the error.

Example:

$ blaze rules.yaml 
transpiling rules.yaml
loading type any definition from /usr/local/lib/node_modules/blaze_compiler/src/../schema/metaschema/any.yaml
loading type boolean definition from /usr/local/lib/node_modules/blaze_compiler/src/../schema/metaschema/boolean.yaml
loading type number definition from /usr/local/lib/node_modules/blaze_compiler/src/../schema/metaschema/number.yaml
loading type object definition from /usr/local/lib/node_modules/blaze_compiler/src/../schema/metaschema/object.yaml
loading type string definition from /usr/local/lib/node_modules/blaze_compiler/src/../schema/metaschema/string.yaml
compile string
compile string
compile string
compile string
compile object
compile object
compile object
compile object
Cannot call method 'has' of undefined

I would really be nice if the error message always contained a line number.

Redirects and rewrites

Hi, do you have any plans to support redirects, rewrites and custom headers in blaze compiler?

Add duplicate examples warning

This is not a big deal and is entirely my fault but I had a list of several examples and accidently duplicated 2 of the examples.

schema:
  type: object
  properties:
    users:
      type: object
      $user_uid:
        type: object
        required: [name, email]
        properties:
          name:
            type: string
          email:
            type: string

        examples:
          - {name: "My Name", email: "[email protected]"}
          - {name: "My Name", email: "[email protected]"}

I got this lovely error message which gave me no indication that the examples were the issue:

error line 8:18
[ 'name', 'email' ]
cannot validate blaze file with blaze schema
data was on path: /schema/properties/users/$user_uid/required
the schema constraint was defined at 
Unknown property (not in schema)
run with -v option for fuller error messages

I finally noticed the duplicate example and figure that might be the issue and sure enough that fixed it. A better error message would be nice. This could be part of the YAML parser itself and be harder to add.

Incorrect compilation for children

I continually see true&&false&&(rule_thats_actually_important) getting generated, often incorrectly. If I understand Firebase correctly, true&&false will always be false regardless of what follows. Here's an example config:

schema:
  type: object
  properties:
    users:
      type: object
      $userId:
        type: object
        properties:
          foos:
            type: object
            $fooId: {type: object}

access:
  # No read or write by default.
  - location: /
    read: false
    write: false

  # Allow owner to write.
  - location: users/$userId/
    write: true
  - location: users/$userId/foos/
    write: true

Which generates the following:

{
  "rules":{
    ".write":"true&&true&&false&&(false||false)",
    ".read":"false||false",
    "users": {
      ".write":"true&&false&&(false||false)",
      ".read":"false||false",
      "$userId": {
        ".write":"true&&true&&false&&(false||false||true)",
        ".read":"false||false||false",
        "foos": {
          ".write":"true&&false&&(false||false||true||true)",
          ".read":"false||false||false||false",
          "$fooId": {
            ".write":"true&&(!newData.parent().parent().parent().parent().exists()||!(newData.parent().parent().parent().parent().isString()||newData.parent().parent().parent().parent().isNumber()||newData.parent().parent().parent().parent().isBoolean()))&&(newData.parent().parent().parent().parent().val()==null||true)&&true&&(!newData.parent().parent().parent().exists()||!(newData.parent().parent().parent().isString()||newData.parent().parent().parent().isNumber()||newData.parent().parent().parent().isBoolean()))&&(newData.parent().parent().parent().val()==null||true)&&true&&(!newData.parent().parent().exists()||!(newData.parent().parent().isString()||newData.parent().parent().isNumber()||newData.parent().parent().isBoolean()))&&(newData.parent().parent().val()==null||true)&&true&&(!newData.parent().exists()||!(newData.parent().isString()||newData.parent().isNumber()||newData.parent().isBoolean()))&&(newData.parent().val()==null||true)&&true&&(!newData.exists()||!(newData.isString()||newData.isNumber()||newData.isBoolean()))&&(newData.val()==null||true)&&(false||false||true||true)",
            ".read":"false||false||false||false"
          }
        }
      }
    }
  }
}

That doesn't seem to jive. You'll see that we expect /users/$userId/ to be writeable, but the value is true&&true&&false&&(false||false||true) which evaluates to false. Same is true for /users/$userId/foos/

Enhancement: Inline Tests for Access

That's really the most important thing to be testing. Type checking is cool, but it doesn't really mean much if some anonymous dude is able to jam stuff into my database.

cannot be cast from JString to JObject

Hi,

Based on the regex example at https://www.firebase.com/docs/security/guide/securing-data.html#section-validate I'm trying to specify a regex constraint:

          mobile: {type: string}
          constraint: (newData.isString() && newData.val().matches(/^\+(?:[0-9] ?){6,14}[0-9]$/)) #E123 https://en.wikipedia.org/wiki/E.123

Transpiling results in:

annotate_schema (newData.isString() && newData.val().matches(/^\+(?:[0-9] ?){6,14}[0-9]$/))
Error: Type error

cannot be cast from JString to JObject
    at JString.JValue.castError (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:142:26)
    at JString.JValue.cast (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:149:24)
    at JString.JValue.asObject (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:163:30)
    at getWildchild (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:562:10)
    at annotate_schema (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:305:9)
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:298:53
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:329:13
    at Dictionary.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/util/Collections.ts:891:27)
    at JObject.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:328:25)
    at annotate_schema (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:296:54)

Am I able to embed this kind of regex into rules.yaml?

Cheers,
Nicholas

What's the use of wilderchildren?

The Readme states: Wilderchildren's constraints are respected only when next!=null

Why is that? What is a good scenario when this could be useful?

Some access expressions are not translated when copied to descendants

Given the next yaml:

functions:
  - isWorkspaceRole(userId, workspaceId, role): root['/rels/workspaces/users/' + workspaceId][userId]['role'] == role

schema:
  properties:
    rels:
      properties:
        workspaces:
          properties:
            users:
              $workspace_id:
                ~$user_id: {}

access:
- location: /rels/workspaces/users/$workspace_id
  write:    isWorkspaceRole(auth.id, $workspace_id, 'administrator') ||
            isWorkspaceRole(auth.id, $workspace_id, 'collaborator') && next != null

The following code is generated:

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "rels": {
      ".write":"false",
      ".read":"false",
      "workspaces": {
        ".write":"false",
        ".read":"false",
        "users": {
          ".write":"false",
          ".read":"false",
          "$workspace_id": {
            ".write":"(((root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='administrator'||root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='collaborator'&&newData.val()!=null)))",
            ".read":"false",
            "$user_id": {
              ".write":"(((root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='administrator'||root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='collaborator'&&newData.val()!=null)))",
              ".validate":"(((root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='administrator'||root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='collaborator'&&newData.val()!=null)))",
              ".read":"false"
            }
          }
        }
      }
    }
  }
}

While newData.val()!=null makes sense for the parent, it doesn't for the child. Shouldn't it be translated to newData.parent().val()!=null ? Thanks.

Examples and nonexamples don't seem to honor constraints

I'm trying to write examples that would test against a defined constraint, and the constraints don't seem to be taken into account when compiling. A simplified example:

email:
  type: string
  constraint: next.val().matches(/^[^@]+@.+\..+$/i) # rudimentary email address checking
examples:
  - "[email protected]"
nonexamples: # for all of these nonexamples compiler says they pass when they should fail
  - "niko"
  - "@tidyplan.com"
  - "niko@com"

The regex matching works when I push it to Firebase, so the problem is definitely with the compiler.

Or maybe this is by design, and the compiler is not supposed to validate against the constraints?

Setting a minimum for a number effectively makes the property required?

If I have the following yaml rules:

items:
  type: object
  $itemId:
    type: object
    required: [title]
    properties:
      title: {type: string}
      timestamp:
        type: number
        minimum: 0

...and I pass the following JSON in the Firebase simulator to /items/item1:

{ "title": "First item" }

...I get this error:

Type error: >= only operates on numbers and strings.

Looking at the compiled rules, I'm assuming the system is comparing null to the minimum value of 0.

I haven't figured a good way around this. A couple of workarounds:

  1. Always pass timestamp:0 in the JSON data, or
  2. Replace the minimum with constraint: (!next.exists()) || (next.isNumber() && next > 0)
  3. Update the documentation to explain that setting a minimum makes the property required. 😃

Error: Cannot find module 'blaze_compiler'

While attempting to load from a node test suite, I found that blaze could not be imported. The reason is that the 'main' file is blaze.js but that file does not exist. Instead, the 'main' file should be src/blaze.js.

Access rules coming from functions are not translated when copied to descendants

functions:
  - isDeleted()                               : prev.exists() && !next.exists()

schema:
  properties:
    rels:
      properties:
        workspaces:
          properties:
            users:
              $workspace_id:
                ~$user_id: {}

access:
  - location: /rels/workspaces/users/$workspace_id
    write:    isDeleted()

generates

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "rels": {
      ".write":"false",
      ".read":"false",
      "workspaces": {
        ".write":"false",
        ".read":"false",
        "users": {
          ".write":"false",
          ".read":"false",
          "$workspace_id": {
            ".write":"((data.exists()&&!newData.exists()))",
            ".read":"false",
            "$user_id": {
              ".write":"((data.exists()&&!newData.exists()))",
              ".validate":"((data.exists()&&!newData.exists()))",
              ".read":"false"
            }
          }
        }
      }
    }
  }
}

Numerical constraints

Hey, is there no way to define numerical constraints (e.g.: must be between 1 and 999)? I've tried this minimal rule set but blaze tells me my 0 nonexample passes :/

schema:
  definitions:
    price:
      type: number
      constraint: next > 0 && next < 100000
      examples:
        - 1
        - 1000
        - 99900
      nonexamples:
        - 0
        - 100000
        - 1000000

  type: object
  properties:
    prices:
      type: object
      $priceId: {$ref: "#/definitions/price"}

Is that just not possible at all?

Thanks!

Thorben

Fresh checkout doesn't build.

The file /src/processors/greatjson.js - or more likely a .ts version - seems to be missing.

1.Do a fresh clone.
2. Try to run grunt compile.
3. Fail

Doesn't seem to really be used, but it prevents the use of the tool without modification.

Unknown variable error when using functions

I tried a function with an argument:

functions:
  - within24hours(nxt): nxt > (now - 1000*60*60*24)

...and elsewhere I'm passing the value to the argument:

itemDate:
  type: number
  constraint: within24hours(next.val()) # not sure if .val() is needed...

The compiler outputs the following rules that cause an error when saving the rules in Firebase dashboard:

newData.isNumber() && nxt>now-1000*60*60*24

Unexpected function scope

In my rules yaml I have the following code:

functions:
  - within24hours(): next > (now - 1000*60*60*24)

items:
  type: object
  $itemId:
    type: object
    properties:
      title:
        type: string
      itemDate:
        type: number
        constraint: within24hours()

I expect this would write rules along the lines of:

newData.child('title').isString()) && newData.child('itemDate').val() > now-1000*60*60*24

Instead I get this (which obviously doesn't work):

newData.child('title').isString()) && newData.val() > now-1000*60*60*24

Is this intended behaviour?

Regex in function throws error

functions:
 - isEmail(str): str.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,14}$/i) != null

throws error during compilation with message Error: Bug: 4 Unexpected situation in type system rule.

Is this as expected? Does blaze not support regular expressions in functions?

The wildchild constraint seem to be ignored

Given the next yaml file:

functions:
  - isWorkspaceRole(userId, workspaceId, role): root['/rels/workspaces/users/' + workspaceId][userId]['role'] == role

schema:
  properties:
    rels:
      properties:
        workspaces:
          properties:
            users:
              $workspace_id:
                constraint: isWorkspaceRole(auth.id, $workspace_id, 'administrator') ||
                            isWorkspaceRole(auth.id, $workspace_id, 'collaborator') && next != null
                ~$user_id: {}

This is what gets generated with the latest three versions of blaze:

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "rels": {
      ".write":"false",
      ".read":"false",
      "workspaces": {
        ".write":"false",
        ".read":"false",
        "users": {
          ".write":"false",
          ".read":"false",
          "$workspace_id": {
            ".write":"false",
            ".read":"false",
            "$user_id": {
              ".write":"false",
              ".validate":"false",
              ".read":"false"
            }
          }
        }
      }
    }
  }
}

This is the output of blaze 0.0.14:

{
  "rules":{
    ".write":"true&&true&&true&&true&&false&&false",
    ".read":"false",
    "rels": {
      ".write":"true&&true&&true&&false&&false",
      ".read":"false",
      "workspaces": {
        ".write":"true&&true&&false&&false",
        ".read":"false",
        "users": {
          ".write":"true&&false&&false",
          ".read":"false",
          "$workspace_id": {
            ".write":"true&&true&&false",
            ".read":"false",
            "$user_id": {
              ".write":"true&&true&&true&&true&&(root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='administrator'||root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='collaborator'&&newData.parent().val()!=null)&&true&&false",
              ".validate":"true&&true&&true&&true&&(root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='administrator'||root.child('/rels/workspaces/users/'+$workspace_id).child(auth.id).child('role').val()=='collaborator'&&newData.parent().val()!=null)&&true&&false",
              ".read":"false"
            }
          }
        }
      }
    }
  }
}

Am I doing something wrong? Thanks.

Add warnings for ignored access rules that have no corresponding schema or automatically generate them

I spent several hours trying to figure out why this:

functions:
  - hasAuth(): auth !== null
  - isAdmin(): hasAuth() && root.users[auth.uid].role === 'admin'
  - authMatchesPath(userUid): hasAuth() && auth.uid === userUid

schema:
  type: object
  properties:
    menu:
      type: object
    pages:
      type: object
    users:
      type: object
      $useruid:
        type: object
        required: [name, email]
        properties:
          name:
            type: string
          email:
            type: string
          profilePicture:
            type: string
          role:
            type: string
            enum: [admin, user]
        additionalProperties: false

        examples:
          - {name: "My Name", email: "[email protected]"}
          - {name: "My Name", email: "[email protected]", role: "admin"}
          - {name: "My Name", email: "[email protected]", role: "user"}
          - {name: "My Name", email: "[email protected]", profilePicture: "http://url.com"}
          - {name: "My Name", email: "[email protected]", profilePicture: "http://url.com", role: "admin"}
          - {name: "My Name", email: "[email protected]", profilePicture: "http://url.com", role: "user"}
        nonexamples:
          - {new:   "value"}
          - {name:  "My Name"}
          - {email: "[email protected]"}
          - {name:  "My Name", email: "[email protected]", role: "somethingElse"}

access:
  - location: "/menu/"
    read: true
    write: isAdmin()
  - location: "/pages/"
    read: true
    write: isAdmin()
  - location: "/users/"
    read: isAdmin()
  - location: "/users/$user_uid/"
    read: authMatchesPath($user_uid) || isAdmin()
    write: authMatchesPath($user_uid)
  - location: "/users/$user_uid/role/"
    write: isAdmin()

generated this

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "menu": {
      ".write":"((((!newData.exists()||newData.hasChildren())&&auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin')))",
      ".read":"true"
    },
    "pages": {
      ".write":"((((!newData.exists()||newData.hasChildren())&&auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin')))",
      ".read":"true"
    },
    "users": {
      ".write":"((false))",
      ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))",
      "$useruid": {
        ".write":"(false)",
        ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))",
        "name": {
          ".write":"(false)",
          ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))"
        },
        "email": {
          ".write":"(false)",
          ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))"
        },
        "profilePicture": {
          ".write":"(false)",
          ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))"
        },
        "role": {
          ".write":"(false)",
          ".read":"((auth!==null&&root.child('users').child(auth.uid).child('role').val()==='admin'))"
        },
        "$other":{".validate":"false"}
      }
    }
  }
}

where the rules under location: "/users/$user_uid/" and location: "/users/$user_uid/role/"did absolutely nothing.

Then I discovered that in schema I had $useruid and in access I had $user_uid. I made them the same named variable and it worked. I REALLY would have appreciated some documentation or error messages explaining that the all the paths in access have to map to something in schema. I think this might be the same issue as #29.

I think warnings should be added for any access rule that is ignored because there is no matching schema for it. I had the same problem with defining /menu/ and /pages/ rules and discovered I needed to specify a schema for them.

Maybe it is good to force a strict schema but for development purposes I think it would be nice to be able to specify access rules without having to create a schema that matches every single location in order to test security rules. Without that feature, it just drops rules with no explanation.

regex constraints not working with nonexamples

Hi,

I'm trying to set up a regex constraint on a mobile phone number model, with examples and nonexamples. It appears that the nonexamples somehow pass erroneously.

Here is a simpler example to illustrate the problem:

schema:
  definitions:
    red:
      type: string
      constraint: next.val().matches(/red/)
      examples:
          - "red"
      nonexamples:
          - "blue"
          - "green"

  type: object
  required: [users]
  properties:
    users:
      type: object
      $uid:
        properties:
          favourite_colour:
            $red: { $ref: "#/definitions/red" }

Output when running this:

♪  firebase-schema git:(master) ✗ blaze test.yaml -v
args { _: [ 'test.yaml' ], v: true, java: false, '$0': 'blaze' }
transpiling test.yaml

input:
{"schema":{"definitions":{"red":{"type":"string","constraint":"next.val().matches(/red/)","examples":["red"],"nonexamples":["blue","green"]}},"type":"object","required":["users"],"properties":{"users":{"type":"object","$uid":{"properties":{"favourite_colour":{"$red":{"$ref":"#/definitions/red"}}}}}}}}
Warning:  no access list defined, this Firebase will be inactive
loading type any definition from /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/../schema/metaschema/any.yaml
loading type boolean definition from /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/../schema/metaschema/boolean.yaml
loading type number definition from /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/../schema/metaschema/number.yaml
loading type object definition from /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/../schema/metaschema/object.yaml
loading type string definition from /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/../schema/metaschema/string.yaml
annotate_schema { type: 'object',
  '$uid': { properties: { favourite_colour: [Object] } } }
annotate_schema { '$red': { '$ref': '#/definitions/red' } }
fetchRef#/definitions/red retrieved {"type":"string","constraint":"next.val().matches(/red/)","examples":["red"],"nonexamples":["blue","green"]}
annotate_schema { type: 'string',
  constraint: 'next.val().matches(/red/)',
  examples: [ 'red' ],
  nonexamples: [ 'blue', 'green' ] }
compile string
addConstraint !next.exists() || next.isString()
getField on enum result: undefined { type: 'string',
  constraint: 'next.val().matches(/red/)',
  examples: [ 'red' ],
  nonexamples: [ 'blue', 'green' ] }
error line 9:13
blue
Error: nonexample erroneously passed
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:408:88
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:262:13
    at LinkedList.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/util/Collections.ts:635:17)
    at JArray.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:261:23)
    at annotate_schema (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:405:28)
    at annotate_schema (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:310:43)
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/src/schema.ts:298:53
    at /Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:329:13
    at Dictionary.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/util/Collections.ts:891:27)
    at JObject.forEach (/Users/nicholasf/.nvm/versions/node/v0.12.7/lib/node_modules/blaze_compiler/node_modules/source-processor/src/source-processor.ts:328:25)

I'm new to blaze compiler so it may be something I've done.

Thanks,
Nicholas

Validation vs Write

Is there a reason that constraints are compiled to long write rules instead of validate rules at different levels? It seems that would create a smaller output file.

additionalProperties value not being inherited from definitions.

Hey there,

Full disclosure: I'm trying to get my first big project spec'd out in blaze, so it's definitely possible I'm doing this wrong. However, I'm not seeing additionalProperties be passed down from a definition.

Here's a stripped down rules.yml to illustrate

schema:
    definitions:
        user:
            type: object
            additionalProperties: false
            properties:
                first_name: {type: string}

    type: object
    properties:
        users: 
            type: object
            additionalProperties: false
            $userid: 
                type: object
                additionalProperties: false
                properties:
                    profile: {$ref: "#/definitions/user" }

            examples:
                - {"123": {"profile": {first_name: "Sally"}}}
            nonexamples:
                - {"123": {"profile": {first_name: "Sally", "bonus_data": "some"}}}


access:
    -   location:   "/"
        read:       true
        write:      false

I'd expect this to pass, but instead, I get:

error line 23:19
{ '123': { profile: { first_name: 'Sally', bonus_data: 'some' } } }
nonexample erroneously passed

Do I have this formatted incorrectly / have wrong assumptions? Or is there a bug lurking?

Many thanks!

Maybe less logging by default?

$ cat rules.yaml
schema:
  type: object
  additionalProperties: false
  properties:
    foo:
      type: string
$ blaze rules.yaml|&wc -l
1407

1407 lines of output seems excessive for a compiler that isn't even reporting errors.

Can I turn off the complex part of Blaze?

First what I love about blaze: dry and short security rules. Apart from this this, I can see the value of the schema definition, though we are not currently using it.

What I don't like is that blaze tries to be too smart when decided to overcame the limits of the firebase security rules by mixing rules between ascendents and descendants. It is buggy (the latest 0.0.21 is the first version that "worked" for us), it is mostly useless (I might be wrong, but it's logical inconsistent) and introduces unnecessary complexity.

The low level security parsing, though limited, it is simple, robust, thus manageable.

I'ld love to have a blaze compiler just to define the plain old read/write/validate rules in a dry manner. Meanwhile I'll stick to managing a huge, ugly rules.json file.

Cheers.

Output full of duplicate logic

Compiling default_rules.yaml in the examples yields this:

{
  "rules":{
    ".write":"true&&(false||true)",
    ".read":"false||true"
  }
}

I would have expected something closer to this:

{
  "rules":{
    ".write":"true",
    ".read":"false"
  }
}

It's significantly worse with more complex rule files.

Function at constraint different than function body at constraint

I have two cases:

functions:
  - createOrUpdate(): next.exists() && prev.exists() || !prev.exists()

schema:
  properties:
    menus:
      $user_id:
        $menu_id:
          properties:
            days:
              constraint: next.exists() && prev.exists() || !prev.exists()

access:
  - location: 'menu/$user_id/$menu_id'
  - write: true

Which generates:

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "menus": {
      ".write":"((false))",
      ".read":"(auth!==null)",
      "$user_id": {
        ".write":"((false))",
        ".read":"(auth!==null)",
        "$menu_id": {
          ".write":"((((newData.child('days').exists()&&data.child('days').exists()||!data.child('days').exists()))))",
          ".read":"((auth!==null))",
          "days": {
            ".write":"(((newData.exists()&&data.exists()||!data.exists())))",
            ".read":"((auth!==null))"
          }
        }
      }
    }
}

However if I replace the constraint with the function I get a different output. I don't understand, why?

functions:
  - createOrUpdate(): next.exists() && prev.exists() || !prev.exists()
schema:
  properties:
    dev: {}
    lists:
      type: object
      $list_name:
        type: object
        $list_item: {}

    menus:
      $user_id:
        $menu_id:
          properties:
            days:
              constraint: createOrUpdate()
{
  "rules":{
    ".write":"false",
    ".read":"false",
   "menus": {
      ".write":"((false))",
      ".read":"(auth!==null)",
      "$user_id": {
        ".write":"((false))",
        ".read":"(auth!==null)",
        "$menu_id": {
          ".write":"((((newData.exists()&&data.exists()||!data.exists()))))",
          ".read":"((auth!==null))",
          "days": {
            ".write":"(((newData.exists()&&data.exists()||!data.exists())))",
            ".read":"((auth!==null))"
          }
        }
      }
    }
}

Acces rules of wilderchild are ignored

Given:

functions:
  - isWorkspaceRole(userId, workspaceId, role): root['/rels/workspaces/users/'][workspaceId][userId]['role'] == role

schema:
  properties:
    rels:
      properties:
        workspaces:
          properties:
            users:
              $workspace_id:
                ~$user_id: {}

access:
  - location: /rels/workspaces/users/$workspace_id/$user_id
    write:    isWorkspaceRole(auth.id, $workspace_id, 'administrator')

Results in:

{
  "rules":{
    ".write":"false",
    ".read":"false",
    "rels": {
      ".write":"false",
      ".read":"false",
      "workspaces": {
        ".write":"false",
        ".read":"false",
        "users": {
          ".write":"false",
          ".read":"false",
          "$workspace_id": {
            ".write":"false",
            ".read":"false",
            "$user_id": {
              ".write":"false",
              ".validate":"false",
              ".read":"false"
            }
          }
        }
      }
    }
  }
}

However the access rule is considered if I turn ~$user into $user.

Producing global read/write rules

Just getting started with a simple rule set:

functions:
  - isLoggedIn(): auth.id !== null

schema: {}

access:
  - location: dashboard/$slug/
    read:  isLoggedIn() && prev.user === auth.uid
    write: false

Produces:

{
  "rules":{
    ".write":"false",
    ".read":"false"
  }
}

What happened to the write rules?

This is what I'm trying to recreate:

{
    "rules": {
        "dashboard": {
          "$slug": {
            ".read": "data.child('user').val() === auth.uid",
            ".write": false
          }
        }
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.