Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ os:
- osx

env:
- V=0.9.0
- V=0.14.1

before_install:
- OS=linux
Expand Down
4 changes: 4 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ py_test(
"$(location @com_google_protobuf//:protoc)",
"$(location //proto:firebase_rules_options_proto_file)",
"$(location @com_google_protobuf//:descriptor_proto)",
"$(location //example:example.rules)",
"$(location //example/testdata:golden.rules)",
"$(locations :testdata)",
],
data = [
"//example/testdata:golden.rules",
"//example:example.rules",
":testdata",
"//firebase_rules_generator:protoc-gen-firebase_rules",
"//proto:firebase_rules_options_proto_file",
Expand Down
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function istutorial_Person_PhoneTypeEnum(resource) {
// Start your rules...
```

## Building
## Standalone usage

1) Install [Bazel](http://www.bazel.io/docs/install.html).

Expand All @@ -194,6 +194,41 @@ function istutorial_Person_PhoneTypeEnum(resource) {
3) A sample invocation of the plugin, `protoc-gen-firebase_rules`, is available
in `example_usage.sh`. This script can be run from the command line.

## Using with bazel

It's easy to use protobuf_rules_gen if your project already uses Bazel.

1) Add protobuf_rules_gen to your WORKSPACE:

```python
proto_gen_firebase_rules_commit = "TODO"
http_archive(
name = "proto_gen_firebase_rules",
sha256 = "TODO",
strip_prefix = "protobuf-rules-gen-" + proto_gen_firebase_rules_commit,
url = "http://github.com/FirebaseExtended/protobuf-rules-gen/archive/" + proto_gen_firebase_rules_commit + ".tar.gz",
)

load("@proto_gen_firebase_rules//bazel:repositories.bzl", "protobuf_rules_gen_repositories")
protobuf_rules_gen_repositories()
```

2) Update your BUILD file:
```python
load("@proto_gen_firebase_rules//bazel:defs.bzl", "firestore_rules_proto_library", "firestore_rules_binary")
```

There are three rules available:
- firestore_rules_proto_library generates a .rules file from the protobuf
schema
- firestore_rules_binary combines multiple .rules files (e.g. the auto
generated rules with your ACLs that use them)
- firestore_rules_library wraps up one or more .rules files so that a
firestore_rules_binary can depend on it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this doesn't format nicely. Try s/-/*/?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping - these still aren't printing nicely.


See example/BUILD for an example of how to use these rules.


## Releasing

1) Build the `proto-gen-firebase_rules` binary via `bazel build //...`
Expand Down
16 changes: 2 additions & 14 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
workspace(name = "proto_gen_firebase_rules")

protobuf_commit = "099d99759101c295244c24d8954ec85b8ac65ce3"
load("//bazel:repositories.bzl", "protobuf_rules_gen_repositories")

http_archive(
name = "com_google_protobuf",
sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0",
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)

http_archive(
name = "com_google_protobuf_cc",
sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0",
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)
protobuf_rules_gen_repositories()
1 change: 1 addition & 0 deletions bazel/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

86 changes: 86 additions & 0 deletions bazel/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
load("@com_google_protobuf//:protobuf.bzl", "proto_gen")

def _outputs(srcs, ext):
return [s[:-len(".proto")] + ".pb" + ext for s in srcs]

def firestore_rules_library_impl(ctx):
files = depset(order="postorder", direct=ctx.files.srcs, transitive=[d.fsrules.files for d in ctx.attr.deps])
return struct(fsrules=struct(files=files))

def firestore_rules_binary_impl(ctx):
files = depset(order="postorder", direct=ctx.files.srcs, transitive=[d.fsrules.files for d in ctx.attr.deps])
args = ctx.actions.args()
args.add_all(files)
ctx.actions.run_shell(outputs=[ctx.outputs.bin], inputs=files, command='cat >"%s" "$@"' % ctx.outputs.bin.path, arguments=[args])

firestore_rules_library = rule(
attrs = {
"srcs": attr.label_list(
allow_files = True,
allow_empty = False,
),
"deps": attr.label_list(providers = ["fsrules"]),
},
implementation = firestore_rules_library_impl,
)

firestore_rules_binary = rule(
attrs = {
"srcs": attr.label_list(
allow_files = True,
allow_empty = False,
),
"deps": attr.label_list(providers = ["fsrules"]),
},
outputs = {
"bin": "%{name}.rules",
},
implementation = firestore_rules_binary_impl,
)

def firestore_rules_proto_library(
name,
srcs = [],
deps = [],
include = None,
protoc = "@com_google_protobuf//:protoc",
**kargs):
"""Bazel rule to generate firestore security rules from protobuf schema

Args:
name: the name of the firestore_rules_proto_library.
srcs: the .proto files of the firestore_rules_proto_library.
deps: a list of dependency labels; must be firestore_rules_proto_library.
include: a string indicating the include path of the .proto files.
protoc: the label of the protocol compiler to generate the sources.
**kargs: other keyword arguments that are passed to ts_library.

"""
outs = _outputs(srcs, ".rules")

includes = []
if include != None:
includes = [include]

plugin = "//firebase_rules_generator:protoc-gen-firebase_rules"

proto_gen(
name = name + "_genproto_rules",
srcs = srcs,
deps = [s + "_genproto_rules" for s in deps]+ ["//proto:firebase_rules_options_genproto_rules"],
includes = includes,
protoc = protoc,
outs = outs,
visibility = ["//visibility:public"],
plugin = plugin,
plugin_language = "protoc-gen-firebase_rules",
plugin_options = ["bazel"],
)

firestore_rules_library(
name = name,
srcs = outs,
deps = deps,
**kargs
)

13 changes: 13 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
protobuf_commit = "099d99759101c295244c24d8954ec85b8ac65ce3"

protobuf_sha256 = "c0ab1b088e220c1d56446f34001f0178e590270efdef1c46a77da4b9faa9d7b0"


def protobuf_rules_gen_repositories():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good pattern for deps is to allow someone to override them. See an example here: https://github.com/grpc/grpc/blob/master/bazel/grpc_deps.bzl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if "com_google_protobuf" not in native.existing_rules():
native.http_archive(
name = "com_google_protobuf",
sha256 = protobuf_sha256,
strip_prefix = "protobuf-" + protobuf_commit,
url = "https://github.com/google/protobuf/archive/" + protobuf_commit + ".tar.gz",
)
13 changes: 13 additions & 0 deletions example/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@proto_gen_firebase_rules//bazel:defs.bzl", "firestore_rules_proto_library", "firestore_rules_binary")

firestore_rules_proto_library(
name = "schema",
srcs = ["schema.proto"],
)

firestore_rules_binary(
name = "example",
srcs = ["main.rules"],
deps = [":schema"],
visibility = ["//visibility:public"],
)
9 changes: 9 additions & 0 deletions example/main.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow write: if isPersonMessage(request.resource.data) &&
request.auth.uid == userId;
}
}
}
19 changes: 19 additions & 0 deletions example/schema.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";
package example;

