├── .gitattributes ├── .github └── workflows │ ├── cla.yaml │ ├── lint.yaml │ ├── manual-client-update.yaml │ ├── release-client-update.yaml │ └── release.yaml ├── .pre-commit-config.yaml ├── .yamllint ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── DCO ├── LICENSE ├── Makefile ├── README.md ├── authzed └── api │ ├── materialize │ └── v0 │ │ ├── watchpermissions.proto │ │ └── watchpermissionsets.proto │ └── v1 │ ├── core.proto │ ├── debug.proto │ ├── error_reason.proto │ ├── experimental_service.proto │ ├── openapi.proto │ ├── permission_service.proto │ ├── schema_service.proto │ └── watch_service.proto ├── buf.gen.yaml ├── buf.lock ├── buf.md ├── buf.yaml ├── docs └── apidocs.swagger.json └── magefiles ├── dev.go ├── go.mod └── go.sum /.gitattributes: -------------------------------------------------------------------------------- 1 | buf.lock -diff linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "CLA" 3 | on: # yamllint disable-line rule:truthy 4 | issue_comment: 5 | types: 6 | - "created" 7 | pull_request_target: 8 | types: 9 | - "opened" 10 | - "closed" 11 | - "synchronize" 12 | merge_group: 13 | types: 14 | - "checks_requested" 15 | jobs: 16 | cla: 17 | name: "Check Signature" 18 | runs-on: "ubuntu-latest" 19 | steps: 20 | - uses: "authzed/actions/cla-check@main" 21 | with: 22 | github_token: "${{ secrets.GITHUB_TOKEN }}" 23 | cla_assistant_token: "${{ secrets.CLA_ASSISTANT_ACCESS_TOKEN }}" 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Lint" 3 | on: 4 | push: 5 | branches: 6 | - "!dependabot/*" 7 | - "*" 8 | pull_request: 9 | branches: 10 | - "*" 11 | types: 12 | # This makes the buf checks run again when attributes of the PRs change. 13 | - "ready_for_review" 14 | - "labeled" 15 | - "unlabeled" 16 | # These are the defaults 17 | - "opened" 18 | - "synchronize" 19 | - "reopened" 20 | 21 | jobs: 22 | lint: 23 | name: "Lint & Publish Draft/Branch" 24 | runs-on: "ubuntu-latest" 25 | steps: 26 | - uses: "actions/checkout@v4" 27 | - uses: "authzed/actions/yaml-lint@main" 28 | - uses: "bufbuild/buf-action@v1" 29 | with: 30 | token: "${{ secrets.BUF_REGISTRY_TOKEN }}" 31 | breaking_against: "https://github.com/authzed/api.git#branch=main" 32 | -------------------------------------------------------------------------------- /.github/workflows/manual-client-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Manually invoke client updates for API change" 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | buftag: 7 | description: "Tag or commit from https://buf.build/authzed/api/tags/main" 8 | required: true 9 | type: "string" 10 | jobs: 11 | trigger: 12 | runs-on: "ubuntu-latest" 13 | name: "📦 Trigger Client Updates" 14 | steps: 15 | - uses: "peter-evans/repository-dispatch@v2" 16 | name: "🕸️ Update authzed-node" 17 | with: 18 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 19 | repository: "authzed/authzed-node" 20 | event-type: "api_update" 21 | client-payload: '{"BUFTAG": "${{ inputs.buftag }}"}' 22 | - uses: "peter-evans/repository-dispatch@v2" 23 | name: "🐍 Update authzed-py" 24 | with: 25 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 26 | repository: "authzed/authzed-py" 27 | event-type: "api_update" 28 | client-payload: '{"BUFTAG": "${{ inputs.buftag }}"}' 29 | - uses: "peter-evans/repository-dispatch@v2" 30 | name: "💎 Update authzed-rb" 31 | with: 32 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 33 | repository: "authzed/authzed-rb" 34 | event-type: "api_update" 35 | client-payload: '{"BUFTAG": "${{ inputs.buftag }}"}' 36 | - uses: "peter-evans/repository-dispatch@v2" 37 | name: "☕ Update authzed-java" 38 | with: 39 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 40 | repository: "authzed/authzed-java" 41 | event-type: "api_update" 42 | client-payload: '{"BUFTAG": "${{ inputs.buftag }}"}' 43 | -------------------------------------------------------------------------------- /.github/workflows/release-client-update.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Client updates for released API change" 3 | on: 4 | release: 5 | types: ["published"] 6 | jobs: 7 | trigger: 8 | runs-on: "ubuntu-latest" 9 | name: "📦 Release Client Updates" 10 | if: "${{ contains(github.ref_name, 'v') }}" 11 | steps: 12 | - uses: "peter-evans/repository-dispatch@v2" 13 | name: "🕸️ Update authzed-node" 14 | with: 15 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 16 | repository: "authzed/authzed-node" 17 | event-type: "api_update" 18 | client-payload: '{"BUFTAG": "${{ github.ref_name }}"}' 19 | - uses: "peter-evans/repository-dispatch@v2" 20 | name: "🐍 Update authzed-py" 21 | with: 22 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 23 | repository: "authzed/authzed-py" 24 | event-type: "api_update" 25 | client-payload: '{"BUFTAG": "${{ github.ref_name }}"}' 26 | - uses: "peter-evans/repository-dispatch@v2" 27 | name: "💎 Update authzed-rb" 28 | with: 29 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 30 | repository: "authzed/authzed-rb" 31 | event-type: "api_update" 32 | client-payload: '{"BUFTAG": "${{ github.ref_name }}"}' 33 | - uses: "peter-evans/repository-dispatch@v2" 34 | name: "☕ Update authzed-java" 35 | with: 36 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 37 | repository: "authzed/authzed-java" 38 | event-type: "api_update" 39 | client-payload: '{"BUFTAG": "${{ github.ref_name }}"}' 40 | - uses: "peter-evans/repository-dispatch@v2" 41 | name: "🔵🥅 Update authzed-dotnet" 42 | with: 43 | token: "${{ secrets.EXTERNAL_REPO_TOKEN }}" 44 | repository: "authzed/authzed-dotnet" 45 | event-type: "api_update" 46 | client-payload: '{"BUFTAG": "${{ github.ref_name }}"}' 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Release" 3 | on: # yamllint disable-line rule:truthy 4 | push: 5 | tags: 6 | - "*" 7 | jobs: 8 | buf: 9 | name: "Push BSR tag" 10 | runs-on: "ubuntu-latest" 11 | steps: 12 | - uses: "actions/checkout@v4" 13 | - uses: "bufbuild/buf-action@v1" 14 | with: 15 | token: "${{ secrets.BUF_REGISTRY_TOKEN }}" 16 | breaking_against: "https://github.com/authzed/api.git#branch=main" 17 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: "https://github.com/bufbuild/buf" 4 | rev: "v1.54.0" 5 | hooks: 6 | - id: "buf-generate" 7 | - id: "buf-lint" 8 | - id: "buf-format" 9 | - repo: "https://github.com/adrienverge/yamllint.git" 10 | rev: "v1.37.1" 11 | hooks: 12 | - id: "yamllint" 13 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | # vim: ft=yaml 2 | --- 3 | yaml-files: 4 | - "*.yaml" 5 | - "*.yml" 6 | - ".yamllint" 7 | extends: "default" 8 | rules: 9 | quoted-strings: "enable" 10 | line-length: "disable" 11 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 2 | 3 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 4 | 5 | Examples of unacceptable behavior by participants include: 6 | 7 | - The use of sexualized language or imagery 8 | - Personal attacks 9 | - Trolling or insulting/derogatory comments 10 | - Public or private harassment 11 | - Publishing other’s private information, such as physical or electronic addresses, without explicit permission 12 | - Other unethical or unprofessional conduct 13 | 14 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. 15 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. 16 | Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the Contributor Covenant, version 1.2.0, available at https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Communication 4 | 5 | - Issues: [GitHub](https://github.com/authzed/authzed-go/issues) 6 | - Email: [Google Groups](https://groups.google.com/g/authzed-oss) 7 | - Discord: [Zanzibar Discord](https://discord.gg/jTysUaxXzM) 8 | 9 | All communication must follow our [Code of Conduct]. 10 | 11 | [Code of Conduct]: CODE-OF-CONDUCT.md 12 | 13 | ## Creating issues 14 | 15 | If any part of the project has a bug or documentation mistakes, please let us know by opening an issue. 16 | All bugs and mistakes are considered very seriously, regardless of complexity. 17 | 18 | Before creating an issue, please check that an issue reporting the same problem does not already exist. 19 | To make the issue accurate and easy to understand, please try to create issues that are: 20 | 21 | - Unique -- do not duplicate existing bug report. 22 | Deuplicate bug reports will be closed. 23 | - Specific -- include as much details as possible: which version, what environment, what configuration, etc. 24 | - Reproducible -- include the steps to reproduce the problem. 25 | Some issues might be hard to reproduce, so please do your best to include the steps that might lead to the problem. 26 | - Isolated -- try to isolate and reproduce the bug with minimum dependencies. 27 | It would significantly slow down the speed to fix a bug if too many dependencies are involved in a bug report. 28 | Debugging external systems that rely on this project is out of scope, but guidance or help using the project itself is fine. 29 | - Scoped -- one bug per report. 30 | Do not follow up with another bug inside one report. 31 | 32 | It may be worthwhile to read [Elika Etemad’s article on filing good bug reports][filing-good-bugs] before creating a bug report. 33 | 34 | Maintainers might ask for further information to resolve an issue. 35 | 36 | [filing-good-bugs]: http://fantasai.inkedblade.net/style/talks/filing-good-bugs/ 37 | 38 | ## Contribution flow 39 | 40 | This is a rough outline of what a contributor's workflow looks like: 41 | 42 | - Create an issue 43 | - Fork the project 44 | - Create a branch from where to base the contribution -- this is almost always `main` 45 | - Push changes into a branch of your fork 46 | - Submit a pull request 47 | - Respond to feedback from project maintainers 48 | 49 | Creating new issues is one of the best ways to contribute. 50 | You have no obligation to offer a solution or code to fix an issue that you open. 51 | If you do decide to try and contribute something, please submit an issue first so that a discussion can occur to avoid any wasted efforts. 52 | 53 | ## Legal requirements 54 | 55 | In order to protect both you and ourselves, all commits will require an explicit sign-off that acknowledges the [DCO]. 56 | 57 | Sign-off commits end with the following line: 58 | 59 | ``` 60 | Signed-off-by: Random J Developer 61 | ``` 62 | 63 | This can be done by using the `--signoff` (or `-s` for short) git flag to append this automatically to your commit message. 64 | If you have already authored a commit that is missing the signed-off, you can amend or rebase your commits and force push them to GitHub. 65 | 66 | [DCO]: /DCO 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: ## Show this help 2 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 3 | 4 | install: ## Install dependencies 5 | brew install pre-commit 6 | brew install bufbuild/buf/buf 7 | pre-commit install 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authzed API 2 | 3 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg "Apache 2.0 License")](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![Docs](https://img.shields.io/badge/docs-authzed.com-%234B4B6C "Authzed Documentation")](https://authzed.com/docs) 5 | [![Build Status](https://github.com/authzed/api/workflows/Lint/badge.svg "GitHub Actions")](https://github.com/authzed/api/actions) 6 | [![Discord Server](https://img.shields.io/discord/844600078504951838?color=7289da&logo=discord "Discord Server")](https://authzed.com/discord) 7 | [![Twitter](https://img.shields.io/twitter/follow/authzed?color=%23179CF0&logo=twitter&style=flat-square "@authzed on Twitter")](https://twitter.com/authzed) 8 | 9 | This project contains the definitions of [Protocol Buffers] used by Authzed. 10 | 11 | We use [Buf] to distribute these definitions and generate source code from them. The definitions are published in [Buf Registry Authzed API repository]. 12 | 13 | You can find more info on [HTTP API usage] and the [versioning and deprecation policy] in the [Authzed Docs]. 14 | 15 | You can also use our [Postman collection] to explore the API. 16 | 17 | See [CONTRIBUTING.md] for instructions on how to contribute. 18 | 19 | [Protocol Buffers]: https://developers.google.com/protocol-buffers/ 20 | [Buf]: https://github.com/bufbuild/buf 21 | [HTTP API usage]: https://authzed.com/docs/spicedb/getting-started/client-libraries#http-clients 22 | [Authzed Docs]: https://authzed.com/docs 23 | [versioning and deprecation policy]: https://authzed.com/blog/buf 24 | [Postman collection]: (https://www.postman.com/authzed/spicedb/collection/m26cqyc) 25 | [Buf Registry Authzed API repository]: https://buf.build/authzed/api/docs/main 26 | [CONTRIBUTING.md]: https://github.com/authzed/api/blob/main/CONTRIBUTING.md 27 | 28 | ## Development 29 | 30 | You can run `mage` to see the available commands for development. We assume you have a Mac computer. 31 | 32 | ## Warnings ⚠️ 33 | 34 | - The `version` field found in various buf YAML configurations is actually the version of the schema of the YAML file and is not related to the version of the definitions. 35 | -------------------------------------------------------------------------------- /authzed/api/materialize/v0/watchpermissions.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.materialize.v0; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | 6 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/materialize/v0"; 7 | option java_multiple_files = true; 8 | option java_package = "com.authzed.api.materialize.v0"; 9 | 10 | service WatchPermissionsService { 11 | // WatchPermissions returns a stream of PermissionChange events for the given permissions. 12 | // 13 | // WatchPermissions is a long-running RPC, and will stream events until the client 14 | // closes the connection or the server terminates the stream. The consumer is responsible of 15 | // keeping track of the last seen revision and resuming the stream from that point in the event 16 | // of disconnection or client-side restarts. 17 | // 18 | // The API does not offer a sharding mechanism and thus there should only be one consumer per target system. 19 | // Implementing an active-active HA consumer setup over the same target system will require coordinating which 20 | // revisions have been consumed in order to prevent transitioning to an inconsistent state. 21 | // 22 | // Usage of WatchPermissions requires to be explicitly enabled on the service, including the permissions to be 23 | // watched. It requires more resources and is less performant than WatchPermissionsSets. It's usage 24 | // is only recommended when performing the set intersections of WatchPermissionSets in the client side is not viable 25 | // or there is a strict application requirement to use consume the computed permissions. 26 | rpc WatchPermissions(WatchPermissionsRequest) returns (stream WatchPermissionsResponse) {} 27 | } 28 | 29 | message WatchPermissionsRequest { 30 | // permissions is a list of permissions to watch for changes. At least one permission must be specified, and it must 31 | // be a subset or equal to the permissions that were enabled for the service. 32 | repeated WatchedPermission permissions = 1; 33 | // optional_starting_after is the revision token to start watching from. If not provided, the stream 34 | // will start from the current revision at the moment of the request. 35 | authzed.api.v1.ZedToken optional_starting_after = 2; 36 | } 37 | 38 | message WatchedPermission { 39 | // resource_type is the type of the resource to watch for changes. 40 | string resource_type = 1; 41 | // permission is the permission to watch for changes. 42 | string permission = 2; 43 | // subject_type is the type of the subject to watch for changes. 44 | string subject_type = 3; 45 | // optional_subject_relation is the relation on the subject to watch for changes. 46 | string optional_subject_relation = 4; 47 | } 48 | 49 | message WatchPermissionsResponse { 50 | oneof response { 51 | // change is the computed permission delta that has occurred as result of a mutation in origin SpiceDB. 52 | // The consumer should apply this change to the current state of the computed permissions in their target system. 53 | // Once an event arrives with completed_revision instead, the consumer shall consider there are not more changes 54 | // originating from that revision. 55 | // 56 | // The consumer should keep track of the revision in order to resume streaming in the event of consumer restarts. 57 | PermissionChange change = 1; 58 | 59 | // completed_revision is the revision token that indicates all changes originating from a revision have been 60 | // streamed and thus the revision should be considered completed. It may also be 61 | // received without accompanying set of changes, indicating that a mutation in the origin SpiceDB cluster did 62 | // not yield any effective changes in the computed permissions 63 | authzed.api.v1.ZedToken completed_revision = 2; 64 | } 65 | } 66 | 67 | message PermissionChange { 68 | enum Permissionship { 69 | PERMISSIONSHIP_UNSPECIFIED = 0; 70 | PERMISSIONSHIP_NO_PERMISSION = 1; 71 | PERMISSIONSHIP_HAS_PERMISSION = 2; 72 | PERMISSIONSHIP_CONDITIONAL_PERMISSION = 3; 73 | } 74 | 75 | // revision represents the revision at which the change occurred. 76 | authzed.api.v1.ZedToken revision = 1; 77 | 78 | // resource is the resource that the permission change is related to. 79 | authzed.api.v1.ObjectReference resource = 2; 80 | // permission is the permission that has changed. 81 | string permission = 3; 82 | // subject is the subject that the permission change is related to. 83 | authzed.api.v1.SubjectReference subject = 4; 84 | // permissionship is the new permissionship of the subject over the resource after the change. 85 | Permissionship permissionship = 5; 86 | } 87 | -------------------------------------------------------------------------------- /authzed/api/materialize/v0/watchpermissionsets.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.materialize.v0; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | 6 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/materialize/v0"; 7 | option java_multiple_files = true; 8 | option java_package = "com.authzed.api.materialize.v0"; 9 | 10 | service WatchPermissionSetsService { 11 | // WatchPermissionSets returns a stream of changes to the sets which can be used to compute the watched permissions. 12 | // 13 | // WatchPermissionSets lets consumers achieve the same thing as WatchPermissions, but trades off a simpler usage model with 14 | // significantly lower computational requirements. Unlike WatchPermissions, this method returns changes to the sets of permissions, 15 | // rather than the individual permissions. Permission sets are a normalized form of the computed permissions, which 16 | // means that the consumer must perform an extra computation over this representation to obtain the final computed 17 | // permissions, typically by intersecting the provided sets. 18 | // 19 | // For example, this would look like a JOIN between the 20 | // materialize permission sets table in a target relation database, the table with the resources to authorize access 21 | // to, and the table with the subject (e.g. a user). 22 | // 23 | // In exchange, the number of changes issued by WatchPermissionSets will be several orders of magnitude less than those 24 | // emitted by WatchPermissions, which has several implications: 25 | // - significantly less resources to compute the sets 26 | // - significantly less messages to stream over the network 27 | // - significantly less events to ingest on the consumer side 28 | // - less ingestion lag from the origin SpiceDB mutation 29 | // 30 | // The type of scenarios WatchPermissionSets is particularly well suited is when a single change 31 | // in the origin SpiceDB can yield millions of changes. For example, in the GitHub authorization model, assigning a role 32 | // to a top-level team of an organization with hundreds of thousands of employees can lead to an explosion of 33 | // permission change events that would require a lot of computational resources to process, both on Materialize and 34 | // the consumer side. 35 | // 36 | // WatchPermissionSets is thus recommended for any larger scale use case where the fan-out in permission changes that 37 | // emerges from a specific schema and data shape is too large to handle effectively. 38 | // 39 | // The API does not offer a sharding mechanism and thus there should only be one consumer per target system. 40 | // Implementing an active-active HA consumer setup over the same target system will require coordinating which 41 | // revisions have been consumed in order to prevent transitioning to an inconsistent state. 42 | rpc WatchPermissionSets(WatchPermissionSetsRequest) returns (stream WatchPermissionSetsResponse) {} 43 | 44 | // LookupPermissionSets returns the current state of the permission sets which can be used to derive the computed permissions. 45 | // It's typically used to backfill the state of the permission sets in the consumer side. 46 | // 47 | // It's a cursored API and the consumer is responsible to keep track of the cursor and use it on each subsequent call. 48 | // Each stream will return permission sets defined by the specified request limit. The server will keep streaming until 49 | // the sets per stream is hit, or the current state of the sets is reached, 50 | // whatever happens first, and then close the stream. The server will indicate there are no more changes to stream 51 | // through the `completed_members` in the cursor. 52 | // 53 | // There may be many elements to stream, and so the consumer should be prepared to resume the stream from the last 54 | // cursor received. Once completed, the consumer may start streaming permission set changes using WatchPermissionSets 55 | // and the revision token from the last LookupPermissionSets response. 56 | rpc LookupPermissionSets(LookupPermissionSetsRequest) returns (stream LookupPermissionSetsResponse) {} 57 | } 58 | 59 | message WatchPermissionSetsRequest { 60 | // optional_starting_after is used to specify the SpiceDB revision to start watching from. 61 | // If not specified, the watch will start from the current SpiceDB revision time of the request ("head revision"). 62 | authzed.api.v1.ZedToken optional_starting_after = 1; 63 | } 64 | 65 | message WatchPermissionSetsResponse { 66 | oneof response { 67 | // change is the permission set delta that has occurred as result of a mutation in origin SpiceDB. 68 | // The consumer should apply this change to the current state of the permission sets in their target system. 69 | // Once an event arrives with completed_revision instead, the consumer shall consider the set of 70 | // changes originating from that revision completed. 71 | // 72 | // The consumer should keep track of the revision in order to resume streaming in the event of consumer restarts. 73 | PermissionSetChange change = 1; 74 | 75 | // completed_revision is the revision token that indicates the completion of a set of changes. It may also be 76 | // received without accompanying set of changes, indicating that a mutation in the origin SpiceDB cluster did 77 | // not yield any effective changes in the permission sets 78 | authzed.api.v1.ZedToken completed_revision = 2; 79 | 80 | // lookup_permission_sets_required is a signal that the consumer should perform a LookupPermissionSets call because 81 | // the permission set snapshot needs to be rebuilt from scratch. This typically happens when the origin SpiceDB 82 | // cluster has seen its schema changed. 83 | LookupPermissionSetsRequired lookup_permission_sets_required = 3; 84 | 85 | // breaking_schema_change is a signal that a breaking schema change has been written to the origin SpiceDB cluster, 86 | // and that the consumer should expect delays in the ingestion of new changes, 87 | // because the permission set snapshot needs to be rebuilt from scratch. Once the snapshot is ready, the consumer 88 | // will receive a LookupPermissionSetsRequired event. 89 | BreakingSchemaChange breaking_schema_change = 4; 90 | } 91 | } 92 | 93 | message Cursor { 94 | // limit is the number of permission sets to stream over a single LookupPermissionSets call that was requested. 95 | uint32 limit = 1; 96 | // token is the snapshot revision at which the cursor was computed. 97 | authzed.api.v1.ZedToken token = 4; 98 | // starting_index is an offset of the permission set represented by this cursor 99 | uint32 starting_index = 5; 100 | // completed_members is a boolean flag that indicates that the cursor has reached the end of the permission sets 101 | bool completed_members = 6; 102 | // starting_key is a string cursor used by some backends to resume the stream from a specific point. 103 | string starting_key = 7; 104 | // cursor is a string-encoded internal materialize cursor. 105 | string cursor = 8; 106 | } 107 | 108 | message LookupPermissionSetsRequest { 109 | // limit is the number of permission sets to stream over a single LookupPermissionSets. Once the limit is reached, 110 | // the server will close the stream. If more permission sets are available, the consume should open a new stream 111 | // providing optional_starting_after_cursor, using the cursor from the last response. 112 | uint32 limit = 1; 113 | // optional_at_revision specifies the client is requesting to lookup PermissionSets at a specific revision. It's 114 | // optional, and if not provided, PermissionSets will be looked up at the current revision. The cursor always 115 | // takes precedence in defining the revision when present. 116 | authzed.api.v1.ZedToken optional_at_revision = 2; 117 | // optional_starting_after_cursor is used to specify the offset to start streaming permission sets from. 118 | Cursor optional_starting_after_cursor = 4; 119 | } 120 | 121 | message LookupPermissionSetsResponse { 122 | // change represents the permission set delta necessary to transition an uninitialized target system to 123 | // a specific snapshot revision. In practice it's not different from the WatchPermissionSetsResponse.change, except 124 | // all changes will be of time SET_OPERATION_ADDED because it's assumed there is no known previous state. 125 | // 126 | // Applying the deltas to a previously initialized target system would yield incorrect results. 127 | PermissionSetChange change = 1; 128 | // cursor points to a specific permission set in a revision. 129 | // The consumer should keep track of the cursor in order to resume streaming in the event of consumer restarts. This 130 | // is particularly important in backfill scenarios that may take hours or event days to complete. 131 | Cursor cursor = 2; 132 | } 133 | 134 | message PermissionSetChange { 135 | enum SetOperation { 136 | SET_OPERATION_UNSPECIFIED = 0; 137 | SET_OPERATION_ADDED = 1; 138 | SET_OPERATION_REMOVED = 2; 139 | } 140 | 141 | // revision represents the revision at which the permission set change occurred. 142 | authzed.api.v1.ZedToken at_revision = 1; 143 | // operation represents the type of set operation that took place as part of the change 144 | SetOperation operation = 2; 145 | // parent_set represents the permission set parent of either another set or a member 146 | SetReference parent_set = 3; 147 | 148 | oneof child { 149 | // child_set represents the scenario where another set is considered member of the parent set 150 | SetReference child_set = 4; 151 | // child_member represents the scenario where an specific object is considered member of the parent set 152 | MemberReference child_member = 5; 153 | } 154 | } 155 | 156 | message SetReference { 157 | // object_type is the type of object in a permission set 158 | string object_type = 1; 159 | // object_id is the ID of a permission set 160 | string object_id = 2; 161 | // permission_or_relation is the permission or relation referenced by this permission set 162 | string permission_or_relation = 3; 163 | } 164 | 165 | message MemberReference { 166 | // object_type is the type of object of a permission set member 167 | string object_type = 1; 168 | // object_id is the ID of a permission set member 169 | string object_id = 2; 170 | // optional_permission_or_relation is the permission or relation referenced by this permission set member 171 | string optional_permission_or_relation = 3; 172 | } 173 | 174 | // LookupPermissionSetsRequired is a signal that the consumer should perform a LookupPermissionSets call because 175 | // the permission set snapshot needs to be rebuilt from scratch. This typically happens when the origin SpiceDB 176 | // cluster has seen its schema changed, see BreakingSchemaChange event. 177 | message LookupPermissionSetsRequired { 178 | // required_lookup_at is the snapshot revision at which the permission set needs to be rebuilt to. 179 | authzed.api.v1.ZedToken required_lookup_at = 1; 180 | } 181 | 182 | // BreakingSchemaChange is used to signal a breaking schema change has happened, and that the consumer should 183 | // expect delays in the ingestion of new changes, because the permission set snapshot needs to be rebuilt from scratch. 184 | // Once the snapshot is ready, the consumer will receive a LookupPermissionSetsRequired event. 185 | message BreakingSchemaChange { 186 | // change_at is the revision at which a breaking schema event has happened. 187 | authzed.api.v1.ZedToken change_at = 1; 188 | } 189 | -------------------------------------------------------------------------------- /authzed/api/v1/core.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "buf/validate/validate.proto"; 5 | import "google/protobuf/struct.proto"; 6 | import "google/protobuf/timestamp.proto"; 7 | import "validate/validate.proto"; 8 | 9 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 10 | option java_multiple_files = true; 11 | option java_package = "com.authzed.api.v1"; 12 | 13 | // Relationship specifies how a resource relates to a subject. Relationships 14 | // form the data for the graph over which all permissions questions are 15 | // answered. 16 | message Relationship { 17 | // resource is the resource to which the subject is related, in some manner 18 | ObjectReference resource = 1 [ 19 | (validate.rules).message.required = true, 20 | (buf.validate.field).required = true 21 | ]; 22 | 23 | // relation is how the resource and subject are related. 24 | string relation = 2 [ 25 | (validate.rules).string = { 26 | pattern: "^[a-z][a-z0-9_]{1,62}[a-z0-9]$" 27 | max_bytes: 64 28 | }, 29 | (buf.validate.field).string = { 30 | pattern: "^[a-z][a-z0-9_]{1,62}[a-z0-9]$" 31 | max_bytes: 64 32 | } 33 | ]; 34 | 35 | // subject is the subject to which the resource is related, in some manner. 36 | SubjectReference subject = 3 [ 37 | (validate.rules).message.required = true, 38 | (buf.validate.field).required = true 39 | ]; 40 | 41 | // optional_caveat is a reference to a the caveat that must be enforced over the relationship 42 | ContextualizedCaveat optional_caveat = 4 [ 43 | (validate.rules).message.required = false, 44 | (buf.validate.field).required = false 45 | ]; 46 | 47 | // optional_expires_at is the time at which the relationship expires, if any. 48 | google.protobuf.Timestamp optional_expires_at = 5; 49 | } 50 | 51 | // ContextualizedCaveat represents a reference to a caveat to be used by caveated relationships. 52 | // The context consists of key-value pairs that will be injected at evaluation time. 53 | // The keys must match the arguments defined on the caveat in the schema. 54 | message ContextualizedCaveat { 55 | // caveat_name is the name of the caveat expression to use, as defined in the schema 56 | string caveat_name = 1 [ 57 | (validate.rules).string = { 58 | pattern: "^([a-zA-Z0-9_][a-zA-Z0-9/_|-]{0,127})$" 59 | max_bytes: 128 60 | }, 61 | (buf.validate.field).string = { 62 | pattern: "^([a-zA-Z0-9_][a-zA-Z0-9/_|-]{0,127})$" 63 | max_bytes: 128 64 | } 65 | ]; 66 | 67 | // context consists of any named values that are defined at write time for the caveat expression 68 | google.protobuf.Struct context = 2 [ 69 | (validate.rules).message.required = false, 70 | (buf.validate.field).required = false 71 | ]; 72 | } 73 | 74 | // SubjectReference is used for referring to the subject portion of a 75 | // Relationship. The relation component is optional and is used for defining a 76 | // sub-relation on the subject, e.g. group:123#members 77 | message SubjectReference { 78 | ObjectReference object = 1 [ 79 | (validate.rules).message.required = true, 80 | (buf.validate.field).required = true 81 | ]; 82 | string optional_relation = 2 [ 83 | (validate.rules).string = { 84 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 85 | max_bytes: 64 86 | }, 87 | (buf.validate.field).string = { 88 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 89 | max_bytes: 64 90 | } 91 | ]; 92 | } 93 | 94 | // ObjectReference is used to refer to a specific object in the system. 95 | message ObjectReference { 96 | string object_type = 1 [ 97 | (validate.rules).string = { 98 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 99 | max_bytes: 128 100 | }, 101 | (buf.validate.field).string = { 102 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 103 | max_bytes: 128 104 | } 105 | ]; 106 | string object_id = 2 [ 107 | (validate.rules).string = { 108 | pattern: "^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)$" 109 | max_bytes: 1024 110 | }, 111 | (buf.validate.field).string = { 112 | pattern: "^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)$" 113 | max_bytes: 1024 114 | } 115 | ]; 116 | } 117 | 118 | // ZedToken is used to provide causality metadata between Write and Check 119 | // requests. 120 | // 121 | // See the authzed.api.v1.Consistency message for more information. 122 | message ZedToken { 123 | string token = 1 [ 124 | (validate.rules).string = {min_bytes: 1}, 125 | (buf.validate.field).string = {min_bytes: 1} 126 | ]; 127 | } 128 | 129 | // Cursor is used to provide resumption of listing between calls to APIs 130 | // such as LookupResources. 131 | message Cursor { 132 | string token = 1 [ 133 | (validate.rules).string = { 134 | min_bytes: 1 135 | max_bytes: 102400 136 | }, 137 | (buf.validate.field).string = { 138 | min_bytes: 1 139 | max_bytes: 102400 140 | } 141 | ]; 142 | } 143 | 144 | // RelationshipUpdate is used for mutating a single relationship within the 145 | // service. 146 | // 147 | // CREATE will create the relationship only if it doesn't exist, and error 148 | // otherwise. 149 | // 150 | // TOUCH will upsert the relationship, and will not error if it 151 | // already exists. 152 | // 153 | // DELETE will delete the relationship. If the relationship does not exist, 154 | // this operation will no-op. 155 | message RelationshipUpdate { 156 | enum Operation { 157 | OPERATION_UNSPECIFIED = 0; 158 | OPERATION_CREATE = 1; 159 | OPERATION_TOUCH = 2; 160 | OPERATION_DELETE = 3; 161 | } 162 | Operation operation = 1 [ 163 | (validate.rules).enum = { 164 | defined_only: true 165 | not_in: [0] 166 | }, 167 | (buf.validate.field).enum = { 168 | defined_only: true 169 | not_in: [0] 170 | } 171 | ]; 172 | Relationship relationship = 2 [ 173 | (validate.rules).message.required = true, 174 | (buf.validate.field).required = true 175 | ]; 176 | } 177 | 178 | // PermissionRelationshipTree is used for representing a tree of a resource and 179 | // its permission relationships with other objects. 180 | message PermissionRelationshipTree { 181 | oneof tree_type { 182 | option (validate.required) = true; 183 | option (buf.validate.oneof).required = true; 184 | 185 | AlgebraicSubjectSet intermediate = 1; 186 | DirectSubjectSet leaf = 2; 187 | } 188 | ObjectReference expanded_object = 3; 189 | string expanded_relation = 4; 190 | } 191 | 192 | // AlgebraicSubjectSet is a subject set which is computed based on applying the 193 | // specified operation to the operands according to the algebra of sets. 194 | // 195 | // UNION is a logical set containing the subject members from all operands. 196 | // 197 | // INTERSECTION is a logical set containing only the subject members which are 198 | // present in all operands. 199 | // 200 | // EXCLUSION is a logical set containing only the subject members which are 201 | // present in the first operand, and none of the other operands. 202 | message AlgebraicSubjectSet { 203 | enum Operation { 204 | OPERATION_UNSPECIFIED = 0; 205 | OPERATION_UNION = 1; 206 | OPERATION_INTERSECTION = 2; 207 | OPERATION_EXCLUSION = 3; 208 | } 209 | 210 | Operation operation = 1 [ 211 | (validate.rules).enum = { 212 | defined_only: true 213 | not_in: [0] 214 | }, 215 | (buf.validate.field).enum = { 216 | defined_only: true 217 | not_in: [0] 218 | } 219 | ]; 220 | repeated PermissionRelationshipTree children = 2 [ 221 | (validate.rules).repeated.items.message.required = true, 222 | (buf.validate.field).repeated.items.required = true 223 | ]; 224 | } 225 | 226 | // DirectSubjectSet is a subject set which is simply a collection of subjects. 227 | message DirectSubjectSet { 228 | repeated SubjectReference subjects = 1; 229 | } 230 | 231 | // PartialCaveatInfo carries information necessary for the client to take action 232 | // in the event a response contains a partially evaluated caveat 233 | message PartialCaveatInfo { 234 | // missing_required_context is a list of one or more fields that were missing and prevented caveats 235 | // from being fully evaluated 236 | repeated string missing_required_context = 1 [ 237 | (validate.rules).repeated.min_items = 1, 238 | (buf.validate.field).repeated.min_items = 1 239 | ]; 240 | } 241 | -------------------------------------------------------------------------------- /authzed/api/v1/debug.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | import "buf/validate/validate.proto"; 6 | import "google/protobuf/duration.proto"; 7 | import "google/protobuf/struct.proto"; 8 | import "google/protobuf/timestamp.proto"; 9 | import "validate/validate.proto"; 10 | 11 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 12 | option java_multiple_files = true; 13 | option java_package = "com.authzed.api.v1"; 14 | 15 | // DebugInformation defines debug information returned by an API call in a footer when 16 | // requested with a specific debugging header. 17 | // 18 | // The specific debug information returned will depend on the type of the API call made. 19 | // 20 | // See the github.com/authzed/authzed-go project for the specific header and footer names. 21 | message DebugInformation { 22 | // check holds debug information about a check request. 23 | CheckDebugTrace check = 1; 24 | 25 | // schema_used holds the schema used for the request. 26 | string schema_used = 2; 27 | } 28 | 29 | // CheckDebugTrace is a recursive trace of the requests made for resolving a CheckPermission 30 | // API call. 31 | message CheckDebugTrace { 32 | enum PermissionType { 33 | PERMISSION_TYPE_UNSPECIFIED = 0; 34 | PERMISSION_TYPE_RELATION = 1; 35 | PERMISSION_TYPE_PERMISSION = 2; 36 | } 37 | 38 | enum Permissionship { 39 | PERMISSIONSHIP_UNSPECIFIED = 0; 40 | PERMISSIONSHIP_NO_PERMISSION = 1; 41 | PERMISSIONSHIP_HAS_PERMISSION = 2; 42 | PERMISSIONSHIP_CONDITIONAL_PERMISSION = 3; 43 | } 44 | 45 | message SubProblems { 46 | repeated CheckDebugTrace traces = 1; 47 | } 48 | 49 | // resource holds the resource on which the Check was performed. 50 | // for batched calls, the object_id field contains a comma-separated list of object IDs 51 | // for all the resources checked in the batch. 52 | ObjectReference resource = 1 [ 53 | (validate.rules).message.required = true, 54 | (buf.validate.field).required = true 55 | ]; 56 | 57 | // permission holds the name of the permission or relation on which the Check was performed. 58 | string permission = 2; 59 | 60 | // permission_type holds information indicating whether it was a permission or relation. 61 | PermissionType permission_type = 3 [ 62 | (validate.rules).enum = { 63 | defined_only: true 64 | not_in: [0] 65 | }, 66 | (buf.validate.field).enum = { 67 | defined_only: true 68 | not_in: [0] 69 | } 70 | ]; 71 | 72 | // subject holds the subject on which the Check was performed. This will be static across all calls within 73 | // the same Check tree. 74 | SubjectReference subject = 4 [ 75 | (validate.rules).message.required = true, 76 | (buf.validate.field).required = true 77 | ]; 78 | 79 | // result holds the result of the Check call. 80 | Permissionship result = 5 [ 81 | (validate.rules).enum = { 82 | defined_only: true 83 | not_in: [0] 84 | }, 85 | (buf.validate.field).enum = { 86 | defined_only: true 87 | not_in: [0] 88 | } 89 | ]; 90 | 91 | // caveat_evaluation_info holds information about the caveat evaluated for this step of the trace. 92 | CaveatEvalInfo caveat_evaluation_info = 8; 93 | 94 | // duration holds the time spent executing this Check operation. 95 | google.protobuf.Duration duration = 9; 96 | 97 | // resolution holds information about how the problem was resolved. 98 | oneof resolution { 99 | option (validate.required) = true; 100 | option (buf.validate.oneof).required = true; 101 | 102 | // was_cached_result, if true, indicates that the result was found in the cache and returned directly. 103 | bool was_cached_result = 6; 104 | 105 | // sub_problems holds the sub problems that were executed to resolve the answer to this Check. An empty list 106 | // and a permissionship of PERMISSIONSHIP_HAS_PERMISSION indicates the subject was found within this relation. 107 | SubProblems sub_problems = 7; 108 | } 109 | 110 | // optional_expires_at is the time at which at least one of the relationships used to 111 | // compute this result, expires (if any). This is *not* related to the caching window. 112 | google.protobuf.Timestamp optional_expires_at = 10; 113 | 114 | // trace_operation_id is a unique identifier for this trace's operation, that will 115 | // be shared for all traces created for the same check operation in SpiceDB. 116 | // 117 | // In cases where SpiceDB performs automatic batching of subproblems, this ID can be used 118 | // to correlate work that was shared across multiple traces. 119 | // 120 | // This identifier is generated by SpiceDB, is to be considered opaque to the caller 121 | // and only guaranteed to be unique within the same overall Check or CheckBulk operation. 122 | string trace_operation_id = 11; 123 | 124 | // source holds the source of the result. It is of the form: 125 | // `:`, where sourcetype can be, among others: 126 | // `spicedb`, `materialize`, etc. 127 | string source = 12; 128 | } 129 | 130 | // CaveatEvalInfo holds information about a caveat expression that was evaluated. 131 | message CaveatEvalInfo { 132 | enum Result { 133 | RESULT_UNSPECIFIED = 0; 134 | 135 | RESULT_UNEVALUATED = 1; 136 | 137 | RESULT_FALSE = 2; 138 | RESULT_TRUE = 3; 139 | RESULT_MISSING_SOME_CONTEXT = 4; 140 | } 141 | 142 | // expression is the expression that was evaluated. 143 | string expression = 1; 144 | 145 | // result is the result of the evaluation. 146 | Result result = 2; 147 | 148 | // context consists of any named values that were used for evaluating the caveat expression. 149 | google.protobuf.Struct context = 3; 150 | 151 | // partial_caveat_info holds information of a partially-evaluated caveated response, if applicable. 152 | PartialCaveatInfo partial_caveat_info = 4; 153 | 154 | // caveat_name is the name of the caveat that was executed, if applicable. 155 | string caveat_name = 5; 156 | } 157 | -------------------------------------------------------------------------------- /authzed/api/v1/error_reason.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 5 | option java_multiple_files = true; 6 | option java_package = "com.authzed.api.v1"; 7 | 8 | // Defines the supported values for `google.rpc.ErrorInfo.reason` for the 9 | // `authzed.com` error domain. 10 | enum ErrorReason { 11 | // Do not use this default value. 12 | ERROR_REASON_UNSPECIFIED = 0; 13 | 14 | // The request gave a schema that could not be parsed. 15 | // 16 | // Example of an ErrorInfo: 17 | // 18 | // { 19 | // "reason": "ERROR_REASON_SCHEMA_PARSE_ERROR", 20 | // "domain": "authzed.com", 21 | // "metadata": { 22 | // "start_line_number": "1", 23 | // "start_column_position": "19", 24 | // "end_line_number": "1", 25 | // "end_column_position": "19", 26 | // "source_code": "somedefinition", 27 | // } 28 | // } 29 | // 30 | // The line numbers and column positions are 0-indexed and may not be present. 31 | ERROR_REASON_SCHEMA_PARSE_ERROR = 1; 32 | 33 | // The request contains a schema with a type error. 34 | // 35 | // Example of an ErrorInfo: 36 | // 37 | // { 38 | // "reason": "ERROR_REASON_SCHEMA_TYPE_ERROR", 39 | // "domain": "authzed.com", 40 | // "metadata": { 41 | // "definition_name": "somedefinition", 42 | // ... additional keys based on the kind of type error ... 43 | // } 44 | // } 45 | ERROR_REASON_SCHEMA_TYPE_ERROR = 2; 46 | 47 | // The request referenced an unknown object definition in the schema. 48 | // 49 | // Example of an ErrorInfo: 50 | // 51 | // { 52 | // "reason": "ERROR_REASON_UNKNOWN_DEFINITION", 53 | // "domain": "authzed.com", 54 | // "metadata": { 55 | // "definition_name": "somedefinition" 56 | // } 57 | // } 58 | ERROR_REASON_UNKNOWN_DEFINITION = 3; 59 | 60 | // The request referenced an unknown relation or permission under a definition in the schema. 61 | // 62 | // Example of an ErrorInfo: 63 | // 64 | // { 65 | // "reason": "ERROR_REASON_UNKNOWN_RELATION_OR_PERMISSION", 66 | // "domain": "authzed.com", 67 | // "metadata": { 68 | // "definition_name": "somedefinition", 69 | // "relation_or_permission_name": "somepermission" 70 | // } 71 | // } 72 | ERROR_REASON_UNKNOWN_RELATION_OR_PERMISSION = 4; 73 | 74 | // The WriteRelationships request contained more updates than the maximum configured. 75 | // 76 | // Example of an ErrorInfo: 77 | // 78 | // { "reason": "ERROR_REASON_TOO_MANY_UPDATES_IN_REQUEST", 79 | // "domain": "authzed.com", 80 | // "metadata": { 81 | // "update_count": "525", 82 | // "maximum_updates_allowed": "500", 83 | // } 84 | // } 85 | ERROR_REASON_TOO_MANY_UPDATES_IN_REQUEST = 5; 86 | 87 | // The request contained more preconditions than the maximum configured. 88 | // 89 | // Example of an ErrorInfo: 90 | // 91 | // { 92 | // "reason": "ERROR_REASON_TOO_MANY_PRECONDITIONS_IN_REQUEST", 93 | // "domain": "authzed.com", 94 | // "metadata": { 95 | // "precondition_count": "525", 96 | // "maximum_preconditions_allowed": "500", 97 | // } 98 | // } 99 | ERROR_REASON_TOO_MANY_PRECONDITIONS_IN_REQUEST = 6; 100 | 101 | // The request contained a precondition that failed. 102 | // 103 | // Example of an ErrorInfo: 104 | // 105 | // { 106 | // "reason": "ERROR_REASON_WRITE_OR_DELETE_PRECONDITION_FAILURE", 107 | // "domain": "authzed.com", 108 | // "metadata": { 109 | // "precondition_resource_type": "document", 110 | // ... other fields for the filter ... 111 | // "precondition_operation": "MUST_EXIST", 112 | // } 113 | // } 114 | ERROR_REASON_WRITE_OR_DELETE_PRECONDITION_FAILURE = 7; 115 | 116 | // A write or delete request was made to an instance that is deployed in read-only mode. 117 | // 118 | // Example of an ErrorInfo: 119 | // 120 | // { 121 | // "reason": "ERROR_REASON_SERVICE_READ_ONLY", 122 | // "domain": "authzed.com" 123 | // } 124 | ERROR_REASON_SERVICE_READ_ONLY = 8; 125 | 126 | // The request referenced an unknown caveat in the schema. 127 | // 128 | // Example of an ErrorInfo: 129 | // 130 | // { 131 | // "reason": "ERROR_REASON_UNKNOWN_CAVEAT", 132 | // "domain": "authzed.com", 133 | // "metadata": { 134 | // "caveat_name": "somecaveat" 135 | // } 136 | // } 137 | ERROR_REASON_UNKNOWN_CAVEAT = 9; 138 | 139 | // The request tries to use a subject type that was not valid for a relation. 140 | // 141 | // Example of an ErrorInfo: 142 | // 143 | // { 144 | // "reason": "ERROR_REASON_INVALID_SUBJECT_TYPE", 145 | // "domain": "authzed.com", 146 | // "metadata": { 147 | // "definition_name": "somedefinition", 148 | // "relation_name": "somerelation", 149 | // "subject_type": "user:*" 150 | // } 151 | // } 152 | ERROR_REASON_INVALID_SUBJECT_TYPE = 10; 153 | 154 | // The request tries to specify a caveat parameter value with the wrong type. 155 | // 156 | // Example of an ErrorInfo: 157 | // 158 | // { 159 | // "reason": "ERROR_REASON_CAVEAT_PARAMETER_TYPE_ERROR", 160 | // "domain": "authzed.com", 161 | // "metadata": { 162 | // "definition_name": "somedefinition", 163 | // "relation_name": "somerelation", 164 | // "caveat_name": "somecaveat", 165 | // "parameter_name": "someparameter", 166 | // "expected_type": "int", 167 | // } 168 | // } 169 | ERROR_REASON_CAVEAT_PARAMETER_TYPE_ERROR = 11; 170 | 171 | // The request tries to perform two or more updates on the same relationship in the same WriteRelationships call. 172 | // 173 | // Example of an ErrorInfo: 174 | // 175 | // { 176 | // "reason": "ERROR_REASON_UPDATES_ON_SAME_RELATIONSHIP", 177 | // "domain": "authzed.com", 178 | // "metadata": { 179 | // "definition_name": "somedefinition", 180 | // "relationship": "somerelationship", 181 | // } 182 | // } 183 | ERROR_REASON_UPDATES_ON_SAME_RELATIONSHIP = 12; 184 | 185 | // The request tries to write a relationship on a permission instead of a relation. 186 | // 187 | // Example of an ErrorInfo: 188 | // 189 | // { 190 | // "reason": "ERROR_REASON_CANNOT_UPDATE_PERMISSION", 191 | // "domain": "authzed.com", 192 | // "metadata": { 193 | // "definition_name": "somedefinition", 194 | // "permission_name": "somerelation", 195 | // } 196 | // } 197 | ERROR_REASON_CANNOT_UPDATE_PERMISSION = 13; 198 | 199 | // The request failed to evaluate a caveat expression due to an error. 200 | // 201 | // Example of an ErrorInfo: 202 | // 203 | // { 204 | // "reason": "ERROR_REASON_CAVEAT_EVALUATION_ERROR", 205 | // "domain": "authzed.com", 206 | // "metadata": { 207 | // "caveat_name": "somecaveat", 208 | // } 209 | // } 210 | ERROR_REASON_CAVEAT_EVALUATION_ERROR = 14; 211 | 212 | // The request failed because the provided cursor was invalid in some way. 213 | // 214 | // Example of an ErrorInfo: 215 | // 216 | // { 217 | // "reason": "ERROR_REASON_INVALID_CURSOR", 218 | // "domain": "authzed.com", 219 | // "metadata": { 220 | // ... additional keys based on the kind of cursor error ... 221 | // } 222 | // } 223 | ERROR_REASON_INVALID_CURSOR = 15; 224 | 225 | // The request failed because there are too many matching relationships to be 226 | // deleted within a single transactional deletion call. To avoid, set 227 | // `optional_allow_partial_deletions` to true on the DeleteRelationships call. 228 | // 229 | // Example of an ErrorInfo: 230 | // 231 | // { 232 | // "reason": "ERROR_REASON_TOO_MANY_RELATIONSHIPS_FOR_TRANSACTIONAL_DELETE", 233 | // "domain": "authzed.com", 234 | // "metadata": { 235 | // ... fields for the filter ... 236 | // } 237 | // } 238 | ERROR_REASON_TOO_MANY_RELATIONSHIPS_FOR_TRANSACTIONAL_DELETE = 16; 239 | 240 | // The request failed because the client attempted to write a relationship 241 | // with a context that exceeded the configured server limit. 242 | // 243 | // Example of an ErrorInfo: 244 | // 245 | // { 246 | // "reason": "ERROR_REASON_MAX_RELATIONSHIP_CONTEXT_SIZE", 247 | // "domain": "authzed.com", 248 | // "metadata": { 249 | // "relationship": "relationship_exceeding_the_limit", 250 | // "max_allowed_size": "server_max_allowed_context_size", 251 | // "context_size": "actual_relationship_context_size" , 252 | // } 253 | // } 254 | ERROR_REASON_MAX_RELATIONSHIP_CONTEXT_SIZE = 17; 255 | 256 | // The request failed because a relationship marked to be CREATEd 257 | // was already present within the datastore. 258 | // 259 | // Example of an ErrorInfo: 260 | // 261 | // { 262 | // "reason": "ERROR_REASON_ATTEMPT_TO_RECREATE_RELATIONSHIP", 263 | // "domain": "authzed.com", 264 | // "metadata": { 265 | // "relationship": "relationship_that_already_existed", 266 | // "resource_type": "resource type", 267 | // "resource_object_id": "resource object id", 268 | // ... additional decomposed relationship fields ... 269 | // } 270 | // } 271 | ERROR_REASON_ATTEMPT_TO_RECREATE_RELATIONSHIP = 18; 272 | 273 | // The request failed because it caused the maximum depth allowed to be 274 | // exceeded. This typically indicates that there is a circular data traversal 275 | // somewhere in the schema, but can also be raised if the data traversal is simply 276 | // too deep. 277 | // 278 | // Example of an ErrorInfo: 279 | // 280 | // { 281 | // "reason": "ERROR_REASON_MAXIMUM_DEPTH_EXCEEDED", 282 | // "domain": "authzed.com", 283 | // "metadata": { 284 | // "maximum_depth_allowed": "50", 285 | // ... additional fields based on request type ... 286 | // } 287 | // } 288 | ERROR_REASON_MAXIMUM_DEPTH_EXCEEDED = 19; 289 | 290 | // The request failed due to a serialization error in the backend database. 291 | // This typically indicates that various in flight transactions conflicted with each other 292 | // and the database had to abort one or more of them. SpiceDB will retry a few times before returning 293 | // the error to the client. 294 | // 295 | // Example of an ErrorInfo: 296 | // 297 | // { 298 | // "reason": "ERROR_REASON_SERIALIZATION_FAILURE", 299 | // "domain": "authzed.com", 300 | // "metadata": {} 301 | // } 302 | ERROR_REASON_SERIALIZATION_FAILURE = 20; 303 | 304 | // The request contained more check items than the maximum configured. 305 | // 306 | // Example of an ErrorInfo: 307 | // 308 | // { 309 | // "reason": "ERROR_REASON_TOO_MANY_CHECKS_IN_REQUEST", 310 | // "domain": "authzed.com", 311 | // "metadata": { 312 | // "check_count": "525", 313 | // "maximum_checks_allowed": "500", 314 | // } 315 | // } 316 | ERROR_REASON_TOO_MANY_CHECKS_IN_REQUEST = 21; 317 | 318 | // The request's specified limit is too large. 319 | // 320 | // Example of an ErrorInfo: 321 | // 322 | // { 323 | // "reason": "ERROR_REASON_EXCEEDS_MAXIMUM_ALLOWABLE_LIMIT", 324 | // "domain": "authzed.com", 325 | // "metadata": { 326 | // "limit_provided": "525", 327 | // "maximum_limit_allowed": "500", 328 | // } 329 | // } 330 | ERROR_REASON_EXCEEDS_MAXIMUM_ALLOWABLE_LIMIT = 22; 331 | 332 | // The request failed because the provided filter was invalid in some way. 333 | // 334 | // Example of an ErrorInfo: 335 | // 336 | // { 337 | // "reason": "ERROR_REASON_INVALID_FILTER", 338 | // "domain": "authzed.com", 339 | // "metadata": { 340 | // "filter": "...", 341 | // } 342 | // } 343 | ERROR_REASON_INVALID_FILTER = 23; 344 | 345 | // The request failed because too many concurrent updates were attempted 346 | // against the in-memory datastore. 347 | // 348 | // Example of an ErrorInfo: 349 | // 350 | // { 351 | // "reason": "ERROR_REASON_INMEMORY_TOO_MANY_CONCURRENT_UPDATES", 352 | // "domain": "authzed.com", 353 | // "metadata": {} 354 | // } 355 | ERROR_REASON_INMEMORY_TOO_MANY_CONCURRENT_UPDATES = 24; 356 | 357 | // The request failed because the precondition specified is empty. 358 | // 359 | // Example of an ErrorInfo: 360 | // 361 | // { 362 | // "reason": "ERROR_REASON_EMPTY_PRECONDITION", 363 | // "domain": "authzed.com", 364 | // "metadata": {} 365 | // } 366 | ERROR_REASON_EMPTY_PRECONDITION = 25; 367 | 368 | // The request failed because the counter was already registered. 369 | // 370 | // Example of an ErrorInfo: 371 | // 372 | // { 373 | // "reason": "ERROR_REASON_COUNTER_ALREADY_REGISTERED", 374 | // "domain": "authzed.com", 375 | // "metadata": { "counter_name": "name" } 376 | // } 377 | ERROR_REASON_COUNTER_ALREADY_REGISTERED = 26; 378 | 379 | // The request failed because the counter was not registered. 380 | // 381 | // Example of an ErrorInfo: 382 | // 383 | // { 384 | // "reason": "ERROR_REASON_COUNTER_NOT_REGISTERED", 385 | // "domain": "authzed.com", 386 | // "metadata": { "counter_name": "name" } 387 | // } 388 | ERROR_REASON_COUNTER_NOT_REGISTERED = 27; 389 | 390 | // The request failed because a wildcard was not allowed. For CheckPermission, 391 | // this means that the subject or resource ID was a wildcard. For LookupResources, 392 | // this means that the subject ID was a wildcard. 393 | // 394 | // Example of an ErrorInfo: 395 | // 396 | // { 397 | // "reason": "ERROR_REASON_WILDCARD_NOT_ALLOWED", 398 | // "domain": "authzed.com", 399 | // "metadata": { "disallowed_field": "subject_id" } 400 | // } 401 | ERROR_REASON_WILDCARD_NOT_ALLOWED = 28; 402 | 403 | // The request failed because the transaction metadata was too large. 404 | // 405 | // Example of an ErrorInfo: 406 | // 407 | // { 408 | // "reason": "ERROR_REASON_TRANSACTION_METADATA_TOO_LARGE", 409 | // "domain": "authzed.com", 410 | // "metadata": { 411 | // "metadata_byte_size": "1024", 412 | // "maximum_allowed_metadata_byte_size": "512", 413 | // } 414 | // } 415 | ERROR_REASON_TRANSACTION_METADATA_TOO_LARGE = 29; 416 | } 417 | -------------------------------------------------------------------------------- /authzed/api/v1/experimental_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | import "authzed/api/v1/permission_service.proto"; 6 | import "buf/validate/validate.proto"; 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/struct.proto"; 9 | import "google/rpc/status.proto"; 10 | import "protoc-gen-openapiv2/options/annotations.proto"; 11 | import "validate/validate.proto"; 12 | 13 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 14 | option java_multiple_files = true; 15 | option java_package = "com.authzed.api.v1"; 16 | 17 | // ExperimentalService exposes a number of APIs that are currently being 18 | // prototyped and tested for future inclusion in the stable API. 19 | service ExperimentalService { 20 | // DEPRECATED: Promoted to ImportBulkRelationships in the stable API. 21 | rpc BulkImportRelationships(stream BulkImportRelationshipsRequest) returns (BulkImportRelationshipsResponse) { 22 | option (google.api.http) = { 23 | post: "/v1/experimental/relationships/bulkimport" 24 | body: "*" 25 | }; 26 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 27 | tags: ["Deprecated"] 28 | }; 29 | option deprecated = true; 30 | } 31 | 32 | // DEPRECATED: Promoted to ExportBulkRelationships in the stable API. 33 | rpc BulkExportRelationships(BulkExportRelationshipsRequest) returns (stream BulkExportRelationshipsResponse) { 34 | option (google.api.http) = { 35 | post: "/v1/experimental/relationships/bulkexport" 36 | body: "*" 37 | }; 38 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 39 | tags: ["Deprecated"] 40 | }; 41 | option deprecated = true; 42 | } 43 | 44 | // DEPRECATED: Promoted to CheckBulkPermission in the stable API. 45 | rpc BulkCheckPermission(BulkCheckPermissionRequest) returns (BulkCheckPermissionResponse) { 46 | option (google.api.http) = { 47 | post: "/v1/experimental/permissions/bulkcheckpermission" 48 | body: "*" 49 | }; 50 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 51 | tags: ["Deprecated"] 52 | }; 53 | option deprecated = true; 54 | } 55 | 56 | // DEPRECATED: Promoted to ReflectSchema in the stable API. 57 | rpc ExperimentalReflectSchema(ExperimentalReflectSchemaRequest) returns (ExperimentalReflectSchemaResponse) { 58 | option (google.api.http) = { 59 | post: "/v1/experimental/reflectschema" 60 | body: "*" 61 | }; 62 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 63 | tags: ["Deprecated"] 64 | }; 65 | option deprecated = true; 66 | } 67 | 68 | // DEPRECATED: Promoted to ComputablePermissions in the stable API. 69 | rpc ExperimentalComputablePermissions(ExperimentalComputablePermissionsRequest) returns (ExperimentalComputablePermissionsResponse) { 70 | option (google.api.http) = { 71 | post: "/v1/experimental/permissions/computable" 72 | body: "*" 73 | }; 74 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 75 | tags: ["Deprecated"] 76 | }; 77 | option deprecated = true; 78 | } 79 | 80 | // DEPRECATED: Promoted to DependentRelations in the stable API. 81 | rpc ExperimentalDependentRelations(ExperimentalDependentRelationsRequest) returns (ExperimentalDependentRelationsResponse) { 82 | option (google.api.http) = { 83 | post: "/v1/experimental/permissions/dependent" 84 | body: "*" 85 | }; 86 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 87 | tags: ["Deprecated"] 88 | }; 89 | option deprecated = true; 90 | } 91 | 92 | // DEPRECATED: Promoted to DiffSchema in the stable API. 93 | rpc ExperimentalDiffSchema(ExperimentalDiffSchemaRequest) returns (ExperimentalDiffSchemaResponse) { 94 | option (google.api.http) = { 95 | post: "/v1/experimental/diffschema" 96 | body: "*" 97 | }; 98 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 99 | tags: ["Deprecated"] 100 | }; 101 | option deprecated = true; 102 | } 103 | 104 | // EXPERIMENTAL: RegisterRelationshipCounter registers a new filter for counting relationships. A filter must be registered before 105 | // a count can be requested. 106 | rpc ExperimentalRegisterRelationshipCounter(ExperimentalRegisterRelationshipCounterRequest) returns (ExperimentalRegisterRelationshipCounterResponse) { 107 | option (google.api.http) = { 108 | post: "/v1/experimental/registerrelationshipcounter" 109 | body: "*" 110 | }; 111 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 112 | tags: ["Experimental"] 113 | }; 114 | } 115 | 116 | // EXPERIMENTAL: CountRelationships returns the count of relationships for *pre-registered* filter. 117 | rpc ExperimentalCountRelationships(ExperimentalCountRelationshipsRequest) returns (ExperimentalCountRelationshipsResponse) { 118 | option (google.api.http) = { 119 | post: "/v1/experimental/countrelationships" 120 | body: "*" 121 | }; 122 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 123 | tags: ["Experimental"] 124 | }; 125 | } 126 | 127 | // EXPERIMENTAL: UnregisterRelationshipCounter unregisters an existing filter for counting relationships. 128 | rpc ExperimentalUnregisterRelationshipCounter(ExperimentalUnregisterRelationshipCounterRequest) returns (ExperimentalUnregisterRelationshipCounterResponse) { 129 | option (google.api.http) = { 130 | post: "/v1/experimental/unregisterrelationshipcounter" 131 | body: "*" 132 | }; 133 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 134 | tags: ["Experimental"] 135 | }; 136 | } 137 | } 138 | 139 | message ExperimentalRegisterRelationshipCounterRequest { 140 | // name is the name of the counter being registered. 141 | string name = 1 [ 142 | (validate.rules).string = { 143 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 144 | max_bytes: 64 145 | }, 146 | (buf.validate.field).string = { 147 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 148 | max_bytes: 64 149 | } 150 | ]; 151 | 152 | // relationship_filter defines the filter to be applied to the relationships 153 | // to be counted. 154 | RelationshipFilter relationship_filter = 2 [ 155 | (validate.rules).message.required = true, 156 | (buf.validate.field).required = true 157 | ]; 158 | } 159 | 160 | message ExperimentalRegisterRelationshipCounterResponse {} 161 | 162 | message ExperimentalCountRelationshipsRequest { 163 | // name is the name of the counter whose count is being requested. 164 | string name = 1 [ 165 | (validate.rules).string = { 166 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 167 | max_bytes: 64 168 | }, 169 | (buf.validate.field).string = { 170 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 171 | max_bytes: 64 172 | } 173 | ]; 174 | } 175 | 176 | message ExperimentalCountRelationshipsResponse { 177 | oneof counter_result { 178 | // counter_still_calculating is true if the counter is still calculating the count. 179 | bool counter_still_calculating = 1; 180 | 181 | // read_counter_value is the value of the counter at the time of the read. 182 | ReadCounterValue read_counter_value = 2; 183 | } 184 | } 185 | 186 | message ReadCounterValue { 187 | // relationship_count is the count of relationships that match the filter. 188 | uint64 relationship_count = 1; 189 | 190 | // read_at is the ZedToken at which the relationship count applies. 191 | ZedToken read_at = 2 [ 192 | (validate.rules).message.required = true, 193 | (buf.validate.field).required = true 194 | ]; 195 | } 196 | 197 | message ExperimentalUnregisterRelationshipCounterRequest { 198 | // name is the name of the counter being unregistered. 199 | string name = 1 [ 200 | (validate.rules).string = { 201 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 202 | max_bytes: 64 203 | }, 204 | (buf.validate.field).string = { 205 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 206 | max_bytes: 64 207 | } 208 | ]; 209 | } 210 | 211 | message ExperimentalUnregisterRelationshipCounterResponse {} 212 | 213 | // NOTE: Deprecated now that BulkCheckPermission has been promoted to the stable API as "CheckBulkPermission". 214 | message BulkCheckPermissionRequest { 215 | Consistency consistency = 1; 216 | 217 | repeated BulkCheckPermissionRequestItem items = 2 [ 218 | (validate.rules).repeated.items.message.required = true, 219 | (buf.validate.field).repeated.items.required = true, 220 | deprecated = true 221 | ]; 222 | } 223 | 224 | message BulkCheckPermissionRequestItem { 225 | ObjectReference resource = 1 [ 226 | (validate.rules).message.required = true, 227 | (buf.validate.field).required = true 228 | ]; 229 | 230 | string permission = 2 [ 231 | (validate.rules).string = { 232 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 233 | max_bytes: 64 234 | }, 235 | (buf.validate.field).string = { 236 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 237 | max_bytes: 64 238 | } 239 | ]; 240 | 241 | SubjectReference subject = 3 [ 242 | (validate.rules).message.required = true, 243 | (buf.validate.field).required = true 244 | ]; 245 | 246 | google.protobuf.Struct context = 4 [ 247 | (validate.rules).message.required = false, 248 | (buf.validate.field).required = false 249 | ]; 250 | } 251 | 252 | message BulkCheckPermissionResponse { 253 | ZedToken checked_at = 1 [ 254 | (validate.rules).message.required = false, 255 | (buf.validate.field).required = false 256 | ]; 257 | 258 | repeated BulkCheckPermissionPair pairs = 2 [ 259 | (validate.rules).repeated.items.message.required = true, 260 | (buf.validate.field).repeated.items.required = true 261 | ]; 262 | } 263 | 264 | message BulkCheckPermissionPair { 265 | BulkCheckPermissionRequestItem request = 1; 266 | oneof response { 267 | BulkCheckPermissionResponseItem item = 2; 268 | google.rpc.Status error = 3; 269 | } 270 | } 271 | 272 | message BulkCheckPermissionResponseItem { 273 | CheckPermissionResponse.Permissionship permissionship = 1 [ 274 | (validate.rules).enum = { 275 | defined_only: true 276 | not_in: [0] 277 | }, 278 | (buf.validate.field).enum = { 279 | defined_only: true 280 | not_in: [0] 281 | } 282 | ]; 283 | 284 | PartialCaveatInfo partial_caveat_info = 2 [ 285 | (validate.rules).message.required = false, 286 | (buf.validate.field).required = false 287 | ]; 288 | } 289 | 290 | // BulkImportRelationshipsRequest represents one batch of the streaming 291 | // BulkImportRelationships API. The maximum size is only limited by the backing 292 | // datastore, and optimal size should be determined by the calling client 293 | // experimentally. When BulkImport is invoked and receives its first request message, 294 | // a transaction is opened to import the relationships. All requests sent to the same 295 | // invocation are executed under this single transaction. If a relationship already 296 | // exists within the datastore, the entire transaction will fail with an error. 297 | message BulkImportRelationshipsRequest { 298 | repeated Relationship relationships = 1 [ 299 | (validate.rules).repeated.items.message.required = true, 300 | (buf.validate.field).repeated.items.required = true 301 | ]; 302 | } 303 | 304 | // BulkImportRelationshipsResponse is returned on successful completion of the 305 | // bulk load stream, and contains the total number of relationships loaded. 306 | message BulkImportRelationshipsResponse { 307 | uint64 num_loaded = 1; 308 | } 309 | 310 | // BulkExportRelationshipsRequest represents a resumable request for 311 | // all relationships from the server. 312 | message BulkExportRelationshipsRequest { 313 | Consistency consistency = 1; 314 | 315 | // optional_limit, if non-zero, specifies the limit on the number of 316 | // relationships the server can return in one page. By default, the server 317 | // will pick a page size, and the server is free to choose a smaller size 318 | // at will. 319 | uint32 optional_limit = 2 [ 320 | (validate.rules).uint32 = {gte: 0}, 321 | (buf.validate.field).uint32 = {gte: 0} 322 | ]; 323 | 324 | // optional_cursor, if specified, indicates the cursor after which results 325 | // should resume being returned. The cursor can be found on the 326 | // BulkExportRelationshipsResponse object. 327 | Cursor optional_cursor = 3; 328 | 329 | // optional_relationship_filter, if specified, indicates the 330 | // filter to apply to each relationship to be exported. 331 | RelationshipFilter optional_relationship_filter = 4; 332 | } 333 | 334 | // BulkExportRelationshipsResponse is one page in a stream of relationship 335 | // groups that meet the criteria specified by the originating request. The 336 | // server will continue to stream back relationship groups as quickly as it can 337 | // until all relationships have been transmitted back. 338 | message BulkExportRelationshipsResponse { 339 | Cursor after_result_cursor = 1; 340 | repeated Relationship relationships = 2; 341 | } 342 | 343 | // Reflection types //////////////////////////////////////////// 344 | 345 | message ExperimentalReflectSchemaRequest { 346 | Consistency consistency = 1; 347 | 348 | // optional_filters defines optional filters that are applied in 349 | // an OR fashion to the schema, before being returned 350 | repeated ExpSchemaFilter optional_filters = 2; 351 | } 352 | 353 | message ExperimentalReflectSchemaResponse { 354 | // definitions are the definitions defined in the schema. 355 | repeated ExpDefinition definitions = 1; 356 | 357 | // caveats are the caveats defined in the schema. 358 | repeated ExpCaveat caveats = 2; 359 | 360 | // read_at is the ZedToken at which the schema was read. 361 | ZedToken read_at = 3; 362 | } 363 | 364 | // ExpSchemaFilter is a filter that can be applied to the schema on reflection. 365 | message ExpSchemaFilter { 366 | // optional_definition_name_filter is a prefix that is matched against the definition name. 367 | string optional_definition_name_filter = 1; 368 | 369 | // optional_caveat_name_filter is a prefix that is matched against the caveat name. 370 | string optional_caveat_name_filter = 2; 371 | 372 | // optional_relation_name_filter is a prefix that is matched against the relation name. 373 | string optional_relation_name_filter = 3; 374 | 375 | // optional_permission_name_filter is a prefix that is matched against the permission name. 376 | string optional_permission_name_filter = 4; 377 | } 378 | 379 | // ExpDefinition is the representation of a definition in the schema. 380 | message ExpDefinition { 381 | string name = 1; 382 | 383 | // comment is a human-readable comments on the definition. Will include 384 | // delimiter characters. 385 | string comment = 2; 386 | 387 | repeated ExpRelation relations = 3; 388 | repeated ExpPermission permissions = 4; 389 | } 390 | 391 | // ExpCaveat is the representation of a caveat in the schema. 392 | message ExpCaveat { 393 | string name = 1; 394 | 395 | // comment is a human-readable comments on the caveat. Will include 396 | // delimiter characters. 397 | string comment = 2; 398 | 399 | repeated ExpCaveatParameter parameters = 3; 400 | string expression = 4; 401 | } 402 | 403 | // ExpCaveatParameter is the representation of a parameter in a caveat. 404 | message ExpCaveatParameter { 405 | string name = 1; 406 | 407 | // type is the type of the parameter. Will be a string representing the 408 | // type, e.g. `int` or `list` 409 | string type = 2; 410 | string parent_caveat_name = 3; 411 | } 412 | 413 | // ExpRelation is the representation of a relation in the schema. 414 | message ExpRelation { 415 | string name = 1; 416 | string comment = 2; 417 | string parent_definition_name = 3; 418 | repeated ExpTypeReference subject_types = 4; 419 | } 420 | 421 | // ExpTypeReference is the representation of a type reference in the schema. 422 | message ExpTypeReference { 423 | // subject_definition_name is the name of the subject's definition. 424 | string subject_definition_name = 1; 425 | 426 | // optional_caveat_name is the name of the caveat that is applied to the subject, if any. 427 | string optional_caveat_name = 2; 428 | 429 | oneof typeref { 430 | // is_terminal_subject is true if the subject is terminal, meaning it is referenced directly vs a sub-relation. 431 | bool is_terminal_subject = 3; 432 | 433 | // optional_relation_name is the name of the relation that is applied to the subject, if any. 434 | string optional_relation_name = 4; 435 | 436 | // is_public_wildcard is true if the subject is a public wildcard. 437 | bool is_public_wildcard = 5; 438 | } 439 | } 440 | 441 | // ExpPermission is the representation of a permission in the schema. 442 | message ExpPermission { 443 | string name = 1; 444 | 445 | // comment is a human-readable comments on the permission. Will include 446 | // delimiter characters. 447 | string comment = 2; 448 | string parent_definition_name = 3; 449 | } 450 | 451 | message ExperimentalComputablePermissionsRequest { 452 | Consistency consistency = 1; 453 | string definition_name = 2; 454 | string relation_name = 3; 455 | 456 | // optional_definition_name_match is a prefix that is matched against the definition name(s) 457 | // for the permissions returned. 458 | // If not specified, will be ignored. 459 | string optional_definition_name_filter = 4; 460 | } 461 | 462 | // ExpRelationReference is a reference to a relation or permission in the schema. 463 | message ExpRelationReference { 464 | string definition_name = 1; 465 | string relation_name = 2; 466 | bool is_permission = 3; 467 | } 468 | 469 | message ExperimentalComputablePermissionsResponse { 470 | repeated ExpRelationReference permissions = 1; 471 | 472 | // read_at is the ZedToken at which the schema was read. 473 | ZedToken read_at = 2; 474 | } 475 | 476 | message ExperimentalDependentRelationsRequest { 477 | Consistency consistency = 1; 478 | string definition_name = 2; 479 | string permission_name = 3; 480 | } 481 | 482 | message ExperimentalDependentRelationsResponse { 483 | repeated ExpRelationReference relations = 1; 484 | 485 | // read_at is the ZedToken at which the schema was read. 486 | ZedToken read_at = 2; 487 | } 488 | 489 | message ExperimentalDiffSchemaRequest { 490 | Consistency consistency = 1; 491 | string comparison_schema = 2; 492 | } 493 | 494 | message ExperimentalDiffSchemaResponse { 495 | repeated ExpSchemaDiff diffs = 1; 496 | 497 | // read_at is the ZedToken at which the schema was read. 498 | ZedToken read_at = 2; 499 | } 500 | 501 | message ExpRelationSubjectTypeChange { 502 | ExpRelation relation = 1; 503 | ExpTypeReference changed_subject_type = 2; 504 | } 505 | 506 | message ExpCaveatParameterTypeChange { 507 | ExpCaveatParameter parameter = 1; 508 | string previous_type = 2; 509 | } 510 | 511 | // ExpSchemaDiff is the representation of a diff between two schemas. 512 | message ExpSchemaDiff { 513 | oneof diff { 514 | ExpDefinition definition_added = 1; 515 | ExpDefinition definition_removed = 2; 516 | ExpDefinition definition_doc_comment_changed = 3; 517 | ExpRelation relation_added = 4; 518 | ExpRelation relation_removed = 5; 519 | ExpRelation relation_doc_comment_changed = 6; 520 | ExpRelationSubjectTypeChange relation_subject_type_added = 7; 521 | ExpRelationSubjectTypeChange relation_subject_type_removed = 8; 522 | ExpPermission permission_added = 9; 523 | ExpPermission permission_removed = 10; 524 | ExpPermission permission_doc_comment_changed = 11; 525 | ExpPermission permission_expr_changed = 12; 526 | ExpCaveat caveat_added = 13; 527 | ExpCaveat caveat_removed = 14; 528 | ExpCaveat caveat_doc_comment_changed = 15; 529 | ExpCaveat caveat_expr_changed = 16; 530 | ExpCaveatParameter caveat_parameter_added = 17; 531 | ExpCaveatParameter caveat_parameter_removed = 18; 532 | ExpCaveatParameterTypeChange caveat_parameter_type_changed = 19; 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /authzed/api/v1/openapi.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "protoc-gen-openapiv2/options/annotations.proto"; 5 | 6 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 7 | option java_multiple_files = true; 8 | option java_package = "com.authzed.api.v1"; 9 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { 10 | info: { 11 | title: "Authzed" 12 | version: "1.0" 13 | contact: { 14 | name: "Authzed, Inc." 15 | url: "https://github.com/authzed/api" 16 | email: "support@authzed.com" 17 | } 18 | license: { 19 | name: "Apache 2.0 License" 20 | url: "https://github.com/authzed/api/blob/main/LICENSE" 21 | } 22 | } 23 | external_docs: { 24 | url: "https://docs.authzed.com/reference/api" 25 | description: "More about the Authzed API." 26 | } 27 | schemes: HTTP 28 | schemes: HTTPS 29 | schemes: WSS 30 | consumes: "application/json" 31 | produces: "application/json" 32 | security_definitions: { 33 | security: { 34 | key: "ApiKeyAuth" 35 | value: { 36 | type: TYPE_API_KEY 37 | in: IN_HEADER 38 | name: "Authorization" 39 | description: "SpiceDB preshared-key, prefixed by Bearer: Bearer " 40 | } 41 | } 42 | } 43 | security: { 44 | security_requirement: {key: "ApiKeyAuth"} 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /authzed/api/v1/permission_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | import "authzed/api/v1/debug.proto"; 6 | import "buf/validate/validate.proto"; 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/struct.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | import "google/rpc/status.proto"; 11 | import "protoc-gen-openapiv2/options/annotations.proto"; 12 | import "validate/validate.proto"; 13 | 14 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 15 | option java_multiple_files = true; 16 | option java_package = "com.authzed.api.v1"; 17 | 18 | // PermissionsService implements a set of RPCs that perform operations on 19 | // relationships and permissions. 20 | service PermissionsService { 21 | // ReadRelationships reads a set of the relationships matching one or more 22 | // filters. 23 | rpc ReadRelationships(ReadRelationshipsRequest) returns (stream ReadRelationshipsResponse) { 24 | option (google.api.http) = { 25 | post: "/v1/relationships/read" 26 | body: "*" 27 | }; 28 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 29 | tags: ["Permissions"] 30 | }; 31 | } 32 | 33 | // WriteRelationships atomically writes and/or deletes a set of specified 34 | // relationships. An optional set of preconditions can be provided that must 35 | // be satisfied for the operation to commit. 36 | rpc WriteRelationships(WriteRelationshipsRequest) returns (WriteRelationshipsResponse) { 37 | option (google.api.http) = { 38 | post: "/v1/relationships/write" 39 | body: "*" 40 | }; 41 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 42 | tags: ["Permissions"] 43 | }; 44 | } 45 | 46 | // DeleteRelationships atomically bulk deletes all relationships matching the 47 | // provided filter. If no relationships match, none will be deleted and the 48 | // operation will succeed. An optional set of preconditions can be provided that must 49 | // be satisfied for the operation to commit. 50 | rpc DeleteRelationships(DeleteRelationshipsRequest) returns (DeleteRelationshipsResponse) { 51 | option (google.api.http) = { 52 | post: "/v1/relationships/delete" 53 | body: "*" 54 | }; 55 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 56 | tags: ["Permissions"] 57 | }; 58 | } 59 | 60 | // CheckPermission determines for a given resource whether a subject computes 61 | // to having a permission or is a direct member of a particular relation. 62 | rpc CheckPermission(CheckPermissionRequest) returns (CheckPermissionResponse) { 63 | option (google.api.http) = { 64 | post: "/v1/permissions/check" 65 | body: "*" 66 | }; 67 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 68 | tags: ["Permissions"] 69 | }; 70 | } 71 | 72 | // CheckBulkPermissions evaluates the given list of permission checks 73 | // and returns the list of results. 74 | rpc CheckBulkPermissions(CheckBulkPermissionsRequest) returns (CheckBulkPermissionsResponse) { 75 | option (google.api.http) = { 76 | post: "/v1/permissions/checkbulk" 77 | body: "*" 78 | }; 79 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 80 | tags: ["Permissions"] 81 | }; 82 | } 83 | 84 | // ExpandPermissionTree reveals the graph structure for a resource's 85 | // permission or relation. This RPC does not recurse infinitely deep and may 86 | // require multiple calls to fully unnest a deeply nested graph. 87 | rpc ExpandPermissionTree(ExpandPermissionTreeRequest) returns (ExpandPermissionTreeResponse) { 88 | option (google.api.http) = { 89 | post: "/v1/permissions/expand" 90 | body: "*" 91 | }; 92 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 93 | tags: ["Permissions"] 94 | }; 95 | } 96 | 97 | // LookupResources returns all the resources of a given type that a subject 98 | // can access whether via a computed permission or relation membership. 99 | rpc LookupResources(LookupResourcesRequest) returns (stream LookupResourcesResponse) { 100 | option (google.api.http) = { 101 | post: "/v1/permissions/resources" 102 | body: "*" 103 | }; 104 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 105 | tags: ["Permissions"] 106 | }; 107 | } 108 | 109 | // LookupSubjects returns all the subjects of a given type that 110 | // have access whether via a computed permission or relation membership. 111 | rpc LookupSubjects(LookupSubjectsRequest) returns (stream LookupSubjectsResponse) { 112 | option (google.api.http) = { 113 | post: "/v1/permissions/subjects" 114 | body: "*" 115 | }; 116 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 117 | tags: ["Permissions"] 118 | }; 119 | } 120 | 121 | // ImportBulkRelationships is a faster path to writing a large number of 122 | // relationships at once. It is both batched and streaming. For maximum 123 | // performance, the caller should attempt to write relationships in as close 124 | // to relationship sort order as possible: (resource.object_type, 125 | // resource.object_id, relation, subject.object.object_type, 126 | // subject.object.object_id, subject.optional_relation). All relationships 127 | // written are done so under a single transaction. 128 | rpc ImportBulkRelationships(stream ImportBulkRelationshipsRequest) returns (ImportBulkRelationshipsResponse) { 129 | option (google.api.http) = { 130 | post: "/v1/relationships/importbulk" 131 | body: "*" 132 | }; 133 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 134 | tags: ["Permissions"] 135 | }; 136 | } 137 | 138 | // ExportBulkRelationships is the fastest path available to exporting 139 | // relationships from the server. It is resumable, and will return results 140 | // in an order determined by the server. 141 | rpc ExportBulkRelationships(ExportBulkRelationshipsRequest) returns (stream ExportBulkRelationshipsResponse) { 142 | option (google.api.http) = { 143 | post: "/v1/relationships/exportbulk" 144 | body: "*" 145 | }; 146 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 147 | tags: ["Permissions"] 148 | }; 149 | } 150 | } 151 | 152 | // Consistency will define how a request is handled by the backend. 153 | // By defining a consistency requirement, and a token at which those 154 | // requirements should be applied, where applicable. 155 | message Consistency { 156 | oneof requirement { 157 | option (validate.required) = true; 158 | option (buf.validate.oneof).required = true; 159 | 160 | // minimize_latency indicates that the latency for the call should be 161 | // minimized by having the system select the fastest snapshot available. 162 | bool minimize_latency = 1 [ 163 | (validate.rules).bool.const = true, 164 | (buf.validate.field).bool.const = true 165 | ]; 166 | 167 | // at_least_as_fresh indicates that all data used in the API call must be 168 | // *at least as fresh* as that found in the ZedToken; more recent data might 169 | // be used if available or faster. 170 | ZedToken at_least_as_fresh = 2; 171 | 172 | // at_exact_snapshot indicates that all data used in the API call must be 173 | // *at the given* snapshot in time; if the snapshot is no longer available, 174 | // an error will be returned to the caller. 175 | ZedToken at_exact_snapshot = 3; 176 | 177 | // fully_consistent indicates that all data used in the API call *must* be 178 | // at the most recent snapshot found. 179 | // 180 | // NOTE: using this method can be *quite slow*, so unless there is a need to 181 | // do so, it is recommended to use `at_least_as_fresh` with a stored 182 | // ZedToken. 183 | bool fully_consistent = 4 [ 184 | (validate.rules).bool.const = true, 185 | (buf.validate.field).bool.const = true 186 | ]; 187 | } 188 | } 189 | 190 | // RelationshipFilter is a collection of filters which when applied to a 191 | // relationship will return relationships that have exactly matching fields. 192 | // 193 | // All fields are optional and if left unspecified will not filter relationships, 194 | // but at least one field must be specified. 195 | // 196 | // NOTE: The performance of the API will be affected by the selection of fields 197 | // on which to filter. If a field is not indexed, the performance of the API 198 | // can be significantly slower. 199 | message RelationshipFilter { 200 | // resource_type is the *optional* resource type of the relationship. 201 | // NOTE: It is not prefixed with "optional_" for legacy compatibility. 202 | string resource_type = 1 [ 203 | (validate.rules).string = { 204 | pattern: "^(([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9])?$" 205 | max_bytes: 128 206 | }, 207 | (buf.validate.field).string = { 208 | pattern: "^(([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9])?$" 209 | max_bytes: 128 210 | } 211 | ]; 212 | 213 | // optional_resource_id is the *optional* resource ID of the relationship. 214 | // If specified, optional_resource_id_prefix cannot be specified. 215 | string optional_resource_id = 2 [ 216 | (validate.rules).string = { 217 | pattern: "^([a-zA-Z0-9/_|\\-=+]{1,})?$" 218 | max_bytes: 1024 219 | }, 220 | (buf.validate.field).string = { 221 | pattern: "^([a-zA-Z0-9/_|\\-=+]{1,})?$" 222 | max_bytes: 1024 223 | } 224 | ]; 225 | 226 | // optional_resource_id_prefix is the *optional* prefix for the resource ID of the relationship. 227 | // If specified, optional_resource_id cannot be specified. 228 | string optional_resource_id_prefix = 5 [ 229 | (validate.rules).string = { 230 | pattern: "^([a-zA-Z0-9/_|\\-=+]{1,})?$" 231 | max_bytes: 1024 232 | }, 233 | (buf.validate.field).string = { 234 | pattern: "^([a-zA-Z0-9/_|\\-=+]{1,})?$" 235 | max_bytes: 1024 236 | } 237 | ]; 238 | 239 | // relation is the *optional* relation of the relationship. 240 | string optional_relation = 3 [ 241 | (validate.rules).string = { 242 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 243 | max_bytes: 64 244 | }, 245 | (buf.validate.field).string = { 246 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 247 | max_bytes: 64 248 | } 249 | ]; 250 | 251 | // optional_subject_filter is the optional filter for the subjects of the relationships. 252 | SubjectFilter optional_subject_filter = 4; 253 | } 254 | 255 | // SubjectFilter specifies a filter on the subject of a relationship. 256 | // 257 | // subject_type is required and all other fields are optional, and will not 258 | // impose any additional requirements if left unspecified. 259 | message SubjectFilter { 260 | message RelationFilter { 261 | string relation = 1 [ 262 | (validate.rules).string = { 263 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 264 | max_bytes: 64 265 | }, 266 | (buf.validate.field).string = { 267 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 268 | max_bytes: 64 269 | } 270 | ]; 271 | } 272 | 273 | string subject_type = 1 [ 274 | (validate.rules).string = { 275 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 276 | max_bytes: 128 277 | }, 278 | (buf.validate.field).string = { 279 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 280 | max_bytes: 128 281 | } 282 | ]; 283 | 284 | string optional_subject_id = 2 [ 285 | (validate.rules).string = { 286 | pattern: "^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)?$" 287 | max_bytes: 1024 288 | }, 289 | (buf.validate.field).string = { 290 | pattern: "^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)?$" 291 | max_bytes: 1024 292 | } 293 | ]; 294 | 295 | RelationFilter optional_relation = 3; 296 | } 297 | 298 | // ReadRelationshipsRequest specifies one or more filters used to read matching 299 | // relationships within the system. 300 | message ReadRelationshipsRequest { 301 | Consistency consistency = 1; 302 | 303 | // relationship_filter defines the filter to be applied to the relationships 304 | // to be returned. 305 | RelationshipFilter relationship_filter = 2 [ 306 | (validate.rules).message.required = true, 307 | (buf.validate.field).required = true 308 | ]; 309 | 310 | // optional_limit, if non-zero, specifies the limit on the number of relationships to return 311 | // before the stream is closed on the server side. By default, the stream will continue 312 | // resolving relationships until exhausted or the stream is closed due to the client or a 313 | // network issue. 314 | uint32 optional_limit = 3 [ 315 | (validate.rules).uint32 = {gte: 0}, 316 | (buf.validate.field).uint32 = {gte: 0} 317 | ]; 318 | 319 | // optional_cursor, if specified, indicates the cursor after which results should resume being returned. 320 | // The cursor can be found on the ReadRelationshipsResponse object. 321 | Cursor optional_cursor = 4; 322 | } 323 | 324 | // ReadRelationshipsResponse contains a Relationship found that matches the 325 | // specified relationship filter(s). A instance of this response message will 326 | // be streamed to the client for each relationship found. 327 | message ReadRelationshipsResponse { 328 | // read_at is the ZedToken at which the relationship was found. 329 | ZedToken read_at = 1 [ 330 | (validate.rules).message.required = true, 331 | (buf.validate.field).required = true 332 | ]; 333 | 334 | // relationship is the found relationship. 335 | Relationship relationship = 2 [ 336 | (validate.rules).message.required = true, 337 | (buf.validate.field).required = true 338 | ]; 339 | 340 | // after_result_cursor holds a cursor that can be used to resume the ReadRelationships stream after this 341 | // result. 342 | Cursor after_result_cursor = 3; 343 | } 344 | 345 | // Precondition specifies how and the existence or absence of certain 346 | // relationships as expressed through the accompanying filter should affect 347 | // whether or not the operation proceeds. 348 | // 349 | // MUST_NOT_MATCH will fail the parent request if any relationships match the 350 | // relationships filter. 351 | // MUST_MATCH will fail the parent request if there are no 352 | // relationships that match the filter. 353 | message Precondition { 354 | enum Operation { 355 | OPERATION_UNSPECIFIED = 0; 356 | OPERATION_MUST_NOT_MATCH = 1; 357 | OPERATION_MUST_MATCH = 2; 358 | } 359 | 360 | Operation operation = 1 [ 361 | (validate.rules).enum = { 362 | defined_only: true 363 | not_in: [0] 364 | }, 365 | (buf.validate.field).enum = { 366 | defined_only: true 367 | not_in: [0] 368 | } 369 | ]; 370 | RelationshipFilter filter = 2 [ 371 | (validate.rules).message.required = true, 372 | (buf.validate.field).required = true 373 | ]; 374 | } 375 | 376 | // WriteRelationshipsRequest contains a list of Relationship mutations that 377 | // should be applied to the service. If the optional_preconditions parameter 378 | // is included, all of the specified preconditions must also be satisfied before 379 | // the write will be committed. All updates will be applied transactionally, 380 | // and if any preconditions fail, the entire transaction will be reverted. 381 | message WriteRelationshipsRequest { 382 | repeated RelationshipUpdate updates = 1 [ 383 | (validate.rules).repeated.items.message.required = true, 384 | (buf.validate.field).repeated.items.required = true 385 | ]; 386 | 387 | repeated Precondition optional_preconditions = 2 [ 388 | (validate.rules).repeated.items.message.required = true, 389 | (buf.validate.field).repeated.items.required = true 390 | ]; // To be bounded by configuration 391 | 392 | // optional_transaction_metadata is an optional field that can be used to store metadata about the transaction. 393 | // If specified, this metadata will be supplied in the WatchResponse for the updates associated with this 394 | // transaction. 395 | google.protobuf.Struct optional_transaction_metadata = 3 [ 396 | (validate.rules).message.required = false, 397 | (buf.validate.field).required = false 398 | ]; 399 | } 400 | 401 | message WriteRelationshipsResponse { 402 | ZedToken written_at = 1; 403 | } 404 | 405 | // DeleteRelationshipsRequest specifies which Relationships should be deleted, 406 | // requesting the delete of *ALL* relationships that match the specified 407 | // filters. If the optional_preconditions parameter is included, all of the 408 | // specified preconditions must also be satisfied before the delete will be 409 | // executed. 410 | message DeleteRelationshipsRequest { 411 | RelationshipFilter relationship_filter = 1 [ 412 | (validate.rules).message.required = true, 413 | (buf.validate.field).required = true 414 | ]; 415 | 416 | repeated Precondition optional_preconditions = 2 [ 417 | (validate.rules).repeated.items.message.required = true, 418 | (buf.validate.field).repeated.items.required = true 419 | ]; // To be bounded by configuration 420 | 421 | // optional_limit, if non-zero, specifies the limit on the number of relationships to be deleted. 422 | // If there are more matching relationships found to be deleted than the limit specified here, 423 | // the deletion call will fail with an error to prevent partial deletion. If partial deletion 424 | // is needed, specify below that partial deletion is allowed. Partial deletions can be used 425 | // in a loop to delete large amounts of relationships in a *non-transactional* manner. 426 | uint32 optional_limit = 3 [ 427 | (validate.rules).uint32 = {gte: 0}, 428 | (buf.validate.field).uint32 = {gte: 0} 429 | ]; 430 | 431 | // optional_allow_partial_deletions, if true and a limit is specified, will delete matching found 432 | // relationships up to the count specified in optional_limit, and no more. 433 | bool optional_allow_partial_deletions = 4; 434 | 435 | // optional_transaction_metadata is an optional field that can be used to store metadata about the transaction. 436 | // If specified, this metadata will be supplied in the WatchResponse for the deletions associated with 437 | // this transaction. 438 | google.protobuf.Struct optional_transaction_metadata = 5 [ 439 | (validate.rules).message.required = false, 440 | (buf.validate.field).required = false 441 | ]; 442 | } 443 | 444 | message DeleteRelationshipsResponse { 445 | enum DeletionProgress { 446 | DELETION_PROGRESS_UNSPECIFIED = 0; 447 | 448 | // DELETION_PROGRESS_COMPLETE indicates that all remaining relationships matching the filter 449 | // were deleted. Will be returned even if no relationships were deleted. 450 | DELETION_PROGRESS_COMPLETE = 1; 451 | 452 | // DELETION_PROGRESS_PARTIAL indicates that a subset of the relationships matching the filter 453 | // were deleted. Only returned if optional_allow_partial_deletions was true, an optional_limit was 454 | // specified, and there existed more relationships matching the filter than optional_limit would allow. 455 | // Once all remaining relationships have been deleted, DELETION_PROGRESS_COMPLETE will be returned. 456 | DELETION_PROGRESS_PARTIAL = 2; 457 | } 458 | 459 | // deleted_at is the revision at which the relationships were deleted. 460 | ZedToken deleted_at = 1; 461 | 462 | // deletion_progress is an enumeration of the possible outcomes that occurred when attempting to delete the specified relationships. 463 | DeletionProgress deletion_progress = 2; 464 | 465 | // relationships_deleted_count is the number of relationships that were deleted. 466 | uint64 relationships_deleted_count = 3; 467 | } 468 | 469 | // CheckPermissionRequest issues a check on whether a subject has a permission 470 | // or is a member of a relation, on a specific resource. 471 | message CheckPermissionRequest { 472 | Consistency consistency = 1; 473 | 474 | // resource is the resource on which to check the permission or relation. 475 | ObjectReference resource = 2 [ 476 | (validate.rules).message.required = true, 477 | (buf.validate.field).required = true 478 | ]; 479 | 480 | // permission is the name of the permission (or relation) on which to execute 481 | // the check. 482 | string permission = 3 [ 483 | (validate.rules).string = { 484 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 485 | max_bytes: 64 486 | }, 487 | (buf.validate.field).string = { 488 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 489 | max_bytes: 64 490 | } 491 | ]; 492 | 493 | // subject is the subject that will be checked for the permission or relation. 494 | SubjectReference subject = 4 [ 495 | (validate.rules).message.required = true, 496 | (buf.validate.field).required = true 497 | ]; 498 | 499 | // context consists of named values that are injected into the caveat evaluation context 500 | google.protobuf.Struct context = 5 [ 501 | (validate.rules).message.required = false, 502 | (buf.validate.field).required = false 503 | ]; 504 | 505 | // with_tracing, if true, indicates that the response should include a debug trace. 506 | // This can be useful for debugging and performance analysis, but adds a small amount 507 | // of compute overhead to the request. 508 | bool with_tracing = 6; 509 | } 510 | 511 | message CheckPermissionResponse { 512 | enum Permissionship { 513 | PERMISSIONSHIP_UNSPECIFIED = 0; 514 | PERMISSIONSHIP_NO_PERMISSION = 1; 515 | PERMISSIONSHIP_HAS_PERMISSION = 2; 516 | PERMISSIONSHIP_CONDITIONAL_PERMISSION = 3; 517 | } 518 | 519 | ZedToken checked_at = 1 [ 520 | (validate.rules).message.required = false, 521 | (buf.validate.field).required = false 522 | ]; 523 | 524 | // Permissionship communicates whether or not the subject has the requested 525 | // permission or has a relationship with the given resource, over the given 526 | // relation. 527 | // 528 | // This value will be authzed.api.v1.PERMISSIONSHIP_HAS_PERMISSION if the 529 | // requested subject is a member of the computed permission set or there 530 | // exists a relationship with the requested relation from the given resource 531 | // to the given subject. 532 | Permissionship permissionship = 2 [ 533 | (validate.rules).enum = { 534 | defined_only: true 535 | not_in: [0] 536 | }, 537 | (buf.validate.field).enum = { 538 | defined_only: true 539 | not_in: [0] 540 | } 541 | ]; 542 | 543 | // partial_caveat_info holds information of a partially-evaluated caveated response 544 | PartialCaveatInfo partial_caveat_info = 3 [ 545 | (validate.rules).message.required = false, 546 | (buf.validate.field).required = false 547 | ]; 548 | 549 | // debug_trace is the debugging trace of this check, if requested. 550 | DebugInformation debug_trace = 4; 551 | 552 | // optional_expires_at is the time at which at least one of the relationships used to 553 | // compute this result, expires (if any). This is *not* related to the caching window. 554 | google.protobuf.Timestamp optional_expires_at = 5; 555 | } 556 | 557 | // CheckBulkPermissionsRequest issues a check on whether a subject has permission 558 | // or is a member of a relation on a specific resource for each item in the list. 559 | // 560 | // The ordering of the items in the response is maintained in the response. 561 | // Checks with the same subject/permission will automatically be batched for performance optimization. 562 | message CheckBulkPermissionsRequest { 563 | Consistency consistency = 1; 564 | 565 | repeated CheckBulkPermissionsRequestItem items = 2 [ 566 | (validate.rules).repeated.items.message.required = true, 567 | (buf.validate.field).repeated.items.required = true 568 | ]; 569 | 570 | // with_tracing, if true, indicates that each response should include a debug trace. 571 | // This can be useful for debugging and performance analysis, but adds a small amount 572 | // of compute overhead to the request. 573 | bool with_tracing = 3; 574 | } 575 | 576 | message CheckBulkPermissionsRequestItem { 577 | ObjectReference resource = 1 [ 578 | (validate.rules).message.required = true, 579 | (buf.validate.field).required = true 580 | ]; 581 | 582 | string permission = 2 [ 583 | (validate.rules).string = { 584 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 585 | max_bytes: 64 586 | }, 587 | (buf.validate.field).string = { 588 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 589 | max_bytes: 64 590 | } 591 | ]; 592 | 593 | SubjectReference subject = 3 [ 594 | (validate.rules).message.required = true, 595 | (buf.validate.field).required = true 596 | ]; 597 | 598 | google.protobuf.Struct context = 4 [ 599 | (validate.rules).message.required = false, 600 | (buf.validate.field).required = false 601 | ]; 602 | } 603 | 604 | message CheckBulkPermissionsResponse { 605 | ZedToken checked_at = 1 [ 606 | (validate.rules).message.required = false, 607 | (buf.validate.field).required = false 608 | ]; 609 | 610 | repeated CheckBulkPermissionsPair pairs = 2 [ 611 | (validate.rules).repeated.items.message.required = true, 612 | (buf.validate.field).repeated.items.required = true 613 | ]; 614 | } 615 | 616 | message CheckBulkPermissionsPair { 617 | CheckBulkPermissionsRequestItem request = 1; 618 | oneof response { 619 | CheckBulkPermissionsResponseItem item = 2; 620 | google.rpc.Status error = 3; 621 | } 622 | } 623 | 624 | message CheckBulkPermissionsResponseItem { 625 | CheckPermissionResponse.Permissionship permissionship = 1 [ 626 | (validate.rules).enum = { 627 | defined_only: true 628 | not_in: [0] 629 | }, 630 | (buf.validate.field).enum = { 631 | defined_only: true 632 | not_in: [0] 633 | } 634 | ]; 635 | 636 | PartialCaveatInfo partial_caveat_info = 2 [ 637 | (validate.rules).message.required = false, 638 | (buf.validate.field).required = false 639 | ]; 640 | 641 | // debug_trace is the debugging trace of this check, if requested. 642 | DebugInformation debug_trace = 3; 643 | } 644 | 645 | // ExpandPermissionTreeRequest returns a tree representing the expansion of all 646 | // relationships found accessible from a permission or relation on a particular 647 | // resource. 648 | // 649 | // ExpandPermissionTreeRequest is typically used to determine the full set of 650 | // subjects with a permission, along with the relationships that grant said 651 | // access. 652 | message ExpandPermissionTreeRequest { 653 | Consistency consistency = 1; 654 | 655 | // resource is the resource over which to run the expansion. 656 | ObjectReference resource = 2 [ 657 | (validate.rules).message.required = true, 658 | (buf.validate.field).required = true 659 | ]; 660 | 661 | // permission is the name of the permission or relation over which to run the 662 | // expansion for the resource. 663 | string permission = 3 [ 664 | (validate.rules).string = { 665 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 666 | max_bytes: 64 667 | }, 668 | (buf.validate.field).string = { 669 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 670 | max_bytes: 64 671 | } 672 | ]; 673 | } 674 | 675 | message ExpandPermissionTreeResponse { 676 | ZedToken expanded_at = 1; 677 | 678 | // tree_root is a tree structure whose leaf nodes are subjects, and 679 | // intermediate nodes represent the various operations (union, intersection, 680 | // exclusion) to reach those subjects. 681 | PermissionRelationshipTree tree_root = 2; 682 | } 683 | 684 | // LookupResourcesRequest performs a lookup of all resources of a particular 685 | // kind on which the subject has the specified permission or the relation in 686 | // which the subject exists, streaming back the IDs of those resources. 687 | message LookupResourcesRequest { 688 | Consistency consistency = 1; 689 | 690 | // resource_object_type is the type of resource object for which the IDs will 691 | // be returned. 692 | string resource_object_type = 2 [ 693 | (validate.rules).string = { 694 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 695 | max_bytes: 128 696 | }, 697 | (buf.validate.field).string = { 698 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 699 | max_bytes: 128 700 | } 701 | ]; 702 | 703 | // permission is the name of the permission or relation for which the subject 704 | // must Check. 705 | string permission = 3 [ 706 | (validate.rules).string = { 707 | pattern: "^[a-z][a-z0-9_]{1,62}[a-z0-9]$" 708 | max_bytes: 64 709 | }, 710 | (buf.validate.field).string = { 711 | pattern: "^[a-z][a-z0-9_]{1,62}[a-z0-9]$" 712 | max_bytes: 64 713 | } 714 | ]; 715 | 716 | // subject is the subject with access to the resources. 717 | SubjectReference subject = 4 [ 718 | (validate.rules).message.required = true, 719 | (buf.validate.field).required = true 720 | ]; 721 | 722 | // context consists of named values that are injected into the caveat evaluation context 723 | google.protobuf.Struct context = 5 [ 724 | (validate.rules).message.required = false, 725 | (buf.validate.field).required = false 726 | ]; 727 | 728 | // optional_limit, if non-zero, specifies the limit on the number of resources to return 729 | // before the stream is closed on the server side. By default, the stream will continue 730 | // resolving resources until exhausted or the stream is closed due to the client or a 731 | // network issue. 732 | uint32 optional_limit = 6 [ 733 | (validate.rules).uint32 = {gte: 0}, 734 | (buf.validate.field).uint32 = {gte: 0} 735 | ]; 736 | 737 | // optional_cursor, if specified, indicates the cursor after which results should resume being returned. 738 | // The cursor can be found on the LookupResourcesResponse object. 739 | Cursor optional_cursor = 7; 740 | } 741 | 742 | // LookupPermissionship represents whether a Lookup response was partially evaluated or not 743 | enum LookupPermissionship { 744 | LOOKUP_PERMISSIONSHIP_UNSPECIFIED = 0; 745 | LOOKUP_PERMISSIONSHIP_HAS_PERMISSION = 1; 746 | LOOKUP_PERMISSIONSHIP_CONDITIONAL_PERMISSION = 2; 747 | } 748 | 749 | // LookupResourcesResponse contains a single matching resource object ID for the 750 | // requested object type, permission, and subject. 751 | message LookupResourcesResponse { 752 | // looked_up_at is the ZedToken at which the resource was found. 753 | ZedToken looked_up_at = 1; 754 | 755 | // resource_object_id is the object ID of the found resource. 756 | string resource_object_id = 2; 757 | 758 | // permissionship indicates whether the response was partially evaluated or not 759 | LookupPermissionship permissionship = 3 [ 760 | (validate.rules).enum = { 761 | defined_only: true 762 | not_in: [0] 763 | }, 764 | (buf.validate.field).enum = { 765 | defined_only: true 766 | not_in: [0] 767 | } 768 | ]; 769 | 770 | // partial_caveat_info holds information of a partially-evaluated caveated response 771 | PartialCaveatInfo partial_caveat_info = 4 [ 772 | (validate.rules).message.required = false, 773 | (buf.validate.field).required = false 774 | ]; 775 | 776 | // after_result_cursor holds a cursor that can be used to resume the LookupResources stream after this 777 | // result. 778 | Cursor after_result_cursor = 5; 779 | } 780 | 781 | // LookupSubjectsRequest performs a lookup of all subjects of a particular 782 | // kind for which the subject has the specified permission or the relation in 783 | // which the subject exists, streaming back the IDs of those subjects. 784 | message LookupSubjectsRequest { 785 | enum WildcardOption { 786 | WILDCARD_OPTION_UNSPECIFIED = 0; 787 | WILDCARD_OPTION_INCLUDE_WILDCARDS = 1; 788 | WILDCARD_OPTION_EXCLUDE_WILDCARDS = 2; 789 | } 790 | 791 | Consistency consistency = 1; 792 | 793 | // resource is the resource for which all matching subjects for the permission 794 | // or relation will be returned. 795 | ObjectReference resource = 2 [ 796 | (validate.rules).message.required = true, 797 | (buf.validate.field).required = true 798 | ]; 799 | 800 | // permission is the name of the permission (or relation) for which to find 801 | // the subjects. 802 | string permission = 3 [ 803 | (validate.rules).string = { 804 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 805 | max_bytes: 64 806 | }, 807 | (buf.validate.field).string = { 808 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 809 | max_bytes: 64 810 | } 811 | ]; 812 | 813 | // subject_object_type is the type of subject object for which the IDs will 814 | // be returned. 815 | string subject_object_type = 4 [ 816 | (validate.rules).string = { 817 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 818 | max_bytes: 128 819 | }, 820 | (buf.validate.field).string = { 821 | pattern: "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 822 | max_bytes: 128 823 | } 824 | ]; 825 | 826 | // optional_subject_relation is the optional relation for the subject. 827 | string optional_subject_relation = 5 [ 828 | (validate.rules).string = { 829 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 830 | max_bytes: 64 831 | }, 832 | (buf.validate.field).string = { 833 | pattern: "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 834 | max_bytes: 64 835 | } 836 | ]; 837 | 838 | // context consists of named values that are injected into the caveat evaluation context 839 | google.protobuf.Struct context = 6 [ 840 | (validate.rules).message.required = false, 841 | (buf.validate.field).required = false 842 | ]; 843 | 844 | // optional_concrete_limit, if non-zero, specifies the limit on the number of 845 | // *concrete* (non-wildcard) subjects to return before the stream is closed on the 846 | // server side. With the default value of zero, the stream will continue resolving 847 | // concrete subjects until exhausted or the stream is closed due to the client or 848 | // a network issue. 849 | // 850 | // NOTE: Wildcard subjects ("*") have special treatment when cursors and limits are used. Because 851 | // wildcards can apply to *any* concrete subjects, if a wildcard subject is found within the dataset, 852 | // a wildcard subject can be returned for *all* LookupSubjects calls, regardless of the cursor or 853 | // limit. 854 | // 855 | // For example, if wildcards are requested, a wildcard subject exists, there is a specified limit 856 | // of 10 concrete subjects, and at least 10 concrete subjects exist, the API will return 11 subjects 857 | // in total: the 10 concrete + the wildcard 858 | // 859 | // Furthermore, if a wildcard has a set of exclusions generated by the dataset, 860 | // the exclusions *will respect the cursor* and only a *partial* set of exclusions will be returned 861 | // for each invocation of the API. 862 | // 863 | // ***IT IS UP TO THE CALLER IN THIS CASE TO COMBINE THE EXCLUSIONS IF DESIRED*** 864 | uint32 optional_concrete_limit = 7 [ 865 | (validate.rules).uint32 = {gte: 0}, 866 | (buf.validate.field).uint32 = {gte: 0} 867 | ]; 868 | 869 | // optional_cursor, if specified, indicates the cursor after which results should resume being returned. 870 | // The cursor can be found on the LookupSubjectsResponse object. 871 | // 872 | // NOTE: See above for notes about how cursors interact with wildcard subjects. 873 | Cursor optional_cursor = 8; 874 | 875 | // wildcard_option specifies whether wildcards should be returned by LookupSubjects. 876 | // For backwards compatibility, defaults to WILDCARD_OPTION_INCLUDE_WILDCARDS if unspecified. 877 | WildcardOption wildcard_option = 9; 878 | } 879 | 880 | // LookupSubjectsResponse contains a single matching subject object ID for the 881 | // requested subject object type on the permission or relation. 882 | message LookupSubjectsResponse { 883 | ZedToken looked_up_at = 1; 884 | 885 | // subject_object_id is the Object ID of the subject found. May be a `*` if 886 | // a wildcard was found. 887 | // deprecated: use `subject` 888 | string subject_object_id = 2 [deprecated = true]; 889 | 890 | // excluded_subject_ids are the Object IDs of the subjects excluded. This list 891 | // will only contain object IDs if `subject_object_id` is a wildcard (`*`) and 892 | // will only be populated if exclusions exist from the wildcard. 893 | // deprecated: use `excluded_subjects` 894 | repeated string excluded_subject_ids = 3 [deprecated = true]; 895 | 896 | // permissionship indicates whether the response was partially evaluated or not 897 | // deprecated: use `subject.permissionship` 898 | LookupPermissionship permissionship = 4 [ 899 | deprecated = true, 900 | (validate.rules).enum = { 901 | defined_only: true 902 | not_in: [0] 903 | }, 904 | (buf.validate.field).enum = { 905 | defined_only: true 906 | not_in: [0] 907 | } 908 | ]; 909 | 910 | // partial_caveat_info holds information of a partially-evaluated caveated response 911 | // deprecated: use `subject.partial_caveat_info` 912 | PartialCaveatInfo partial_caveat_info = 5 [ 913 | deprecated = true, 914 | (validate.rules).message.required = false, 915 | (buf.validate.field).required = false 916 | ]; 917 | 918 | // subject is the subject found, along with its permissionship. 919 | ResolvedSubject subject = 6; 920 | 921 | // excluded_subjects are the subjects excluded. This list 922 | // will only contain subjects if `subject.subject_object_id` is a wildcard (`*`) and 923 | // will only be populated if exclusions exist from the wildcard. 924 | repeated ResolvedSubject excluded_subjects = 7; 925 | 926 | // after_result_cursor holds a cursor that can be used to resume the LookupSubjects stream after this 927 | // result. 928 | Cursor after_result_cursor = 8; 929 | } 930 | 931 | // ResolvedSubject is a single subject resolved within LookupSubjects. 932 | message ResolvedSubject { 933 | // subject_object_id is the Object ID of the subject found. May be a `*` if 934 | // a wildcard was found. 935 | string subject_object_id = 1; 936 | 937 | // permissionship indicates whether the response was partially evaluated or not 938 | LookupPermissionship permissionship = 2 [ 939 | (validate.rules).enum = { 940 | defined_only: true 941 | not_in: [0] 942 | }, 943 | (buf.validate.field).enum = { 944 | defined_only: true 945 | not_in: [0] 946 | } 947 | ]; 948 | 949 | // partial_caveat_info holds information of a partially-evaluated caveated response 950 | PartialCaveatInfo partial_caveat_info = 3 [ 951 | (validate.rules).message.required = false, 952 | (buf.validate.field).required = false 953 | ]; 954 | } 955 | 956 | // ImportBulkRelationshipsRequest represents one batch of the streaming 957 | // ImportBulkRelationships API. The maximum size is only limited by the backing 958 | // datastore, and optimal size should be determined by the calling client 959 | // experimentally. When ImportBulk is invoked and receives its first request message, 960 | // a transaction is opened to import the relationships. All requests sent to the same 961 | // invocation are executed under this single transaction. If a relationship already 962 | // exists within the datastore, the entire transaction will fail with an error. 963 | message ImportBulkRelationshipsRequest { 964 | repeated Relationship relationships = 1 [ 965 | (validate.rules).repeated.items.message.required = true, 966 | (buf.validate.field).repeated.items.required = true 967 | ]; 968 | } 969 | 970 | // ImportBulkRelationshipsResponse is returned on successful completion of the 971 | // bulk load stream, and contains the total number of relationships loaded. 972 | message ImportBulkRelationshipsResponse { 973 | uint64 num_loaded = 1; 974 | } 975 | 976 | // ExportBulkRelationshipsRequest represents a resumable request for 977 | // all relationships from the server. 978 | message ExportBulkRelationshipsRequest { 979 | Consistency consistency = 1; 980 | 981 | // optional_limit, if non-zero, specifies the limit on the number of 982 | // relationships the server can return in one page. By default, the server 983 | // will pick a page size, and the server is free to choose a smaller size 984 | // at will. 985 | uint32 optional_limit = 2 [ 986 | (validate.rules).uint32 = {gte: 0}, 987 | (buf.validate.field).uint32 = {gte: 0} 988 | ]; 989 | 990 | // optional_cursor, if specified, indicates the cursor after which results 991 | // should resume being returned. The cursor can be found on the 992 | // BulkExportRelationshipsResponse object. 993 | Cursor optional_cursor = 3; 994 | 995 | // optional_relationship_filter, if specified, indicates the 996 | // filter to apply to each relationship to be exported. 997 | RelationshipFilter optional_relationship_filter = 4; 998 | } 999 | 1000 | // ExportBulkRelationshipsResponse is one page in a stream of relationship 1001 | // groups that meet the criteria specified by the originating request. The 1002 | // server will continue to stream back relationship groups as quickly as it can 1003 | // until all relationships have been transmitted back. 1004 | message ExportBulkRelationshipsResponse { 1005 | Cursor after_result_cursor = 1; 1006 | repeated Relationship relationships = 2; 1007 | } 1008 | -------------------------------------------------------------------------------- /authzed/api/v1/schema_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | import "authzed/api/v1/permission_service.proto"; 6 | import "buf/validate/validate.proto"; 7 | import "google/api/annotations.proto"; 8 | import "protoc-gen-openapiv2/options/annotations.proto"; 9 | import "validate/validate.proto"; 10 | 11 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 12 | option java_multiple_files = true; 13 | option java_package = "com.authzed.api.v1"; 14 | 15 | // SchemaService implements operations on a Permissions System's Schema. 16 | service SchemaService { 17 | // Read returns the current Object Definitions for a Permissions System. 18 | // 19 | // Errors include: 20 | // - INVALID_ARGUMENT: a provided value has failed to semantically validate 21 | // - NOT_FOUND: no schema has been defined 22 | rpc ReadSchema(ReadSchemaRequest) returns (ReadSchemaResponse) { 23 | option (google.api.http) = { 24 | post: "/v1/schema/read" 25 | body: "*" 26 | }; 27 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 28 | tags: ["Schema"] 29 | }; 30 | } 31 | 32 | // Write overwrites the current Object Definitions for a Permissions System. 33 | rpc WriteSchema(WriteSchemaRequest) returns (WriteSchemaResponse) { 34 | option (google.api.http) = { 35 | post: "/v1/schema/write" 36 | body: "*" 37 | }; 38 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 39 | tags: ["Schema"] 40 | }; 41 | } 42 | 43 | // ReflectSchema reflects the current schema stored in SpiceDB, returning a structural 44 | // form of the schema for use by client tooling. 45 | rpc ReflectSchema(ReflectSchemaRequest) returns (ReflectSchemaResponse) { 46 | option (google.api.http) = { 47 | post: "/v1/schema/reflectschema" 48 | body: "*" 49 | }; 50 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 51 | tags: ["Schema"] 52 | }; 53 | } 54 | 55 | // ComputablePermissions returns the set of permissions that compute based off a relation 56 | // in the current schema. For example, if the schema has a relation `viewer` and a permission 57 | // `view` defined as `permission view = viewer + editor`, then the 58 | // computable permissions for the relation `viewer` will include `view`. 59 | rpc ComputablePermissions(ComputablePermissionsRequest) returns (ComputablePermissionsResponse) { 60 | option (google.api.http) = { 61 | post: "/v1/schema/permissions/computable" 62 | body: "*" 63 | }; 64 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 65 | tags: ["Schema"] 66 | }; 67 | } 68 | 69 | // DependentRelations returns the set of relations and permissions that used 70 | // to compute a permission, recursively, in the current schema. It is the 71 | // inverse of the ComputablePermissions API. 72 | rpc DependentRelations(DependentRelationsRequest) returns (DependentRelationsResponse) { 73 | option (google.api.http) = { 74 | post: "/v1/schema/permissions/dependent" 75 | body: "*" 76 | }; 77 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 78 | tags: ["Schema"] 79 | }; 80 | } 81 | 82 | // DiffSchema returns the difference between the specified schema and the current 83 | // schema stored in SpiceDB. 84 | rpc DiffSchema(DiffSchemaRequest) returns (DiffSchemaResponse) { 85 | option (google.api.http) = { 86 | post: "/v1/schema/diffschema" 87 | body: "*" 88 | }; 89 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 90 | tags: ["Schema"] 91 | }; 92 | } 93 | } 94 | 95 | // ReadSchemaRequest returns the schema from the database. 96 | message ReadSchemaRequest {} 97 | 98 | // ReadSchemaResponse is the resulting data after having read the Object 99 | // Definitions from a Schema. 100 | message ReadSchemaResponse { 101 | // schema_text is the textual form of the current schema in the system 102 | string schema_text = 1; 103 | 104 | // read_at is the ZedToken at which the schema was read. 105 | ZedToken read_at = 2 [ 106 | (validate.rules).message.required = true, 107 | (buf.validate.field).required = true 108 | ]; 109 | } 110 | 111 | // WriteSchemaRequest is the required data used to "upsert" the Schema of a 112 | // Permissions System. 113 | message WriteSchemaRequest { 114 | // The Schema containing one or more Object Definitions that will be written 115 | // to the Permissions System. 116 | string schema = 1 [ 117 | (validate.rules).string.max_bytes = 4194304, 118 | (buf.validate.field).string.max_bytes = 4194304 119 | ]; // 4MiB 120 | } 121 | 122 | // WriteSchemaResponse is the resulting data after having written a Schema to 123 | // a Permissions System. 124 | message WriteSchemaResponse { 125 | // written_at is the ZedToken at which the schema was written. 126 | ZedToken written_at = 1 [ 127 | (validate.rules).message.required = true, 128 | (buf.validate.field).required = true 129 | ]; 130 | } 131 | 132 | // Reflection types //////////////////////////////////////////// 133 | 134 | message ReflectSchemaRequest { 135 | Consistency consistency = 1; 136 | 137 | // optional_filters defines optional filters that are applied in 138 | // an OR fashion to the schema, before being returned 139 | repeated ReflectionSchemaFilter optional_filters = 2; 140 | } 141 | 142 | message ReflectSchemaResponse { 143 | // definitions are the definitions defined in the schema. 144 | repeated ReflectionDefinition definitions = 1; 145 | 146 | // caveats are the caveats defined in the schema. 147 | repeated ReflectionCaveat caveats = 2; 148 | 149 | // read_at is the ZedToken at which the schema was read. 150 | ZedToken read_at = 3; 151 | } 152 | 153 | // ReflectionSchemaFilter is a filter that can be applied to the schema on reflection. 154 | message ReflectionSchemaFilter { 155 | // optional_definition_name_filter is a prefix that is matched against the definition name. 156 | string optional_definition_name_filter = 1; 157 | 158 | // optional_caveat_name_filter is a prefix that is matched against the caveat name. 159 | string optional_caveat_name_filter = 2; 160 | 161 | // optional_relation_name_filter is a prefix that is matched against the relation name. 162 | string optional_relation_name_filter = 3; 163 | 164 | // optional_permission_name_filter is a prefix that is matched against the permission name. 165 | string optional_permission_name_filter = 4; 166 | } 167 | 168 | // ReflectionDefinition is the representation of a definition in the schema. 169 | message ReflectionDefinition { 170 | string name = 1; 171 | 172 | // comment is a human-readable comments on the definition. Will include 173 | // delimiter characters. 174 | string comment = 2; 175 | 176 | repeated ReflectionRelation relations = 3; 177 | repeated ReflectionPermission permissions = 4; 178 | } 179 | 180 | // ReflectionCaveat is the representation of a caveat in the schema. 181 | message ReflectionCaveat { 182 | string name = 1; 183 | 184 | // comment is a human-readable comments on the caveat. Will include 185 | // delimiter characters. 186 | string comment = 2; 187 | 188 | repeated ReflectionCaveatParameter parameters = 3; 189 | string expression = 4; 190 | } 191 | 192 | // ReflectionCaveatParameter is the representation of a parameter in a caveat. 193 | message ReflectionCaveatParameter { 194 | string name = 1; 195 | 196 | // type is the type of the parameter. Will be a string representing the 197 | // type, e.g. `int` or `list` 198 | string type = 2; 199 | string parent_caveat_name = 3; 200 | } 201 | 202 | // ReflectionRelation is the representation of a relation in the schema. 203 | message ReflectionRelation { 204 | string name = 1; 205 | string comment = 2; 206 | string parent_definition_name = 3; 207 | repeated ReflectionTypeReference subject_types = 4; 208 | } 209 | 210 | // ReflectionTypeReference is the representation of a type reference in the schema. 211 | message ReflectionTypeReference { 212 | // subject_definition_name is the name of the subject's definition. 213 | string subject_definition_name = 1; 214 | 215 | // optional_caveat_name is the name of the caveat that is applied to the subject, if any. 216 | string optional_caveat_name = 2; 217 | 218 | oneof typeref { 219 | // is_terminal_subject is true if the subject is terminal, meaning it is referenced directly vs a sub-relation. 220 | bool is_terminal_subject = 3; 221 | 222 | // optional_relation_name is the name of the relation that is applied to the subject, if any. 223 | string optional_relation_name = 4; 224 | 225 | // is_public_wildcard is true if the subject is a public wildcard. 226 | bool is_public_wildcard = 5; 227 | } 228 | } 229 | 230 | // ReflectionPermission is the representation of a permission in the schema. 231 | message ReflectionPermission { 232 | string name = 1; 233 | 234 | // comment is a human-readable comments on the permission. Will include 235 | // delimiter characters. 236 | string comment = 2; 237 | string parent_definition_name = 3; 238 | } 239 | 240 | message ComputablePermissionsRequest { 241 | Consistency consistency = 1; 242 | string definition_name = 2; 243 | string relation_name = 3; 244 | 245 | // optional_definition_name_match is a prefix that is matched against the definition name(s) 246 | // for the permissions returned. 247 | // If not specified, will be ignored. 248 | string optional_definition_name_filter = 4; 249 | } 250 | 251 | // ReflectionRelationReference is a reference to a relation or permission in the schema. 252 | message ReflectionRelationReference { 253 | string definition_name = 1; 254 | string relation_name = 2; 255 | bool is_permission = 3; 256 | } 257 | 258 | message ComputablePermissionsResponse { 259 | repeated ReflectionRelationReference permissions = 1; 260 | 261 | // read_at is the ZedToken at which the schema was read. 262 | ZedToken read_at = 2; 263 | } 264 | 265 | message DependentRelationsRequest { 266 | Consistency consistency = 1; 267 | string definition_name = 2; 268 | string permission_name = 3; 269 | } 270 | 271 | message DependentRelationsResponse { 272 | repeated ReflectionRelationReference relations = 1; 273 | 274 | // read_at is the ZedToken at which the schema was read. 275 | ZedToken read_at = 2; 276 | } 277 | 278 | message DiffSchemaRequest { 279 | Consistency consistency = 1; 280 | string comparison_schema = 2; 281 | } 282 | 283 | message DiffSchemaResponse { 284 | repeated ReflectionSchemaDiff diffs = 1; 285 | 286 | // read_at is the ZedToken at which the schema was read. 287 | ZedToken read_at = 2; 288 | } 289 | 290 | message ReflectionRelationSubjectTypeChange { 291 | ReflectionRelation relation = 1; 292 | ReflectionTypeReference changed_subject_type = 2; 293 | } 294 | 295 | message ReflectionCaveatParameterTypeChange { 296 | ReflectionCaveatParameter parameter = 1; 297 | string previous_type = 2; 298 | } 299 | 300 | // ReflectionSchemaDiff is the representation of a diff between two schemas. 301 | message ReflectionSchemaDiff { 302 | oneof diff { 303 | ReflectionDefinition definition_added = 1; 304 | ReflectionDefinition definition_removed = 2; 305 | ReflectionDefinition definition_doc_comment_changed = 3; 306 | ReflectionRelation relation_added = 4; 307 | ReflectionRelation relation_removed = 5; 308 | ReflectionRelation relation_doc_comment_changed = 6; 309 | ReflectionRelationSubjectTypeChange relation_subject_type_added = 7; 310 | ReflectionRelationSubjectTypeChange relation_subject_type_removed = 8; 311 | ReflectionPermission permission_added = 9; 312 | ReflectionPermission permission_removed = 10; 313 | ReflectionPermission permission_doc_comment_changed = 11; 314 | ReflectionPermission permission_expr_changed = 12; 315 | ReflectionCaveat caveat_added = 13; 316 | ReflectionCaveat caveat_removed = 14; 317 | ReflectionCaveat caveat_doc_comment_changed = 15; 318 | ReflectionCaveat caveat_expr_changed = 16; 319 | ReflectionCaveatParameter caveat_parameter_added = 17; 320 | ReflectionCaveatParameter caveat_parameter_removed = 18; 321 | ReflectionCaveatParameterTypeChange caveat_parameter_type_changed = 19; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /authzed/api/v1/watch_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package authzed.api.v1; 3 | 4 | import "authzed/api/v1/core.proto"; 5 | import "authzed/api/v1/permission_service.proto"; 6 | import "buf/validate/validate.proto"; 7 | import "google/api/annotations.proto"; 8 | import "google/protobuf/struct.proto"; 9 | import "protoc-gen-openapiv2/options/annotations.proto"; 10 | import "validate/validate.proto"; 11 | 12 | option go_package = "github.com/authzed/authzed-go/proto/authzed/api/v1"; 13 | option java_multiple_files = true; 14 | option java_package = "com.authzed.api.v1"; 15 | 16 | service WatchService { 17 | // Watch returns a stream of events that occurred in the datastore in ascending timestamp order. 18 | // The events can be relationship updates, schema updates, or checkpoints. 19 | rpc Watch(WatchRequest) returns (stream WatchResponse) { 20 | option (google.api.http) = { 21 | post: "/v1/watch" 22 | body: "*" 23 | }; 24 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { 25 | tags: ["Watch"] 26 | }; 27 | } 28 | } 29 | 30 | enum WatchKind { 31 | WATCH_KIND_UNSPECIFIED = 0; // Default, just relationship updates (for backwards compatibility) 32 | WATCH_KIND_INCLUDE_RELATIONSHIP_UPDATES = 1; 33 | WATCH_KIND_INCLUDE_SCHEMA_UPDATES = 2; 34 | WATCH_KIND_INCLUDE_CHECKPOINTS = 3; 35 | } 36 | 37 | // WatchRequest specifies what mutations to watch for, and an optional start snapshot for when to start 38 | // watching. 39 | message WatchRequest { 40 | // optional_object_types is a filter of resource object types to watch for relationship changes. 41 | // If specified, only changes to the specified object types will be returned and 42 | // optional_relationship_filters cannot be used. 43 | repeated string optional_object_types = 1 [ 44 | (validate.rules).repeated.min_items = 0, 45 | (buf.validate.field).repeated.min_items = 0, 46 | (validate.rules).repeated.items.string = { 47 | pattern: 48 | "^([a-z][a-z0-9_]{1,62}[a-z0-9]/" 49 | ")*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 50 | max_bytes: 128 51 | }, 52 | (buf.validate.field).repeated.items.string = { 53 | pattern: 54 | "^([a-z][a-z0-9_]{1,62}[a-z0-9]/" 55 | ")*[a-z][a-z0-9_]{1,62}[a-z0-9]$" 56 | max_bytes: 128 57 | } 58 | ]; 59 | 60 | // optional_start_cursor is the ZedToken holding the point-in-time at 61 | // which to start watching for changes. 62 | // If not specified, the watch will begin at the current head revision 63 | // of the datastore, returning any updates that occur after the caller 64 | // makes the request. 65 | // Note that if this cursor references a point-in-time containing data 66 | // that has been garbage collected, an error will be returned. 67 | ZedToken optional_start_cursor = 2; 68 | 69 | // optional_relationship_filters, if specified, indicates the 70 | // filter(s) to apply to each relationship to be returned by watch. 71 | // The relationship will be returned as long as at least one filter matches, 72 | // this allows clients to match relationships on multiple filters on a single watch call. 73 | // If specified, optional_object_types cannot be used. 74 | repeated RelationshipFilter optional_relationship_filters = 3; 75 | 76 | // optional_update_kinds, if specified, indicates what kinds of mutations to include. 77 | repeated WatchKind optional_update_kinds = 4; 78 | } 79 | 80 | // WatchResponse contains all mutation events in ascending timestamp order, 81 | // from the requested start snapshot to a snapshot 82 | // encoded in the watch response. The client can use the snapshot to resume 83 | // watching where the previous watch response left off. 84 | message WatchResponse { 85 | // updates are the RelationshipUpdate events that have occurred since the 86 | // last watch response. 87 | repeated RelationshipUpdate updates = 1; 88 | 89 | // changes_through is the ZedToken that represents the point in time 90 | // that the watch response is current through. This token can be used 91 | // in a subsequent WatchRequest to resume watching from this point. 92 | ZedToken changes_through = 2; 93 | 94 | // optional_transaction_metadata is an optional field that returns the transaction metadata 95 | // given to SpiceDB during the transaction that produced the changes in this response. 96 | // This field may not exist if no transaction metadata was provided. 97 | google.protobuf.Struct optional_transaction_metadata = 3; 98 | 99 | // schema_updated, if true, indicates that the schema was changed in this revision. 100 | bool schema_updated = 4; 101 | 102 | // is_checkpoint, if true, indicates that a checkpoint was reached. 103 | // A checkpoint indicates that the server guarantees that the client 104 | // will not observe any changes at a revision below or equal to the revision in this response. 105 | bool is_checkpoint = 5; 106 | } 107 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S buf generate --template 2 | --- 3 | version: "v1" 4 | plugins: 5 | - plugin: "buf.build/grpc-ecosystem/openapiv2:v2.26.3" 6 | out: "docs" 7 | opt: 8 | - "openapi_naming_strategy=simple" 9 | - "allow_merge=true" 10 | - "disable_service_tags=true" # Hide the Materialize APIs, which aren't exposed via HTTP. 11 | -------------------------------------------------------------------------------- /buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v2 3 | deps: 4 | - name: buf.build/bufbuild/protovalidate 5 | commit: 0409229c37804d6187ee0806eb4eebce 6 | digest: b5:795db9d3a6e066dc61d99ac651fa7f136171869abe2211ca272dd84aada7bc4583b9508249fa5b61300a5b1fe8b6dbf6edbc088aa0345d1ccb9fff705e3d48e9 7 | - name: buf.build/envoyproxy/protoc-gen-validate 8 | commit: daf171c6cdb54629b5f51e345a79e4dd 9 | digest: b5:c745e1521879f43740230b1df673d0729f55704efefdcfc489d4a0a2d40c92a26cacfeab62813403040a8b180142d53b398c7ca784a065e43823605ee49681de 10 | - name: buf.build/googleapis/googleapis 11 | commit: 61b203b9a9164be9a834f58c37be6f62 12 | digest: b5:7811a98b35bd2e4ae5c3ac73c8b3d9ae429f3a790da15de188dc98fc2b77d6bb10e45711f14903af9553fa9821dff256054f2e4b7795789265bc476bec2f088c 13 | - name: buf.build/grpc-ecosystem/grpc-gateway 14 | commit: 4c5ba75caaf84e928b7137ae5c18c26a 15 | digest: b5:c113e62fb3b29289af785866cae062b55ec8ae19ab3f08f3004098928fbca657730a06810b2012951294326b95669547194fa84476b9e9b688d4f8bf77a0691d 16 | -------------------------------------------------------------------------------- /buf.md: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /buf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "v2" 3 | name: "buf.build/authzed/api" 4 | deps: 5 | - "buf.build/envoyproxy/protoc-gen-validate" 6 | - "buf.build/bufbuild/protovalidate" 7 | - "buf.build/googleapis/googleapis" 8 | - "buf.build/grpc-ecosystem/grpc-gateway" 9 | lint: 10 | except: 11 | - "FIELD_NOT_REQUIRED" 12 | - "PACKAGE_NO_IMPORT_CYCLE" 13 | - "PACKAGE_VERSION_SUFFIX" 14 | disallow_comment_ignores: true 15 | breaking: 16 | use: 17 | - "WIRE_JSON" 18 | except: 19 | - "FIELD_SAME_DEFAULT" 20 | -------------------------------------------------------------------------------- /magefiles/dev.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | 7 | "github.com/magefile/mage/mg" 8 | ) 9 | 10 | type Dev mg.Namespace 11 | 12 | // InstallDependencies installs development tools required for the project. 13 | func (Dev) InstallDependencies() error { 14 | commands := [][]string{ 15 | {"brew", "install", "pre-commit"}, 16 | {"brew", "install", "bufbuild/buf/buf"}, 17 | {"pre-commit", "install"}, 18 | } 19 | 20 | for _, cmd := range commands { 21 | fmt.Printf("Running: %s\n", cmd) 22 | c := exec.Command(cmd[0], cmd[1:]...) 23 | if err := c.Run(); err != nil { 24 | return fmt.Errorf("failed to run %v: %w", cmd, err) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | // BufGenerate builds the image and then produces results according to `buf.gen.yaml`. 32 | func (Dev) BufGenerate() error { 33 | fmt.Println("Running buf generate...") 34 | c := exec.Command("buf", "generate") 35 | if err := c.Run(); err != nil { 36 | return fmt.Errorf("failed to run buf generate: %w", err) 37 | } 38 | return nil 39 | } 40 | 41 | // BufBuild compiles protobuf files into an internal image format, verifying that the definitions are syntactically and semantically correct (e.g., resolving types, imports, etc.). 42 | func (Dev) BufBuild() error { 43 | fmt.Println("Running buf build...") 44 | c := exec.Command("buf", "build") 45 | if err := c.Run(); err != nil { 46 | return fmt.Errorf("failed to run buf build: %w", err) 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /magefiles/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/authzed/api/magefiles 2 | 3 | go 1.24.0 4 | 5 | require github.com/magefile/mage v1.15.0 6 | -------------------------------------------------------------------------------- /magefiles/go.sum: -------------------------------------------------------------------------------- 1 | github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= 2 | github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 3 | --------------------------------------------------------------------------------