message Person {
string name = 1;
string email = 2;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

PhoneNumber phone = 3;
}
1 change: 1 addition & 0 deletions example/testdata/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports_files(["golden.rules"])
29 changes: 29 additions & 0 deletions example/testdata/golden.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @@START_GENERATED_FUNCTIONS@@
function isPersonMessage(resource) {
return resource.keys().hasAll([]) &&
(resource.keys().hasOnly(['phone','email','name'])) &&
((!resource.keys().hasAny(['name'])) || (resource.name is string)) &&
((!resource.keys().hasAny(['email'])) || (resource.email is string)) &&
((!resource.keys().hasAny(['phone'])) || (isPerson_PhoneNumberMessage(resource.phone)));
}
function isPerson_PhoneNumberMessage(resource) {
return resource.keys().hasAll([]) &&
(resource.keys().hasOnly(['type','number'])) &&
((!resource.keys().hasAny(['number'])) || (resource.number is string)) &&
((!resource.keys().hasAny(['type'])) || (isPerson_PhoneTypeEnum(resource.type)));
}
function isPerson_PhoneTypeEnum(resource) {
return resource == 0 ||
resource == 1 ||
resource == 2;
}
// @@END_GENERATED_FUNCTIONS@@
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow write: if isPersonMessage(request.resource.data) &&
request.auth.uid == userId;
}
}
}
3 changes: 2 additions & 1 deletion firebase_rules_generator/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

package(default_visibility = ["//visibility:public"])

cc_library(
name = "generator",
srcs = ["generator.cc"],
Expand All @@ -27,7 +29,6 @@ cc_binary(
name = "protoc-gen-firebase_rules",
srcs = ["main.cc"],
linkstatic = 1,
visibility = ["//visibility:public"],
deps = [
":generator",
"@com_google_protobuf//:protobuf",
Expand Down
12 changes: 11 additions & 1 deletion firebase_rules_generator/generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

#define RULES_FILE "firestore.rules"

using google::protobuf::StrCat;
using google::protobuf::StripSuffixString;

namespace google {
namespace firebase {
namespace rules {
Expand Down Expand Up @@ -214,7 +217,14 @@ bool RulesGenerator::Generate(const protobuf::FileDescriptor *file,
const std::string &parameter,
protobuf::compiler::GeneratorContext *context,
std::string *error) const {
protobuf::io::Printer printer(context->Open(RULES_FILE), '$');
std::string filename;
if (parameter == "bazel") {
filename = StrCat(StripSuffixString(file->name(), ".proto"),
".pb.rules");
} else {
filename = RULES_FILE;
}
protobuf::io::Printer printer(context->Open(filename), '$');

// Start by adding a comment
printer.Print("// @@START_GENERATED_FUNCTIONS@@\n");
Expand Down
11 changes: 10 additions & 1 deletion integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ def check_rules_output(test_file, expected, actual):
firebase_protos = path.dirname(sys.argv[3])
google_protos = path.dirname(sys.argv[4])

testdata = sys.argv[5:]
example_rules = sys.argv[5]
example_golden = sys.argv[6]

testdata = sys.argv[7:]


def run_testcase(proto_file, output):
Expand Down Expand Up @@ -82,3 +85,9 @@ def run_testcase(proto_file, output):
proto_file = check_proto_file(testdata[i])
rules_out = check_rules_file(testdata[i + 1])
run_testcase(proto_file, rules_out)


check_rules_output(
'example.rules',
read_file(example_golden),
read_file(example_rules))
19 changes: 19 additions & 0 deletions proto/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("@com_google_protobuf//:protobuf.bzl", "proto_gen")

proto_library(
name = "firebase_rules_options_proto",
srcs = [
Expand All @@ -14,3 +16,20 @@ filegroup(
],
visibility = ["//visibility:public"],
)

proto_gen(
name = "firebase_rules_options_genproto_rules",
srcs = [
"firebase_rules_options.proto",
],
protoc = "@com_google_protobuf//:protoc",
visibility = ["//visibility:public"],
deps = [":well_known_protos"],
)

proto_gen(
name = "well_known_protos",
srcs = ["@com_google_protobuf//:well_known_protos"],
protoc = "@com_google_protobuf//:protoc",
visibility = ["//visibility:public"],
)