diff --git a/.github/sync.yml b/.github/sync.yml new file mode 100644 index 00000000..3b2680ea --- /dev/null +++ b/.github/sync.yml @@ -0,0 +1,19 @@ +Permify/permify-pro: + - source: go.mod + dest: go.mod + - source: go.sum + dest: go.sum + - source: cmd/ + dest: cmd/ + - source: integration-test/ + dest: integration-test/ + - source: internal/ + dest: internal/ + exclude: | + info.go + - source: pkg/ + dest: pkg/ + - source: proto/ + dest: proto/ + - source: tools/ + dest: tools/ \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bd1549da..a12f9f35 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/init@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -64,7 +64,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/autobuild@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -77,6 +77,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fc3a42df..8ae22531 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -24,4 +24,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: 'Dependency Review' - uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # v4.0.0 + uses: actions/dependency-review-action@0fa40c3c10055986a88de3baa0d6ec17c5a894b3 # v4.2.3 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index 056c145d..00000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: Publish Docs - -on: - push: - branches: - - master - paths: - - 'docs/**' - workflow_dispatch: - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - egress-policy: audit - - - name: Checkout Repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version: '16' - - - name: Install Dependencies - run: | - cd docs - yarn install - - - name: Build Project - run: | - cd docs - yarn build - - - name: Deploy to S3 - uses: jakejarvis/s3-sync-action@7ed8b112447abb09f1da74f3466e4194fc7a6311 # master - with: - args: --acl public-read --follow-symlinks --delete - env: - AWS_S3_BUCKET: ${{ secrets.AWS_DOCS_S3_BUCKET }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: 'us-east-1' - SOURCE_DIR: 'docs/build/' - DEST_DIR: '' - - - name: Invalidate CloudFront Distribution - uses: chetan/invalidate-cloudfront-action@fce6f6f546fae2e9fe55f3bd1411063a908f2557 # master - env: - DISTRIBUTION: ${{ secrets.DISTRIBUTION }} - PATHS: '/*' - AWS_REGION: 'us-east-1' - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 200224b7..96e6575a 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -25,20 +25,20 @@ jobs: with: go-version: ~1.21.3 - name: Log in to GHCR - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Login to dockerhub - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name : Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # v3.2.0 - name: Run GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4f45f58..61cc6a35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,20 +25,20 @@ jobs: with: go-version: ~1.21.3 - name: Log in to GHCR - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GH_TOKEN }} - name: Login to dockerhub - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # v3.2.0 - name: Run GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e4f1134d..8890c1e4 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/upload-sarif@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 with: sarif_file: results.sarif diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml new file mode 100644 index 00000000..25579a0a --- /dev/null +++ b/.github/workflows/sdk-generator.yml @@ -0,0 +1,50 @@ +name: Generate Client SDKs from OpenAPI + +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.SDK_GH_TOKEN }} + ORG_NAME: permify + SWAGGER_PATH: docs/api-reference/apidocs.swagger.json + + strategy: + matrix: + language: [python, javascript] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Generate Python Client + uses: openapi-generators/openapitools-generator-action@d27bd4385276f24d23ea92157dfdf4c47be4bbca # v1 + with: + generator: ${{ matrix.language }} + openapi-file: ${SWAGGER_PATH} + command-args: -o permify-client --git-user-id ${ORG_NAME} --git-repo-id permify-${{ matrix.language }} --api-package permify --package-name permify + + - name: Push SDK to GitHub + run: | + git config --global user.name 'GitHub Actions Bot' + git config --global user.email '<>' + git clone https://${GITHUB_TOKEN}@github.com/${ORG_NAME}/permify-${{ matrix.language }}.git temp + cp -r permify-client/* temp/ + cd temp + git add . + git diff-index --quiet HEAD || git commit -m "Update ${{ matrix.language }} SDK from OpenAPI changes" + git push https://${GITHUB_TOKEN}@github.com/${ORG_NAME}/permify-${{ matrix.language }}.git main --force + rm -rf permify-client diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 00000000..9d5b3b4d --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,21 @@ +name: Sync to Permify Pro +on: + push: + branches: + - master + workflow_dispatch: +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # master + - name: Run GitHub File Sync + uses: BetaHuhn/repo-file-sync-action@3023dac7ce66c18b119e2012348437eadeaea116 # v1.21.0 + with: + GH_PAT: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fe8a9886..ec32b3a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ # Step 1: Builder -FROM golang:1.21-alpine3.18@sha256:d8b99943fb0587b79658af03d4d4e8b57769b21dcf08a8401352a9f2a7228754 as permify-builder +FROM golang:1.22-alpine3.18@sha256:2745a45f77ae2e7be569934fa9a111f067d04c767f54577e251d9b101250e46b as permify-builder WORKDIR /go/src/app RUN apk update && apk add --no-cache git COPY . . RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod CGO_ENABLED=0 go build -v ./cmd/permify/ # Step 2: Final -FROM cgr.dev/chainguard/static:latest@sha256:fd59d10894f38ce93eb6e587595ccdd8570bfd9c8f6fde7df4c589a5cefd82e2 +FROM cgr.dev/chainguard/static:latest@sha256:17c46078cc3a08fa218189d8446f88990361e8fd9e2cb6f6f535a7496c389e8e COPY --from=ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.19 /ko-app/grpc-health-probe /usr/local/bin/grpc_health_probe COPY --from=permify-builder /go/src/app/permify /usr/local/bin/permify ENV PATH="$PATH:/usr/local/bin" diff --git a/Dockerfile.local b/Dockerfile.local index 2b6759c8..6ffc1709 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,4 +1,4 @@ -FROM golang:1.21-alpine +FROM golang:1.22-alpine RUN apk --no-cache add curl # Install the air binary so we get live code-reloading when we save files diff --git a/Makefile b/Makefile index 9449ed1f..69742733 100644 --- a/Makefile +++ b/Makefile @@ -86,8 +86,4 @@ serve: build .PHONY: serve-playground serve-playground: - cd ./playground && yarn start - -.PHONY: serve-docs -serve-docs: - cd ./docs && yarn start \ No newline at end of file + cd ./playground && yarn start \ No newline at end of file diff --git a/README.md b/README.md index 8a179865..5bbae064 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,15 @@ Our goal is to make Google's Zanzibar available to everyone and help them build ### With Permify, you can: -๐Ÿ”ฎ Create permissions and policies using [Permify's flexible authorization language](https://docs.permify.co/docs/getting-started/modeling) that is compatible with traditional roles and permissions (RBAC), arbitrary relations between users and objects (ReBAC), and attributes (ABAC). +๐Ÿ”ฎ Create permissions and policies using [Permify's flexible authorization language](https://docs.permify.co/getting-started/modeling) that is compatible with traditional roles and permissions (RBAC), arbitrary relations between users and objects (ReBAC), and attributes (ABAC). -๐Ÿ” [Manage and store authorization data](https://docs.permify.co/docs/getting-started/sync-data) in your preferred database with high availability and consistency. +๐Ÿ” [Manage and store authorization data](https://docs.permify.co/getting-started/sync-data) in your preferred database with high availability and consistency. -โœ… [Interact with the Permify API](https://docs.permify.co/docs/getting-started/enforcement) to perform access checks, filter your resources with specific permissions, perform bulk permission checks for various resources, and more. +โœ… [Interact with the Permify API](https://docs.permify.co/getting-started/enforcement) to perform access checks, filter your resources with specific permissions, perform bulk permission checks for various resources, and more. -๐Ÿงช Test your authorization logic with [Permify's schema testing](https://docs.permify.co/docs/getting-started/testing). You can conduct scenario-based testing, policy coverage analysis, and IDL parser integration to achieve end-to-end validations for your desired authorization schema. +๐Ÿงช Test your authorization logic with [Permify's schema testing](https://docs.permify.co/getting-started/testing). You can conduct scenario-based testing, policy coverage analysis, and IDL parser integration to achieve end-to-end validations for your desired authorization schema. -โš™๏ธ Create custom and isolated authorization models for different applications using Permify [Multi-Tenancy](https://docs.permify.co/docs/use-cases/multi-tenancy) support, all managed within a single place, Permify instance. +โš™๏ธ Create custom and isolated authorization models for different applications using Permify [Multi-Tenancy](https://docs.permify.co/use-cases/multi-tenancy) support, all managed within a single place, Permify instance. ## Getting Started @@ -53,9 +53,9 @@ Our goal is to make Google's Zanzibar available to everyone and help them build - Explore overview of [Permify API] and learn how to interact with it. - See [our article] to examine [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) in a nutshell. -[Permify's Authorization Language]: https://docs.permify.co/docs/getting-started/modeling +[Permify's Authorization Language]: https://docs.permify.co/getting-started/modeling [playground]: https://play.permify.co/ -[Permify API]: https://docs.permify.co/docs/api-overview +[Permify API]: https://docs.permify.co/api-reference [our article]: https://permify.co/post/google-zanzibar-in-a-nutshell ### QuickStart @@ -73,7 +73,7 @@ This will start Permify with the default configuration options: See [all of the options] that you can use to set up and deploy Permify in your servers. -[all of the options]: https://docs.permify.co/docs/installation +[all of the options]: https://docs.permify.co/setting-up #### Test your connection diff --git a/buf.gen.yaml b/buf.gen.yaml index 62b9dd70..defc4465 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -28,7 +28,7 @@ plugins: - paths=source_relative - logtostderr=true - plugin: buf.build/grpc-ecosystem/openapiv2:v2.16.2 - out: docs + out: docs/api-reference opt: - openapi_naming_strategy=simple - allow_merge=true diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 00000000..5832fb72 Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 3d94332c..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - - -# Generated files -.docusaurus -.cache-loader - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* \ No newline at end of file diff --git a/docs/LICENSE b/docs/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/docs/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/README.md b/docs/README.md index 5bffd7d6..c89c478d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,32 @@ -# Permify Documentation +# Mintlify Starter Kit -Documentation for [Permify], open source authorization service. +Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including -[Permify]: https://github.com/Permify/permify +- Guide pages +- Navigation +- Customizations +- API Reference pages +- Use of popular components -

- Permify Licence  - Permify Discord Channel  -

\ No newline at end of file +### Development + +Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command + +``` +npm i -g mintlify +``` + +Run the following command at the root of your documentation (where mint.json is) + +``` +mintlify dev +``` + +### Publishing Changes + +Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. + +#### Troubleshooting + +- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. +- Page loads as a 404 - Make sure you are running in a folder with `mint.json` diff --git a/docs/api-reference/.DS_Store b/docs/api-reference/.DS_Store new file mode 100644 index 00000000..bd22b593 Binary files /dev/null and b/docs/api-reference/.DS_Store differ diff --git a/docs/apidocs.swagger.json b/docs/api-reference/apidocs.swagger.json similarity index 73% rename from docs/apidocs.swagger.json rename to docs/api-reference/apidocs.swagger.json index ac38c267..bdf04722 100644 --- a/docs/apidocs.swagger.json +++ b/docs/api-reference/apidocs.swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Permify API", "description": "Permify is an open source authorization service for creating fine-grained and scalable authorization systems.", - "version": "v0.7.6", + "version": "v0.7.8", "contact": { "name": "API Support", "url": "https://github.com/Permify/permify/issues", @@ -46,7 +46,7 @@ "paths": { "/v1/tenants/create": { "post": { - "summary": "create new tenant", + "summary": "create tenant", "operationId": "tenants.create", "responses": { "200": { @@ -75,6 +75,23 @@ ], "tags": [ "Tenancy" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Tenancy.Create(context.Background(), \u0026v1.TenantCreateRequest {\n Id: \"\"\n Name: \"\"\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.tenancy.create({\n id: \"\",\n name: \"\"\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'http://localhost:3476/v1/tenants/create' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"id\": \"\",\n \"name\": \"\"\n}'" + } ] } }, @@ -109,6 +126,23 @@ ], "tags": [ "Tenancy" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Tenancy.List(context.Background(), \u0026v1.TenantListRequest{\n PageSize: 20,\n ContinuousToken: \"\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.tenancy.list({\n pageSize: 20,\n continuousToken: \"\",\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/list' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'" + } ] } }, @@ -141,6 +175,23 @@ ], "tags": [ "Tenancy" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Tenancy.Delete(context.Background(), \u0026v1.TenantDeleteRequest {\n Id: \"\"\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.tenancy.delete({\n id: \"\",\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1'" + } ] } }, @@ -165,6 +216,7 @@ "parameters": [ { "name": "tenant_id", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -187,6 +239,23 @@ ], "tags": [ "Bundle" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Bundle.Delete(context.Background(), \u0026v1.BundleDeleteRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.delete({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'" + } ] } }, @@ -211,6 +280,7 @@ "parameters": [ { "name": "tenant_id", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -231,6 +301,23 @@ ], "tags": [ "Bundle" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Bundle.Read(context.Background(), \u0026v1.BundleReadRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.read({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'" + } ] } }, @@ -255,6 +342,7 @@ "parameters": [ { "name": "tenant_id", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -281,12 +369,29 @@ ], "tags": [ "Bundle" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err := client.Bundle.Write(context.Background(), \u0026v1.BundleWriteRequest{\n TenantId: \"t1\",\n Bundles: []*v1.DataBundle{\n {\n Name: \"organization_created\",\n Arguments: []string{\n \"creatorID\",\n \"organizationID\",\n },\n Operations: []*v1.Operation{\n {\n RelationshipsWrite: []string{\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n },\n AttributesWrite: []string{\n \"organization:{{.organizationID}}$public|boolean:false\",\n },\n },\n },\n },\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.write({\n tenantId: \"t1\",\n bundles: [\n {\n name: \"organization_created\",\n arguments: [\n \"creatorID\",\n \"organizationID\",\n ],\n operations: [\n {\n relationships_write: [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n attributes_write: [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ]\n }\n ]\n }\n ]\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"bundles\": [\n {\n \"name\": \"organization_created\"\n \"arguments\": [\n \"creatorID\",\n \"organizationID\"\n ],\n \"operations\": [\n {\n \"relationships_write\": [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n \"attributes_write\": [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ],\n },\n ],\n },\n ],\n}'" + } ] } }, "/v1/tenants/{tenant_id}/data/attributes/read": { "post": { - "summary": "read attribute(s)", + "summary": "read attributes", "operationId": "data.attributes.read", "responses": { "200": { @@ -305,7 +410,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant from which the attributes are being read.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -341,6 +446,23 @@ ], "tags": [ "Data" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.ReadAttributes(context.Background(), \u0026 v1.Data.AttributeReadRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.Data.AttributeReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: \u0026v1.AttributeFilter {\n Entity: \u0026v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Attributes: []string {\"private\"},\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.readAttributes({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}'" + } ] } }, @@ -365,7 +487,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant from which the data will be deleted.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -392,12 +514,29 @@ ], "tags": [ "Data" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.Delete(context.Background(), \u0026 v1.DataDeleteRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.DataDeleteRequestMetadata {\n SnapToken: \"\"\n },\n TupleFilter: \u0026v1.TupleFilter {\n Entity: \u0026v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"admin\",\n Subject: \u0026v1.SubjectFilter {\n Type: \"user\",\n Id: []string {\"1\"},\n Relation: \"\"\n }}\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.delete({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n tupleFilter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"admin\",\n subject: {\n type: \"user\",\n ids: [\n \"1\"\n ],\n relation: \"\"\n }\n }\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"tupleFilter\": {\n \"entity\": {\n \"type\": \"organization\",\n \"ids\": [\n \"1\"\n ]\n },\n \"relation\": \"admin\",\n \"subject\": {\n \"type\": \"user\",\n \"ids\": [\n \"1\"\n ],\n \"relation\": \"\"\n }\n },\n}'" + } ] } }, "/v1/tenants/{tenant_id}/data/relationships/read": { "post": { - "summary": "read relation tuple(s)", + "summary": "read relationships", "operationId": "data.relationships.read", "responses": { "200": { @@ -416,7 +555,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant for which relationships are read.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -452,6 +591,23 @@ ], "tags": [ "Data" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.ReadRelationships(context.Background(), \u0026 v1.Data.RelationshipReadRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.Data.RelationshipReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: \u0026v1.TupleFilter {\n Entity: \u0026v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"member\",\n Subject: \u0026v1.SubjectFilter {\n Type: \"\",\n Id: []string {\"\"},\n Relation: \"\"\n }}\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.readRelationships({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}'" + } ] } }, @@ -476,6 +632,7 @@ "parameters": [ { "name": "tenant_id", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -505,12 +662,29 @@ ], "tags": [ "Data" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.RunBundle(context.Background(), \u0026v1.BundleRunRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n Arguments: map[string]string{\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.runBundle({\n tenantId: \"t1\",\n name: \"organization_created\",\n arguments: {\n creatorID: \"564\",\n organizationID: \"789\",\n }\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/run-bundle' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n \"arguments\": {\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n }\n}'" + } ] } }, "/v1/tenants/{tenant_id}/data/write": { "post": { - "summary": "create data", + "summary": "write data", "operationId": "data.write", "responses": { "200": { @@ -529,7 +703,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant for which data is written.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -568,12 +742,29 @@ ], "tags": [ "Data" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "// Convert the wrapped attribute value into Any proto message\nvalue, err := anypb.New(\u0026v1.BooleanValue{\n Data: true,\n})\nif err != nil {\n // Handle error\n}\n\ncr, err := client.Data.Write(context.Background(), \u0026v1.DataWriteRequest{\n TenantId: \"t1\",,\n Metadata: \u0026v1.DataWriteRequestMetadata{\n SchemaVersion: \"\",\n },\n Tuples: []*v1.Attribute{\n {\n Entity: \u0026v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Relation: \"editor\",\n Subject: \u0026v1.Subject{\n Type: \"user\",\n Id: \"1\",\n Relation: \"\",\n },\n },\n },\n Attributes: []*v1.Attribute{\n {\n Entity: \u0026v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Attribute: \"is_private\",\n Value: value,\n },\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "const booleanValue = BooleanValue.fromJSON({ data: true });\n\nconst value = Any.fromJSON({\n typeUrl: 'type.googleapis.com/base.v1.BooleanValue',\n value: BooleanValue.encode(booleanValue).finish()\n});\n\nclient.data.write({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: \"\"\n },\n tuples: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n relation: \"editor\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n }],\n attributes: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n attribute: \"is_private\",\n value: value,\n }]\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n{\n \"metadata\": {\n \"schema_version\": \"\"\n },\n \"tuples\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"relation\": \"editor\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\"\n }\n }\n ],\n \"attributes\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"attribute\": \"is_private\",\n \"value\": {\n \"@type\": \"type.googleapis.com/base.v1.BooleanValue\",\n \"data\": true\n }\n }\n ]\n}\n}'" + } ] } }, "/v1/tenants/{tenant_id}/permissions/check": { "post": { - "summary": "This method returns a decision about whether user can perform an permission on a certain resource.", + "summary": "check api", "operationId": "permissions.check", "responses": { "200": { @@ -592,7 +783,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -610,11 +801,12 @@ }, "entity": { "$ref": "#/definitions/Entity", + "example": "repository:1", "description": "Entity on which the permission needs to be checked, required." }, "permission": { "type": "string", - "description": "Name of the permission or relation, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + "description": "The action the user wants to perform on the resource" }, "subject": { "$ref": "#/definitions/Subject", @@ -622,7 +814,7 @@ }, "context": { "$ref": "#/definitions/Context", - "description": "Context associated with this request." + "description": "Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../operations/contextual-tuples)" }, "arguments": { "type": "array", @@ -639,12 +831,29 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Permission.Check(context.Background(), \u0026v1.PermissionCheckRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.PermissionCheckRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: \u0026v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"edit\",\n Subject: \u0026v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n\n if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n // RESULT_ALLOWED\n } else {\n // RESULT_DENIED\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.check({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) =\u003e {\n if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n console.log(\"RESULT_ALLOWED\")\n } else {\n console.log(\"RESULT_DENIED\")\n }\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"edit\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'" + } ] } }, "/v1/tenants/{tenant_id}/permissions/expand": { "post": { - "summary": "expand relationships according to schema", + "summary": "expand api", "operationId": "permissions.expand", "responses": { "200": { @@ -663,7 +872,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -706,12 +915,29 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.Expand(context.Background(), \u0026v1.PermissionExpandRequest{\n TenantId: \"t1\",\n Metadata: \u0026v1.PermissionExpandRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n },\n Entity: \u0026v1.Entity{\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"push\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.expand({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"push\",\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"\",\n \"snap_token\": \"\"\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"push\"\n}'" + } ] } }, "/v1/tenants/{tenant_id}/permissions/lookup-entity": { "post": { - "summary": "Retrieve an entity by its identifier.", + "summary": "lookup entity", "operationId": "permissions.lookupEntity", "responses": { "200": { @@ -730,7 +956,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -769,12 +995,29 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.LookupEntity(context.Background(), \u0026 v1.PermissionLookupEntityRequest {\n TenantId: \"t1\",\n Metadata: \u0026 v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\"\n SchemaVersion: \"\"\n Depth: 20,\n },\n EntityType: \"document\",\n Permission: \"edit\",\n Subject: \u0026 v1.Subject {\n Type: \"user\",\n Id: \"1\",\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.lookupEntity({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity_type: \"document\",\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) =\u003e {\n console.log(response.entity_ids)\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity_type\": \"document\",\n \"permission\": \"edit\",\n \"subject\": {\n \"type\":\"user\",\n \"id\":\"1\"\n }\n}'" + } ] } }, "/v1/tenants/{tenant_id}/permissions/lookup-entity-stream": { "post": { - "summary": "Stream entities by their identifiers.", + "summary": "lookup entity stream", "operationId": "permissions.lookupEntityStream", "responses": { "200": { @@ -802,7 +1045,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -841,12 +1084,24 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "str, err: = client.Permission.LookupEntityStream(context.Background(), \u0026v1.PermissionLookupEntityRequest {\n Metadata: \u0026v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\", \n SchemaVersion: \"\" \n Depth: 50,\n },\n EntityType: \"document\",\n Permission: \"view\",\n Subject: \u0026v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})\n\n// handle stream response\nfor {\n res, err: = str.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.EntityId\n}" + }, + { + "label": "node", + "lang": "javascript", + "source": "const permify = require(\"@permify/permify-node\");\nconst {PermissionLookupEntityStreamResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.permission.lookupEntityStream({\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entityType: \"document\",\n permission: \"view\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable\u003cPermissionLookupEntityStreamResponse\u003e) {\n for await (const response of res) {\n // response.entityId\n }\n}" + } ] } }, "/v1/tenants/{tenant_id}/permissions/lookup-subject": { "post": { - "summary": "Retrieve a subject by its identifier.", + "summary": "lookup-subject", "operationId": "permissions.lookupSubject", "responses": { "200": { @@ -865,7 +1120,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -904,12 +1159,29 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.LookupSubject(context.Background(), \u0026v1.PermissionLookupSubjectRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.PermissionLookupSubjectRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: \u0026v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Permission: \"edit\",\n SubjectReference: \u0026v1.RelationReference{\n Type: \"user\",\n Relation: \"\",\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.lookupSubject({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n depth: 20,\n },\n Entity: {\n Type: \"document\",\n Id: \"1\",\n },\n permission: \"edit\",\n subject_reference: {\n type: \"user\",\n relation: \"\"\n }\n}).then((response) =\u003e {\n console.log(response.subject_ids)\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\"\n \"depth\": 20,\n },\n \"entity\": {\n type: \"document\",\n id: \"1'\n },\n \"permission\": \"edit\",\n \"subject_reference\": {\n \"type\": \"user\",\n \"relation\": \"\"\n }\n}'" + } ] } }, "/v1/tenants/{tenant_id}/permissions/subject-permission": { "post": { - "summary": "Retrieve permissions related to a specific subject.", + "summary": "subject permission", "operationId": "permissions.subjectPermission", "responses": { "200": { @@ -928,7 +1200,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -963,6 +1235,23 @@ ], "tags": [ "Permission" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.SubjectPermission(context.Background(), \u0026v1.PermissionSubjectPermissionRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.PermissionSubjectPermissionRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n OnlyPermission: false,\n Depth: 20,\n },\n Entity: \u0026v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Subject: \u0026v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.subjectPermission({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n onlyPermission: true,\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) =\u003e {\n console.log(response);\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"only_permission\": true,\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'" + } ] } }, @@ -987,6 +1276,7 @@ "parameters": [ { "name": "tenant_id", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1013,7 +1303,7 @@ }, "/v1/tenants/{tenant_id}/relationships/write": { "post": { - "summary": "create new relationships", + "summary": "write relationships", "operationId": "relationships.write", "responses": { "200": { @@ -1032,7 +1322,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Unique identifier for the tenant with specific constraints.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1068,7 +1358,7 @@ }, "/v1/tenants/{tenant_id}/schemas/list": { "post": { - "summary": "list all authorization models", + "summary": "list schema", "operationId": "schemas.list", "responses": { "200": { @@ -1087,7 +1377,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id is a string that identifies the tenant. It must match the pattern \"[a-zA-Z0-9-,]+\",\nbe a maximum of 64 bytes, and must not be empty.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1115,12 +1405,29 @@ ], "tags": [ "Schema" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.List(context.Background(), \u0026v1.SchemaListRequest {\n TenantId: \"t1\",\n PageSize: \"10\",\n ContinuousToken: \"\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.schema.list({\n tenantId: \"t1\",\n continuousToken: \"\"\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'" + } ] } }, "/v1/tenants/{tenant_id}/schemas/read": { "post": { - "summary": "read your authorization model", + "summary": "read schema", "operationId": "schemas.read", "responses": { "200": { @@ -1139,7 +1446,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id is a string that identifies the tenant. It must match the pattern \"[a-zA-Z0-9-,]+\",\nbe a maximum of 64 bytes, and must not be empty.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1162,12 +1469,29 @@ ], "tags": [ "Schema" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.Read(context.Background(), \u0026v1.SchemaReadRequest {\n TenantId: \"t1\",\n Metadata: \u0026v1.SchemaReadRequestMetadata{\n SchemaVersion: \"cnbe6se5fmal18gpc66g\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.schema.read({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: swResponse.schemaVersion,\n },\n })" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"cnbe6se5fmal18gpc66g\"\n }\n}'" + } ] } }, "/v1/tenants/{tenant_id}/schemas/write": { "post": { - "summary": "write your authorization model", + "summary": "write schema", "operationId": "schemas.write", "responses": { "200": { @@ -1186,7 +1510,7 @@ "parameters": [ { "name": "tenant_id", - "description": "tenant_id is a string that identifies the tenant. It must match the pattern \"[a-zA-Z0-9-,]+\",\nbe a maximum of 64 bytes, and must not be empty.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1209,11 +1533,29 @@ ], "tags": [ "Schema" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.Write(context.Background(), \u0026v1.SchemaWriteRequest {\n TenantId: \"t1\",\n Schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `,\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.schema.write({\n tenantId: \"t1\",\n schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `\n}).then((response) =\u003e {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"schema\": \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n}'" + } ] } }, "/v1/tenants/{tenant_id}/watch": { "post": { + "summary": "watch changes", "operationId": "watch.watch", "responses": { "200": { @@ -1241,7 +1583,7 @@ "parameters": [ { "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant \u003ccode\u003et1\u003c/code\u003e for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", "in": "path", "required": true, "type": "string" @@ -1255,7 +1597,7 @@ "properties": { "snap_token": { "type": "string", - "description": "Snap token to be used for watching." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." } }, "description": "WatchRequest is the request message for the Watch RPC. It contains the\ndetails needed to establish a watch stream." @@ -1264,6 +1606,18 @@ ], "tags": [ "Watch" + ], + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Watch.Watch(context.Background(), \u0026v1.WatchRequest{\n TenantId: \"t1\",\n SnapToken: \"\",\n})\n// handle stream response\nfor {\n res, err := cr.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.Changes\n}\n" + }, + { + "label": "node", + "lang": "javascript", + "source": "const permify = require(\"@permify/permify-node\");\nconst {WatchResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.watch.watch({\n tenantId: \"t1\",\n snapToken: \"\"\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable\u003cWatchResponse\u003e) {\n for await (const response of res) {\n // response.changes\n }\n}\n" + } ] } } @@ -1361,7 +1715,7 @@ "properties": { "snap_token": { "type": "string", - "description": "snap_token represents a specific state or \"snapshot\" of the database." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "description": "AttributeReadRequestMetadata defines the structure for the metadata of an attribute read request.\nIt includes the snap_token associated with a particular state of the database." @@ -1421,7 +1775,7 @@ "properties": { "snap_token": { "type": "string", - "description": "Token related to the bundle execution." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "description": "BundleRunResponse is the response for a BundleRunRequest.\nIt includes a snap_token, which may be used for tracking the execution or its results." @@ -1748,7 +2102,7 @@ "properties": { "snap_token": { "type": "string", - "description": "snap_token represents the state of the database after the requested deletions." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "description": "DataDeleteResponse defines the structure of the response to a data delete request.\nIt includes a snap_token representing the state of the database after the deletion." @@ -1768,7 +2122,7 @@ "properties": { "snap_token": { "type": "string", - "description": "snap_token is the token generated after the data write operation, representing a snapshot of the data." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." } }, "description": "DataWriteResponse defines the structure of the response after writing data.\nIt contains the snap_token generated after the write operation." @@ -2107,15 +2461,15 @@ }, "snap_token": { "type": "string", - "description": "Token associated with the snap." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" }, "depth": { "type": "integer", "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." + "description": "Query limit when if recursive database queries got in loop" } }, - "description": "PermissionCheckRequestMetadata is the metadata associated with a PermissionCheckRequest." + "description": "PermissionCheckRequestMetadata metadata for the PermissionCheckRequest." }, "PermissionCheckResponse": { "type": "object", @@ -2140,7 +2494,7 @@ "description": "The count of the checks performed." } }, - "description": "PermissionCheckResponseMetadata is the metadata associated with a PermissionCheckResponse." + "description": "PermissionCheckResponseMetadata metadata for the PermissionCheckResponse." }, "PermissionDefinition": { "type": "object", @@ -2165,10 +2519,10 @@ }, "snap_token": { "type": "string", - "description": "Token associated with the snap." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." } }, - "description": "PermissionExpandRequestMetadata is the metadata associated with a PermissionExpandRequest." + "description": "PermissionExpandRequestMetadata metadata for the PermissionExpandRequest." }, "PermissionExpandResponse": { "type": "object", @@ -2189,15 +2543,15 @@ }, "snap_token": { "type": "string", - "description": "Token associated with the snap." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." }, "depth": { "type": "integer", "format": "int32", - "description": "Depth of lookup, required, must be greater or equal to 3." + "description": "Query limit when if recursive database queries got in loop." } }, - "description": "PermissionLookupEntityRequestMetadata is the metadata associated with a PermissionLookupEntityRequest." + "description": "PermissionLookupEntityRequestMetadata metadata for the PermissionLookupEntityRequest." }, "PermissionLookupEntityResponse": { "type": "object", @@ -2231,15 +2585,15 @@ }, "snap_token": { "type": "string", - "description": "Token associated with the snap." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." }, "depth": { "type": "integer", "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." + "description": "Query limit when if recursive database queries got in loop." } }, - "description": "PermissionLookupSubjectRequestMetadata is the metadata associated with a PermissionLookupSubjectRequest." + "description": "PermissionLookupSubjectRequestMetadata metadata for the PermissionLookupSubjectRequest." }, "PermissionLookupSubjectResponse": { "type": "object", @@ -2263,7 +2617,7 @@ }, "snap_token": { "type": "string", - "description": "Token associated with the snap." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." }, "only_permission": { "type": "boolean", @@ -2272,10 +2626,10 @@ "depth": { "type": "integer", "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." + "description": "Query limit when if recursive database queries got in loop." } }, - "description": "PermissionSubjectPermissionRequestMetadata is the metadata associated with a PermissionSubjectPermissionRequest." + "description": "PermissionSubjectPermissionRequestMetadata metadata for the PermissionSubjectPermissionRequest." }, "PermissionSubjectPermissionResponse": { "type": "object", @@ -2340,7 +2694,8 @@ "type": "object", "properties": { "snap_token": { - "type": "string" + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "title": "RelationshipDeleteResponse" @@ -2350,7 +2705,7 @@ "properties": { "snap_token": { "type": "string", - "description": "snap_token represents a specific state or \"snapshot\" of the database." + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "description": "RelationshipReadRequestMetadata defines the structure of the metadata for a read request focused on relationships.\nIt includes the snap_token associated with a particular state of the database." @@ -2386,7 +2741,8 @@ "type": "object", "properties": { "snap_token": { - "type": "string" + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" } }, "title": "RelationshipWriteResponse" diff --git a/docs/api-reference/bundle/delete-bundle.mdx b/docs/api-reference/bundle/delete-bundle.mdx new file mode 100644 index 00000000..21fe3193 --- /dev/null +++ b/docs/api-reference/bundle/delete-bundle.mdx @@ -0,0 +1,10 @@ +--- +title: Delete Bundle +openapi: post /v1/tenants/{tenant_id}/bundle/delete +--- + +The "Delete Bundle" API is designed for removing specific data bundles within a multi-tenant application environment. This API facilitates the deletion of a bundle, identified by its unique name, from a designated tenant's environment. + + +To see what Data Bundles are and how they work, check out the [Data Bundles](../../operations/bundle) section. + \ No newline at end of file diff --git a/docs/api-reference/bundle/read-bundle.mdx b/docs/api-reference/bundle/read-bundle.mdx new file mode 100644 index 00000000..e6870100 --- /dev/null +++ b/docs/api-reference/bundle/read-bundle.mdx @@ -0,0 +1,10 @@ +--- +title: Read Bundle +openapi: post /v1/tenants/{tenant_id}/bundle/read +--- + +The "Read Bundle" API is a crucial tool for retrieving details of specific data bundles in a multi-tenant application setup. It is designed to access information about a bundle, uniquely identified by its name, within the specified tenant's environment. + + +To see what Data Bundles are and how they work, check out the [Data Bundles](../../operations/bundle) section. + \ No newline at end of file diff --git a/docs/docs/bundle.md b/docs/api-reference/bundle/write-bundle.mdx similarity index 76% rename from docs/docs/bundle.md rename to docs/api-reference/bundle/write-bundle.mdx index ca7db9ec..9688a7e6 100644 --- a/docs/docs/bundle.md +++ b/docs/api-reference/bundle/write-bundle.mdx @@ -1,15 +1,13 @@ --- -id: bundle -title: Bundle Service -sidebar_label: Data Bundles -slug: /api-overview/bundle +title: Write Bundle +openapi: post /v1/tenants/{tenant_id}/bundle/write --- -## What is Data Bundles +## What is Data Bundles? Ensuring that authorization data remains in sync with the business model is an important practice when using Permify. -Prior to Data Bundles, it was the responsibility of the services (such as [WriteData](./api-overview/data/write-data.md) API) to structure how relations are created and deleted when actions occur on resources. +Prior to Data Bundles, it was the responsibility of the services (such as [WriteData API](../data/write-data)) to structure how relations are created and deleted when actions occur on resources. With the Data Bundles, you be able to bundle and model the creation and deletion of relations and attributes when specific actions occur on resources in your applications. @@ -19,7 +17,7 @@ We believe this functionality will streamline managing authorization data as wel Let's examine how Bundles operates with basic example. -Let's say you want to model how data will be created when an organization created in your application. For this purpose, you can utilize the [WriteBundle](./api-overview/bundle/write-bundle.md) API endpoint. This API enables users to define or update data bundles, each distinguished by a unique name. +Let's say you want to model how data will be created when an organization created in your application. For this purpose, you can utilize the WriteBundle API endpoint. This API enables users to define or update data bundles, each distinguished by a unique name. Here's an example body for WriteBundle in this scenario: @@ -78,7 +76,11 @@ This will result in the creation of the following data in Permify: ## Endpoints -- [WriteBundle](./api-overview/bundle/write-bundle.md) -- [RunBundle](./api-overview/data/run-bundle.md) -- [DeleteBundle](./api-overview/bundle/delete-bundle.md) -- [ReadBundle](./api-overview/bundle/read-bundle.md) +- [WriteBundle](./write-bundle) +- [RunBundle](../data/run-bundle) +- [DeleteBundle](./delete-bundle) +- [ReadBundle](./read-bundle) + +## Write Bundles Request + +The "Write Bundle" API is designed for handling data in a multi-tenant application environment. Its primary function is to write and delete data according to predefined structures. This API allows users to define or update data bundles, each distinguished by a unique name. \ No newline at end of file diff --git a/docs/api-reference/data/delete-data.mdx b/docs/api-reference/data/delete-data.mdx new file mode 100644 index 00000000..c3e7f79a --- /dev/null +++ b/docs/api-reference/data/delete-data.mdx @@ -0,0 +1,4 @@ +--- +title: Delete Data +openapi: post /v1/tenants/{tenant_id}/data/delete +--- \ No newline at end of file diff --git a/docs/api-reference/data/delete-relationships.mdx b/docs/api-reference/data/delete-relationships.mdx new file mode 100644 index 00000000..d0a6f57f --- /dev/null +++ b/docs/api-reference/data/delete-relationships.mdx @@ -0,0 +1,4 @@ +--- +title: Delete Relationships +openapi: post /v1/tenants/{tenant_id}/relationships/delete +--- \ No newline at end of file diff --git a/docs/api-reference/data/read-attributes.mdx b/docs/api-reference/data/read-attributes.mdx new file mode 100644 index 00000000..c070820b --- /dev/null +++ b/docs/api-reference/data/read-attributes.mdx @@ -0,0 +1,6 @@ +--- +title: Read Attributes +openapi: post /v1/tenants/{tenant_id}/data/attributes/read +--- + +Read API allows for directly querying the stored graph data to display and filter stored attributes. diff --git a/docs/api-reference/data/read-relationships.mdx b/docs/api-reference/data/read-relationships.mdx new file mode 100644 index 00000000..316d1e63 --- /dev/null +++ b/docs/api-reference/data/read-relationships.mdx @@ -0,0 +1,6 @@ +--- +title: Read Relationships +openapi: post /v1/tenants/{tenant_id}/data/relationships/read +--- + +Read API allows for directly querying the stored graph data to display and filter stored relational tuples. diff --git a/docs/api-reference/data/run-bundle.mdx b/docs/api-reference/data/run-bundle.mdx new file mode 100644 index 00000000..3b095823 --- /dev/null +++ b/docs/api-reference/data/run-bundle.mdx @@ -0,0 +1,10 @@ +--- +title: Run Bundle +openapi: post /v1/tenants/{tenant_id}/data/run-bundle +--- + +The "Run Bundle" API provides a straightforward way to execute predefined bundles within your application's tenant environment. By sending a POST request to this endpoint, you can activate specific functionalities or processes encapsulated in a bundle. + + +To see what Data Bundles are and how they work, check out the [Data Bundles](../../operations/bundle) section. + \ No newline at end of file diff --git a/docs/docs/api-overview/data/write-data.md b/docs/api-reference/data/write-data.mdx similarity index 70% rename from docs/docs/api-overview/data/write-data.md rename to docs/api-reference/data/write-data.mdx index 67e87027..7be26346 100644 --- a/docs/docs/api-overview/data/write-data.md +++ b/docs/api-reference/data/write-data.mdx @@ -1,57 +1,41 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +--- +title: Write Authorization Data +openapi: post /v1/tenants/{tenant_id}/data/write +--- -# Write Authorization Data - -In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a [preferred database]. +In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database. Since these attributes and relations are live instances, meaning they can be affected by specific user actions within the application, they can be created/deleted with a simple Permify API call at runtime. More specifically, the application client should update preferred database about the changes happening in entities or resources that are related to the authorization structure. -If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [preferred database] up-to-date. Besides, each attribute or relationship should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as tuples and store in [preferred database]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) +If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write tuples to keep preferred database up-to-date. Besides, each attribute or relationship should be created according to its authorization model, Permify Schema. -[relational tuples]: ../../../getting-started/sync-data -[preferred database]: ../../../getting-started/sync-data#where-relational-tuples-used +Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as tuples and store in preferred database. -## Write Request - -:::info -You can use the **/v1/tenants/{tenant_id}/data/write** endpoint for both creating **relation tuples** and for creating **attribute data**. -::: + +You can use the `/v1/tenants/{tenant_id}/data/write` endpoint for both creating **relation tuples** and for creating **attribute data**. + **Path:** ```javascript POST /v1/tenants/{tenant_id}/data/write ``` -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.write) - -#### Glossary for parameters & payload objects: - -| Required | Argument | Type | Default | Description | -| -------- | -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this parameter. | -| [ ] | schema_version | string | 8 | Version of the schema. | -| [x] | tuples | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **relation**, and **subject** arguments.| -| [x] | attributes | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **attribute**, and **value** arguments. | -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€ | -| [x] | subject | string | - | User or user set who wants to take the action. | -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc. | -| [x] | attribute | string | - | Custom attribute name. | -| [x] | value | object | - | Represents value and type of the attribute data. | +## Content +- [Example Relationship Creation](#example-relationship-creation) +- [Example Attributes Creation](#example-attribute-creation) +- [Creating Attributes and Relationship In Single Request](#creating-attributes-relationships-in-singe-request) +- [Suggested Workflow](#suggested-workflow) +- [Parameters & Properties](#parameters-and-properties) -### Creating Relational Tuple +### Example Relationship Creation Let's create an example relation tuple. If user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` should be created as follows: - + ```go rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { @@ -75,9 +59,9 @@ rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { }) ``` - + - + ```javascript client.data @@ -105,8 +89,30 @@ client.data }); ``` - - + + +```python +with permify.ApiClient(configuration) as api_client: + api_instance = permify.DataApi(api_client) + + body = permify.DataWriteRequest( + tenant_id='t1', + metadata={"schemaVersion": ""}, + tuples=[{ + "entity": { + "type": "organization", + "id": "1", + }, + "relation": "admin", + "subject": { + "type": "user", + "id": "3", + }, + }] + ) +``` + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -132,22 +138,22 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write }' ``` - + -### Creating Attribute Data +### Example Attribute Creation You can use `attributes` argument to create attribute/attributes with a single API call, similarly creating a `relational tuple`. Let's say **document:1** is a **private (boolean)** document, that only specific users have view access - `document:1$is_private|boolean:true`. -:::info Attribute Data Syntax + As you noticed, the attribute tuple syntax differs from the relationship syntax, structured similarly as: `entity $ attribute | value` -::: + - + ```go // Convert the wrapped attribute value into Any proto message @@ -176,9 +182,9 @@ cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{ }) ``` - + - + ```javascript const booleanValue = BooleanValue.fromJSON({ data: true }); @@ -206,8 +212,35 @@ client.data.write({ }) ``` - - + + +``` +boolean_value = BooleanValue.from_json({"data": True}) + +value = Any.from_json({ + "typeUrl": 'type.googleapis.com/base.v1.BooleanValue', + "value": BooleanValue.encode(boolean_value).finish() +}) + +with permify.ApiClient(configuration) as api_client: + api_instance = permify.DataApi(api_client) + tenant_id = 't1' + + body = permify.DataWriteRequest( + tenant_id=tenant_id, + metadata={"schemaVersion": ""}, + attributes=[{ + "entity": { + "type": "document", + "id": "1" + }, + "attribute": "is_private", + "value": value, + }] + ) +``` + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -234,11 +267,11 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write }' ``` - + -:::warning Attribute **value** field -**value** field is mandatory on attribute data creation. + +**value** field is mandatory on attribute data creation! Here are the available attribute value types: @@ -250,9 +283,9 @@ Here are the available attribute value types: - **type.googleapis.com/base.v1.BooleanArrayValue** - **type.googleapis.com/base.v1.IntegerArrayValue** - **type.googleapis.com/base.v1.DoubleArrayValue** -::: + -#### Creating Attributes and Relations In Single Request +### Creating Attributes and Relationship In Single Request Assume we want to both create relational tuple and attribute within in single request. Specifically we want to create following tuples, @@ -261,7 +294,7 @@ Assume we want to both create relational tuple and attribute within in single re - + ```go // Convert the wrapped attribute value into Any proto message @@ -304,9 +337,9 @@ cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{ }) ``` - + - + ```javascript const booleanValue = BooleanValue.fromJSON({ data: true }); @@ -345,8 +378,47 @@ client.data.write({ }) ``` - - + + + +``` +boolean_value = BooleanValue.from_json({"data": True}) + +value = Any.from_json({ + "typeUrl": 'type.googleapis.com/base.v1.BooleanValue', + "value": BooleanValue.encode(boolean_value).finish() +}) + +with permify.ApiClient(configuration) as api_client: + api_instance = permify.DataApi(api_client) + tenant_id = 't1' + + body = permify.DataWriteRequest( + tenant_id=tenant_id, + metadata={"schemaVersion": ""}, + tuples=[{ + "entity": { + "type": "document", + "id": "1" + }, + "relation": "editor", + "subject": { + "type": "user", + "id": "1" + }, + }], + attributes=[{ + "entity": { + "type": "document", + "id": "1" + }, + "attribute": "is_private", + "value": value, + }] + ) + + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -386,26 +458,14 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write }' ``` - + -## Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../reference/snap-tokens) and how its avoiding stale cache. - -## Suggested Workflow +### Suggested Workflow The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? -### Two Phase Commit Approach +#### Two Phase Commit Approach In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. @@ -443,7 +503,7 @@ func CreateDocuments(db *gorm.DB) error { The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. -### Data That Not Stored In Application Database +#### Data That Not Stored In Application Database Although ownership generally stored in application databases, there are some data that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. @@ -472,6 +532,4 @@ entity project { This **team member** relation won't need to be stored in the application database. Storing it only in Permify - preferred database - is enough. In that situation, `WriteData` can be performed in any logical place in your stack. -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). +### Parameters & Properties diff --git a/docs/api-reference/data/write-relationships.mdx b/docs/api-reference/data/write-relationships.mdx new file mode 100644 index 00000000..b511a3fa --- /dev/null +++ b/docs/api-reference/data/write-relationships.mdx @@ -0,0 +1,4 @@ +--- +title: Write Relationships +openapi: post /v1/tenants/{tenant_id}/relationships/write +--- \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/getting-started/enforcement.md b/docs/api-reference/introduction.mdx similarity index 63% rename from docs/versioned_docs/version-0.6.x/getting-started/enforcement.md rename to docs/api-reference/introduction.mdx index df4c80f5..d6773ccb 100644 --- a/docs/versioned_docs/version-0.6.x/getting-started/enforcement.md +++ b/docs/api-reference/introduction.mdx @@ -1,9 +1,8 @@ --- -sidebar_position: 4 +title: 'Introduction' +description: 'Example section for showcasing API endpoints' --- -# Interacting With The API - Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. We structured Permify API in 4 core parts: @@ -13,43 +12,76 @@ We structured Permify API in 4 core parts: - [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. - [TenancyService]: Consists tenant operations such as creating, deleting and listing. -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ../../api-overview/permission -[DataService]: ../../api-overview/data -[SchemaService]: ../../api-overview/schema -[TenancyService]: ../../api-overview/tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). +Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) and [REST](https://restfulapi.net/). + +[PermissionService]: ./permission/check-api +[DataService]: ./data/write-data +[SchemaService]: ./schema/write-schema +[TenancyService]: ./tenancy/create-tenant + +**SDKs:** + +- [Golang](https://github.com/Permify/permify-go) +- [NodeJS](https://github.com/Permify/permify-node) +- [Python](https://github.com/Permify/permify-python) +- [Javascript](https://github.com/Permify/permify-javascript) + +
+ + Run in Postman + + + View in Swagger + +
+ + +

Integration with a Service Mesh

+ +Our software does not include built-in support for service meshes (eg. Istio). However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: + +
## Core Paths -- Configure your authorization model with [Schema Write](../api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](../api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](../api-overview/data/read-relationships.md) -- Check access with [Check API](../api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](../api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](../api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](../api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](../api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](../api-overview/watch/watch-changes.md) +- Configure your authorization model with [Schema Write](./schema/write-schema) +- Write relational tuples with [Write Data](./data/write-data) +- Read relation tuples and filter them with [Read Relationships](./data/read-relationships) +- Check access with [Check API](./permission/check-api) +- Check entities permissions with [Lookup Entity](./permission/lookup-entity) +- Check subject permissions with [Lookup Subject](./permission/lookup-subject) +- Delete relation tuples with [Delete Tuple](./data/delete-data) +- Expand schema actions with [Expand API](./permission/expand-api) +- Watch changes in the relation tuples in real-time with [Watch API](./watch/watch-changes) ## Authentication -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../../reference/configuration) +You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../../setting-up/configuration) To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. +## Latency & Performance + +With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. + +Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../operations/cache) + ## Availability of the Service For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: @@ -78,3 +110,4 @@ Default rate limit is set to 100 requests per second. However, users can adjust ## Need any help ? Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). + diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json new file mode 100644 index 00000000..941caf0a --- /dev/null +++ b/docs/api-reference/openapi.json @@ -0,0 +1,3510 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Permify API", + "description": "Permify is an open source authorization service for creating fine-grained and scalable authorization systems.", + "contact": { + "name": "API Support", + "url": "https://github.com/Permify/permify/issues", + "email": "hello@permify.co" + }, + "license": { + "name": "Apache-2.0 license", + "url": "https://github.com/Permify/permify/blob/master/LICENSE" + }, + "version": "v0.7.7" + }, + "servers": [ + { + "url": "/" + } + ], + "tags": [ + { + "name": "Permission" + }, + { + "name": "Watch" + }, + { + "name": "Schema" + }, + { + "name": "Data" + }, + { + "name": "Bundle" + }, + { + "name": "Tenancy" + } + ], + "paths": { + "/v1/tenants/create": { + "post": { + "tags": [ + "Tenancy" + ], + "summary": "create tenant", + "operationId": "tenants.create", + "requestBody": { + "description": "TenantCreateRequest is the message used for the request to create a tenant.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantCreateResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Tenancy.Create(context.Background(), &v1.TenantCreateRequest {\n Id: \"\"\n Name: \"\"\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.tenancy.create({\n id: \"\",\n name: \"\"\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'http://localhost:3476/v1/tenants/create' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"id\": \"\",\n \"name\": \"\"\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/list": { + "post": { + "tags": [ + "Tenancy" + ], + "summary": "list tenants", + "operationId": "tenants.list", + "requestBody": { + "description": "TenantListRequest is the message used for the request to list all tenants.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantListRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantListResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Tenancy.List(context.Background(), &v1.TenantListRequest{\n PageSize: 20,\n ContinuousToken: \"\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.tenancy.list({\n pageSize: 20,\n continuousToken: \"\",\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/list' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{id}": { + "delete": { + "tags": [ + "Tenancy" + ], + "summary": "delete tenant", + "operationId": "tenants.delete", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id is the unique identifier of the tenant to be deleted.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantDeleteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Tenancy.Delete(context.Background(), &v1.TenantDeleteRequest {\n Id: \"\"\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.tenancy.delete({\n id: \"\",\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/bundle/delete": { + "post": { + "tags": [ + "Bundle" + ], + "summary": "delete bundle", + "operationId": "bundle.delete", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the bundle to be deleted." + } + }, + "description": "BundleDeleteRequest is used to request the deletion of a bundle.\nIt contains the tenant_id to specify the tenant and the name of the bundle to be deleted." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BundleDeleteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Bundle.Delete(context.Background(), &v1.BundleDeleteRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.delete({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/bundle/read": { + "post": { + "tags": [ + "Bundle" + ], + "summary": "read bundle", + "operationId": "bundle.read", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BundleReadResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Bundle.Read(context.Background(), &v1.BundleReadRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.read({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/bundle/write": { + "post": { + "tags": [ + "Bundle" + ], + "summary": "write bundle", + "operationId": "bundle.write", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "bundles": { + "type": "array", + "description": "Contains the bundle data to be written.", + "items": { + "$ref": "#/components/schemas/DataBundle" + } + } + }, + "description": "BundleWriteRequest is used to request the writing of a bundle.\nIt contains the tenant_id to identify the tenant and the Bundles object." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BundleWriteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err := client.Bundle.Write(context.Background(), &v1.BundleWriteRequest{\n TenantId: \"t1\",\n Bundles: []*v1.DataBundle{\n {\n Name: \"organization_created\",\n Arguments: []string{\n \"creatorID\",\n \"organizationID\",\n },\n Operations: []*v1.Operation{\n {\n RelationshipsWrite: []string{\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n },\n AttributesWrite: []string{\n \"organization:{{.organizationID}}$public|boolean:false\",\n },\n },\n },\n },\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.bundle.write({\n tenantId: \"t1\",\n bundles: [\n {\n name: \"organization_created\",\n arguments: [\n \"creatorID\",\n \"organizationID\",\n ],\n operations: [\n {\n relationships_write: [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n attributes_write: [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ]\n }\n ]\n }\n ]\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"bundles\": [\n {\n \"name\": \"organization_created\"\n \"arguments\": [\n \"creatorID\",\n \"organizationID\"\n ],\n \"operations\": [\n {\n \"relationships_write\": [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n \"attributes_write\": [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ],\n },\n ],\n },\n ],\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/data/attributes/read": { + "post": { + "tags": [ + "Data" + ], + "summary": "read attributes", + "operationId": "data.attributes.read", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/AttributeReadRequestMetadata" + }, + "filter": { + "$ref": "#/components/schemas/AttributeFilter" + }, + "page_size": { + "type": "integer", + "description": "page_size specifies the number of results to return in a single page.\nIf more results are available, a continuous_token is included in the response.", + "format": "int64" + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is used in case of paginated reads to get the next page of results." + } + }, + "description": "AttributeReadRequest defines the structure of a request for reading attributes.\nIt includes the tenant_id, metadata, attribute filter, page size for pagination, and a continuous token for multi-page results." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AttributeReadResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.ReadAttributes(context.Background(), & v1.Data.AttributeReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.Data.AttributeReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: &v1.AttributeFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Attributes: []string {\"private\"},\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.readAttributes({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/data/delete": { + "post": { + "tags": [ + "Data" + ], + "summary": "delete data", + "operationId": "data.delete", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tuple_filter": { + "$ref": "#/components/schemas/TupleFilter" + }, + "attribute_filter": { + "$ref": "#/components/schemas/AttributeFilter" + } + }, + "description": "DataDeleteRequest defines the structure of a request to delete data.\nIt includes the tenant_id and filters for selecting tuples and attributes to be deleted." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataDeleteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.Delete(context.Background(), & v1.DataDeleteRequest {\n TenantId: \"t1\",\n Metadata: &v1.DataDeleteRequestMetadata {\n SnapToken: \"\"\n },\n TupleFilter: &v1.TupleFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"admin\",\n Subject: &v1.SubjectFilter {\n Type: \"user\",\n Id: []string {\"1\"},\n Relation: \"\"\n }}\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.delete({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n tupleFilter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"admin\",\n subject: {\n type: \"user\",\n ids: [\n \"1\"\n ],\n relation: \"\"\n }\n }\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"tupleFilter\": {\n \"entity\": {\n \"type\": \"organization\",\n \"ids\": [\n \"1\"\n ]\n },\n \"relation\": \"admin\",\n \"subject\": {\n \"type\": \"user\",\n \"ids\": [\n \"1\"\n ],\n \"relation\": \"\"\n }\n },\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/data/relationships/read": { + "post": { + "tags": [ + "Data" + ], + "summary": "read relationships", + "operationId": "data.relationships.read", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/RelationshipReadRequestMetadata" + }, + "filter": { + "$ref": "#/components/schemas/TupleFilter" + }, + "page_size": { + "type": "integer", + "description": "page_size specifies the number of results to return in a single page.\nIf more results are available, a continuous_token is included in the response.", + "format": "int64" + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is used in case of paginated reads to get the next page of results." + } + }, + "description": "RelationshipReadRequest defines the structure of a request for reading relationships.\nIt contains the necessary information such as tenant_id, metadata, and filter for the read operation." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipReadResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.ReadRelationships(context.Background(), & v1.Data.RelationshipReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.Data.RelationshipReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: &v1.TupleFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"member\",\n Subject: &v1.SubjectFilter {\n Type: \"\",\n Id: []string {\"\"},\n Relation: \"\"\n }}\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.readRelationships({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/data/run-bundle": { + "post": { + "tags": [ + "Data" + ], + "summary": "run bundle", + "operationId": "bundle.run", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the bundle to be executed." + }, + "arguments": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Additional key-value pairs for execution arguments." + } + }, + "description": "BundleRunRequest is used to request the execution of a bundle.\nIt includes tenant_id, the name of the bundle, and additional arguments for execution." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BundleRunResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "rr, err: = client.Data.RunBundle(context.Background(), &v1.BundleRunRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n Arguments: map[string]string{\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.data.runBundle({\n tenantId: \"t1\",\n name: \"organization_created\",\n arguments: {\n creatorID: \"564\",\n organizationID: \"789\",\n }\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/run-bundle' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n \"arguments\": {\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/data/write": { + "post": { + "tags": [ + "Data" + ], + "summary": "write data", + "operationId": "data.write", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/DataWriteRequestMetadata" + }, + "tuples": { + "type": "array", + "description": "tuples contains the list of tuples (entity-relation-entity triples) that need to be written.", + "items": { + "$ref": "#/components/schemas/Tuple" + } + }, + "attributes": { + "type": "array", + "description": "attributes contains the list of attributes (entity-attribute-value triples) that need to be written.", + "items": { + "$ref": "#/components/schemas/Attribute" + } + } + }, + "description": "DataWriteRequest defines the structure of a request for writing data.\nIt contains the necessary information such as tenant_id, metadata,\ntuples and attributes for the write operation." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataWriteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "// Convert the wrapped attribute value into Any proto message\nvalue, err := anypb.New(&v1.BooleanValue{\n Data: true,\n})\nif err != nil {\n // Handle error\n}\n\ncr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{\n TenantId: \"t1\",,\n Metadata: &v1.DataWriteRequestMetadata{\n SchemaVersion: \"\",\n },\n Tuples: []*v1.Attribute{\n {\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Relation: \"editor\",\n Subject: &v1.Subject{\n Type: \"user\",\n Id: \"1\",\n Relation: \"\",\n },\n },\n },\n Attributes: []*v1.Attribute{\n {\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Attribute: \"is_private\",\n Value: value,\n },\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "const booleanValue = BooleanValue.fromJSON({ data: true });\n\nconst value = Any.fromJSON({\n typeUrl: 'type.googleapis.com/base.v1.BooleanValue',\n value: BooleanValue.encode(booleanValue).finish()\n});\n\nclient.data.write({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: \"\"\n },\n tuples: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n relation: \"editor\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n }],\n attributes: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n attribute: \"is_private\",\n value: value,\n }]\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n{\n \"metadata\": {\n \"schema_version\": \"\"\n },\n \"tuples\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"relation\": \"editor\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\"\n }\n }\n ],\n \"attributes\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"attribute\": \"is_private\",\n \"value\": {\n \"@type\": \"type.googleapis.com/base.v1.BooleanValue\",\n \"data\": true\n }\n }\n ]\n}\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/check": { + "post": { + "tags": [ + "Permission" + ], + "summary": "check api", + "operationId": "permissions.check", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionCheckRequestMetadata" + }, + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "permission": { + "type": "string", + "description": "The action the user wants to perform on the resource" + }, + "subject": { + "$ref": "#/components/schemas/Subject" + }, + "context": { + "$ref": "#/components/schemas/Context" + }, + "arguments": { + "type": "array", + "description": "Additional arguments associated with this request.", + "items": { + "$ref": "#/components/schemas/Argument" + } + } + }, + "description": "PermissionCheckRequest is the request message for the Check method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionCheckResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Permission.Check(context.Background(), &v1.PermissionCheckRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionCheckRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: &v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"edit\",\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n\n if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n // RESULT_ALLOWED\n } else {\n // RESULT_DENIED\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.check({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n console.log(\"RESULT_ALLOWED\")\n } else {\n console.log(\"RESULT_DENIED\")\n }\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"edit\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/expand": { + "post": { + "tags": [ + "Permission" + ], + "summary": "expand api", + "operationId": "permissions.expand", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionExpandRequestMetadata" + }, + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "permission": { + "type": "string", + "description": "Name of the permission to be expanded, not required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + }, + "context": { + "$ref": "#/components/schemas/Context" + }, + "arguments": { + "type": "array", + "description": "Additional arguments associated with this request.", + "items": { + "$ref": "#/components/schemas/Argument" + } + } + }, + "description": "PermissionExpandRequest is the request message for the Expand method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionExpandResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{\n TenantId: \"t1\",\n Metadata: &v1.PermissionExpandRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n },\n Entity: &v1.Entity{\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"push\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.expand({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"push\",\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"\",\n \"snap_token\": \"\"\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"push\"\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/lookup-entity": { + "post": { + "tags": [ + "Permission" + ], + "summary": "lookup entity", + "operationId": "permissions.lookupEntity", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionLookupEntityRequestMetadata" + }, + "entity_type": { + "type": "string", + "description": "Type of the entity to lookup, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + }, + "permission": { + "type": "string", + "description": "Name of the permission to check, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + }, + "subject": { + "$ref": "#/components/schemas/Subject" + }, + "context": { + "$ref": "#/components/schemas/Context" + } + }, + "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionLookupEntityResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest {\n TenantId: \"t1\",\n Metadata: & v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\"\n SchemaVersion: \"\"\n Depth: 20,\n },\n EntityType: \"document\",\n Permission: \"edit\",\n Subject: & v1.Subject {\n Type: \"user\",\n Id: \"1\",\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.lookupEntity({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity_type: \"document\",\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n console.log(response.entity_ids)\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity_type\": \"document\",\n \"permission\": \"edit\",\n \"subject\": {\n \"type\":\"user\",\n \"id\":\"1\"\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/lookup-entity-stream": { + "post": { + "tags": [ + "Permission" + ], + "summary": "lookup entity stream", + "operationId": "permissions.lookupEntityStream", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionLookupEntityRequestMetadata" + }, + "entity_type": { + "type": "string", + "description": "Type of the entity to lookup, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + }, + "permission": { + "type": "string", + "description": "Name of the permission to check, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes." + }, + "subject": { + "$ref": "#/components/schemas/Subject" + }, + "context": { + "$ref": "#/components/schemas/Context" + } + }, + "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "content": { + "application/json": { + "schema": { + "title": "Stream result of PermissionLookupEntityStreamResponse", + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/PermissionLookupEntityStreamResponse" + }, + "error": { + "$ref": "#/components/schemas/Status" + } + } + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest {\n Metadata: &v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\", \n SchemaVersion: \"\" \n Depth: 50,\n },\n EntityType: \"document\",\n Permission: \"view\",\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})\n\n// handle stream response\nfor {\n res, err: = str.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.EntityId\n}" + }, + { + "label": "node", + "lang": "javascript", + "source": "const permify = require(\"@permify/permify-node\");\nconst {PermissionLookupEntityStreamResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.permission.lookupEntityStream({\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entityType: \"document\",\n permission: \"view\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable) {\n for await (const response of res) {\n // response.entityId\n }\n}" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/lookup-subject": { + "post": { + "tags": [ + "Permission" + ], + "summary": "lookup-subject", + "operationId": "permissions.lookupSubject", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionLookupSubjectRequestMetadata" + }, + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "permission": { + "type": "string", + "description": "Permission to be checked, can be a permission or relation. Required, and must match the pattern \"^([a-zA-Z][a-zA-Z0-9_]{1,62}[a-zA-Z0-9])$\", max 64 bytes." + }, + "subject_reference": { + "$ref": "#/components/schemas/RelationReference" + }, + "context": { + "$ref": "#/components/schemas/Context" + } + }, + "description": "PermissionLookupSubjectRequest is the request message for the LookupSubject method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionLookupSubjectResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionLookupSubjectRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Permission: \"edit\",\n SubjectReference: &v1.RelationReference{\n Type: \"user\",\n Relation: \"\",\n }\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.lookupSubject({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n depth: 20,\n },\n Entity: {\n Type: \"document\",\n Id: \"1\",\n },\n permission: \"edit\",\n subject_reference: {\n type: \"user\",\n relation: \"\"\n }\n}).then((response) => {\n console.log(response.subject_ids)\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\"\n \"depth\": 20,\n },\n \"entity\": {\n type: \"document\",\n id: \"1'\n },\n \"permission\": \"edit\",\n \"subject_reference\": {\n \"type\": \"user\",\n \"relation\": \"\"\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/permissions/subject-permission": { + "post": { + "tags": [ + "Permission" + ], + "summary": "subject permission", + "operationId": "permissions.subjectPermission", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/PermissionSubjectPermissionRequestMetadata" + }, + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "subject": { + "$ref": "#/components/schemas/Subject" + }, + "context": { + "$ref": "#/components/schemas/Context" + } + }, + "description": "PermissionSubjectPermissionRequest is the request message for the SubjectPermission method in the Permission service." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionSubjectPermissionResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionSubjectPermissionRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n OnlyPermission: false,\n Depth: 20,\n },\n Entity: &v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.permission.subjectPermission({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n onlyPermission: true,\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n console.log(response);\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"only_permission\": true,\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/relationships/delete": { + "post": { + "tags": [ + "Data" + ], + "summary": "delete relationships", + "operationId": "relationships.delete", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "RelationshipDeleteRequest", + "type": "object", + "properties": { + "filter": { + "$ref": "#/components/schemas/TupleFilter" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipDeleteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/relationships/write": { + "post": { + "tags": [ + "Data" + ], + "summary": "write relationships", + "operationId": "relationships.write", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/RelationshipWriteRequestMetadata" + }, + "tuples": { + "type": "array", + "description": "List of tuples for the request. Must have between 1 and 100 items.", + "items": { + "$ref": "#/components/schemas/Tuple" + } + } + }, + "description": "Represents a request to write relationship data." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipWriteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/schemas/list": { + "post": { + "tags": [ + "Schema" + ], + "summary": "list schema", + "operationId": "schemas.list", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "page_size": { + "type": "integer", + "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100.", + "format": "int64" + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is an optional parameter used for pagination.\nIt should be the value received in the previous response." + } + }, + "description": "SchemaListRequest is the request message for the List method in the Schema service.\nIt contains tenant_id for which the schemas are to be listed." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchemaListResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.List(context.Background(), &v1.SchemaListRequest {\n TenantId: \"t1\",\n PageSize: \"10\",\n ContinuousToken: \"\",\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.schema.list({\n tenantId: \"t1\",\n continuousToken: \"\"\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/schemas/read": { + "post": { + "tags": [ + "Schema" + ], + "summary": "read schema", + "operationId": "schemas.read", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/components/schemas/SchemaReadRequestMetadata" + } + }, + "description": "SchemaReadRequest is the request message for the Read method in the Schema service.\nIt contains tenant_id and metadata about the schema to be read." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.Read(context.Background(), &v1.SchemaReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.SchemaReadRequestMetadata{\n SchemaVersion: \"cnbe6se5fmal18gpc66g\",\n },\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "let res = client.schema.read({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: swResponse.schemaVersion,\n },\n })" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"cnbe6se5fmal18gpc66g\"\n }\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/schemas/write": { + "post": { + "tags": [ + "Schema" + ], + "summary": "write schema", + "operationId": "schemas.write", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "schema": { + "type": "string", + "description": "schema is the string representation of the schema to be written." + } + }, + "description": "SchemaWriteRequest is the request message for the Write method in the Schema service.\nIt contains tenant_id and the schema to be written." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchemaWriteResponse" + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest {\n TenantId: \"t1\",\n Schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `,\n})" + }, + { + "label": "node", + "lang": "javascript", + "source": "client.schema.write({\n tenantId: \"t1\",\n schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `\n}).then((response) => {\n // handle response\n})" + }, + { + "label": "cURL", + "lang": "curl", + "source": "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"schema\": \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n}'" + } + ], + "x-codegen-request-body-name": "body" + } + }, + "/v1/tenants/{tenant_id}/watch": { + "post": { + "tags": [ + "Watch" + ], + "summary": "watch changes", + "operationId": "watch.watch", + "parameters": [ + { + "name": "tenant_id", + "in": "path", + "description": "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + } + }, + "description": "WatchRequest is the request message for the Watch RPC. It contains the\ndetails needed to establish a watch stream." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "content": { + "application/json": { + "schema": { + "title": "Stream result of WatchResponse", + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/WatchResponse" + }, + "error": { + "$ref": "#/components/schemas/Status" + } + } + } + } + } + }, + "default": { + "description": "An unexpected error response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Status" + } + } + } + } + }, + "x-codeSamples": [ + { + "label": "go", + "lang": "go", + "source": "cr, err := client.Watch.Watch(context.Background(), &v1.WatchRequest{\n TenantId: \"t1\",\n SnapToken: \"\",\n})\n// handle stream response\nfor {\n res, err := cr.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.Changes\n}\n" + }, + { + "label": "node", + "lang": "javascript", + "source": "const permify = require(\"@permify/permify-node\");\nconst {WatchResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.watch.watch({\n tenantId: \"t1\",\n snapToken: \"\"\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable) {\n for await (const response of res) {\n // response.changes\n }\n}\n" + } + ], + "x-codegen-request-body-name": "body" + } + } + }, + "components": { + "schemas": { + "AbstractType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The fully qualified name of this abstract type." + }, + "parameterTypes": { + "type": "array", + "description": "Parameter types for this abstract type.", + "items": { + "$ref": "#/components/schemas/v1alpha1.Type" + } + } + }, + "description": "Application defined abstract type." + }, + "Any": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics." + } + }, + "additionalProperties": { + "type": "object" + }, + "description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(&foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n\nExample 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\nExample 4: Pack and unpack a message in Go\n\n foo := &pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := &pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\n\nJSON\n\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": ,\n \"lastName\": \n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }" + }, + "Argument": { + "type": "object", + "properties": { + "computedAttribute": { + "$ref": "#/components/schemas/ComputedAttribute" + }, + "contextAttribute": { + "$ref": "#/components/schemas/ContextAttribute" + } + }, + "description": "Argument defines the type of argument in a Call. It can be either a ComputedAttribute or a ContextAttribute." + }, + "Attribute": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "attribute": { + "title": "Name of the attribute", + "type": "string" + }, + "value": { + "$ref": "#/components/schemas/Any" + } + }, + "description": "Attribute represents an attribute of an entity with a specific type and value." + }, + "AttributeDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the attribute, which follows a specific string pattern and has a maximum byte size." + }, + "type": { + "$ref": "#/components/schemas/AttributeType" + } + }, + "description": "The AttributeDefinition message provides detailed information about a specific attribute." + }, + "AttributeFilter": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/EntityFilter" + }, + "attributes": { + "title": "Names of the attributes to be filtered", + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "AttributeFilter is used to filter attributes based on the entity and attribute names." + }, + "AttributeReadRequestMetadata": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + }, + "description": "AttributeReadRequestMetadata defines the structure for the metadata of an attribute read request.\nIt includes the snap_token associated with a particular state of the database." + }, + "AttributeReadResponse": { + "type": "object", + "properties": { + "attributes": { + "type": "array", + "description": "attributes is a list of the attributes retrieved in the read operation.", + "items": { + "$ref": "#/components/schemas/Attribute" + } + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is used in the case of paginated reads to retrieve the next page of results." + } + }, + "description": "AttributeReadResponse defines the structure of the response to an attribute read request.\nIt includes the attributes retrieved and a continuous token for handling result pagination." + }, + "AttributeType": { + "type": "string", + "description": "Enumerates the types of attribute.\n\n - ATTRIBUTE_TYPE_UNSPECIFIED: Not specified attribute type. This is the default value.\n - ATTRIBUTE_TYPE_BOOLEAN: A boolean attribute type.\n - ATTRIBUTE_TYPE_BOOLEAN_ARRAY: A boolean array attribute type.\n - ATTRIBUTE_TYPE_STRING: A string attribute type.\n - ATTRIBUTE_TYPE_STRING_ARRAY: A string array attribute type.\n - ATTRIBUTE_TYPE_INTEGER: An integer attribute type.\n - ATTRIBUTE_TYPE_INTEGER_ARRAY: An integer array attribute type.\n - ATTRIBUTE_TYPE_DOUBLE: A double attribute type.\n - ATTRIBUTE_TYPE_DOUBLE_ARRAY: A double array attribute type.", + "default": "ATTRIBUTE_TYPE_UNSPECIFIED", + "enum": [ + "ATTRIBUTE_TYPE_UNSPECIFIED", + "ATTRIBUTE_TYPE_BOOLEAN", + "ATTRIBUTE_TYPE_BOOLEAN_ARRAY", + "ATTRIBUTE_TYPE_STRING", + "ATTRIBUTE_TYPE_STRING_ARRAY", + "ATTRIBUTE_TYPE_INTEGER", + "ATTRIBUTE_TYPE_INTEGER_ARRAY", + "ATTRIBUTE_TYPE_DOUBLE", + "ATTRIBUTE_TYPE_DOUBLE_ARRAY" + ] + }, + "BundleDeleteResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "BundleReadResponse": { + "type": "object", + "properties": { + "bundle": { + "$ref": "#/components/schemas/DataBundle" + } + } + }, + "BundleRunResponse": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + }, + "description": "BundleRunResponse is the response for a BundleRunRequest.\nIt includes a snap_token, which may be used for tracking the execution or its results." + }, + "BundleWriteResponse": { + "type": "object", + "properties": { + "names": { + "type": "array", + "description": "Identifier or acknowledgment of the written bundle.", + "items": { + "type": "string" + } + } + }, + "description": "BundleWriteResponse is the response for a BundleWriteRequest.\nIt includes a name which could be used as an identifier or acknowledgment." + }, + "CheckResult": { + "type": "string", + "description": "Enumerates results of a check operation.\n\n - CHECK_RESULT_UNSPECIFIED: Not specified check result. This is the default value.\n - CHECK_RESULT_ALLOWED: Represents a successful check (the check allowed the operation).\n - CHECK_RESULT_DENIED: Represents a failed check (the check denied the operation).", + "default": "CHECK_RESULT_UNSPECIFIED", + "enum": [ + "CHECK_RESULT_UNSPECIFIED", + "CHECK_RESULT_ALLOWED", + "CHECK_RESULT_DENIED" + ] + }, + "CheckedExpr": { + "type": "object", + "properties": { + "referenceMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/v1alpha1.Reference" + }, + "description": "A map from expression ids to resolved references.\n\nThe following entries are in this table:\n\n- An Ident or Select expression is represented here if it resolves to a\n declaration. For instance, if `a.b.c` is represented by\n `select(select(id(a), b), c)`, and `a.b` resolves to a declaration,\n while `c` is a field selection, then the reference is attached to the\n nested select expression (but not to the id or or the outer select).\n In turn, if `a` resolves to a declaration and `b.c` are field selections,\n the reference is attached to the ident expression.\n- Every Call expression has an entry here, identifying the function being\n called.\n- Every CreateStruct expression for a message has an entry, identifying\n the message." + }, + "typeMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/v1alpha1.Type" + }, + "description": "A map from expression ids to types.\n\nEvery expression node which has a type different than DYN has a mapping\nhere. If an expression has type DYN, it is omitted from this map to save\nspace." + }, + "sourceInfo": { + "$ref": "#/components/schemas/SourceInfo" + }, + "exprVersion": { + "type": "string", + "description": "The expr version indicates the major / minor version number of the `expr`\nrepresentation.\n\nThe most common reason for a version change will be to indicate to the CEL\nruntimes that transformations have been performed on the expr during static\nanalysis. In some cases, this will save the runtime the work of applying\nthe same or similar transformations prior to evaluation." + }, + "expr": { + "$ref": "#/components/schemas/Expr" + } + }, + "description": "A CEL expression which has been successfully type checked." + }, + "Child": { + "type": "object", + "properties": { + "leaf": { + "$ref": "#/components/schemas/Leaf" + }, + "rewrite": { + "$ref": "#/components/schemas/Rewrite" + } + }, + "description": "Child represents a node in the permission tree." + }, + "Comprehension": { + "type": "object", + "properties": { + "iterVar": { + "type": "string", + "description": "The name of the iteration variable." + }, + "iterRange": { + "$ref": "#/components/schemas/Expr" + }, + "accuVar": { + "type": "string", + "description": "The name of the variable used for accumulation of the result." + }, + "accuInit": { + "$ref": "#/components/schemas/Expr" + }, + "loopCondition": { + "$ref": "#/components/schemas/Expr" + }, + "loopStep": { + "$ref": "#/components/schemas/Expr" + }, + "result": { + "$ref": "#/components/schemas/Expr" + } + }, + "description": "A comprehension expression applied to a list or map.\n\nComprehensions are not part of the core syntax, but enabled with macros.\nA macro matches a specific call signature within a parsed AST and replaces\nthe call with an alternate AST block. Macro expansion happens at parse\ntime.\n\nThe following macros are supported within CEL:\n\nAggregate type macros may be applied to all elements in a list or all keys\nin a map:\n\n* `all`, `exists`, `exists_one` - test a predicate expression against\n the inputs and return `true` if the predicate is satisfied for all,\n any, or only one value `list.all(x, x < 10)`.\n* `filter` - test a predicate expression against the inputs and return\n the subset of elements which satisfy the predicate:\n `payments.filter(p, p > 1000)`.\n* `map` - apply an expression to all elements in the input and return the\n output aggregate type: `[1, 2, 3].map(i, i * i)`.\n\nThe `has(m.x)` macro tests whether the property `x` is present in struct\n`m`. The semantics of this macro depend on the type of `m`. For proto2\nmessages `has(m.x)` is defined as 'defined, but not set`. For proto3, the\nmacro tests whether the property is set to its default. For map and struct\ntypes, the macro tests whether the property `x` is defined on `m`." + }, + "ComputedAttribute": { + "type": "object", + "properties": { + "name": { + "title": "Name of the computed attribute", + "type": "string" + } + }, + "description": "ComputedAttribute defines a computed attribute which includes its name." + }, + "ComputedUserSet": { + "type": "object", + "properties": { + "relation": { + "title": "Relation name", + "type": "string" + } + }, + "description": "ComputedUserSet defines a set of computed users which includes the relation name." + }, + "Constant": { + "type": "object", + "properties": { + "nullValue": { + "type": "string", + "description": "null value." + }, + "boolValue": { + "type": "boolean", + "description": "boolean value." + }, + "int64Value": { + "type": "string", + "description": "int64 value.", + "format": "int64" + }, + "uint64Value": { + "type": "string", + "description": "uint64 value.", + "format": "uint64" + }, + "doubleValue": { + "type": "number", + "description": "double value.", + "format": "double" + }, + "stringValue": { + "type": "string", + "description": "string value." + }, + "bytesValue": { + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string", + "description": "bytes value.", + "format": "byte" + }, + "durationValue": { + "type": "string", + "description": "protobuf.Duration value.\n\nDeprecated: duration is no longer considered a builtin cel type." + }, + "timestampValue": { + "type": "string", + "description": "protobuf.Timestamp value.\n\nDeprecated: timestamp is no longer considered a builtin cel type.", + "format": "date-time" + } + }, + "description": "Represents a primitive literal.\n\nNamed 'Constant' here for backwards compatibility.\n\nThis is similar as the primitives supported in the well-known type\n`google.protobuf.Value`, but richer so it can represent CEL's full range of\nprimitives.\n\nLists and structs are not included as constants as these aggregate types may\ncontain [Expr][google.api.expr.v1alpha1.Expr] elements which require evaluation and are thus not constant.\n\nExamples of literals include: `\"hello\"`, `b'bytes'`, `1u`, `4.2`, `-2`,\n`true`, `null`." + }, + "Context": { + "type": "object", + "properties": { + "tuples": { + "type": "array", + "description": "A repeated field of tuples involved in the operation.", + "items": { + "$ref": "#/components/schemas/Tuple" + } + }, + "attributes": { + "type": "array", + "description": "A repeated field of attributes associated with the operation.", + "items": { + "$ref": "#/components/schemas/Attribute" + } + }, + "data": { + "type": "object", + "properties": {}, + "description": "Additional data associated with the context." + } + }, + "description": "Context encapsulates the information related to a single operation,\nincluding the tuples involved and the associated attributes." + }, + "ContextAttribute": { + "type": "object", + "properties": { + "name": { + "title": "Name of the context attribute", + "type": "string" + } + }, + "description": "ContextAttribute defines a context attribute which includes its name." + }, + "CreateList": { + "type": "object", + "properties": { + "elements": { + "type": "array", + "description": "The elements part of the list.", + "items": { + "$ref": "#/components/schemas/Expr" + } + }, + "optionalIndices": { + "type": "array", + "description": "The indices within the elements list which are marked as optional\nelements.\n\nWhen an optional-typed value is present, the value it contains\nis included in the list. If the optional-typed value is absent, the list\nelement is omitted from the CreateList result.", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "description": "A list creation expression.\n\nLists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g.\n`dyn([1, 'hello', 2.0])`" + }, + "CreateStruct": { + "type": "object", + "properties": { + "messageName": { + "type": "string", + "description": "The type name of the message to be created, empty when creating map\nliterals." + }, + "entries": { + "type": "array", + "description": "The entries in the creation expression.", + "items": { + "$ref": "#/components/schemas/Entry" + } + } + }, + "description": "A map or message creation expression.\n\nMaps are constructed as `{'key_name': 'value'}`. Message construction is\nsimilar, but prefixed with a type name and composed of field ids:\n`types.MyType{field_id: 'value'}`." + }, + "DataBundle": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "'name' is a simple string field representing the name of the DataBundle." + }, + "arguments": { + "type": "array", + "description": "'arguments' is a repeated field, which means it can contain multiple strings.\nThese are used to store a list of arguments related to the DataBundle.", + "items": { + "type": "string" + } + }, + "operations": { + "type": "array", + "description": "'operations' is a repeated field containing multiple Operation messages.\nEach Operation represents a specific action or set of actions to be performed.", + "items": { + "$ref": "#/components/schemas/v1.Operation" + } + } + }, + "description": "DataBundle is a message representing a bundle of data, which includes a name,\na list of arguments, and a series of operations." + }, + "DataChange": { + "type": "object", + "properties": { + "operation": { + "$ref": "#/components/schemas/DataChange.Operation" + }, + "tuple": { + "$ref": "#/components/schemas/Tuple" + }, + "attribute": { + "$ref": "#/components/schemas/Attribute" + } + }, + "description": "DataChange represents a single change in data, with an operation type and the actual change which could be a tuple or an attribute." + }, + "DataChange.Operation": { + "type": "string", + "description": " - OPERATION_UNSPECIFIED: Default operation, not specified.\n - OPERATION_CREATE: Creation operation.\n - OPERATION_DELETE: Deletion operation.", + "default": "OPERATION_UNSPECIFIED", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_CREATE", + "OPERATION_DELETE" + ] + }, + "DataChanges": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snapshot token." + }, + "data_changes": { + "type": "array", + "description": "The list of data changes.", + "items": { + "$ref": "#/components/schemas/DataChange" + } + } + }, + "description": "DataChanges represent changes in data with a snap token and a list of data change objects." + }, + "DataDeleteResponse": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + }, + "description": "DataDeleteResponse defines the structure of the response to a data delete request.\nIt includes a snap_token representing the state of the database after the deletion." + }, + "DataWriteRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "schema_version represents the version of the schema for the data being written." + } + }, + "description": "DataWriteRequestMetadata defines the structure of metadata for a write request.\nIt includes the schema version of the data to be written." + }, + "DataWriteResponse": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + } + }, + "description": "DataWriteResponse defines the structure of the response after writing data.\nIt contains the snap_token generated after the write operation." + }, + "Entity": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "description": "Entity represents an entity with a type and an identifier." + }, + "EntityDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the entity, which follows a specific string pattern and has a maximum byte size." + }, + "relations": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RelationDefinition" + }, + "description": "Map of relation definitions within this entity. The key is the relation name, and the value is the RelationDefinition." + }, + "permissions": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PermissionDefinition" + }, + "description": "Map of permission definitions within this entity. The key is the permission name, and the value is the PermissionDefinition." + }, + "attributes": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AttributeDefinition" + }, + "description": "Map of attribute definitions within this entity. The key is the attribute name, and the value is the AttributeDefinition." + }, + "references": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/EntityDefinition.Reference" + }, + "description": "Map of references indicating whether a string pertains to a relation, permission, or attribute." + } + }, + "description": "The EntityDefinition message provides detailed information about a specific entity." + }, + "EntityDefinition.Reference": { + "type": "string", + "description": "The Reference enum specifies whether a name pertains to a relation, permission, or attribute.\n\n - REFERENCE_UNSPECIFIED: Default, unspecified reference.\n - REFERENCE_RELATION: Indicates that the name refers to a relation.\n - REFERENCE_PERMISSION: Indicates that the name refers to a permission.\n - REFERENCE_ATTRIBUTE: Indicates that the name refers to an attribute.", + "default": "REFERENCE_UNSPECIFIED", + "enum": [ + "REFERENCE_UNSPECIFIED", + "REFERENCE_RELATION", + "REFERENCE_PERMISSION", + "REFERENCE_ATTRIBUTE" + ] + }, + "EntityFilter": { + "type": "object", + "properties": { + "type": { + "title": "Type of the entity", + "type": "string" + }, + "ids": { + "title": "List of entity IDs", + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "EntityFilter is used to filter entities based on the type and ids." + }, + "Entry": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Required. An id assigned to this node by the parser which is unique\nin a given expression tree. This is used to associate type\ninformation and other attributes to the node.", + "format": "int64" + }, + "fieldKey": { + "type": "string", + "description": "The field key for a message creator statement." + }, + "mapKey": { + "$ref": "#/components/schemas/Expr" + }, + "value": { + "$ref": "#/components/schemas/Expr" + }, + "optionalEntry": { + "type": "boolean", + "description": "Whether the key-value pair is optional." + } + }, + "description": "Represents an entry." + }, + "Expand": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "permission": { + "type": "string", + "description": "permission is the permission applied to the entity." + }, + "arguments": { + "type": "array", + "description": "arguments are the additional information or context used to evaluate permissions.", + "items": { + "$ref": "#/components/schemas/Argument" + } + }, + "expand": { + "$ref": "#/components/schemas/ExpandTreeNode" + }, + "leaf": { + "$ref": "#/components/schemas/ExpandLeaf" + } + }, + "description": "Expand is used to define a hierarchical structure for permissions.\nIt has an entity, permission, and arguments. The node can be either another hierarchical structure or a set of subjects." + }, + "ExpandLeaf": { + "type": "object", + "properties": { + "subjects": { + "$ref": "#/components/schemas/Subjects" + }, + "values": { + "$ref": "#/components/schemas/Values" + }, + "value": { + "$ref": "#/components/schemas/Any" + } + }, + "description": "ExpandLeaf is the leaf node of an Expand tree and can be either a set of Subjects or a set of Values." + }, + "ExpandTreeNode": { + "type": "object", + "properties": { + "operation": { + "$ref": "#/components/schemas/ExpandTreeNode.Operation" + }, + "children": { + "title": "The children of this tree node", + "type": "array", + "items": { + "$ref": "#/components/schemas/Expand" + } + } + }, + "description": "ExpandTreeNode represents a node in an expansion tree with a specific operation and its children." + }, + "ExpandTreeNode.Operation": { + "type": "string", + "description": "Operation is an enum representing the type of operation to be applied on the tree node.", + "default": "OPERATION_UNSPECIFIED", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_UNION", + "OPERATION_INTERSECTION", + "OPERATION_EXCLUSION" + ] + }, + "Expr": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Required. An id assigned to this node by the parser which is unique in a\ngiven expression tree. This is used to associate type information and other\nattributes to a node in the parse tree.", + "format": "int64" + }, + "constExpr": { + "$ref": "#/components/schemas/Constant" + }, + "identExpr": { + "$ref": "#/components/schemas/Ident" + }, + "selectExpr": { + "$ref": "#/components/schemas/Select" + }, + "callExpr": { + "$ref": "#/components/schemas/Expr.Call" + }, + "listExpr": { + "$ref": "#/components/schemas/CreateList" + }, + "structExpr": { + "$ref": "#/components/schemas/CreateStruct" + }, + "comprehensionExpr": { + "$ref": "#/components/schemas/Comprehension" + } + }, + "description": "An abstract representation of a common expression.\n\nExpressions are abstractly represented as a collection of identifiers,\nselect statements, function calls, literals, and comprehensions. All\noperators with the exception of the '.' operator are modelled as function\ncalls. This makes it easy to represent new operators into the existing AST.\n\nAll references within expressions must resolve to a [Decl][google.api.expr.v1alpha1.Decl] provided at\ntype-check for an expression to be valid. A reference may either be a bare\nidentifier `name` or a qualified identifier `google.api.name`. References\nmay either refer to a value or a function declaration.\n\nFor example, the expression `google.api.name.startsWith('expr')` references\nthe declaration `google.api.name` within a [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression, and\nthe function declaration `startsWith`." + }, + "Expr.Call": { + "type": "object", + "properties": { + "target": { + "$ref": "#/components/schemas/Expr" + }, + "function": { + "type": "string", + "description": "Required. The name of the function or method being called." + }, + "args": { + "type": "array", + "description": "The arguments.", + "items": { + "$ref": "#/components/schemas/Expr" + } + } + }, + "description": "A call expression, including calls to predefined functions and operators.\n\nFor example, `value == 10`, `size(map_value)`." + }, + "FunctionType": { + "type": "object", + "properties": { + "resultType": { + "$ref": "#/components/schemas/v1alpha1.Type" + }, + "argTypes": { + "type": "array", + "description": "Argument types of the function.", + "items": { + "$ref": "#/components/schemas/v1alpha1.Type" + } + } + }, + "description": "Function type with result and arg types." + }, + "Ident": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Required. Holds a single, unqualified identifier, possibly preceded by a\n'.'.\n\nQualified names are represented by the [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression." + } + }, + "description": "An identifier expression. e.g. `request`." + }, + "Leaf": { + "type": "object", + "properties": { + "computedUserSet": { + "$ref": "#/components/schemas/ComputedUserSet" + }, + "tupleToUserSet": { + "$ref": "#/components/schemas/TupleToUserSet" + }, + "computedAttribute": { + "$ref": "#/components/schemas/ComputedAttribute" + }, + "call": { + "$ref": "#/components/schemas/v1.Call" + } + }, + "description": "Leaf represents a leaf node in the permission tree." + }, + "ListType": { + "type": "object", + "properties": { + "elemType": { + "$ref": "#/components/schemas/v1alpha1.Type" + } + }, + "description": "List type with typed elements, e.g. `list`." + }, + "MapType": { + "type": "object", + "properties": { + "keyType": { + "$ref": "#/components/schemas/v1alpha1.Type" + }, + "valueType": { + "$ref": "#/components/schemas/v1alpha1.Type" + } + }, + "description": "Map type with parameterized key and value types, e.g. `map`." + }, + "NullValue": { + "type": "string", + "description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value.", + "default": "NULL_VALUE", + "enum": [ + "NULL_VALUE" + ] + }, + "PermissionCheckRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "Version of the schema." + }, + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + }, + "depth": { + "type": "integer", + "description": "Query limit when if recursive database queries got in loop", + "format": "int32" + } + }, + "description": "PermissionCheckRequestMetadata metadata for the PermissionCheckRequest." + }, + "PermissionCheckResponse": { + "type": "object", + "properties": { + "can": { + "$ref": "#/components/schemas/CheckResult" + }, + "metadata": { + "$ref": "#/components/schemas/PermissionCheckResponseMetadata" + } + }, + "description": "PermissionCheckResponse is the response message for the Check method in the Permission service." + }, + "PermissionCheckResponseMetadata": { + "type": "object", + "properties": { + "check_count": { + "type": "integer", + "description": "The count of the checks performed.", + "format": "int32" + } + }, + "description": "PermissionCheckResponseMetadata metadata for the PermissionCheckResponse." + }, + "PermissionDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the permission, which follows a specific string pattern and has a maximum byte size." + }, + "child": { + "$ref": "#/components/schemas/Child" + } + }, + "description": "The PermissionDefinition message provides detailed information about a specific permission." + }, + "PermissionExpandRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "Version of the schema." + }, + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + } + }, + "description": "PermissionExpandRequestMetadata metadata for the PermissionExpandRequest." + }, + "PermissionExpandResponse": { + "type": "object", + "properties": { + "tree": { + "$ref": "#/components/schemas/Expand" + } + }, + "description": "PermissionExpandResponse is the response message for the Expand method in the Permission service." + }, + "PermissionLookupEntityRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "Version of the schema." + }, + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + }, + "depth": { + "type": "integer", + "description": "Query limit when if recursive database queries got in loop.", + "format": "int32" + } + }, + "description": "PermissionLookupEntityRequestMetadata metadata for the PermissionLookupEntityRequest." + }, + "PermissionLookupEntityResponse": { + "type": "object", + "properties": { + "entity_ids": { + "type": "array", + "description": "List of identifiers for entities that match the lookup.", + "items": { + "type": "string" + } + } + }, + "description": "PermissionLookupEntityResponse is the response message for the LookupEntity method in the Permission service." + }, + "PermissionLookupEntityStreamResponse": { + "type": "object", + "properties": { + "entity_id": { + "type": "string", + "description": "Identifier for an entity that matches the lookup." + } + }, + "description": "PermissionLookupEntityStreamResponse is the response message for the LookupEntityStream method in the Permission service." + }, + "PermissionLookupSubjectRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "Version of the schema." + }, + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + }, + "depth": { + "type": "integer", + "description": "Query limit when if recursive database queries got in loop.", + "format": "int32" + } + }, + "description": "PermissionLookupSubjectRequestMetadata metadata for the PermissionLookupSubjectRequest." + }, + "PermissionLookupSubjectResponse": { + "type": "object", + "properties": { + "subject_ids": { + "type": "array", + "description": "List of identifiers for subjects that match the lookup.", + "items": { + "type": "string" + } + } + }, + "description": "PermissionLookupSubjectResponse is the response message for the LookupSubject method in the Permission service." + }, + "PermissionSubjectPermissionRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "Version of the schema." + }, + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)." + }, + "only_permission": { + "type": "boolean", + "description": "Whether to only check permissions." + }, + "depth": { + "type": "integer", + "description": "Query limit when if recursive database queries got in loop.", + "format": "int32" + } + }, + "description": "PermissionSubjectPermissionRequestMetadata metadata for the PermissionSubjectPermissionRequest." + }, + "PermissionSubjectPermissionResponse": { + "type": "object", + "properties": { + "results": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CheckResult" + }, + "description": "Map of results for each permission check." + } + }, + "description": "PermissionSubjectPermissionResponse is the response message for the SubjectPermission method in the Permission service." + }, + "PrimitiveType": { + "type": "string", + "description": "CEL primitive types.\n\n - PRIMITIVE_TYPE_UNSPECIFIED: Unspecified type.\n - BOOL: Boolean type.\n - INT64: Int64 type.\n\nProto-based integer values are widened to int64.\n - UINT64: Uint64 type.\n\nProto-based unsigned integer values are widened to uint64.\n - DOUBLE: Double type.\n\nProto-based float values are widened to double values.\n - STRING: String type.\n - BYTES: Bytes type.", + "default": "PRIMITIVE_TYPE_UNSPECIFIED", + "enum": [ + "PRIMITIVE_TYPE_UNSPECIFIED", + "BOOL", + "INT64", + "UINT64", + "DOUBLE", + "STRING", + "BYTES" + ] + }, + "RelationDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the relation, which follows a specific string pattern and has a maximum byte size." + }, + "relationReferences": { + "type": "array", + "description": "A list of references to other relations.", + "items": { + "$ref": "#/components/schemas/RelationReference" + } + } + }, + "description": "The RelationDefinition message provides detailed information about a specific relation." + }, + "RelationReference": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of the referenced entity, which follows a specific string pattern and has a maximum byte size." + }, + "relation": { + "type": "string", + "description": "The name of the referenced relation, which follows a specific string pattern and has a maximum byte size." + } + }, + "description": "The RelationReference message provides a reference to a specific relation." + }, + "RelationshipDeleteResponse": { + "title": "RelationshipDeleteResponse", + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + } + }, + "RelationshipReadRequestMetadata": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + }, + "description": "RelationshipReadRequestMetadata defines the structure of the metadata for a read request focused on relationships.\nIt includes the snap_token associated with a particular state of the database." + }, + "RelationshipReadResponse": { + "type": "object", + "properties": { + "tuples": { + "type": "array", + "description": "tuples is a list of the relationships retrieved in the read operation, represented as entity-relation-entity triples.", + "items": { + "$ref": "#/components/schemas/Tuple" + } + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is used in the case of paginated reads to retrieve the next page of results." + } + }, + "description": "RelationshipReadResponse defines the structure of the response after reading relationships.\nIt includes the tuples representing the relationships and a continuous token for handling result pagination." + }, + "RelationshipWriteRequestMetadata": { + "title": "RelationshipWriteRequestMetadata", + "type": "object", + "properties": { + "schema_version": { + "type": "string" + } + } + }, + "RelationshipWriteResponse": { + "title": "RelationshipWriteResponse", + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + } + }, + "Rewrite": { + "type": "object", + "properties": { + "rewriteOperation": { + "$ref": "#/components/schemas/Rewrite.Operation" + }, + "children": { + "type": "array", + "description": "A list of children that are operated upon by the rewrite operation.", + "items": { + "$ref": "#/components/schemas/Child" + } + } + }, + "description": "The Rewrite message represents a specific rewrite operation.\nThis operation could be one of the following: union, intersection, or exclusion." + }, + "Rewrite.Operation": { + "type": "string", + "description": "Operation enum includes potential rewrite operations.\nOPERATION_UNION: Represents a union operation.\nOPERATION_INTERSECTION: Represents an intersection operation.\nOPERATION_EXCLUSION: Represents an exclusion operation.\n\n - OPERATION_UNSPECIFIED: Default, unspecified operation.\n - OPERATION_UNION: Represents a union operation.\n - OPERATION_INTERSECTION: Represents an intersection operation.\n - OPERATION_EXCLUSION: Represents an exclusion operation.", + "default": "OPERATION_UNSPECIFIED", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_UNION", + "OPERATION_INTERSECTION", + "OPERATION_EXCLUSION" + ] + }, + "RuleDefinition": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the rule, which follows a specific string pattern and has a maximum byte size." + }, + "arguments": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/AttributeType" + }, + "description": "Map of arguments for this rule. The key is the attribute name, and the value is the AttributeType." + }, + "expression": { + "$ref": "#/components/schemas/CheckedExpr" + } + }, + "description": "The RuleDefinition message provides detailed information about a specific rule." + }, + "SchemaDefinition": { + "type": "object", + "properties": { + "entityDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/EntityDefinition" + }, + "description": "Map of entity definitions. The key is the entity name, and the value is the corresponding EntityDefinition." + }, + "ruleDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/RuleDefinition" + }, + "description": "Map of rule definitions. The key is the rule name, and the value is the corresponding RuleDefinition." + }, + "references": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/SchemaDefinition.Reference" + }, + "description": "Map of references to signify whether a string refers to an entity or a rule." + } + }, + "description": "The SchemaDefinition message provides definitions for entities and rules,\nand includes references to clarify whether a name refers to an entity or a rule." + }, + "SchemaDefinition.Reference": { + "type": "string", + "description": "The Reference enum helps distinguish whether a name corresponds to an entity or a rule.\n\n - REFERENCE_UNSPECIFIED: Default, unspecified reference.\n - REFERENCE_ENTITY: Indicates that the name refers to an entity.\n - REFERENCE_RULE: Indicates that the name refers to a rule.", + "default": "REFERENCE_UNSPECIFIED", + "enum": [ + "REFERENCE_UNSPECIFIED", + "REFERENCE_ENTITY", + "REFERENCE_RULE" + ] + }, + "SchemaList": { + "title": "SchemaList provides a list of schema versions with their corresponding creation timestamps", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "created_at": { + "type": "string" + } + } + }, + "SchemaListResponse": { + "title": "SchemaListResponse is the response message for the List method in the Schema service.\nIt returns a paginated list of schemas", + "type": "object", + "properties": { + "head": { + "title": "head of the schemas is the latest version available for the tenant", + "type": "string" + }, + "schemas": { + "title": "list of schema versions with creation timestamps", + "type": "array", + "items": { + "$ref": "#/components/schemas/SchemaList" + } + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is a string that can be used to paginate and retrieve the next set of results." + } + } + }, + "SchemaReadRequestMetadata": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "schema_version is the string that identifies the version of the schema to be read." + } + }, + "description": "SchemaReadRequestMetadata provides additional information for the Schema Read request.\nIt contains schema_version to specify which version of the schema should be read." + }, + "SchemaReadResponse": { + "type": "object", + "properties": { + "schema": { + "$ref": "#/components/schemas/SchemaDefinition" + } + }, + "description": "SchemaReadResponse is the response message for the Read method in the Schema service.\nIt returns the requested schema." + }, + "SchemaWriteResponse": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "description": "schema_version is the string that identifies the version of the written schema." + } + }, + "description": "SchemaWriteResponse is the response message for the Write method in the Schema service.\nIt returns the version of the written schema." + }, + "Select": { + "type": "object", + "properties": { + "operand": { + "$ref": "#/components/schemas/Expr" + }, + "field": { + "type": "string", + "description": "Required. The name of the field to select.\n\nFor example, in the select expression `request.auth`, the `auth` portion\nof the expression would be the `field`." + }, + "testOnly": { + "type": "boolean", + "description": "Whether the select is to be interpreted as a field presence test.\n\nThis results from the macro `has(request.auth)`." + } + }, + "description": "A field selection expression. e.g. `request.auth`." + }, + "SourceInfo": { + "type": "object", + "properties": { + "syntaxVersion": { + "type": "string", + "description": "The syntax version of the source, e.g. `cel1`." + }, + "location": { + "type": "string", + "description": "The location name. All position information attached to an expression is\nrelative to this location.\n\nThe location could be a file, UI element, or similar. For example,\n`acme/app/AnvilPolicy.cel`." + }, + "lineOffsets": { + "type": "array", + "description": "Monotonically increasing list of code point offsets where newlines\n`\\n` appear.\n\nThe line number of a given position is the index `i` where for a given\n`id` the `line_offsets[i] < id_positions[id] < line_offsets[i+1]`. The\ncolumn may be derivd from `id_positions[id] - line_offsets[i]`.", + "items": { + "type": "integer", + "format": "int32" + } + }, + "positions": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + }, + "description": "A map from the parse node id (e.g. `Expr.id`) to the code point offset\nwithin the source." + }, + "macroCalls": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Expr" + }, + "description": "A map from the parse node id where a macro replacement was made to the\ncall `Expr` that resulted in a macro expansion.\n\nFor example, `has(value.field)` is a function call that is replaced by a\n`test_only` field selection in the AST. Likewise, the call\n`list.exists(e, e > 10)` translates to a comprehension expression. The key\nin the map corresponds to the expression id of the expanded macro, and the\nvalue is the call `Expr` that was replaced." + } + }, + "description": "Source information collected at parse time." + }, + "Status": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Any" + } + } + } + }, + "Subject": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "relation": { + "type": "string" + } + }, + "description": "Subject represents an entity subject with a type, an identifier, and a relation." + }, + "SubjectFilter": { + "type": "object", + "properties": { + "type": { + "title": "Type of the subject", + "type": "string" + }, + "ids": { + "title": "List of subject IDs", + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "string" + } + }, + "description": "SubjectFilter is used to filter subjects based on the type, ids and relation." + }, + "Subjects": { + "type": "object", + "properties": { + "subjects": { + "type": "array", + "description": "A list of subjects.", + "items": { + "$ref": "#/components/schemas/Subject" + } + } + }, + "description": "Subjects holds a repeated field of Subject type." + }, + "Tenant": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the tenant." + }, + "name": { + "type": "string", + "description": "The name of the tenant." + }, + "created_at": { + "type": "string", + "description": "The time at which the tenant was created.", + "format": "date-time" + } + }, + "description": "Tenant represents a tenant with an id, a name, and a timestamp indicating when it was created." + }, + "TenantCreateRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "id is a unique identifier for the tenant." + }, + "name": { + "type": "string", + "description": "name is the name of the tenant." + } + }, + "description": "TenantCreateRequest is the message used for the request to create a tenant." + }, + "TenantCreateResponse": { + "type": "object", + "properties": { + "tenant": { + "$ref": "#/components/schemas/Tenant" + } + }, + "description": "TenantCreateResponse is the message returned from the request to create a tenant." + }, + "TenantDeleteResponse": { + "type": "object", + "properties": { + "tenant": { + "$ref": "#/components/schemas/Tenant" + } + }, + "description": "TenantDeleteResponse is the message returned from the request to delete a tenant." + }, + "TenantListRequest": { + "type": "object", + "properties": { + "page_size": { + "type": "integer", + "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100.", + "format": "int64" + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is an optional parameter used for pagination.\nIt should be the value received in the previous response." + } + }, + "description": "TenantListRequest is the message used for the request to list all tenants." + }, + "TenantListResponse": { + "type": "object", + "properties": { + "tenants": { + "type": "array", + "description": "tenants is a list of tenants.", + "items": { + "$ref": "#/components/schemas/Tenant" + } + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is a string that can be used to paginate and retrieve the next set of results." + } + }, + "description": "TenantListResponse is the message returned from the request to list all tenants." + }, + "Tuple": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/Entity" + }, + "relation": { + "type": "string" + }, + "subject": { + "$ref": "#/components/schemas/Subject" + } + }, + "description": "Tuple is a structure that includes an entity, a relation, and a subject." + }, + "TupleFilter": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/components/schemas/EntityFilter" + }, + "relation": { + "type": "string" + }, + "subject": { + "$ref": "#/components/schemas/SubjectFilter" + } + }, + "description": "TupleFilter is used to filter tuples based on the entity, relation and the subject." + }, + "TupleSet": { + "type": "object", + "properties": { + "relation": { + "type": "string" + } + }, + "description": "TupleSet represents a set of tuples associated with a specific relation." + }, + "TupleToUserSet": { + "type": "object", + "properties": { + "tupleSet": { + "$ref": "#/components/schemas/TupleSet" + }, + "computed": { + "$ref": "#/components/schemas/ComputedUserSet" + } + }, + "description": "TupleToUserSet defines a mapping from tuple sets to computed user sets." + }, + "Values": { + "type": "object", + "properties": { + "values": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Any" + } + } + } + }, + "WatchResponse": { + "type": "object", + "properties": { + "changes": { + "$ref": "#/components/schemas/DataChanges" + } + }, + "description": "WatchResponse is the response message for the Watch RPC. It contains the\nchanges in the data that are being watched." + }, + "WellKnownType": { + "type": "string", + "description": "Well-known protobuf types treated with first-class support in CEL.\n\n - WELL_KNOWN_TYPE_UNSPECIFIED: Unspecified type.\n - ANY: Well-known protobuf.Any type.\n\nAny types are a polymorphic message type. During type-checking they are\ntreated like `DYN` types, but at runtime they are resolved to a specific\nmessage type specified at evaluation time.\n - TIMESTAMP: Well-known protobuf.Timestamp type, internally referenced as `timestamp`.\n - DURATION: Well-known protobuf.Duration type, internally referenced as `duration`.", + "default": "WELL_KNOWN_TYPE_UNSPECIFIED", + "enum": [ + "WELL_KNOWN_TYPE_UNSPECIFIED", + "ANY", + "TIMESTAMP", + "DURATION" + ] + }, + "v1.Call": { + "type": "object", + "properties": { + "ruleName": { + "title": "Name of the rule", + "type": "string" + }, + "arguments": { + "title": "Arguments passed to the rule", + "type": "array", + "items": { + "$ref": "#/components/schemas/Argument" + } + } + }, + "description": "Call represents a call to a rule. It includes the name of the rule and the arguments passed to it." + }, + "v1.Operation": { + "type": "object", + "properties": { + "relationships_write": { + "type": "array", + "description": "'relationships_write' is a repeated string field for storing relationship keys\nthat are to be written or created.", + "items": { + "type": "string" + } + }, + "relationships_delete": { + "type": "array", + "description": "'relationships_delete' is a repeated string field for storing relationship keys\nthat are to be deleted or removed.", + "items": { + "type": "string" + } + }, + "attributes_write": { + "type": "array", + "description": "'attributes_write' is a repeated string field for storing attribute keys\nthat are to be written or created.", + "items": { + "type": "string" + } + }, + "attributes_delete": { + "type": "array", + "description": "'attributes_delete' is a repeated string field for storing attribute keys\nthat are to be deleted or removed.", + "items": { + "type": "string" + } + } + }, + "description": "Operation is a message representing a series of operations that can be performed.\nIt includes fields for writing and deleting relationships and attributes." + }, + "v1alpha1.Reference": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The fully qualified name of the declaration." + }, + "overloadId": { + "type": "array", + "description": "For references to functions, this is a list of `Overload.overload_id`\nvalues which match according to typing rules.\n\nIf the list has more than one element, overload resolution among the\npresented candidates must happen at runtime because of dynamic types. The\ntype checker attempts to narrow down this list as much as possible.\n\nEmpty if this is not a reference to a\n[Decl.FunctionDecl][google.api.expr.v1alpha1.Decl.FunctionDecl].", + "items": { + "type": "string" + } + }, + "value": { + "$ref": "#/components/schemas/Constant" + } + }, + "description": "Describes a resolved reference to a declaration." + }, + "v1alpha1.Type": { + "type": "object", + "properties": { + "dyn": { + "type": "object", + "properties": {}, + "description": "Dynamic type." + }, + "null": { + "type": "string", + "description": "Null value." + }, + "primitive": { + "$ref": "#/components/schemas/PrimitiveType" + }, + "wrapper": { + "$ref": "#/components/schemas/PrimitiveType" + }, + "wellKnown": { + "$ref": "#/components/schemas/WellKnownType" + }, + "listType": { + "$ref": "#/components/schemas/ListType" + }, + "mapType": { + "$ref": "#/components/schemas/MapType" + }, + "function": { + "$ref": "#/components/schemas/FunctionType" + }, + "messageType": { + "type": "string", + "description": "Protocol buffer message type.\n\nThe `message_type` string specifies the qualified message type name. For\nexample, `google.plus.Profile`." + }, + "typeParam": { + "type": "string", + "description": "Type param type.\n\nThe `type_param` string specifies the type parameter name, e.g. `list`\nwould be a `list_type` whose element type was a `type_param` type\nnamed `E`." + }, + "type": { + "$ref": "#/components/schemas/v1alpha1.Type" + }, + "error": { + "type": "object", + "properties": {}, + "description": "Error type.\n\nDuring type-checking if an expression is an error, its type is propagated\nas the `ERROR` type. This permits the type-checker to discover other\nerrors present in the expression." + }, + "abstractType": { + "$ref": "#/components/schemas/AbstractType" + } + }, + "description": "Represents a CEL type." + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } + }, + "x-original-swagger-version": "2.0" +} \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/check-api.md b/docs/api-reference/permission/check-api.mdx similarity index 54% rename from docs/versioned_docs/version-0.6.x/api-overview/permission/check-api.md rename to docs/api-reference/permission/check-api.mdx index c6bfed58..5cb9da79 100644 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/check-api.md +++ b/docs/api-reference/permission/check-api.mdx @@ -1,39 +1,34 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control +--- +title: Check Access Control +openapi: post /v1/tenants/{tenant_id}/permissions/check +--- In Permify, you can perform two different types access checks, -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **subject based** authorization checks, in form of `Which resources can user U edit ?` +- **resource based** authorization checks, structured in the following form: `Can user U perform action Y in resource Z ?` +- **subject based** authorization checks, structured in the following form: `Which resources can user U edit ?` + +In this section we'll look at the resource based check request of Permify. + +You can find subject based access checks in [Entity (Data) Filtering] section. + +[Entity (Data) Filtering]: ./lookup-entity -In this section we'll look at the resource based check request of Permify. You can find subject based access checks in [Entity (Data) Filtering] section. +## Content -[Entity (Data) Filtering]: ../lookup-entity +- [Example Check Request](#example-check-request) +- [How Access Decisions Evaluated?](#how-access-decisions-evaluated) +- [Latency & Performance](#latency-and-performance) +- [Parameters & Properties](#parameters-and-properties) -## Request +## Example Check Request -**Path:** ```javascript POST /v1/permissions/check ``` -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.check) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - + ```go cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { @@ -61,8 +56,8 @@ cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequ }) ``` - - + + ```javascript client.permission.check({ @@ -90,9 +85,43 @@ client.permission.check({ }) ``` - - + + +```python +with permify.ApiClient(configuration) as api_client: + api_instance = permify.PermissionApi(api_client) + tenant_id = 't1' + + body = PermissionsCheckRequest( + tenant_id=tenant_id, + metadata={ + "snapToken": "", + "schemaVersion": "", + "depth": 20 + }, + entity={ + "type": "repository", + "id": "1" + }, + permission="edit", + subject={ + "type": "user", + "id": "1" + } + ) + + try: + api_response = api_instance.permissions_check(tenant_id, body) + if api_response.can == PermissionCheckResponse.Result.RESULT_ALLOWED: + print("RESULT_ALLOWED") + else: + print("RESULT_DENIED") + except ApiException as e: + print(f"Exception permissions_check: {e}") +``` + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ --header 'Content-Type: application/json' \ @@ -114,20 +143,9 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permission }, }' ``` - + -## Response - -```json -{ - "can": "RESULT_ALLOWED", - "remaining_depth": 0 -} -``` - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. - ## How Access Decisions Evaluated? Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. @@ -187,11 +205,6 @@ Rather than **or**, if we had an **and** relation then Permify Engine waits the With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../reference/cache.md) - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: +Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../operations/cache) +## Parameters & Properties \ No newline at end of file diff --git a/docs/api-reference/permission/expand-api.mdx b/docs/api-reference/permission/expand-api.mdx new file mode 100644 index 00000000..e3fbf538 --- /dev/null +++ b/docs/api-reference/permission/expand-api.mdx @@ -0,0 +1,14 @@ +--- +title: Expand API +openapi: post /v1/tenants/{tenant_id}/permissions/expand +--- + +Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission + +Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. + + +Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. + +It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. + \ No newline at end of file diff --git a/docs/api-reference/permission/lookup-entity-stream.mdx b/docs/api-reference/permission/lookup-entity-stream.mdx new file mode 100644 index 00000000..51313643 --- /dev/null +++ b/docs/api-reference/permission/lookup-entity-stream.mdx @@ -0,0 +1,6 @@ +--- +title: Lookup Entity (Streaming) +openapi: post /v1/tenants/{tenant_id}/permissions/lookup-entity-stream +--- + +The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. diff --git a/docs/api-reference/permission/lookup-entity.mdx b/docs/api-reference/permission/lookup-entity.mdx new file mode 100644 index 00000000..82decbf6 --- /dev/null +++ b/docs/api-reference/permission/lookup-entity.mdx @@ -0,0 +1,50 @@ +--- +title: Lookup Entity (Data Filtering) +openapi: post /v1/tenants/{tenant_id}/permissions/lookup-entity +--- + +Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. + +So, we provide 2 separate endpoints for data filtering check request, + +- Lookup Entity +- [Lookup Entity Streaming](./lookup-entity-stream) + +In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. + +### How Lookup Operations Evaluated + +We explicitly designed reverse lookup to be more performant with changing its evaluation pattern. We do not query all the documents in bulk to get response, instead of this Permify first finds the necessary relations with given subject and the permission/action in the API call. Then query these relations with the subject id this way we reduce lots of additional queries. + +To give an example, + +```jsx +entity user {} + +entity organization { + relation admin @user +} + +entity container { + relation parent @organization + relation container_admin @user + action admin = parent.admin or container_admin +} + +entity document { + relation container @container + relation viewer @user + relation owner @user + action view = viewer or owner or container.admin +} +``` + +Lets say we called (reverse) lookup API to find the documents that user:1 can view. Permify first finds the relations that linked with view action, these are + +- `document#viewer` +- `document#owner` +- `organization#admin` +- `container#``container_admin` + +Then queries each of them with `user:1.` + diff --git a/docs/api-reference/permission/lookup-subject.mdx b/docs/api-reference/permission/lookup-subject.mdx new file mode 100644 index 00000000..aa81a943 --- /dev/null +++ b/docs/api-reference/permission/lookup-subject.mdx @@ -0,0 +1,8 @@ +--- +title: Subject Filtering +openapi: post /v1/tenants/{tenant_id}/permissions/lookup-subject +--- + +Lookup Subject endpoint lets you ask questions in form of **โ€œWhich subjects can do action Y on entity:X?โ€**. As a response of this youโ€™ll get a subject results in a format of string array. + +In this endpoint you'll get directly the IDs' of the subjects that are authorized in an array. \ No newline at end of file diff --git a/docs/api-reference/permission/subject-permission.mdx b/docs/api-reference/permission/subject-permission.mdx new file mode 100644 index 00000000..48d6b1ae --- /dev/null +++ b/docs/api-reference/permission/subject-permission.mdx @@ -0,0 +1,8 @@ +--- +title: Subject Permission List +openapi: post /v1/tenants/{tenant_id}/permissions/subject-permission +--- + +The Subject Permission List endpoint allows you to inquire in the form of **โ€œWhich permissions user:x can perform on entity:y?โ€**. In response, you'll receive a list of permissions specific to the user for the given entity, returned in the format of a map. + +In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as "sample-permission" -> "ALLOWED". This represents the permissions and their associated states in a key-value pair format. diff --git a/docs/api-reference/schema/list-schema.mdx b/docs/api-reference/schema/list-schema.mdx new file mode 100644 index 00000000..85c5af9c --- /dev/null +++ b/docs/api-reference/schema/list-schema.mdx @@ -0,0 +1,13 @@ +--- +title: List Schema +openapi: post /v1/tenants/{tenant_id}/schemas/list +--- + +Models written to Permify using the [write schema API](./write-schema) can be listed using this API with the timestamps at which the models were created. + +Request needs to be made to the API endpoint **/v1/schemas/list** to list all the models. + +### Example Request on Postman +**POST** `/v1/tenants/{tenant_id}/schemas/list` + +![permify-schema](https://github.com/Permify/permify/assets/30985448/aa73c993-e808-496e-bebc-f91ced3a3399) diff --git a/docs/api-reference/schema/read-schema.mdx b/docs/api-reference/schema/read-schema.mdx new file mode 100644 index 00000000..3a5ea98c --- /dev/null +++ b/docs/api-reference/schema/read-schema.mdx @@ -0,0 +1,13 @@ +--- +title: Read Schema +openapi: post /v1/tenants/{tenant_id}/schemas/read +--- + +When a model is written to Permify using the [write schema API](./write-schema) a schema version will be returned by the API. That schema version can be used to inspect the schema. + +Permify Schema needed to be send to API endpoint **/v1/schemas/read** for configuration of your authorization model on Permify API. + +### Example Request on Postman +**POST** `/v1/tenants/{tenant_id}/schemas/read"` + +![permify-schema](https://github.com/Permify/permify/assets/30985448/a6944e3d-6a58-4489-b16f-da2fdf5f60f2) diff --git a/docs/versioned_docs/version-0.2.x/api-overview/write-schema.md b/docs/api-reference/schema/write-schema.mdx similarity index 53% rename from docs/versioned_docs/version-0.2.x/api-overview/write-schema.md rename to docs/api-reference/schema/write-schema.mdx index f97a9f84..cdac796e 100644 --- a/docs/versioned_docs/version-0.2.x/api-overview/write-schema.md +++ b/docs/api-reference/schema/write-schema.mdx @@ -1,72 +1,25 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema +--- +title: Write Schema +openapi: post /v1/tenants/{tenant_id}/schemas/write +--- Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. -:::caution Use Playground For Testing + If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. ![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: + Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. -### Path : ** POST "/v1/schemas/write"** -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | schema | string | - | Permify Schema as string| - -**Example Request on Postman:** +### Example Request on Postman +**POST** `/v1/tenants/{tenant_id}/schemas/write` ![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) -### Using gRPC Clients - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - Schema: ` - entity user {} - - entity document { - relation viewer @user - - action view = viewer - } - `, -}) -``` - - - - -```javascript -client.schema.write({ - schema: ` - entity user {} - - entity document { - relation viewer @user - - action view = viewer - } - ` -}).then((response) => { - // handle response -}) -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file +See the following FAQ page to refer to the suggested workflow for: [Managing Schema Changes](../../introduction/faqs#how-to-manage-schema-changes). \ No newline at end of file diff --git a/docs/api-reference/tenancy/create-tenant.mdx b/docs/api-reference/tenancy/create-tenant.mdx new file mode 100644 index 00000000..08817c23 --- /dev/null +++ b/docs/api-reference/tenancy/create-tenant.mdx @@ -0,0 +1,10 @@ +--- +title: Create Tenant +openapi: post /v1/tenants/create +--- + +Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. + + +We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. + diff --git a/docs/api-reference/tenancy/delete-tenant.mdx b/docs/api-reference/tenancy/delete-tenant.mdx new file mode 100644 index 00000000..611625ad --- /dev/null +++ b/docs/api-reference/tenancy/delete-tenant.mdx @@ -0,0 +1,4 @@ +--- +title: Delete Tenant +openapi: delete /v1/tenants/{id} +--- \ No newline at end of file diff --git a/docs/api-reference/tenancy/list-tenants.mdx b/docs/api-reference/tenancy/list-tenants.mdx new file mode 100644 index 00000000..f3355b67 --- /dev/null +++ b/docs/api-reference/tenancy/list-tenants.mdx @@ -0,0 +1,4 @@ +--- +title: List Tenants +openapi: post /v1/tenants/list +--- \ No newline at end of file diff --git a/docs/api-reference/watch/watch-changes.mdx b/docs/api-reference/watch/watch-changes.mdx new file mode 100644 index 00000000..b77dfbc9 --- /dev/null +++ b/docs/api-reference/watch/watch-changes.mdx @@ -0,0 +1,70 @@ +--- +title: Watch API +openapi: post /v1/tenants/{tenant_id}/watch +--- + +The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples. + +The Watch API exclusively supports gRPC and works with PostgreSQL, given the track_commit_timestamp option is enabled. Please note, it doesn't support in-memory databases or HTTP communication. + +## Requirements + +- PostgreSQL database set up with track_commit_timestamp option enabled + +## Enabling track_commit_timestamp on PostgreSQL + +To ensure data consistency and synchronization between your application and Permify, enable track_commit_timestamp on +your PostgreSQL server. This can be done by executing the following options in your PostgreSQL: + +### Option 1: SQL Command + +1. Open your PostgreSQL command line interface. +2. Execute the following command: + + ```sql + ALTER SYSTEM SET track_commit_timestamp = ON; + ``` + +3. Reload the configuration with the following command: + + ```sql + SELECT pg_reload_conf(); + ``` + +### Option 2: Editing postgresql.conf + +1. Find and open the postgresql.conf file in a text editor. Its location depends on your PostgreSQL installation. Common + locations are: + - Debian-based systems: /etc/postgresql/[version]/main/postgresql.conf + - Red Hat-based systems: /var/lib/pgsql/data/postgresql.conf + +2. Add or modify the following line in the postgresql.conf file: + ``` + track_commit_timestamp = on + ``` + +3. Save and close the postgresql.conf file. +4. Reload the PostgreSQL configuration for the changes to take effect. This can be done via the PostgreSQL console: + ```sql + SELECT pg_reload_conf(); + ``` + + Or if you have command line access, use: + + ```bash + sudo service postgresql reload + ``` + +Please ensure you have the necessary permissions to execute these commands or modify the postgresql.conf file. Also, remember that changes in the postgresql.conf file will persist across restarts, while the SQL method may need to be reapplied depending on your PostgreSQL version and setup. + + +Important Configuration Requirement: To use the Watch API, it must be enabled in your configuration file. Add or modify the following lines: + +```yaml +service: + watch: + enabled: true +``` + + + diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index e00595da..00000000 --- a/docs/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/client-development-en/0.pack b/docs/client-development-en/0.pack deleted file mode 100644 index aa55caba..00000000 Binary files a/docs/client-development-en/0.pack and /dev/null differ diff --git a/docs/client-development-en/index.pack b/docs/client-development-en/index.pack deleted file mode 100644 index 26728a8d..00000000 Binary files a/docs/client-development-en/index.pack and /dev/null differ diff --git a/docs/development.mdx b/docs/development.mdx new file mode 100644 index 00000000..87830089 --- /dev/null +++ b/docs/development.mdx @@ -0,0 +1,98 @@ +--- +title: 'Development' +description: 'Learn how to preview changes locally' +--- + + + **Prerequisite** You should have installed Node.js (version 18.10.0 or + higher). + + +Step 1. Install Mintlify on your OS: + + + +```bash npm +npm i -g mintlify +``` + +```bash yarn +yarn global add mintlify +``` + + + +Step 2. Go to the docs are located (where you can find `mint.json`) and run the following command: + +```bash +mintlify dev +``` + +The documentation website is now available at `http://localhost:3000`. + +### Custom Ports + +Mintlify uses port 3000 by default. You can use the `--port` flag to customize the port Mintlify runs on. For example, use this command to run in port 3333: + +```bash +mintlify dev --port 3333 +``` + +You will see an error like this if you try to run Mintlify in a port that's already taken: + +```md +Error: listen EADDRINUSE: address already in use :::3000 +``` + +## Mintlify Versions + +Each CLI is linked to a specific version of Mintlify. Please update the CLI if your local website looks different than production. + + + +```bash npm +npm i -g mintlify@latest +``` + +```bash yarn +yarn global upgrade mintlify +``` + + + +## Deployment + + + Unlimited editors available under the [Startup + Plan](https://mintlify.com/pricing) + + +You should see the following if the deploy successfully went through: + + + + + +## Troubleshooting + +Here's how to solve some common problems when working with the CLI. + + + + Update to Node v18. Run `mintlify install` and try again. + + +Go to the `C:/Users/Username/.mintlify/` directory and remove the `mint` +folder. Then Open the Git Bash in this location and run `git clone +https://github.com/mintlify/mint.git`. + +Repeat step 3. + + + + Try navigating to the root of your device and delete the ~/.mintlify folder. + Then run `mintlify dev` again. + + + +Curious about what changed in a CLI version? [Check out the CLI changelog.](/changelog/command-line) diff --git a/docs/docs/api-overview.md b/docs/docs/api-overview.md deleted file mode 100644 index d505b336..00000000 --- a/docs/docs/api-overview.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Using the Permify API - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests and options. -- [DataService]: Authorization data operations such as creating, deleting and reading relational tuples and attributes. -- [BundleService]: Facilitates the creation and execution of bundles that perform predefined operations, establishing relationships and attributes based on provided arguments. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permifyco/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ./permission -[DataService]: ./data -[BundleService]: ./bundle -[SchemaService]: ./schema -[TenancyService]: ./tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](./api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](./api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](./api-overview/data/read-relationships.md) -- Check access with [Check API](./api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](./api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](./api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](./api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](./api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](./api-overview/watch/watch-changes.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our [documentation](https://docs.permify.co/docs/reference/configuration). We used [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm for rate limiting. - -## Error Handling - -Permify API uses a set of defined error codes to indicate various types of failures or issues. -Understanding these error codes and their implications is vital for effective error handling and troubleshooting within the Permify API. -Each error code is designed to provide clear insights into what went wrong and how to resolve it, ensuring a smoother integration and operation of the API in your applications -Refer to the [Error Codes](https://github.com/Permify/permify/blob/master/proto/base/v1/errors.proto) documentation for detailed descriptions and resolution steps for each error code. - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/_category_.json b/docs/docs/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/docs/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/docs/api-overview/bundle/_category_.json b/docs/docs/api-overview/bundle/_category_.json deleted file mode 100644 index d8d37140..00000000 --- a/docs/docs/api-overview/bundle/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Bundle Service", - "position": 4, - "collapsed": true -} diff --git a/docs/docs/api-overview/bundle/delete-bundle.md b/docs/docs/api-overview/bundle/delete-bundle.md deleted file mode 100644 index d175eda8..00000000 --- a/docs/docs/api-overview/bundle/delete-bundle.md +++ /dev/null @@ -1,58 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Bundle [Beta] - -The "Delete Bundle" API is designed for removing specific data bundles within a multi-tenant application environment. This API facilitates the deletion of a bundle, identified by its unique name, from a designated tenant's environment. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/delete - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.delete) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | - - - - -```go -rr, err: = client.Bundle.Delete(context.Background(), &v1.BundleDeleteRequest{ - TenantId: "t1", - Name: "organization_created", -}) -``` - - - - - -```javascript -client.bundle.delete({ - tenantId: "t1", - name: "organization_created", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/bundle/read-bundle.md b/docs/docs/api-overview/bundle/read-bundle.md deleted file mode 100644 index f96a54ce..00000000 --- a/docs/docs/api-overview/bundle/read-bundle.md +++ /dev/null @@ -1,58 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Bundle [Beta] - -The "Read Bundle" API is a crucial tool for retrieving details of specific data bundles in a multi-tenant application setup. It is designed to access information about a bundle, uniquely identified by its name, within the specified tenant's environment. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/read - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.read) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | - - - - -```go -rr, err: = client.Bundle.Read(context.Background(), &v1.BundleReadRequest{ - TenantId: "t1", - Name: "organization_created", -}) -``` - - - - - -```javascript -client.bundle.read({ - tenantId: "t1", - name: "organization_created", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/bundle/write-bundle.md b/docs/docs/api-overview/bundle/write-bundle.md deleted file mode 100644 index 9ee9d653..00000000 --- a/docs/docs/api-overview/bundle/write-bundle.md +++ /dev/null @@ -1,117 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Bundle [Beta] - -The "Write Bundle" API is designed for handling data in a multi-tenant application environment. Its primary function is to write and delete data according to predefined structures. This API allows users to define or update data bundles, each distinguished by a unique name. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/write - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.write) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | -| [x] | operations | object | Represent actions that can be performed on data, such as adding or deleting relationships or attributes when certain events occur. | -| [x] | arguments | string[] | Parameters that will be used in the operations | - - - - -```go -rr, err := client.Bundle.Write(context.Background(), &v1.BundleWriteRequest{ - TenantId: "t1", - Bundles: []*v1.DataBundle{ - { - Name: "organization_created", - Arguments: []string{ - "creatorID", - "organizationID", - }, - Operations: []*v1.Operation{ - { - RelationshipsWrite: []string{ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - }, - AttributesWrite: []string{ - "organization:{{.organizationID}}$public|boolean:false", - }, - }, - }, - }, - }, -}) -``` - - - - - -```javascript -client.bundle.write({ - tenantId: "t1", - bundles: [ - { - name: "organization_created", - arguments: [ - "creatorID", - "organizationID", - ], - operations: [ - { - relationships_write: [ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - ], - attributes_write: [ - "organization:{{.organizationID}}$public|boolean:false", - ] - } - ] - } - ] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "bundles": [ - { - "name": "organization_created" - "arguments": [ - "creatorID", - "organizationID" - ], - "operations": [ - { - "relationships_write": [ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - ], - "attributes_write": [ - "organization:{{.organizationID}}$public|boolean:false", - ], - }, - ], - }, - ], -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/data/_category_.json b/docs/docs/api-overview/data/_category_.json deleted file mode 100644 index 1a2612e1..00000000 --- a/docs/docs/api-overview/data/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Data Service", - "position": 3, - "collapsed": true -} diff --git a/docs/docs/api-overview/data/delete-data.md b/docs/docs/api-overview/data/delete-data.md deleted file mode 100644 index b8d09e33..00000000 --- a/docs/docs/api-overview/data/delete-data.md +++ /dev/null @@ -1,111 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Data - -You can delete any stored relation tuples or attributes with following API - -## Request - -**Path:** -```javascript -POST /v1/tenants/{tenant_id}/data/delete -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.delete) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | tuples_filter | object |filter to delete relational tuples. Contains **entity**, **relation** and **subject**. -| [x] | attribute_filter | object | filter to delete attributes. Contains **entity** and **attributes**. -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | relation of the given entity | -| [x] | attribute | string array | attributes to be deleted | -| [ ] | subject | object | the user or user set. It contains type and id of the subject. || - - - - -```go -rr, err: = client.Data.Delete(context.Background(), & v1.DataDeleteRequest { - TenantId: "t1", - Metadata: &v1.DataDeleteRequestMetadata { - SnapToken: "" - }, - TupleFilter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.delete({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - tupleFilter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tupleFilter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - }, -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/api-overview/data/read-attributes.md b/docs/docs/api-overview/data/read-attributes.md deleted file mode 100644 index 86b4af1b..00000000 --- a/docs/docs/api-overview/data/read-attributes.md +++ /dev/null @@ -1,95 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Attributes - -Read API allows for directly querying the stored graph data to display and filter stored attributes. - -## Request -```javascript -POST /v1/tenants/{tenant_id}/data/attributes/read -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.attributes.read) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | attributes | string array | attributes of the given entity | - - - - - -```go -rr, err: = client.Data.ReadAttributes(context.Background(), & v1.Data.AttributeReadRequest { - TenantId: "t1", - Metadata: &v1.Data.AttributeReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.AttributeFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Attributes: []string {"private"}, -}) -``` - - - - - -```javascript -client.data.readAttributes({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/data/read-relationships.md b/docs/docs/api-overview/data/read-relationships.md deleted file mode 100644 index f59a3d57..00000000 --- a/docs/docs/api-overview/data/read-relationships.md +++ /dev/null @@ -1,106 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -## Request -```javascript -POST /v1/tenants/{tenant_id}/data/relationships/read -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.relationships.read) - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Data.ReadRelationships(context.Background(), & v1.Data.RelationshipReadRequest { - TenantId: "t1", - Metadata: &v1.Data.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.readRelationships({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/data/run-bundle.md b/docs/docs/api-overview/data/run-bundle.md deleted file mode 100644 index a86d53ea..00000000 --- a/docs/docs/api-overview/data/run-bundle.md +++ /dev/null @@ -1,75 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Run Bundle [Beta] - -The "Run Bundle" API provides a straightforward way to execute predefined bundles within your application's tenant -environment. By sending a POST request to this endpoint, you can activate specific functionalities or processes -encapsulated in a bundle. - -## Request - -```javascript - POST /v1/tenants/{tenant_id}/data/run-bundle -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/bundle.run) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | -| [ ] | arguments | map | parameters for the bundle in key-value format. | - - - - -```go -rr, err: = client.Data.RunBundle(context.Background(), &v1.BundleRunRequest{ - TenantId: "t1", - Name: "organization_created", - Arguments: map[string]string{ - "creatorID": "564", - "organizationID": "789", - }, -}) -``` - - - - - -```javascript -client.data.runBundle({ - tenantId: "t1", - name: "organization_created", - arguments: { - creatorID: "564", - organizationID: "789", - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/run-bundle' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", - "arguments": { - "creatorID": "564", - "organizationID": "789", - } -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/permission/_category_.json b/docs/docs/api-overview/permission/_category_.json deleted file mode 100644 index e810c587..00000000 --- a/docs/docs/api-overview/permission/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Permission Service", - "position": 2, - "collapsed": true -} diff --git a/docs/docs/api-overview/permission/check-api.md b/docs/docs/api-overview/permission/check-api.md deleted file mode 100644 index c6bfed58..00000000 --- a/docs/docs/api-overview/permission/check-api.md +++ /dev/null @@ -1,197 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control - -In Permify, you can perform two different types access checks, - -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **subject based** authorization checks, in form of `Which resources can user U edit ?` - -In this section we'll look at the resource based check request of Permify. You can find subject based access checks in [Entity (Data) Filtering] section. - -[Entity (Data) Filtering]: ../lookup-entity - -## Request - -**Path:** -```javascript -POST /v1/permissions/check -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.check) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "", - SchemaVersion: "", - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "can": "RESULT_ALLOWED", - "remaining_depth": 0 -} -``` - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. - -## How Access Decisions Evaluated? - -Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. - -In high level, access of an subject related with the relationships or attributes created between the subject and the resource. You can define this data in Permify Schema then create and store them as relational tuples and attributes, which is basically forms your authorization data. - -Permify Engine to compute access decision in 2 steps, -1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. -2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. - -Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. - -[relational tuples]: ../../getting-started/sync-data.md -[Permify Schema]: ../../getting-started/modeling.md - -When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity document { - - // represents documents parent organization - relation parent @organization - - // represents owner of this document - relation owner @user - - // permissions - action edit = parent.admin or owner - action delete = owner -} -``` - -Which has a directed graph as follows: - -![relational-tuples](https://github.com/Permify/permify/assets/39353278/cec9936c-f907-42c0-a419-032ebb45454e) - -As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: - -**Q1:** Get the owners of the `document:12`. - -**Q2:** Get admins of the organization where `document:12` belongs to. - -Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. - -Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. - -## Latency & Performance - -With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. - -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../reference/cache.md) - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - diff --git a/docs/docs/api-overview/permission/expand-api.md b/docs/docs/api-overview/permission/expand-api.md deleted file mode 100644 index 998955c1..00000000 --- a/docs/docs/api-overview/permission/expand-api.md +++ /dev/null @@ -1,319 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API - -Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission - -Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. - -:::caution When To Use ? -Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. - -It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. -::: - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.expand) - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{ - TenantId: "t1", - Metadata: &v1.PermissionExpandRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -}' -``` - - - -## Example Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** -```javascript -POST /v1/tenants/{tenant_id}/permissions/expand -``` - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. | -| [x] | permission | string | - | The permission the user wants to perform on the resource | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- diff --git a/docs/docs/api-overview/permission/lookup-entity.md b/docs/docs/api-overview/permission/lookup-entity.md deleted file mode 100644 index b87d77a4..00000000 --- a/docs/docs/api-overview/permission/lookup-entity.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -title: Entity (Data) Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Entity Filtering - -Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So, we provide 2 separate endpoints for data filtering check request, - -- [Lookup Entity](#lookup-entity) -- [Lookup Entity (Streaming)](#lookup-entity-streaming) - -## Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. - -**Path** -```javascript - POST /v1/permissions/lookup-entity -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntity) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens) | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - EntityType: "document", - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - } -}) -``` - - - - -```javascript -client.permission.lookupEntity({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity_type: "document", - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response.entity_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type":"user", - "id":"1" - } -}' -``` - - - -## How Lookup Operations Evaluated - -We explicitly designed reverse lookup to be more performant with changing its evaluation pattern. We do not query all the documents in bulk to get response, instead of this Permify first finds the necessary relations with given subject and the permission/action in the API call. Then query these relations with the subject id this way we reduce lots of additional queries. - -To give an example, - -```jsx -entity user {} - -entity organization { - relation admin @user -} - -entity container { - relation parent @organization - relation container_admin @user - action admin = parent.admin or container_admin -} - -entity document { - relation container @container - relation viewer @user - relation owner @user - action view = viewer or owner or container.admin -} -``` - -Lets say we called (reverse) lookup API to find the documents that user:1 can view. Permify first finds the relations that linked with view action, these are - -- `document#viewer` -- `document#owner` -- `organization#admin` -- `container#``container_admin` - -Then queries each of them with `user:1.` - -## Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -**Path** -```javascript - POST /v1/permissions/lookup-entity-stream -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntityStream) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md) | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest { - Metadata: &v1.PermissionLookupEntityRequestMetadata { - SnapToken: "", - SchemaVersion: "" - Depth: 50, - }, - EntityType: "document", - Permission: "view", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) - -// handle stream response -for { - res, err: = str.Recv() - - if err == io.EOF { - break - } - - // res.EntityId -} -``` - - - - -```javascript -const permify = require("@permify/permify-node"); -const {PermissionLookupEntityStreamResponse} = require("@permify/permify-node/dist/src/grpc/generated/base/v1/service"); - -function main() { - const client = new permify.grpc.newClient({ - endpoint: "localhost:3478", - }) - - let res = client.permission.lookupEntityStream({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entityType: "document", - permission: "view", - subject: { - type: "user", - id: "1" - } - }) - - handle(res) -} - -async function handle(res: AsyncIterable) { - for await (const response of res) { - // response.entityId - } -} -``` - - - \ No newline at end of file diff --git a/docs/docs/api-overview/permission/lookup-subject.md b/docs/docs/api-overview/permission/lookup-subject.md deleted file mode 100644 index 0d0e17ff..00000000 --- a/docs/docs/api-overview/permission/lookup-subject.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Subject Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Filtering - -Lookup Subject endpoint lets you ask questions in form of **โ€œWhich subjects can do action Y on entity:X?โ€**. As a response of this youโ€™ll get a subject results in a format of string array. - -So, we provide 1 endpoint for subject filtering request, - -- [/v1/permissions/lookup-subject](#lookup-subject) - -## Lookup Subject - -In this endpoint you'll get directly the IDs' of the subjects that are authorized in an array. - -**POST** -```javascript -/v1/permissions/lookup-subject -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupSubject) - -| Required | Argument | Type | Default | Description | -|----------|---------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1 | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject_reference | object | - | the subject or subject reference who wants to take the action. It contains type and relation of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest { - TenantId: "t1", - Metadata: &v1.PermissionLookupSubjectRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - Depth: 20, - }, - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Permission: "edit", - SubjectReference: &v1.RelationReference{ - Type: "user", - Relation: "", - } -}) -``` - - - - -```javascript -client.permission.lookupSubject({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - depth: 20, - }, - Entity: { - Type: "document", - Id: "1", - }, - permission: "edit", - subject_reference: { - type: "user", - relation: "" - } -}).then((response) => { - console.log(response.subject_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "" - "depth": 20, - }, - "entity": { - type: "document", - id: "1' - }, - "permission": "edit", - "subject_reference": { - "type": "user", - "relation": "" - } -}' -``` - - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/api-overview/permission/subject-permission.md b/docs/docs/api-overview/permission/subject-permission.md deleted file mode 100644 index 8157f3dc..00000000 --- a/docs/docs/api-overview/permission/subject-permission.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Subject Permission List ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Permission List - -The Subject Permission List endpoint allows you to inquire in the form of **โ€œWhich permissions user:x can perform on entity:y?โ€**. In response, you'll receive a list of permissions specific to the user for the given entity, returned in the format of a map. - -So, we provide 1 endpoint for subject permission list, - -- [/v1/permissions/subject-permission](#subject-permission) - -In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as "sample-permission" -> "ALLOWED". This represents the permissions and their associated states in a key-value pair format. - -## Request - -**Path:** -```javascript -POST /v1/permissions/subject-permission -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.subjectPermission) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | only_permission | bool | false | By default, the endpoint returns both permissions and relations associated with the user and entity. However, when the "only_permission" parameter is set to true, it returns only the permissions. | | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest { - TenantId: "t1", - Metadata: &v1.PermissionSubjectPermissionRequestMetadata { - SnapToken: "", - SchemaVersion: "", - OnlyPermission: false, - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) -``` - - - - -```javascript -client.permission.subjectPermission({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - onlyPermission: true, - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response); -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "only_permission": true, - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "results": [ - { - "key": "delete", - "value": "RESULT_ALLOWED" - }, - { - "key": "edit", - "value": "RESULT_ALLOWED" - } - ] -} -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/docs/api-overview/schema/_category_.json b/docs/docs/api-overview/schema/_category_.json deleted file mode 100644 index 8fd1e959..00000000 --- a/docs/docs/api-overview/schema/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Schema Service", - "position": 1, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/docs/api-overview/schema/list-schema.md b/docs/docs/api-overview/schema/list-schema.md deleted file mode 100644 index 6a3248e2..00000000 --- a/docs/docs/api-overview/schema/list-schema.md +++ /dev/null @@ -1,54 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# List Schema - -Models written to Permify using the [write schema API](./write-schema.md) can be listed using this API with the timestamps at which the models were created. - -Request needs to be made to the API endpoint **/v1/schemas/list** to list all the models. - -## Request - -```javascript -POST /v1/tenants/{tenant_id}/schemas/list -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Schema/schemas.list) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | page_size | string | - | Number of schema versions to be fetched | -| [ ] | continuous_token | string | - | Continuation token for subsequent pages to be fetched | - - - - -```go -sr, err: = client.Schema.List(context.Background(), &v1.SchemaListRequest { - TenantId: "t1", - PageSize: "10", - ContinuousToken: "", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "page_size": "10", - "continuous_token": "" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/list" - -**Example Request on Postman:** -![permify-schema](https://github.com/Permify/permify/assets/30985448/aa73c993-e808-496e-bebc-f91ced3a3399) - diff --git a/docs/docs/api-overview/schema/read-schema.md b/docs/docs/api-overview/schema/read-schema.md deleted file mode 100644 index 7a0587d3..00000000 --- a/docs/docs/api-overview/schema/read-schema.md +++ /dev/null @@ -1,55 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Schema - -When a model is written to Permify using the [write schema API](./write-schema.md) a schema version will be returned by the API. That schema version can be used to inspect the schema. - -Permify Schema needed to be send to API endpoint **/v1/schemas/read** for configuration of your authorization model on Permify API. - -## Request - -```javascript -POST /v1/tenants/{tenant_id}/schemas/read -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Schema/schemas.read) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | schema_version | string | - | Permify Schema version to read| - - - - -```go -sr, err: = client.Schema.Read(context.Background(), &v1.SchemaReadRequest { - TenantId: "t1", - Metadata: &v1.SchemaReadRequestMetadata{ - SchemaVersion: "cnbe6se5fmal18gpc66g", - }, -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "cnbe6se5fmal18gpc66g" - } -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/read"** - -**Example Request on Postman:** - -![permify-schema](https://github.com/Permify/permify/assets/30985448/a6944e3d-6a58-4489-b16f-da2fdf5f60f2) diff --git a/docs/docs/api-overview/schema/write-schema.md b/docs/docs/api-overview/schema/write-schema.md deleted file mode 100644 index 4ae10d83..00000000 --- a/docs/docs/api-overview/schema/write-schema.md +++ /dev/null @@ -1,97 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema - -Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. - -We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: - -Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -## Request - -```javascript -POST /v1/tenants/{tenant_id}/schemas/write -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Schema/schemas.write) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - TenantId: "t1", - Schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - `, -}) -``` - - - - -```javascript -client.schema.write({ - tenantId: "t1", - schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - ` -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "schema": "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/write"** - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - - -## Suggested Workflow For Schema Changes - -It's expected that your initial schema will eventually change as your product or system evolves - -As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. - -A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database unless you delete them with the [delete API](../data/delete-data.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. - -We recommend applying the following pattern to safely handle schema changes: - -- Set up a central git repository that includes the schema. -- Teams or individuals who need to update the schema should add new permissions or relations to this repository. -- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. -- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. - -## Need any help ? - -Depending on the frequency and the type of the changes that you made on the schemas, this method may not be optimal for you - In such cases, we are open to exploring alternative solutions. Please feel free to [schedule a call with one of our engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/api-overview/tenancy/_category_.json b/docs/docs/api-overview/tenancy/_category_.json deleted file mode 100644 index e1ebce3c..00000000 --- a/docs/docs/api-overview/tenancy/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Tenancy Service", - "position": 5, - "collapsed": true -} diff --git a/docs/docs/api-overview/tenancy/create-tenant.md b/docs/docs/api-overview/tenancy/create-tenant.md deleted file mode 100644 index 20a35d7e..00000000 --- a/docs/docs/api-overview/tenancy/create-tenant.md +++ /dev/null @@ -1,59 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Tenant - -Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. - -:::caution -We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. -::: - -## Request - -```javascript -POST /v1/tenants/create -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.create) - - - - -```go -rr, err: = client.Tenancy.Create(context.Background(), & v1.TenantCreateRequest { - Id: "" - Name: "" -}) -``` - - - - - -```javascript -client.tenancy.create({ - id: "", - name: "" -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'http://localhost:3476/v1/tenants/create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "id": "", - "name": "" -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/api-overview/tenancy/delete-tenant.md b/docs/docs/api-overview/tenancy/delete-tenant.md deleted file mode 100644 index aca60ef7..00000000 --- a/docs/docs/api-overview/tenancy/delete-tenant.md +++ /dev/null @@ -1,47 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Tenant - -You can delete a tenant with following API. - -## Request -```javascript -DELETE /v1/tenants/{id} -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.delete) - - - - -```go -rr, err: = client.Tenancy.Delete(context.Background(), & v1.TenantDeleteRequest { - Id: "" -}) -``` - - - - - -```javascript -client.tenancy.delete({ - id: "", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/api-overview/watch/_category_.json b/docs/docs/api-overview/watch/_category_.json deleted file mode 100644 index bb0c647b..00000000 --- a/docs/docs/api-overview/watch/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Watch Service", - "position": 6, - "collapsed": true -} diff --git a/docs/docs/api-overview/watch/watch-changes.md b/docs/docs/api-overview/watch/watch-changes.md deleted file mode 100644 index aabc7416..00000000 --- a/docs/docs/api-overview/watch/watch-changes.md +++ /dev/null @@ -1,145 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Watch - -The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples. - -The Watch API exclusively supports gRPC and works with PostgreSQL, given the track_commit_timestamp option is enabled. Please note, it doesn't support in-memory databases or HTTP communication. - -# Requirements - -- PostgreSQL database set up with track_commit_timestamp option enabled - -## Enabling track_commit_timestamp on PostgreSQL - -To ensure data consistency and synchronization between your application and Permify, enable track_commit_timestamp on -your PostgreSQL server. This can be done by executing the following options in your PostgreSQL: - -### Option 1: SQL Command - -1. Open your PostgreSQL command line interface. -2. Execute the following command: - - ```sql - ALTER SYSTEM SET track_commit_timestamp = ON; - ``` - -3. Reload the configuration with the following command: - - ```sql - SELECT pg_reload_conf(); - ``` - -### Option 2: Editing postgresql.conf - -1. Find and open the postgresql.conf file in a text editor. Its location depends on your PostgreSQL installation. Common - locations are: - - Debian-based systems: /etc/postgresql/[version]/main/postgresql.conf - - Red Hat-based systems: /var/lib/pgsql/data/postgresql.conf - -2. Add or modify the following line in the postgresql.conf file: - ``` - track_commit_timestamp = on - ``` - -3. Save and close the postgresql.conf file. -4. Reload the PostgreSQL configuration for the changes to take effect. This can be done via the PostgreSQL console: - ```sql - SELECT pg_reload_conf(); - ``` - - Or if you have command line access, use: - - ```bash - sudo service postgresql reload - ``` - -Please ensure you have the necessary permissions to execute these commands or modify the postgresql.conf file. Also, remember that changes in the postgresql.conf file will persist across restarts, while the SQL method may need to be reapplied depending on your PostgreSQL version and setup. - -:::info -Important Configuration Requirement: To use the Watch API, it must be enabled in your configuration file. Add or modify the following lines: - -```yaml -service: - watch: - enabled: true -``` - -::: - -## Request - -**Path:** -```javascript -POST /v1/watch/watch -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Watch/watch.watch) - -| Required | Argument | Type | Default | Description | -|----------|------------|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | snap_token | string | - | specifies the starting point for broadcasting changes. If a snap_token is provided, all changes following that specific snapshot will be broadcasted. If a snap_token is not provided, the Watch API will broadcast all changes that occur after the Watch API is initiated., see more details on [Snap Tokens](../../../reference/snap-tokens). | - - -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```go) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```javascript) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -## Response - -```json -{ - "changes": { - "tuple_changes": [ - { - "operation": "OPERATION_CREATE", - "tuple": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "56", - "relation": "" - } - } - } - ], - "snap_token": "MgMAAAAAAAA=" - } -} -``` - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or -have any questions about this -example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/docs/comparision.md b/docs/docs/comparision.md deleted file mode 100644 index 75fb39c4..00000000 --- a/docs/docs/comparision.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: comparison -title: Comparison Between Other Zanzibar implementations ---- - -:::caution Note -This comparison table shows the differentiation between authorization solutions based or inspired by Google Zanzibar paper. If you use any of these solutions and feel the information could be improved, feel free to reach out. -::: - -## General Aspects - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|---------------------------------|------------|------------|-----------|-----------| -| **Zanzibar Paper Faithfulness** | Medium | High | High | High | -| **Scalability** | Medium | Medium | High | High | -| **Consistency & Cache** | No Zookies | No Zookies | Supported | Supported | -| **Dev UX** | Average | Average | High | High | - -## Feature Set - -- โœ…  Supported, and ready to use with no added configuration or code -- ๐ŸŸก  Limited support and requires extra user-code to implement. -- โ›”  Not officially supported or documented. - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|--------------------------|----------|---------|---------|---------| -| **Check API** | โœ… | โœ… | โœ… | โœ… | -| **Write API** | โœ… | โœ… | โœ… | โœ… | -| **Read API** | โœ… | โœ… | โœ… | โœ… | -| **Expand API** | โœ… | โœ… | โœ… | โœ… | -| **Watch API** | โœ… | โœ… | โœ… | โœ… | -| **RBAC** | โœ… | โœ… | โœ… | โœ… | -| **ReBAC** | โœ… | โœ… | โœ… | โœ… | -| **ABAC** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Data Filtering** | โ›” | โœ… | โœ… | โœ… | -| **Multi Tenancy** | โ›” | โœ… | โ›” | โœ… | -| **Testing & Validation** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Logging & Tracing** | ๐ŸŸก | โœ… | โœ… | โœ… | diff --git a/docs/docs/examples.md b/docs/docs/examples.md deleted file mode 100644 index ecc4d10a..00000000 --- a/docs/docs/examples.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: examples -title: Real World Examples -sidebar_label: Real World Examples -slug: /getting-started/examples ---- - -* [Google Docs]: Explore how users can gain direct access to a document through **organizational roles** or through **inherited/nested permissions**. -* [Facebook Groups]: Explore how users can perform various actions based on the **roles and permissions within the groups** they belong. -* [Notion]: Explore how **one global entity (workspace) can manage access rights** in the child entities that belong to it. -* [Instagram]: Explore how **public/private attributes** play role in granting access to specific users. -* [Mercury]: Explore how **attributes and rules interact within the hierarchical relationships**. - -[Google Docs]:./getting-started/examples/google-docs.md -[Facebook Groups]:./getting-started/examples/facebook-groups.md -[Notion]:./getting-started/examples/notion.md -[Instagram]:./getting-started/examples/instagram.md -[Mercury]:./getting-started/examples/mercury.md \ No newline at end of file diff --git a/docs/docs/getting-started/_category_.json b/docs/docs/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/docs/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/docs/getting-started/examples/_category_.json b/docs/docs/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f801..00000000 --- a/docs/docs/getting-started/examples/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Example Permission Structures", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/docs/getting-started/examples/facebook-groups.md b/docs/docs/getting-started/examples/facebook-groups.md deleted file mode 100644 index 1b918630..00000000 --- a/docs/docs/getting-started/examples/facebook-groups.md +++ /dev/null @@ -1,546 +0,0 @@ -# Facebook Groups - -This example demonstrate the authorization structure for Facebook groups, which enables users to perform various actions based on their roles and permissions within the group. - -### Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) - -```perm -// Represents a user -entity user {} - -// Represents a Facebook group -entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator -} - -// Represents a post in a Facebook group -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner -} - -// Represents a comment like on a post in a Facebook group -entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member -} - -// Definition of poll entity -entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin -} - -// Definition of file entity -entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin -} - -// Definition of event entity -entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member -} -``` - -## Brief Examination of the Model - -The model defines several entities and relations, as well as actions and permissions that can be taken by users within the group. Let's examine them shortly; - -### Entities & Relations - -* **`user`** entity represents a user in the Facebook. - -* **`group`** entity represents the Facebook group, and it has several relations including member, admin, and moderator to represent the members, admins, and moderators of the group. Additionally, there are relations to represent the posts and comments in the group. - -* **`post`** entity represents a post in the Facebook group, and it has relations to represent the owner of the post and the group that the post belongs to. - -* **`comment`** entity represents a comment on a post in the Facebook group, and it has relations to represent the owner of the comment, the post that the comment belongs to, and the comment itself. - -* **`like`** entity represents a like on a post in the Facebook group, and it has relations to represent the owner of the like and the post that the like belongs to. - -* **`poll`** entity represents a poll in the Facebook group, and it has relations to represent the owner of the poll and the group that the poll belongs to. - -* **`file`** entity represents a file in the Facebook group, and it has relations to represent the owner of the file and the group that the file belongs to. - -* **`event`** entity represents an event in the Facebook group, and it has relations to represent the owner of the event and the group that the event belongs to. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. - -For example, the `create_group` action can only be performed by a `member`, as follows: - -#### Creating a group permission - -```perm -entity group { - - // Relation to represent the members of the group - relation member @user - - .. - - // Create group permission - action create_group = member - - .. - .. -} -``` - -Another example would be given from the `edit_post` action in the post entity, which specifies the permissions required to edit a post in a Facebook group. - -#### Editing a post permission - -```perm -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - .. - - action edit_post = owner or group.admin - - .. - .. -} -``` - -An **owner** of a post can always edit their own post. In addition, members who are defined as **admin** of the group - which the post belongs to - can also edit the post. - -Since most entities are deeply nested together, we also have multiple hierarchical permissions. - -#### Nested Hierarchies - -For example, we can define a permission "view_comment" if only user is owner of that comment or user is a member of the group which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -//group relationships -group:1#member@user:1 -group:1#admin@user:2 -group:2#moderator@user:3 -group:2#member@user:4 -group:1#member@user:5 - -//post relationships -post:1#owner@user:1 -post:1#group@group:1 -post:2#owner@user:4 -post:2#group@group:1 - -//comment relationships -comment:1#owner@user:2 -comment:1#post@post:1 -comment:2#owner@user:5 -comment:2#post@post:2 - -//like relationships -like:1#owner@user:3 -like:1#post@post:1 -like:2#owner@user:4 -like:2#post@post:2 - -//poll relationships -poll:1#owner@user:2 -poll:1#group@group:1 -poll:2#owner@user:5 -poll:2#group@group:1 - -//like relationships -file:1#owner@user:1 -file:1#group@group:1 - -//event relationships -event:1#owner@user:3 -event:1#group@group:1 -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:4 RSVP_to_event event:1 ? -

- -```perm - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - - .. - .. - - action RSVP_to_event = owner or group.member - } -``` - -According to what we have defined for the **'RSVP_to_event'** action, users who are either the owner of `event:1` or a member of the group that belongs to `event:1` can grant access to RSVP to the event. - -According to the relation tuples we created, `user:4` is not the **owner** of the event. Furthermore, when we check whether `user:4` is a **member** of the only group (`group:1`) that `event:1` is part of (`event:1#group@group:1`), we see that there is no **member** relation for `user:4` in that group. - -Therefore, the `user:4 RSVP_to_event event:1` check request should yield a **'false'** response. - -

-
- -
can user:5 view_comment comment:1 ? -

- -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -According to the relation tuples we created, `user:5` is not the **owner** of the comment. But member of the `group:1` and thats grant `user:5` (`group:1#member@user:5`) access to perform view the comment:1. In particularly, `comment:1` is part of the `post:1` (`comment:1#post@post:1`) and `post:1` is part of the group:1 (`post:1#group@group:1`). And from the action definition on above model group:1 members can view the `comment:1`. - -Therefore, the `user:5 view_comment comment:1` check request should yield a **'true'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator - } - - entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member - } - - entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner - } - - entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member - } - - entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin - } - - entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin - } - - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member - } - -relationships: - - group:1#member@user:1 - - group:1#admin@user:2 - - group:2#moderator@user:3 - - group:2#member@user:4 - - group:1#member@user:5 - - post:1#owner@user:1 - - post:1#group@group:1 - - post:2#owner@user:4 - - post:2#group@group:1 - - comment:1#owner@user:2 - - comment:1#post@post:1 - - comment:2#owner@user:5 - - comment:2#post@post:2 - - like:1#owner@user:3 - - like:1#post@post:1 - - like:2#owner@user:4 - - like:2#post@post:2 - - poll:1#owner@user:2 - - poll:1#group@group:1 - - poll:2#owner@user:5 - - poll:2#group@group:1 - - file:1#owner@user:1 - - file:1#group@group:1 - - event:1#owner@user:3 - - event:1#group@group:1 - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "event:1" - subject: "user:4" - assertions: - RSVP_to_event : false - - entity: "comment:1" - subject: "user:5" - assertions: - view_comment : true -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233152003-1fbaf2af-d208-4290-af1f-359870b0de49.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/getting-started/examples/google-docs.md b/docs/docs/getting-started/examples/google-docs.md deleted file mode 100644 index 3f14e1f7..00000000 --- a/docs/docs/getting-started/examples/google-docs.md +++ /dev/null @@ -1,344 +0,0 @@ -# Google Docs Simplified - -This example models a simplified version of Google Docs style permission system where users can be granted direct access to a document, or access via organizations and nested groups. - -### Schema | [Open in playground](https://play.permify.co/?s=iuRic3nR1HeZJcFyRNKPo) - -```perm -entity user {} - -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} - -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} - -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -## Breakdown of the Model - -### User - -```perm -entity user {} -``` - -Represents a user who can be granted permission to access a documents directly, or through their membership in a group or organization. - -### Document - -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -Represents a document that users can be granted permission to access. The document entity has two relationships: - -#### Relations - -**org:** Represents organization that document belongs to. - -**manager:** A relationship between users who are authorized to manage the document. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group member and manager relations. - -**viewer:** A relationship between users who are authorized to view the document. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager relations. - -The document entity has two actions defined: - -#### Actions - -**manage:**: An action that can be performed by users who are authorized to manage the document, as determined by the manager relationship. - -**view:** An action that can be performed by users who are authorized to view the document, as determined by the viewer and manager relationships. - -### Group - -```perm -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} -``` - -Represents a group of users who can be granted permission to access a document. The group entity has two relationships: - -#### Relations - -**manager:** A relationship between users who are authorized to manage the group. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are members of the group. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager. - -The group entity has one action defined: - -### Organization - -```perm -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} -``` - -Represents an organization that can contain groups, users, and documents. The organization entity has several relationships: - -#### Relations - -**group:** A relationship between the organization and its groups. This relationship is defined by the `@group` annotation on the end corresponding to the group entity. - -**document:** A relationship between the organization and its document. This relationship is defined by the `@document` annotation on the end corresponding to the group entity. - -**administrator:** A relationship between users who are authorized to manage the organization. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are directly members of the organization. This relationship is defined by the `@user` annotation on the end corresponding to the user entity. - -The organization entity has two permissions defined: - -#### Permissions - -**admin:** An permission that can be performed by users who are authorized to manage the organization, as determined by the administrator relationship. - -**member:** An permission that can be performed by users who are directly members of the organization, or who have administrator relationship, or who are members of groups that are part of the organization, - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different groups -group:tech#manager@user:ashley -group:tech#direct_member@user:david -group:marketing#manager@user:john -group:marketing#direct_member@user:jenny -group:hr#manager@user:josh -group:hr#direct_member@user:joe - -// Assign groups to other groups -group:tech#direct_member@group:marketing#direct_member -group:tech#direct_member@group:hr#direct_member - -// Connect groups to organization -organization:acme#group@group:tech -organization:acme#group@group:marketing -organization:acme#group@group:hr - -// Add some documents under the organization -organization:acme#document@document:product_database -organization:acme#document@document:marketing_materials -organization:acme#document@document:hr_documents - -// Assign a user and members of a group as administrators for the organization -organization:acme#administrator@group:tech#manager -organization:acme#administrator@user:jenny - -// Set the permissions on some documents -document:product_database#manager@group:tech#manager -document:product_database#viewer@group:tech#direct_member -document:marketing_materials#viewer@group:marketing#direct_member -document:hr_documents#manager@group:hr#manager -document:hr_documents#viewer@group:hr#direct_member -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:ashley edit document:product_database ? -

- -```perm - entity document { - relation org @organization - - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } -``` - -According what we have defined for the edit action managers and admins, of the organization that document belongs, can edit product database. In this context, Permify engine will check does subject `user:ashley` has any direct or indirect manager relation within `document:product_database`. Consecutively it will check does `user:ashley` has admin relation in the Acme Org - `organization:acme#document@document:product_database`. - -Ashley doesn't have any administrative relation in Acme Org but she is the manager in group tech (`group:tech#manager@user:ashley`) and we have defined that manager of group tech is manager of product_database with the tuple (`document:product_database#manager@group:tech#manager`). Therefore, the **user:ashley edit document:product_database** check request should yield **true** response. - -

-
- -
can user:joe view document:hr_documents ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check whether subject `user:joe` has any direct or indirect manager or viewer relation within `document:hr_documents`. Also consecutively it will check does `user:joe` has admin relation in the Acme Org - `organization:acme#document@document:hr_documents`. - -Joe doesn't have administrative role/relation in Acme Org. - -Also he doesn't have have manager relationship in that document or within any entity. - -But he is member in the hr group (`group:hr#member@user:joe`) and we defined hr members have viewer relationship in hr documents (`document:hr_documents#viewer@group:hr#member`). So that, this enforcement should yield **true** response. - -

-
- -
can user:david view document:marketing_materials ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check does subject `user:david` has any direct or indirect manager or viewer relation within `document:marketing_materials`. Also consecutively it will check does `user:david` has admin relation in the Acme Org - `organization:acme#document@document:marketing_materials`. - -Similar Joe and Ashley, David also doesn't have administrative role/relation in Acme Org. - -Also David doesn't have member or manager relationship related with marketing group - `document:marketing_materials`. So that, this enforcement should yield **false** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member - } - - entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager - } - - entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } - -relationships: - - group:tech#manager@user:ashley - - group:tech#direct_member@user:david - - group:marketing#manager@user:john - - group:marketing#direct_member@user:jenny - - group:hr#manager@user:josh - - group:hr#direct_member@user:joe - - - group:tech#direct_member@group:marketing#direct_member - - group:tech#direct_member@group:hr#direct_member - - - organization:acme#group@group:tech - - organization:acme#group@group:marketing - - organization:acme#group@group:hr - - organization:acme#document@document:product_database - - organization:acme#document@document:marketing_materials - - organization:acme#document@document:hr_documents - - organization:acme#administrator@group:tech#manager - - organization:acme#administrator@user:jenny - - - document:product_database#manager@group:tech#manager - - document:product_database#viewer@group:tech#direct_member - - document:marketing_materials#viewer@group:marketing#direct_member - - document:hr_documents#manager@group:hr#manager - - document:hr_documents#viewer@group:hr#direct_member - - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "document:product_database" - subject: "user:ashley" - assertions: - edit: true - - entity: "document:hr_documents" - subject: "user:joe" - assertions: - view: true - - entity: "document:marketing_materials" - subject: "user:david" - assertions: - view: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://github.com/Permify/permify/assets/39353278/85b96987-5932-4805-ac81-89820daad7e9) - -## Need any help ? - -This is the end of modeling Google Docs style permission system. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/getting-started/examples/instagram.md b/docs/docs/getting-started/examples/instagram.md deleted file mode 100644 index 9cd83163..00000000 --- a/docs/docs/getting-started/examples/instagram.md +++ /dev/null @@ -1,376 +0,0 @@ -# Instagram - -This example presents an Instagram Authorization Schema, outlining the intricate relationships between users, accounts, and posts on the platform. It defines user access levels, privacy settings, and interactions, offering insights into how followers, account owners, and post restrictions are managed within the Instagram ecosystem. - -## Schema | [Open in playground](https://play.permify.co/?s=instagram&tab=schema) - -```perm -entity user {} - -entity account { - // users have accounts - relation owner @user - - // accounts can follow other users/accounts. - relation following @user - - // other users/accounts can follow account. - relation follower @user - - // accounts can be private or public. - attribute public boolean - - // users can view an account if they're followers, owners, or if the account is not private. - action view = (owner or follower) or public - -} - -entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - // users can view the posts, if they have access to view the linked accounts. - action view = account.view - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted -} -``` - -## Brief Examination of the Model - -The Instagram Authorization Schema models the relationships between users, accounts, and posts in the Instagram platform. - -Users can own accounts, follow other accounts, and be followed by other users. Accounts can have public or private settings, and access to view an account is determined by ownership, followers, and privacy settings. Posts are associated with accounts and can have restricted comments and likes based on account privacy. - -### Entities & Relations - -- **`User`**: Represents a user on the Instagram platform. - -- **`Account`**: Represents a user account on Instagram. Accounts have owners, followers, and can follow other accounts. - -- **`Post`**: Represents a post on Instagram. Posts are linked to accounts and can have restricted comments and likes. - -### Permissions - -Users can view an account if they are the owner, a follower, or if the account is public. -Users can comment and like posts if they have access to view the linked account and the post is unrestricted. - -### Relationships and Attributes - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Relationships -// Users, Accounts and Posts: - account:1#owner@user:kevin - account:2#owner@user:george - account:1#following@user:george - account:2#follower@user:kevin - post:1#account@account:1 - post:2#account@account:2 - -// Attributes -// Accounts and Posts: - account:1$public|boolean:true - account:2$public|boolean:false - post:1$restricted|boolean:false - post:2$restricted|boolean:true -``` - -## Test & Validation - -To validate our authorization logic, let's run some tests on different scenarios using the Instagram Authorization Schema. - -### Test 1: Checking Account Viewing Permissions - -
- - Can user:kevin view account:1? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:kevin` is the owner of `account:1`. Hence, `user:kevin` should be able to view `account:1`. The expected result is `'true'`. - -

-
- -
- - Can user:kevin view account:2? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:kevin` follows `account:2`. Hence, `user:kevin` should be able to view `account:2` because he is a follower. The expected result is `'true'`. - -

-
- -
- - Can user:george view account:1? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:george` can view `account:1`, because the account is public. Hence, `user:george` should be able to view `account:1`. The expected result is `'true'`. - -

-
- -
- - Can user:george view account:2? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:george` is the owner of `account:2`. Hence, `user:george` should be able to view `account:2`. The expected result is `'true'`. - -

-
- -### Test 2: Checking Post Viewing Permissions - -
Can user:george view post:1? - -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:1` is linked with `account:1`, and it does not have restricted access. Also, `user:george` is following `account:1`. Hence, `user:george` should be able to view `post:1`. The expected result is `'true'`. - -

-
- -
Can user:kevin view post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it has restricted access. Also, `user:george` is not following `account:1`. Hence, `user:kevin` should not be able to view `post:2`. The expected result is `'false'`. - -

-
- -
Can user:george view post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it is restricted access. Also, `user:george` can view his own `post:2`. The expected result is `'true'`. - -

-
- -### Test 3: Checking Post Commenting Permissions - -
Can user:george comment post:1? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action comment = account.following not restricted -} -``` - -According to the schema, `post:1` is linked with `account:1`, and it is not restricted. Also, `user:george` can comment on `post:1`. The expected result is `'true'`. - -

-
- -
Can user:kevin comment post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action comment = account.following not restricted -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it is restricted. `user:kevin` cannot comment on `post:2`. The expected result is `'false'`. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: |- - entity user {} - - entity account { - // users have accounts - relation owner @user - - // accounts can follow other users/accounts. - relation following @user - - // other users/accounts can follow account. - relation follower @user - - // accounts can be private or public. - attribute public boolean - - // users can view an account if they're followers, owners, or if the account is not private. - action view = (owner or follower) or public - - } - - entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - // users can view the posts, if they have access to view the linked accounts. - action view = account.view - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted - } -relationships: - - account:1#owner@user:kevin - - account:2#owner@user:george - - account:1#following@user:george - - account:2#follower@user:kevin - - post:1#account@account:1 - - post:2#account@account:2 -attributes: - - account:1$public|boolean:true - - account:2$public|boolean:false - - post:1$restricted|boolean:false - - post:2$restricted|boolean:true -scenarios: - - name: Account Viewing Permissions - description: Evaluate account viewing permissions for 'kevin' and 'george'. - checks: - - entity: account:1 - subject: user:kevin - assertions: - view: true - - entity: account:2 - subject: user:kevin - assertions: - view: true - - entity: account:1 - subject: user:george - assertions: - view: true - - entity: account:2 - subject: user:george - assertions: - view: true - - name: Post Viewing Permissions - description: Determine post viewing permissions for 'kevin' and 'george'. - checks: - - entity: post:1 - subject: user:george - assertions: - view: true - - entity: post:2 - subject: user:kevin - assertions: - view: true - - entity: post:2 - subject: user:george - assertions: - view: true - - name: Post Commenting Permissions - description: Evaluate post commenting permissions for 'kevin' and 'george'. - checks: - - entity: post:1 - subject: user:george - assertions: - comment: true - - entity: post:2 - subject: user:kevin - assertions: - comment: false -``` - -## Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve` - -![Running Permify](https://github.com/Permify/permify/assets/48759364/eb4cde6e-09bf-4e38-88bc-251a811f9c4f) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://github.com/Permify/permify/assets/48759364/2fb9a1ab-40d4-48e0-857a-3d59de575134) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. diff --git a/docs/docs/getting-started/testing.md b/docs/docs/getting-started/testing.md deleted file mode 100644 index 977fcf2d..00000000 --- a/docs/docs/getting-started/testing.md +++ /dev/null @@ -1,279 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Testing & Validation - -Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. - -Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. - -We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Adding Validate Action To Your Workflow - -After adding [permify-validate-action] to your Github Action workflow, you need to define the schema validation yaml file as, - -- **With local file:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "test.yaml" -``` - -- **With external url:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" -``` - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Schema Validation File - -Below you can examine an example schema validation yaml file. It consists 3 parts; -- `schema` which is the authorization model you want to test, -- `relationships` sample data to test your model, -- `scenarios` to test access check queries within created scenarios. - -### Defining the Schema: - -You can define the `schema` in the YAML file in one of two ways: - -1. **Directly in the File:** Define the schema directly within the YAML file. - - ```yaml - schema: >- - entity user {} - entity organization { - ... - } - -2. **Via URL or File Path:** Specify a URL or a file path to an external schema file. - **Example with URL:** - - ```yaml - schema: https://example.com/path/to/schema.txt - ``` - - **Example with File Path:** - ```yaml - schema: /path/to/your/schema/file.txt - ``` - -Here is an example Schema Validation file, - -```yaml -schema: >- - entity user {} - - entity organization { - - relation admin @user - relation member @user - - action create_repository = (admin or member) - action delete = admin - } - - entity repository { - - relation owner @user @organization#member - relation parent @organization - - action push = owner - action read = (owner and (parent.admin and parent.member)) - action delete = (parent.member and (parent.admin or owner)) - action edit = parent.member not owner - } - -relationships: - - "organization:1#admin@user:1" - - "organization:1#member@user:1" - - "repository:1#owner@user:1" - - "repository:2#owner@user:2" - - "repository:2#owner@user:3" - - "repository:1#parent@organization:1#..." - - "organization:1#member@user:43" - - "repository:1#owner@user:43" - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - assertions: - push : true - owner : true - - entity: "repository:2" - subject: "user:1" - assertions: - push : false - - entity: "repository:3" - subject: "user:1" - context: - - "repository:3#owner@user:1" - assertions: - push : true - - entity: "repository:1" - subject: "user:43" - assertions: - edit : false - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - - "repository:3#owner@user:1" - - "repository:4#owner@user:1" - - "repository:5#owner@user:1" - assertions: - push : ["1", "3", "4", "5"] - edit : [] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - - "organization:1#member@user:58" - assertions: - push : ["1", "43"] - edit : ["58"] -``` - -Assuming that you're well-familiar with the `schema` and `relationships` sections of the above YAML file. If not, please see the previous sections to learn how to create an authorization model (schema) and generate data (relationships) according to it. - -We'll continue by examining how to create scenarios. - -## Creating Test Scenarios - -You can create multiple access checks at once to test whether your authorization logic behaves as expected or not. - -Besides simple access checks you can also test subject filtering queries and data (entity) filtering with it. - -Let's deconstruct the `scenarios`, - -### Scenarios - -```js -scenarios: - - name: // name of the scenario - description: // description of the scenario - checks: // simple access check case/cases - entity_filters: // entity (data) filtering query/queries - subject_filters: // subject filtering query/queries -``` - -### Access Check - -You can create `check` inside `scenarios` to test multiple access check cases, - -```js -checks: - - entity: "repository:3" // resource/entity that you want to check access for - subject: "user:1" // subject that performs the access check - context: // additional data provided during an access check to be evaluated - - "repository:3#owner@user:1" - assertions: // expected result/results for specific action/s or an permission/s. - push : true -``` - -Semantics for above check is: whether `user:1` can push to `repository:3`, additional to stored tuples take account that user:1 is owner of repository:3 (`repository:3#owner@user:1`). Expected result for that check it **true** - `push : true` - -:::info Contextual Tuples -We use `context` (Contextual Tuples) with simple relational tuples for simplicity in this example. However, it is primarily used for dynamic access checks, such as those involving time, date, or IP address, etc. - -To learn more about how `context` works, see the [Contextual Tuples](../../reference/contextual-tuples) section. -::: - -### Entity Filtering - -You can create `entity_filters` within `scenarios` to test your data filtering queries. - -```js -entity_filters: - - entity_type: "repository" // entity that you want to filter - subject: "user:1" // subject that you want to perform data filtering - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "3", "4", "5"] // IDs of the resources that we expected to return - edit : [] -``` - -The major difference between `check` lies in the assertions part. Since we're performing data filtering with bulk data, instead of a true-false result, we enter the IDs of the resources that we expect to be returned - -### Subject Filtering - -You can create `subject_filters` within `scenarios` to test your subject filtering queries, a.k.a which users can perform action Y or have permission X on entity:Z? - -```js -- subject_reference: "user" - entity: "repository:1" - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "43"] // IDs of the users that we expected to return - edit : ["58"] -``` - -:::info API Endpoints -You can find the related API endpoints for `check`, `entity_filters`, and `subject_filters` in the Permission service in the [Using The API](../../api-overview) section. -::: - -## Coverage Analysis - -By using the command `permify coverage {path of your schema validation file}`, you can measure the coverage for your schema. - -The coverage is calculated by analyzing the relationships and assertions in your created model, identifying any missing elements. - -The output of the example provided above is as follows. - -![schema-coverage](https://user-images.githubusercontent.com/39353278/236303688-15cc2673-05e6-42d3-9ad4-0c538f546fb0.png) - -## Testing in Local - -You can also test your new authorization model in your local (Permify clone) without using [permify-validate-action] at all. - -For that open up a new file and add a schema yaml file inside. Then build your project with, run `make build` command and run `./permify validate {path of your schema validation file}`. - -If we use the above example schema validation file, after running `./permify validate {path of your schema validation file}` it gives a result on the terminal as: - -![schema-validation](https://user-images.githubusercontent.com/39353278/236303542-930de83f-ebdd-4b0a-a09e-5c069744cc5c.png) - -[permify-validate-action]: https://github.com/Permify/permify-validate-action - -## AST Conversion - -By utilizing the command `permify ast {path of your schema validation file}`, you can effortlessly convert your model into an Abstract Syntax Tree (AST) representation. - -The conversion to AST provides a structured representation of your model, making it easier to navigate, modify, and analyze. This process ensures that your model is syntactically correct and can be processed by other tools without issues. - -The output after running the above example command is illustrated below. - - -![ast-conversion](https://github.com/Permify/permify/assets/39353278/822902d7-9612-46a6-95e9-1cb09bc0ebb2) - -## Unit Tests For Schema Changes - -We recommend leveraging Permify's in-memory databases for a simplified and isolated testing environment. These in-memory databases can be easily created and disposed of for each individual unit test, ensuring that your tests do not interfere with each other and each one starts with a clean slate. - -For managing permission/relation changes, we suggest storing schema in an abstracted place such as a git repo and centrally checking and approving every change before deploying it via the CI pipeline that utilizes theย **Write Schema API**. - -We recommend adding ourย [schema validator](https://github.com/Permify/permify-validate-action)ย to the pipeline to ensure that any changes are automatically validated. - -You can find more details about our suggested workflow to handle schema changes in [Write Schema](../../api-overview/schema/write-schema#suggested-workflow-for-schema-changes) section. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - - diff --git a/docs/docs/installation.md b/docs/docs/installation.md deleted file mode 100644 index d76986e1..00000000 --- a/docs/docs/installation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: installation -title: Setup Permify -slug: /installation ---- - -# Setup Permify - -Here is some options that you can use to set up and deploy Permify in your servers. - -```mdx-code-block -import {CardList} from '../src/components/Card'; - - -``` - -If options your deployment preference is not listed below please let us know Also if you have any questions join our [Discord community](https://discord.gg/n6KfzYxhPp) or send us an email at support@permify.co. \ No newline at end of file diff --git a/docs/docs/installation/_category_.json b/docs/docs/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/docs/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/docs/installation/aws.md b/docs/docs/installation/aws.md deleted file mode 100644 index c1c204d9..00000000 --- a/docs/docs/installation/aws.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: AWS ECS, ECR & EC2 ---- - -# ย Deploy on AWS ECS, ECR & EC2 - -AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. - -There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). - -ECS is a container management service. You can run your containers as task definitions, and Itโ€™s one of the easiest ways to deploy containers. - -If youโ€™d like to watch this tutorial rather than reading. Hereโ€™s the video version. - - - -There is no prerequisite in this tutorial. You can simply deploy permify by following this step-by-step guide. However, if you want to integrate more advanced AWS security & networking features, weโ€™ll follow up with a new tutorial guideline. - -At the end of this tutorial youโ€™ll be able to; - -1. [Create a security group](#create-an-ec2-security-group) -2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) -3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) -4. [Running our task definition](#4-running-our-task-definition) - -## 1. Create an EC2 Security Group - -So first thing first, letโ€™s go over into security groups and create our security group. Weโ€™ll need this security group while creating our cluster. - -![security-group-1](https://user-images.githubusercontent.com/34595361/208877994-e9461acc-4ffd-4591-b43e-db254366d25d.png) - -Search for โ€œSecurity Groupsโ€ in the search bar. And go to the EC2 security groups feature. - -![security-group-2](https://user-images.githubusercontent.com/34595361/208877493-ab11228c-1aa0-4bc5-b41d-4527737028e9.png) - -Then start creating a new security group. - -![security-group-3](https://user-images.githubusercontent.com/34595361/208877500-2c299883-6107-4b70-aa96-0f28eb00cf3d.png) - -You have to name your security group, and give a description. Also, you need to choose the same VPC that youโ€™ll going to use in EC2. So, I choose the default one. And Iโ€™m going to use same one while creating the ECS cluster. - -The next step is to configure our inbound rules. Hereโ€™s the configuration; - -```json -//for mapping HTTP request port. -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for mapping RPC request port. -type = "Custom TCP", protocol = "TCP", port_range = "3478",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for using SSH for connecting from your local computer. -type = "Custom TCP", protocol = "TCP", port_range = "22",source = "Anywhere", 0.0.0.0/0 -``` - -We have configured the HTTP and RPC ports for Permify. Also, we added port โ€œ22โ€ for SSH connection. So, we can connect to EC2 through our local terminal. - -Now, weโ€™re good to go. You can create the security group. And itโ€™s ready to use in our ECS. - -## 2. Creating an ECS cluster - -![create-ecs-cluster-1](https://user-images.githubusercontent.com/34595361/208878666-98c5d3ce-b079-444d-bc66-53f13038a08a.png) - -The next step is to create an ECS cluster. From your AWS console search for Elastic Container Service or ECS for short. - -![create-ecs-cluster-2](https://user-images.githubusercontent.com/34595361/208878675-2f266cfc-defb-4c7f-9186-b4de39f1743b.png) - -Then go over the clusters. As you can see there are 2 types of clusters. One is for ECS and another for EKS. We need to use ECS, EKS stands for Elastic Kubernetes Service. Today weโ€™re not going to cover Kubernetes. - -Click **โ€œCreate Clusterโ€** - -![create-ecs-cluster-3](https://user-images.githubusercontent.com/34595361/208878685-3edac67b-5b3d-4f0d-b2f7-70a5ec2e4870.png) - -Letโ€™s create our first Cluster. Simply you have 3 options; Serverless(Network Only), Linux, and Windows. Weโ€™re going to cover EC2 Linux + Networking option. - -![create-ecs-cluster-4](https://user-images.githubusercontent.com/34595361/208878681-d98a77db-16b1-42af-a697-3036cc604c85.png) - -The next step is to configure our Cluster, starting with your Cluster name. Since weโ€™re deploying Permify, Iโ€™ll call it โ€œpermifyโ€. - -Then choose your instance type. You can take a look at different instances and pricing from [here](https://aws.amazon.com/ec2/pricing/on-demand/). Iโ€™m going with the t4 large. For cost purposes, you can choose t2.micro if youโ€™re just trying out. Itโ€™s free tier eligible. - -Also, if you want to connect this EC2 instance from your local computer. You need to use SSH. Thus choose a key pair. If you have no such intention, leave it โ€œnoneโ€. - -![create-ecs-cluster-5](https://user-images.githubusercontent.com/34595361/208878989-801839f5-8fce-4410-99e0-0a2dcccb47fa.png) - -Now, we need to configure networking. First, choose your VPC, we use the default VPC as we did in the security groups. And choose any subnet on that VPC. - -You want to enable auto-assigned IP to make your app reachable from the internet. - -Choose the security group we have created previously. - -And voila, you can create your cluster. Now, we need to run our container in this cluster. To do that, letโ€™s go over task definitions. And create our container definition. - -## 3. Creating and running task definitions - -Go over to ECS, and click the task definitions. - -![create-run-task-1](https://user-images.githubusercontent.com/34595361/208879726-fe5aac07-16a8-4f8c-9cc9-1c95ca191a42.png) - -And create a new task definition. - -![create-run-task-2](https://user-images.githubusercontent.com/34595361/208879733-e9aa6fa4-9f66-44e4-8c70-dfa0e33c1b73.png) - -Again, youโ€™re going to ask to choose between; FARGATE, EC2, and EXTERNAL (On-premise). Weโ€™ll continue with EC2. - -Leave everything in default under the โ€œConfigure task and container definitionsโ€ section. - -![create-run-task-3](https://user-images.githubusercontent.com/34595361/208879735-789ec411-5829-47be-9634-c09c7b0c0320.png) - -Under the IAM role section you can choose โ€œecsTaskExecutionRoleโ€ if you want to use Cloud Watch later. - -You can leave task size in default since itโ€™s optional for EC2. - -The critical part over here is to add our container. Click on the โ€œAdd Containerโ€ button. - -![create-run-task-4](https://user-images.githubusercontent.com/34595361/208879740-4515e884-1efd-46fd-8e8c-cfa86634b673.png) - -Then we need to add our container details. First, give a name. And then the most important part is our image URI. Permify is registered on the Github Registry so our image is; - -```yaml -ghcr.io/permify/permify:latest -``` - -Then we need to define memory limit for the container, I went with 1024. You can define as much as your instance allows. - -Next step is to mapping our ports. As we mentioned in security groups, Permify by default listens; - -- `3476 for HTTP port` -- `3478 for RPC port` - -![create-run-task-5](https://user-images.githubusercontent.com/34595361/208879746-5991a04c-73d5-4e35-97b0-67aa9ebf61fc.png) - -Then we need to define command under the environment section. So, in order to start permify we first need to add โ€œserveโ€ command. - -For using properly we need a few other. Hereโ€™s the commands we need. - -```yaml -serve, --database-engine=postgres, --database-uri=postgres://:@:/, --database-pool-max=20 -``` - -- `serve` โ‡’ for starting the Permify. -- `--database-engine=postgres` โ‡’ for defining the db we use. -- `--database-uri=postgres://:password@:/` โ‡’ for connecting your database with URI. -- `--database-pool-max=20` โ‡’ the depth for running in graph. - -Weโ€™re nice and clear, add the container and then just create your task definition. Weโ€™ll use this definition to run in our cluster. - -So, letโ€™s go over and run our task definition. - -## 4. Running our task definition - -![run-task-definition-1](https://user-images.githubusercontent.com/34595361/208880326-c5ecb48c-e210-47f8-bd92-d1f789be24ff.png) - -Letโ€™s go to ECS and enter into our cluster. And go over into the tasks to run our task. - -![run-task-definition-2](https://user-images.githubusercontent.com/34595361/208880332-97a5732d-bc7d-401e-bae9-216d4273c5bf.png) - -Click to โ€œRun new Taskโ€ - -![run-task-definition-3](https://user-images.githubusercontent.com/34595361/208880335-b3ce229f-33ff-4f03-90e7-6d6a306928ae.png) - -Choose EC2 as a launch type. Then pick the task definition we just created. And leave everything else in the default. You can run your task now. - -We have just deployed our container into EC2 instance with ECS. Letโ€™s test it. - -Now you can go over into EC2, and click on the running instances. Find the instance named `ECS Instance - EC2ContainerService-` in the running instances. - -![run-task-definition-4](https://user-images.githubusercontent.com/34595361/208880339-a508354c-99ee-4219-8ace-1c7fdbbe90ed.png) - -Copy the Public IPv4 DNS from the right corner, and paste it into your browser. But you need to add `:3476` to access our http endpoint. So it should be like this; - -`:3476` - -and if you add healthz at the end like this; - -`:3476/healthz` - -you should get Serving status :) - -![run-task-definition-5](https://user-images.githubusercontent.com/34595361/208880346-d19a6877-3013-4347-86c9-9f865b8a3e3c.png) - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/docs/installation/azure.md b/docs/docs/installation/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/docs/installation/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/docs/installation/kubernetes.md b/docs/docs/installation/kubernetes.md deleted file mode 100644 index f2a1e21b..00000000 --- a/docs/docs/installation/kubernetes.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Kubernetes Cluster ---- - -# ย Deploy on Kubernetes Cluster - -In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. - -Hereโ€™s what weโ€™re going to do step-by-step; - -1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) -3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) - -There are a couple of small prerequisites for this tutorial. - -### Pre-requisites - -- An AWS account. -- The AWS Command Line Interface (CLI) is installed and configured on your local machine. โ€” [Click here](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/home) to go to IAM -- The AWS IAM Authenticator for Kubernetes is installed and configured on your local machine. - -## Configure AWS CLI with your IAM account. - -The first step is to configure our AWS IAM account into our local terminal so that we can run commands. Most of you probably have a configured AWS account if you ever set up anything into AWS programmatically, so you can skip this. If you donโ€™t follow these steps. - -### Create an AWS IAM Programmatic Access Account - -First, letโ€™s create IAM credentials for ourselves. Search IAM from the AWS console. You need to write down the account ID if you want to log in AWS console with this account as well. Letโ€™s go over users and start creating our credentials. - -![kubernetes-1](https://user-images.githubusercontent.com/34595361/211697636-6e106115-bd68-4909-aea0-5a7b6f8d5e18.png) - -At Users screen click to โ€œAdd usersโ€ โ€” and youโ€™ll end up in your first screen creating user credentials. Here you can define the name of the user. Also there 2 options that you can choose simultaneously. - -But you must choose โ€œAccess key - Programmatic accessโ€ option. Itโ€™ll allow us to configure our AWS CLI on our local machine. - -You can also choose โ€œPassword - AWS Management Console accessโ€ if you want to log in to this account through the console. But youโ€™ll need the Account ID that I mentioned in the IAM console screen. - -In the next screen, youโ€™ll be asked to create or copy the user-set permissions. For this tutorial, youโ€™ll only need to access EKS resources and features. So lets create group by clicking the โ€œCreate groupโ€ โ€” and then at pop-up screen search for EKS. - -![kubernetes-2](https://user-images.githubusercontent.com/34595361/211697647-f39d73e7-b6e2-40ae-8c3b-ad68032d6b21.png) - -Iโ€™ll choose all EKS permissions but if you have certain policies internally, just stick with them. Youโ€™ll only need following permission to; - -- `AmazonEKSClusterPolicy` -- `AmazonEKSServicePolicy` -- `AmazonEKSVPCResourceController` -- `AmazonEKSWorkerNodePolicy` - -Then simply you can review and create the user. - -![kubernetes-4](https://user-images.githubusercontent.com/34595361/211697655-1b75d4f9-a2ee-4b7e-9e1e-0be0b5aaad7d.png) - -Once you created the credentials youโ€™ll prompt the โ€œAccess key IDโ€ and โ€œSecret access keyโ€, you should save this down somewhere. Weโ€™re going the use these to configure our local machine with AWS CLI. - -### **Configure AWS CLI with your IAM account** - -Letโ€™s open our local terminal - -```jsx -aws configure -``` - -Next youโ€™ll ask for the following credentials; - -- `AWS Access Key ID` -- `AWS Secret Access Key` -- `Default region name` -- `Default output format` (leave it empty) - -## Creating an AWS EKS Cluster - -For the first step, we need to install [eksctl](https://eksctl.io/) โ€” which is like kubectl but for AWS EKS. It helps us to set up and deploy our cluster and nodes within a fraction of the time. - -Letโ€™s download eksctl using brew. - - -```jsx -brew tap weaveworks/tap -``` - -While installing the eksctl, weโ€™ll end up getting kubectl and other dependencies. - -```jsx -brew install weaveworks/tap/eksctl -``` - -Now, weโ€™re ready to create our EKS cluster. You can define certain things while deploying standard the cluster beside the name and version like; the region you want to deploy, the EC2 instance type of each node, and the number of nodes you want to run. - -```bash -eksctl create cluster \ ---name \ ---version 1.24 \ ---region ย \ ---nodegroup-name permify \ ---node-type t2.small \ ---nodes 2 -``` - -## Deploying & Running Permify in Nodes - -The next stop is applying our manifests which will help us to deploy and configure our container/Permify. - -Letโ€™s create our deployment manifest first. - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: permify - name: permify -spec: - replicas: 2 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://postgres:nOcodeSTIAnLAba@permify-test.ceuo5kqsxyea.us-east-1.rds.amazonaws.com:5432/demo" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always -status: {} -``` - -Now letโ€™s apply our deployment manifest - -```jsx -kubectl apply -f deployment.yaml -``` - -The next step is to create a service manifest, this will allow us to configure our container app. - -```jsx -apiVersion: v1 -kind: Service -metadata: - name: permify -spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer -status: - loadBalancer: {} -``` - -Letโ€™s apply service.yaml to our nodes. - -```jsx -kubectl apply -f service.yaml -``` - -Last but not least, we can check our pods & nodes. And we can start using the container with load balancer \ No newline at end of file diff --git a/docs/docs/installation/overview.md b/docs/docs/installation/overview.md deleted file mode 100644 index 76fb8a56..00000000 --- a/docs/docs/installation/overview.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Guide - -This guide shows you how to set up Permify in your servers and use it across your applications. - -:::info Minimum Requirements -PostgreSQL: Version 13.8 or higher -::: - -Please ensure your system meets these requirements before proceeding with the following steps: - -1. [Set Up & Run Permify Service](#set-up-permify-service) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Manage and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) -4. [Perform Access Check](#perform-access-check) - -:::info Talk to an Permify Engineer -Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -## Set Up Permify Service - -You can run Permify Service with various options but in that tutorial we'll run it via docker container. - -### Run From Docker Container - -Production usage of Permify needs some configurations such as defining running options, selecting datastore to store authorization data and more. - -However, for the sake of this tutorial we'll not do any configurations and quickly start Permify on your local with running the docker command below: - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -:::info -You can examine [Deploy using Docker] section to get more about the configuration options and learn the full integration to run Permify Service from docker container. - -[Deploy using Docker]: ../container -::: - -### Test your connection - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core endpoints. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -## Model your Authorization with Permify Schema - -After installation completed and Permify server is running, next step is modeling authorization with Permify authorization language - [Permify Schema]- and configure it to Permify API. - -You can define your entities, relations between them and access control decisions of each actions with using [Permify Schema]. - -### Creating your authorization model - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: - -Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action view_files = admin or member - action edit_files = admin - -} -``` - -We have 2 entities these are **"user"** and **"organization"**. Entities represents your main tables. We strongly advise naming entities the same as your original database entities. - -Lets roll back our example, - -- The `user` entity represents users. This entity is empty because it's only responsible for referencing users. - -- The `organization` entity has its own relations (`admin` and `member`) which related with user entity. This entity also has 2 actions, respectively: - - Organization member and admin can view files. - - Only admins can edit files. - -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. - -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../use-cases/simple-rbac -::: - -### Configuring Schema via API - -After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../api-overview/schema/write-schema.md) for configuration of your authorization model on Permify authorization service. - -:::caution Before Continue on Writing Schema -You'll see **tenant_id** parameter almost all Permify APIs including Write Schema. With version 0.3.x Permify became a tenancy based authorization infrastructure, and supports multi-tenancy by default so its a mandatory parameter when doing any operations. - -We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. -::: - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - -**POST /v1/tenants/{tenant_id}/schemas/write** - -![permify-schema](https://user-images.githubusercontent.com/34595361/214457054-19b141ac-6bfa-4db4-aeab-f7b7149c3351.png) - -## Store Authorization Data as Relational Tuples - -After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. - -### Create Relational Tuples - -You can create relational tuples as authorization rules by using [Write Data API](../api-overview/data/write-data.md) - -For our guide let's grant one of the team members (Ashley) an admin role. - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - -**POST /v1/tenants/{tenant_id}/data/write** - -```json -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" //Organization identifier - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1", //Ashley's identifier - "relation": "" - } - } - ] -} -``` - -![write-data](https://user-images.githubusercontent.com/34595361/214458203-8264e141-642d-48b0-9242-416bbf6f8795.png) - -**Created relational tuple:** organization:1#admin@user:1 - -**Semantics:** User 1 (Ashley) has admin role on organization 1. - -:::tip -In ideal production usage Permify stores your authorization data in a database you prefer. You can configure the database with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) or CLI flag options. - -But in this tutorial Permify Service running default configurations on local, so authorization data will be stored in memory. You can find more detailed explanation how Permify stores authorization data in [Managing Authorization Data] section. - -[Managing Authorization Data]: ../../getting-started/sync-data -::: - -## Perform Access Check - -Finally we're ready to control authorization. Access decision results computed according to relational tuples and the stored model, [Permify Schema] action conditions. - -Lets get back to our example and perform an example access check via [Check API]. We want to check whether an specific user has an access to view files in a organization. - -[Check API]: ../../api-overview/permission/check-api -[Permify Schema]: ../../getting-started/modeling - -#### Example HTTP Request: - -***Can the user 45 view files on organization 1 ?*** - -**POST /v1/tenants/{tenant_id}/permissions/check** - -| Required | Argument | Type | Default | Description | -|----------|----------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. | -| [x] | entity | object | - | name and id of the entity. Example: organization:1. | -| [x] | action | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action | -| [ ] | schema_version | string | - | get results according to given schema version | -| [ ] | depth | integer | 8 | - | - -### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_files", - "subject": { - "type": "user", - "id": "45", - "relation": "" - }, -} -``` - -### Response - -```json -{ - "can": "RESULT_ALLOW", - "metadata": { - "check_count": 0 - } -} -``` - -See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify - -[Access Control Check]: ../api-overview/permission/check-api.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you struggle with installation or have any questions, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/docs/permify-overview/_category_.json b/docs/docs/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/docs/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/docs/permify-overview/infrastructure.md b/docs/docs/permify-overview/infrastructure.md deleted file mode 100644 index 0f29223d..00000000 --- a/docs/docs/permify-overview/infrastructure.md +++ /dev/null @@ -1,79 +0,0 @@ - -# Architecture & Deployment - -Permify is a infrastructure for ease the process of creating and managing scalable authorization systems in your environment. - -This section shows where and how does Permify fit into your environment with examining Permify's high level design, internal architecture, deployment patterns and the usage with the authentication and identity providers. - -## High Level Design - -You can model your authorization logic with **Permify's domain specific language** and your applications can interpolate with Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data and more! - -Permify stores access control relations in a **database of your choice**, and each API request evaluates and takes into account access decisions based on the stored relations. - -So this preferred database behaves as a **centralized data source** for your authorization system. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify vs Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Architecture - -Permify supports both HTTP and GRPC. HTTP requests are converted to GRPC and then transferred to Permify servers. - -There are 4 servers in a Permify Instance: Permission, Relationship, Schema, and Watch. - -- **Permission Server:** The permission server forwards the request to the invoker. The invoker checks for any missing parts of the query, letโ€™s say if no snapshot is provided, it finds the head snapshot. It then hashes the request (with snapshot and schema version) and forwards it to the most convenient Permify instance. If the hash matches its own, it directs it to the local cache. If the cache does not contain the request, it proceeds to the engine. The engine breaks down the query into sub-queries and returns it to the invoker. This process continues until a final decision is made. -- **Relationship Server:** After validating the request, it passes it to the database access layer. -- **Schema Server:** After validating the request, it passes it to the database access layer. -- **Watch Server:** It broadcasts changes in relationships based on their snapshots. - -![architecture](https://github.com/Permify/permify/assets/34595361/b943bc0d-5faf-4a06-abb9-fbd70eb42ea0) - -Database abstractions for the reader and writer can use a database like Aurora Postgres. - -When deploying, separate hosts can be used in the Permify config for the reader and writer. This way, different Permify instances can read from different read replicas. - -**Note:** we are using serf (https://github.com/hashicorp/serf) agent for node discovery on hashring. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. - -Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. - -See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. - -As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/docs/permify-overview/intro.md b/docs/docs/permify-overview/intro.md deleted file mode 100644 index 69c135f2..00000000 --- a/docs/docs/permify-overview/intro.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_position: 1 ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is an **open source authorization service** for creating fine-grained and scalable authorization systems. - -With Permify, you can easily structure your authorization model, store authorization data in your preferred database, and interact with the Permify API to handle all authorization queries from your applications or services. - -Permify is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://permify.co/post/google-zanzibar-in-a-nutshell/). - -### Motivation - -Our goal is to make **Google's Zanzibar** available to everyone and help them to build robust, flexible, and easily auditable authorization system that establishes a [natural linkage between permissions](https://permify.co/post/relationship-based-access-control-rebac/) across the business units, functions, and entities of an organization. - -## Key Features - -๐Ÿ›ก๏ธ **Production ready** authorization API that serve as **gRPC** and **REST**. - -๐Ÿ”ฎ Domain Specific Authorization Language to **easily model** your authorization. Supporting RBAC, ReBAC, ABAC and more. - -๐Ÿ” Database Configuration to store your permissions with **high availability** and **low latency**. - -โœ… Perform access control checks and get answers **down to 10ms** with our various cache mechanisms that we operate. - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -โš™๏ธ Create custom permissions for your **tenants**, and manage them in a single place with **Multi Tenancy**. - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin]. - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Getting Started - -In Permify, authorization is divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify will [Store Authorization Data] as relations. -- Perform [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../../getting-started/modeling -[Store Authorization Data]: ../../getting-started/sync-data -[Access Checks]: ../../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want to try it out and examine it instantly, - - - -## Community & Support - -We would love to hear from you :heart: - -You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. - -For feature requests, bugs, or any improvements you can always open an [issue](https://github.com/permify/permify/issues). - -### Want to Contribute? Here are the ways to contribute to Permify - -* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. -* **Improve documentation:** Alongside our codebase, documentation is an important part of our open-source journey. We're trying to give the best DX possible to explain ourselves and Permify. And you can help with that by importing resources or adding new ones. -* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. - -You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). - -## Communication Channels - -If you like Permify, please consider giving us a :star: on [github](https://github.com/permify/permify) - -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Roadmap - -You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! - -## Need any help on Authorization ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify or how it might fit into your authorization workflow, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/docs/playground.md b/docs/docs/playground.md deleted file mode 100644 index 384c3485..00000000 --- a/docs/docs/playground.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization schema in a browser. - -Our playground consists 3 main sections, - -- [Schema (Authorization Model)](#schema-authorization-model) -- [Authorization Data](#authorization-data) -- [Enforcement](#enforcement-access-check-scenarios) - -Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Schema (Authorization Model) - -You can create your authorization model in this section with using our domain specific language. - -You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured. Also, you can check our docs to [learn more about how to model authorization](./getting-started/modeling.md) in Permify. - -To demonstrate how the playground works, let's create a simple authorization model as follows. This model should be selected as the default when you open the playground. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents owner of this repository - relation owner @user - - // permissions - permission edit = parent.admin or owner - permission delete = owner -} -``` - -We have 2 permissions, `edit` for access of editing repository and `delete` for access of deleting repository. - -Repositories has parent child relation with organizations. The `parent` relation in the repository entity represents that parent child association, while ownership of the repository is represented with the `owner` relation. - -Organizations can have organizational wide roles such as admin and member, which defined as `admin` and `member` relation in organization entity. - -:::info Automatic Saving for Schema Changes -Schema changes are captured automatically, and other sections update accordingly. Some delays may occur at times; please feel free to reach out if these delays hinder your testing process. -::: - -### Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. - -So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. - -![relational-tuples](https://github.com/Permify/permify/assets/34595361/f8b77c18-dd46-461c-9408-392b642cc900) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as tuples and these tuples stored in a database that you preferred. - -The basic tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. - -The user is basically the user or user set in our system. Let's say we want make the **user 1** `admin` in **organization 1** then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the **Add Relationship** button. - -![create-tuple-empty](https://github.com/Permify/permify/assets/34595361/33b85fe7-25e2-400d-8055-94d305023d8c) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![create-tuple-user](https://github.com/Permify/permify/assets/34595361/016d6f9e-955a-4c39-ab55-21a9fd6dffd9) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 - `โ€repository:1#parent@organization:1#...` as follows: - -![create-tuple-parent](https://github.com/Permify/permify/assets/34595361/42daf251-818a-4bd2-8790-1c8656cd497f) - -Created tuples shown in the **Data** section as follows. - -![authorization-data](https://github.com/Permify/permify/assets/34595361/ccc25da1-5212-425d-b604-6a31a8f9555f) - -## Enforcement (Access Check Scenarios) - -Finally as we have a sample data let's perform an access check! - -The YAML in the Enforcement section represents a test scenario for conducting access checks. This scenario-based testing process provides the ability to execute complex access scenarios in a single place. - -Let's name our scenario **"admin_access_test"** and create tests to check: - -- Whether user:1 (admin) can edit repository:1? -- Whether user:1 (admin) can delete repository:1? - -Below is the YAML scenario covering these two tests: - -![scenario-check](https://github.com/Permify/permify/assets/34595361/934add02-6b6a-45ed-9b5b-6a2539778fcf) - -In the above YAML structure, - -#### entity - -Represents the resource for which we want to check access - `repository:1` - -#### subject - -Represents the subject that performs the action or grants access - `user:1`. - -#### assertions - -Assertions stands for defining the expected result for specific action or an permission. In our case we're evaluating access for edit action. - -Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission as: - -`โ€permission edit = parent.admin or owner` - -:::note -which `โ€parent.admin`โ€ indicates admin in the organization that repository belongs to. -::: - -So user:1 should be able to edit resource:1, therefore expected outcome for that access request is true. -- `edit: true` - -On the other hand, user:1 should't be able to delete resource:1, because only owners can. Therefore expected outcome for that is false. -- `delete: false` - -:::info Create More Advanced Scenarios -For simplicity, we've created a basic scenario. However, you can create more advanced scenarios using our validation YAML structure. - -To learn how to use this syntax for complex scenarios, refer to the [Creating Test Scenarios](../getting-started/testing#creating-test-scenarios) section in [Testing & Validation](./getting-started/testing.md) page. -::: - -Let's click the Run button to execute our scenario. - -![scenario-check-true](https://github.com/Permify/permify/assets/34595361/a90c042f-e0f8-46a0-9800-383620226acd) - -Let's change the expected outcome as false (`edit: false`) and hit the **Run** button again we'll see an error message. - -![scenario-check-false](https://github.com/Permify/permify/assets/34595361/9f9768bf-c534-4b1d-9447-e55cab2dafca) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/reference/_category_.json b/docs/docs/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/docs/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/docs/reference/cache.md b/docs/docs/reference/cache.md deleted file mode 100644 index 1910705a..00000000 --- a/docs/docs/reference/cache.md +++ /dev/null @@ -1,95 +0,0 @@ -# Cache Mechanisms - -This section showcases the cache mechanisms that Permify uses. - -## Schema Cache - -Schemas are stored in an in-memory cache based on their versions. If a version is specified in the request metadata, it will be searched for in the in-memory cache. If not found, it will query the database for the version and store it in the cache. If no version information is given in the metadata, versions will be assumed to be alphanumeric and sorted in that order, and Permify will request the head version and check if it exists in the memory cache. - -The size of this can be determined through the Permify configuration. Here is an example configuration: -service: - -```yaml -โ€ฆ - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB -โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -## Data Cache - -Permify applies the MVCC (Multi Version Concurrency Control) pattern for Postgres, creating a separate database snapshot for each write and delete operation. This both enhances performance and provides a consistent cache. - -An example of a cache key is: -check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request} - -Permify hashes each request and searches for the same key. If it cannot find it, it runs the check engine and writes to the cache, thus creating a consistently working hash. - -The size of this can also be determined via the Permify configuration. Hereโ€™s an example: -service: - -```yaml - โ€ฆ - permission: - bulk_limit: 100 - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -Note: Another advantage of the MVCC pattern is the ability to historically store data. However, it has a downside of accumulation of too many relationships. For this, we have developed a garbage collector that will delete old data at a time period you specify. - -## Distributed Cache - -Permify does provide a distributed cache across availability zones (within an AWS region) via **Consistent Hashing**. Permify uses Consistent Hashing across its distributed instances for more efficient use of their individual caches. - -This would allow for high availability and resilience in the face of individual nodes or even entire availability zone failure, as well as improved performance due to data locality benefits. - -Consistent Hashing is a distributed hashing scheme that operates independently of the number of objects in a distributed hash table. This method hashes according to the nodesโ€™ peers, estimating which node a key would be on and thereby ensuring the most suitable request goes to the most suitable node, effectively creating a natural load balancer. - -### How Consistent Hashing Operates in Permify - -With a single instance, when an API request is made, request and corresponding response stored in its corresponding local cache. - -If we have more than one Permify instance consistent hashing activates on API calls, hashes the request, and outputs a unique key representing the node/instance that will store the request's data. Suppose it stored in the instance 2, subsequent API calls with the same hash will retrieve the response from the instance 2, regardless of which instance that API called from. - -Using this consistent hashing approach, we can effectively utilize individual cache capacities. Adding more instances automatically increases the total cache capacity in Permify. - -You can learn more about consistent hashing from the following blog post: [Introducing Consistent Hashing](https://itnext.io/introducing-consistent-hashing-9a289769052e) - -:::info -Note, however, that while the consistent hashing approach will distribute keys evenly across the cache nodes, it's up to the application logic to ensure the cache is used effectively (i.e., that it reads from and writes to the cache appropriately). -::: - -Here is an example configuration: - -```yaml -distributed: - # Indicates whether the distributed mode is enabled or not - enabled: true - - # The address of the distributed service. - # Using a Kubernetes DNS name suggests this service runs in a Kubernetes cluster - # under the 'default' namespace and is named 'permify' - address: "kubernetes:///permify.default:5000" - - # The port on which the service is exposed - port: "5000" -``` - -Additional to that weโ€™re using a [circuit breaker](https://blog.bitsrc.io/circuit-breaker-pattern-in-microservices-26bf6e5b21ff) pattern to detect and handle failures when the underlying database is unavailable. It prevents unnecessary calls when the database is down and handles the process on the rebooting phase. - -## Need any help ? - -Our team is happy help you to structure right architecture for your permission system. Feel free to [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - diff --git a/docs/docs/reference/contextual-tuples.md b/docs/docs/reference/contextual-tuples.md deleted file mode 100644 index 3765e967..00000000 --- a/docs/docs/reference/contextual-tuples.md +++ /dev/null @@ -1,189 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Contextual Data (Dynamic Permissions) - -Contextual tuples are relations that can be dynamically added to permission request operations. When you send these relations along with your requests, they get processed alongside existing relations in the database and will return a result. - -You can utilize Contextual Tuples in authorization checks that depend on certain dynamic or contextual data (such as location, time, IP address, etc) that have not been written as traditional Permify relation tuples. - -## Use Case - -Let's give an example to better understand the usage of Contextual Tuples aka dynamic permissions in access checks. - -Consider you're modeling an permission system for an internal application that belongs to an multi regional organization. - -### Authorization Model - -In that application an employee that belongs to HR department can view details of another employee if: - -1. If he/she is an Manager in HR department -2. Connected via the branch's internal network or through the branch's VPN - -As you notice we can model the rule **1.** easily with our existing schema language, which gives ability to define arbitrary relations between users and objects such as manager of HR entity, as follows, - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - -} -``` - -But to create the `view_employee` permission in the organization entity, we need to consider not only whether the employee is a manager but also check the IP address. - -At this point, traditional relation tuples of Permify are insufficient since network address is an dynamic variable that cannot be added as static relations. - -So, to incorporate the IP address into our authorization model we will use Contextual Tuples and send dynamic relations values when sending the access check request. - -Let's extend our authorization model with adding contextual entities and relations to create the `view_employee` action. - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - - relation ip_address_range @ip_address_range - - action view_employee = hr_manager and ip_address_range.user - -} - -entity ip_address_range { - relation user @user -} -``` - -A quick breakdown we define **type** for contextual variable `ip_address_range` and related them with user. Afterwards call that dynamic entities inside our organization entity and form the `view_employee` permission as follows: - -```perm -action view_employee = hr_manager and ip_address_range.user -``` - -### Access Check With Contextual Tuples - -Since we cannot create relation statically for `ip_address_range` we need to send ip value on runtime, specifically when performing access control check. - -So let's say user Ashley trying to view employee X. And lets assume that, - -- She has a **manager** relation in HR department with the tuple `organization:1#hr_manager@user:1` -- She connected to VPN which connected to network 192.158.1.38 - which is Branch's internal network. - - - - -```go -data, err := structpb.NewStruct(map[string]interface{}{ - "ip_address": "192.158.1.38", -}) - -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: &v1.Entity { - Type: "organization", - Id: "1", - }, - Permission: "hr_manager", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - Context: *v1.Context { - Data: data, - } - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission - .check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20, - }, - entity: { - type: "organization", - id: "1", - }, - permission: "hr_manager", - subject: { - type: "user", - id: "1", - }, - context: { - data: { - ip_address: "192.158.1.38", - }, - }, - }) - .then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED"); - } else { - console.log("RESULT_DENIED"); - } - }); -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "hr_manager", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, - "context": { - "data": { - "ip_address": "192.158.1.38", - } - } -}' -``` - - - - -:::info -Besides data, you can also provide relational tuples and attributes alongside the access check using contextual tuples. You can view the full parameters for the [permission check in our swagger docs](https://permify.github.io/permify-swagger/#/Permission/permissions.check). -::: - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/docs/reference/glossary.md b/docs/docs/reference/glossary.md deleted file mode 100644 index 03bc6817..00000000 --- a/docs/docs/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Permission Database - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure it when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control Models](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/docs/reference/snap-tokens.md b/docs/docs/reference/snap-tokens.md deleted file mode 100644 index 95eec606..00000000 --- a/docs/docs/reference/snap-tokens.md +++ /dev/null @@ -1,59 +0,0 @@ - -# Snap Tokens & Zookies - -A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. - -## Why you should use Snap Tokens ? - -Basically, you should use snap tokens both for consistency and performance. The main goal of Permify is to provide an authorization system that ensures excellent performance that can handle millions of requests from different environments while ensuring data consistency. - -Performance standards can be achievable with caching. In Permify, the cache mechanism eliminates re-computing of access control checks that once occurred, unless any relationships of resources don't change. - -Still, all caches suffer from the risk of becoming stale. If some schema update happens, or relations change then all of the caches should be updated according to it to prevent false positive or false negative results. - -Permify avoids this problem with an approach of snapshot reads. Simply, it ensures that access control is evaluated at a consistent point in time to prevent inconsistency. - -To achieve this, we developed tokens called Snap Tokens that consist of a timestamp that is compared in access checks to ensure that the snapshot of the access control is at least as fresh as the resource timestamp - basically its stored snap token. - -## How to use Snap Tokens - -Snap Tokens used in endpoints to represent the snapshot and get fresh results of the API's. It mainly used in [Write API] and [Check API]. - -The general workflow for using snap token is getting the snap token from the reponse of Write API request - basically when writing a relational tuple - then mapped it with the resource. One way of doing that is storing snap token in the additional column in your relational database. - -Then this snap token can be used in endpoints. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -```json -{ - "schema_version": "ce8siqtmmud16etrelag", - "snap_token": "gp/twGSvLBc=", - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - }, -} -``` - -[Write API]: ../../api-overview/data/write-data/ -[Check API]: ../../api-overview/permission/check-api - -### When Snap Token is NOT Provided - -In Permify, every transaction is recorded in the 'transactions' table, and when a Snap Token is not provided, it retrieves the ID of the latest transaction from this table. This ID represents the most current snapshot of the database. After a query is executed with this ID, the results are then cached using this ID. - -When two identical requests are made and neither specifies a Snap Token, the latest transaction ID will be requested from the database for both requests. Subsequently, the first request will write its result to the cache using a key and value like this: - -``` -check_{TRANSACTION_ID}_{schema_version}_{context}_organization:1#admin@user:1 -> true -``` - -When the second request arrives, since a transaction ID was not provided, the latest transaction ID will again be requested from the database. However, since the first request has already written the example above to the cache, and the second request will generate the same hash, this result will be retrieved from the cache. - -## More on Cache Mechanism - -Permify implements several cache mecnanisims in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](./cache.md) \ No newline at end of file diff --git a/docs/docs/reference/tracing.md b/docs/docs/reference/tracing.md deleted file mode 100644 index 13fb142d..00000000 --- a/docs/docs/reference/tracing.md +++ /dev/null @@ -1,56 +0,0 @@ - -# Tracing Tools - -Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: - -- [Jaeger](https://www.jaegertracing.io/) -- [OpenTelemetry](https://opentelemetry.io/) -- [Signoz](https://signoz.io/) -- [Zipkin](https://zipkin.io/) - -## Usage - -### Set Up - -Adding one of these tracing tools to your authorization system is quite simple, you just need to define it in the Permify configuration file as **tracer**. - -```yaml -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -``` - -- ***exporter***: enter the tool name that you want to use. `jaeger` , `otlp`, `signoz`, and `zipkin`. -- ***endpoint***: export url for tracing data. -- ***disabled***: switch option for tracing. -- ***insecure***: configures the exporter to connect to the collcetor using HTTP instead of HTTPS. This configuration is relevant only for `signoz` and `otlp`. -- ***urlpath***: allows one to override the default URL path used for sending traces. If unset, default ("/v1/traces") will be used. This configuration is relevant only for `otlp`. - -**Example YAML configuration file** - -```yaml -app: - name: โ€˜permifyโ€™ -http: - port: 3476 -logger: - log_level: โ€˜debugโ€™ - rollbar_env: โ€˜permifyโ€™ -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -database: - write: - connection: 'postgres' - database: 'morf-health-demo' - uri: 'postgres://postgres:SphU4Uf3QXNntT@permify.us-east-1.rds.amazonaws.com:5432' - pool_max: 2 -``` - -After running Permify in your server, you should run Zipkin as well. If you're using docker here is the docker pull request for Zipkin: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` diff --git a/docs/docs/use-cases.md b/docs/docs/use-cases.md deleted file mode 100644 index 76f7bb33..00000000 --- a/docs/docs/use-cases.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: use-cases -title: Common Use Cases -slug: /use-cases ---- - -# Common Use Cases - -Common modeling patterns and uses cases we have seen so far from the users from small startups with simple RBAC to multi-regional enterprises that run tens of Permify instances with deeply nested relationships. - -:::success Missing a specific use case? -No problem, let's discuss it together! just open an [issue](https://github.com/Permify/permify/issues) about it or join our conversation at [discord](https://discord.gg/n6KfzYxhPp)! -::: - -```mdx-code-block -import {CaseList} from '@site/src/components/Case'; -import list from './use-cases/_list.json'; - - -``` diff --git a/docs/docs/use-cases/_category_.json b/docs/docs/use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/docs/use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/docs/use-cases/_list.json b/docs/docs/use-cases/_list.json deleted file mode 100644 index 7b6e7fb1..00000000 --- a/docs/docs/use-cases/_list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 1, - "title": "Role Based Access Control (RBAC)", - "description": "Want to implement role to your application ? Define an entity and manage your roles throught your applications.", - "link": "./simple-rbac" - }, - { - "id": 2, - "title": "Attribute Based Access Control (ABAC)", - "description": "Grant access what based on specific characteristics or attributes.", - "link": "./abac" - }, - { - "id": 3, - "title": "Relationship Based Access Control (ReBAC)", - "description": "Define permissions based on the relationships between resources and subjects in your system", - "link": "./rebac" - }, - { - "id": 4, - "title": "Custom Roles", - "description": "Assign specific permissions to users based on the custom roles that they are assigned within the system.", - "link": "./custom-roles" - }, - { - "id": 5, - "title": "Multi Tenancy", - "description": "Create custom authorization schema and relation tuples for the different tenants and manage them in a single place.", - "link": "./multi-tenancy" - } -] \ No newline at end of file diff --git a/docs/docs/use-cases/multi-tenancy.md b/docs/docs/use-cases/multi-tenancy.md deleted file mode 100644 index c8e5be9a..00000000 --- a/docs/docs/use-cases/multi-tenancy.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: "Multi Tenancy" ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -With version 0.3.x Permify moved to a tenancy-based infrastructure, which affects almost all of the API operations. - -## Multi Tenancy on Permify - -Multi-tenancy in Permify refers to an authorization architecture where a single Permify authorization service serves multiple applications/organizations (tenants). - -This allows customization of the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create a custom authorization schema and relation tuples for the different tenants and manage them in a single place. - -For the users that don't have/need multi-tenancy in their authorization structure, we created a pre-inserted tenant (id: **t1**) that comes default when you serve a Permify service. - -Several things changed when we moved to tenant based infrastructure, these are: - -- [Multi Tenancy on Permify](#multi-tenancy-on-permify) - - [API endpoints now have Tenant ID field](#api-endpoints-now-have-tenant-id-field) - - [Check API](#check-api) - - [Added Tenancy Service](#added-tenancy-service) - - [Permission Database Tenancy Table and Tenant Id column](#permission-database-tenancy-table-and-tenant-id-column) - - [Tenant Table](#tenant-table) - - [Tenant ID Column](#tenant-id-column) -- [Need any help ?](#need-any-help-) - -### API endpoints now have Tenant ID field - -All API endpoints now have a `โ€tenant_id` mandatory field. Let's examine a check request below, - -#### Check API - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../api-overview.md). - -### Added Tenancy Service - -To manage tenants we have added a Tenancy service; you can create, delete and list tenants. See the [Tenancy Service](../../api-overview/tenancy) in Using The API section. - -### Permission Database Tenancy Table and Tenant Id column - -#### Tenant Table - -A tenants table has been added to the Permission database to store tenant's details. The new folder structure changed as follows: -``` -tables -โ”œโ”€โ”€ migrations -โ”œโ”€โ”€ relation_tuples -โ”œโ”€โ”€ schema_definitions -โ”œโ”€โ”€ tenants -โ”œโ”€โ”€ transactions -``` - -#### Tenant ID Column - -Relation tuples and schema definition tables now have a tenant_id column, which stores the id of the tenant that the data belongs. - -Let's take a look at a snapshot of the demo table on an example Permission Database. - -Example Relation Tuples data table: -![tenant-id-tuples](https://user-images.githubusercontent.com/34595361/214724165-a3775756-0649-4869-b994-d837fadd271d.png) - -Example Schema Definitions data table -![tenant-id-schema](https://user-images.githubusercontent.com/34595361/214724727-01eadad3-720c-4c10-a88d-6ee293ecf4a8.png) - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. diff --git a/docs/docs/use-cases/rebac.md b/docs/docs/use-cases/rebac.md deleted file mode 100644 index 561b500e..00000000 --- a/docs/docs/use-cases/rebac.md +++ /dev/null @@ -1,424 +0,0 @@ - -# Relationship Based Access Control - -Permify was designed and structured as a true [Relationship Based Access Control(ReBAC)](https://permify.co/post/relationship-based-access-control-rebac/) solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -Here are some common use cases where you can benefit from using ReBAC models in your Permify Schema. - -- [Protecting Organizational-Wide Resources](#protecting-organizational-wide-resources) -- [Deeply Nested Hierarchies](#deeply-nested-hierarchies) -- [User Groups & Team Permissions](#user-groups--team-permissions) - -## Protecting Organizational-Wide Resources - -This example demonstrates grouping the users by organization and giving them access to organizational-wide resources. - -In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with our authorization language DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists of 3 entities, - -- `user`, represents users. This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -#### Relations - -To define a relation, **relations** need to be created as entity attributes. - -##### organization entity - -In our schema we defined 2 relations in the organization entity: ``admin`` and ``member``. - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents a default user that belongs to that organization. - -##### repository entity - -Repository entities have 2 relations: ``parent`` and ``owner``. Both of these relations represent actual database relations with other entities rather than a role-based approach similar to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -The ``parent`` relation represents the parent organization of a repository. And ``owner`` represents the specific user, the repository's owner. - -#### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -##### repository actions - -In our schema, we examined one of the main functionalities user can make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who have an admin or member role of the parent organization, can ``read``. -- Repository owners or admins of the parent organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since `parent` represents the parent organization of a repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -### Sample Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -For instance, you can define that a user has certain permissions because of their relation to other entities. - -An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -## Deeply Nested Hierarchies - -This use case shows solving deeply nested hierarchies with the [Permify Schema]. - -We have a unique **action** usage for nested hierarchies, where parent and child entities can share permissions between them. Let's follow the below team project authorization model to examine this case. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organization user types - relation admin @user -} - -entity team { - - //refers to the organization that a team belongs to - relation org @organization - - // Only the organization administrator can edit - action edit = org.admin -} - -entity project { - - //refers to the team that a project belongs to - relation team @team - - // This action is responsible for nested permission inheritance - // team.edit refers to the edit action on the team entity which we defined above - // This means that the organization admin, who can edit the team - // can also edit the project related to the team. - action edit = team.edit -} -``` - -### Sample Relational Tuples - -organization:1#admin@user:1 - -team:1#org@organization:1#... - -project:1#team@team:1#... - -Lets assume we created the above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** since the `user:1` is an admin of the `organization:1` and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md - -Let's break down this case, - -```perm -entity project { - - relation team @team - - action edit = team.edit -} -``` - -In the above `team.edit` points to the **edit** action in the **team** (that the project belongs to). That edit action on the team entity (`action edit = org.admin`) states that only admins of the **organization (which that team belongs to)** can edit. So our project inherits that action and conducts a result accordingly. - -If we go back to our question: `Can user:1 edit project:1?` this will give an **Allow** result, because user:1 is an admin in an organization that the projects' parent team belongs to. - -## User Groups & Team Permissions - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // represents the organization that the team belongs to - relation org @organization - - // organization admins or team owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be an organization admin and either an owner or member of this team - action invite = org.admin and (owner or member) - - // only team owners can remove users - action remove_user = owner - -} - -entity project { - - // represents team and organization that a project belongs to - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists of 4 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents an organization that contain teams. - -- `team`, represents teams, which belong to an organization. - -- `project`, represents projects that belong to teams. - -#### Relations - -##### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates that each organization has its own roles. - -##### team entity - -The team entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -##### project entity - -The project entity has ``team`` and ``org`` relations. Both these relations represent parent relationships with other entities, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -#### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or*** and ***not*** operators to define actions. - -##### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can edit and delete team specific resources. - -- Moreover, to invite a colleague to a team you must have an organizational ***admin role*** and either be a ***owner*** or ***member*** of that team. - -- To remove users in team you must be an ***owner*** of that team. - -And these rules are defined in Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -##### project actions - -And here are the project actions. The actions consist of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Sample Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organization:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 are also members of organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -## Need any help on Authorization ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/docs/use-cases/simple-rbac.md b/docs/docs/use-cases/simple-rbac.md deleted file mode 100644 index 85d70b6b..00000000 --- a/docs/docs/use-cases/simple-rbac.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Role Based Access Control - -Want to implement roles and permissions in your application? Permify fully covers you at that point. The example below shows how to model simple role based access controls for organizational roles and permissions with our authorization language, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists of 2 entities, - -- `user`, represents users (maybe corresponds to employees). This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents the organization the user (employees) belongs. It has several roles and permissions related to the specific resources such as organization files and vendor files. - -### Relations - -#### organization entity - -We can use **relations** to define roles. In this example, we have 4 organization wide roles: admin, manager, member, and agent. - -```perm -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - -} -``` - -Roles (relations) can be scoped to different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### organization actions - -In our schema, we define several actions for controlling access permissions on organization files and organization vendor's files. - -```perm -entity organization { - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -let's take a look at some of the actions: - -- ``action edit_files = admin or manager`` -indicates that only the admin or manager has permission to edit files in the organization. - -- ``action view_files = admin or manager or (member not agent)`` -indicates that the admin, manager, or members (without having the agent role) can view organization files. - - - -## Example Relational Tuples for this case - -organization:2#admin@user:daniel - -organization:5#member@user:ashley - -organization:17#manager@user:mert - -organization:21#agent@user:ege - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. - diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js deleted file mode 100644 index 58f71914..00000000 --- a/docs/docusaurus.config.js +++ /dev/null @@ -1,154 +0,0 @@ -// Note: type annotations allow type checking and IDEs autocompletion - -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/dracula'); - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: 'Permify', - url: 'https://docs.permify.co/', - tagline: "Open Source Authorization Service Based on Google Zanzibar", - baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', - favicon: 'img/favicon.ico', - organizationName: 'Permify', // Usually your GitHub org/user name. - projectName: 'permify', // Usually your repo name. - trailingSlash: true, - - onBrokenLinks: 'warn', - - plugins: [ - [ - require.resolve("@cmfcmf/docusaurus-search-local"), - { - indexDocs: true, - }, - ], - ], - - presets: [ - [ - '@docusaurus/preset-classic', - { - docs: { - sidebarPath: require.resolve('./sidebars.js'), - lastVersion: 'current', - versions: { - current: { - label: '0.7.x', - }, - }, - editUrl: ({ docPath }) => { - return `https://holocron.so/github/pr/Permify/permify/master/editor/docs/docs/${docPath}` - }, - }, - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - gtag: { - trackingID: 'G-BSRXWHBYR1', - }, - }, - ], - ], - - themeConfig: - { - navbar: { - title: 'Permify', - logo: { - alt: 'Permify Logo', - src: 'img/logo.svg', - }, - items: [ - { - type: 'doc', - docId: 'permify-overview/intro', - position: 'left', - label: 'Docs', - }, - { - label: 'Playground', - href: 'https://play.permify.co', - position: 'left', - className: 'header-playground-link' - }, - { - type: 'docsVersionDropdown', - position: 'right', - }, - { - href: 'https://github.com/Permify/permify', - position: 'right', - className: 'header-github-link' - }, - { - href: 'https://discord.gg/n6KfzYxhPp', - position: 'right', - className: 'header-discord-link' - }, - { - href: 'https://twitter.com/getPermify', - position: 'right', - className: 'header-twitter-link' - } - ], - }, - metadata: [ - { - name: "keywords", - content: - "google zanzibar, authorization, permissions, rbac, rebac, abac, access control, fine grained", - }, - ], - footer: { - style: 'dark', - links: [ - { - title: 'Docs', - items: [ - { - label: 'Docs', - to: '/', - }, - ], - }, - { - title: 'Community', - items: [ - { - label: 'Twitter', - href: 'https://twitter.com/getPermify', - }, - ], - }, - { - title: 'More', - items: [ - { - label: 'Blog', - to: 'https://permify.co/post/', - }, - { - label: 'GitHub', - href: 'https://github.com/Permify', - }, - ], - }, - ], - copyright: `Copyright ยฉ ${new Date().getFullYear()} Permify.`, - }, - colorMode: { - disableSwitch: false, - respectPrefersColorScheme: true, - }, - prism: { - theme: darkCodeTheme, - darkTheme: darkCodeTheme, - additionalLanguages: ['php'], - }, - }, -}; - -module.exports = config; diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 00000000..0f54248f --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/docs/getting-started/enforcement.md b/docs/getting-started/enforcement.mdx similarity index 62% rename from docs/docs/getting-started/enforcement.md rename to docs/getting-started/enforcement.mdx index df4c80f5..ece71dfa 100644 --- a/docs/docs/getting-started/enforcement.md +++ b/docs/getting-started/enforcement.mdx @@ -1,9 +1,9 @@ --- sidebar_position: 4 +icon: 'plug' +title: 'Interacting With The API' --- -# Interacting With The API - Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. We structured Permify API in 4 core parts: @@ -13,43 +13,76 @@ We structured Permify API in 4 core parts: - [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. - [TenancyService]: Consists tenant operations such as creating, deleting and listing. -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ../../api-overview/permission -[DataService]: ../../api-overview/data -[SchemaService]: ../../api-overview/schema -[TenancyService]: ../../api-overview/tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). +Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) and [REST](https://restfulapi.net/). + +[PermissionService]: ./permission/check-api +[DataService]: ./data/write-data +[SchemaService]: ./schema/write-schema +[TenancyService]: ./tenancy/create-tenant + +### SDKs + +- [Golang](https://github.com/Permify/permify-go) +- [NodeJS]:(https://github.com/Permify/permify-node) +- [Python](https://github.com/Permify/permify-python) +- [Javascript](https://github.com/Permify/permify-javascript) + + + + +

Integration with a Service Mesh

+ +Our software does not include built-in support for service meshes (eg. Istio). However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: + +
## Core Paths -- Configure your authorization model with [Schema Write](../api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](../api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](../api-overview/data/read-relationships.md) -- Check access with [Check API](../api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](../api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](../api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](../api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](../api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](../api-overview/watch/watch-changes.md) +- Configure your authorization model with [Schema Write](../../api-reference/schema/write-schema) +- Write relational tuples with [Write Data](../../api-reference/data/write-data) +- Read relation tuples and filter them with [Read Relationships](../../api-reference/data/read-relationships) +- Check access with [Check API](../../api-reference/permission/check-api) +- Check entities permissions with [Lookup Entity](../../api-reference/permission/lookup-entity) +- Check subject permissions with [Lookup Subject](../../api-reference/permission/lookup-subject) +- Delete relation tuples with [Delete Tuple](../../api-reference/data/delete-data) +- Expand schema actions with [Expand API](../../api-reference/permission/expand-api) +- Watch changes in the relation tuples in real-time with [Watch API](../../api-reference/watch/watch-changes) ## Authentication -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../../reference/configuration) +You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../../setting-up/configuration) To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. +## Latency & Performance + +With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. + +Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../operations/cache) + ## Availability of the Service For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: diff --git a/docs/versioned_docs/version-0.4.x/getting-started/examples/facebook-groups.md b/docs/getting-started/examples/facebook-groups.mdx similarity index 98% rename from docs/versioned_docs/version-0.4.x/getting-started/examples/facebook-groups.md rename to docs/getting-started/examples/facebook-groups.mdx index 1b918630..61fe94e1 100644 --- a/docs/versioned_docs/version-0.4.x/getting-started/examples/facebook-groups.md +++ b/docs/getting-started/examples/facebook-groups.mdx @@ -1,8 +1,6 @@ -# Facebook Groups - This example demonstrate the authorization structure for Facebook groups, which enables users to perform various actions based on their roles and permissions within the group. -### Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) +## Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) ```perm // Represents a user @@ -296,9 +294,7 @@ event:1#group@group:1 Finally, let's check some permissions and test our authorization logic. -
can user:4 RSVP_to_event event:1 ? -

- + ```perm entity event { @@ -321,14 +317,10 @@ According to what we have defined for the **'RSVP_to_event'** action, users who According to the relation tuples we created, `user:4` is not the **owner** of the event. Furthermore, when we check whether `user:4` is a **member** of the only group (`group:1`) that `event:1` is part of (`event:1#group@group:1`), we see that there is no **member** relation for `user:4` in that group. Therefore, the `user:4 RSVP_to_event event:1` check request should yield a **'false'** response. + -

-
- -
can user:5 view_comment comment:1 ? -

- -```perm + + ```perm // Represents a post in a Facebook group entity post { @@ -369,9 +361,7 @@ entity comment { According to the relation tuples we created, `user:5` is not the **owner** of the comment. But member of the `group:1` and thats grant `user:5` (`group:1#member@user:5`) access to perform view the comment:1. In particularly, `comment:1` is part of the `post:1` (`comment:1#post@post:1`) and `post:1` is part of the group:1 (`post:1#group@group:1`). And from the action definition on above model group:1 members can view the `comment:1`. Therefore, the `user:5 view_comment comment:1` check request should yield a **'true'** response. - -

-
+ Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. diff --git a/docs/versioned_docs/version-0.4.x/getting-started/examples/google-docs.md b/docs/getting-started/examples/google-docs.mdx similarity index 97% rename from docs/versioned_docs/version-0.4.x/getting-started/examples/google-docs.md rename to docs/getting-started/examples/google-docs.mdx index 3f14e1f7..12db3e00 100644 --- a/docs/versioned_docs/version-0.4.x/getting-started/examples/google-docs.md +++ b/docs/getting-started/examples/google-docs.mdx @@ -1,5 +1,3 @@ -# Google Docs Simplified - This example models a simplified version of Google Docs style permission system where users can be granted direct access to a document, or access via organizations and nested groups. ### Schema | [Open in playground](https://play.permify.co/?s=iuRic3nR1HeZJcFyRNKPo) @@ -175,10 +173,8 @@ document:hr_documents#viewer@group:hr#direct_member Finally, let's check some permissions and test our authorization logic. -
can user:ashley edit document:product_database ? -

- -```perm + + ```perm entity document { relation org @organization @@ -193,14 +189,10 @@ Finally, let's check some permissions and test our authorization logic. According what we have defined for the edit action managers and admins, of the organization that document belongs, can edit product database. In this context, Permify engine will check does subject `user:ashley` has any direct or indirect manager relation within `document:product_database`. Consecutively it will check does `user:ashley` has admin relation in the Acme Org - `organization:acme#document@document:product_database`. Ashley doesn't have any administrative relation in Acme Org but she is the manager in group tech (`group:tech#manager@user:ashley`) and we have defined that manager of group tech is manager of product_database with the tuple (`document:product_database#manager@group:tech#manager`). Therefore, the **user:ashley edit document:product_database** check request should yield **true** response. + -

-
- -
can user:joe view document:hr_documents ? -

- -```perm + + ```perm entity document { relation org @organization @@ -219,14 +211,10 @@ Joe doesn't have administrative role/relation in Acme Org. Also he doesn't have have manager relationship in that document or within any entity. But he is member in the hr group (`group:hr#member@user:joe`) and we defined hr members have viewer relationship in hr documents (`document:hr_documents#viewer@group:hr#member`). So that, this enforcement should yield **true** response. + -

-
- -
can user:david view document:marketing_materials ? -

- -```perm + + ```perm entity document { relation org @organization @@ -243,9 +231,7 @@ According what we have defined for the view action viewers or managers or org.ad Similar Joe and Ashley, David also doesn't have administrative role/relation in Acme Org. Also David doesn't have member or manager relationship related with marketing group - `document:marketing_materials`. So that, this enforcement should yield **false** response. - -

-
+ Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/instagram.md b/docs/getting-started/examples/instagram.mdx similarity index 89% rename from docs/versioned_docs/version-0.5.x/getting-started/examples/instagram.md rename to docs/getting-started/examples/instagram.mdx index 9cd83163..5353424f 100644 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/instagram.md +++ b/docs/getting-started/examples/instagram.mdx @@ -1,5 +1,3 @@ -# Instagram - This example presents an Instagram Authorization Schema, outlining the intricate relationships between users, accounts, and posts on the platform. It defines user access levels, privacy settings, and interactions, offering insights into how followers, account owners, and post restrictions are managed within the Instagram ecosystem. ## Schema | [Open in playground](https://play.permify.co/?s=instagram&tab=schema) @@ -88,13 +86,9 @@ To validate our authorization logic, let's run some tests on different scenarios ### Test 1: Checking Account Viewing Permissions -
- - Can user:kevin view account:1? - - -

+ + ```perm entity account { relation owner @user @@ -106,18 +100,10 @@ To validate our authorization logic, let's run some tests on different scenarios ``` According to the schema, `user:kevin` is the owner of `account:1`. Hence, `user:kevin` should be able to view `account:1`. The expected result is `'true'`. + -

-
- -
- - Can user:kevin view account:2? - - -

- -```perm + + ```perm entity account { relation owner @user relation following @user @@ -128,17 +114,10 @@ According to the schema, `user:kevin` is the owner of `account:1`. Hence, `user: ``` According to the schema, `user:kevin` follows `account:2`. Hence, `user:kevin` should be able to view `account:2` because he is a follower. The expected result is `'true'`. + -

-
- -
- - Can user:george view account:1? - - -

- + + ```perm entity account { relation owner @user @@ -150,18 +129,10 @@ According to the schema, `user:kevin` follows `account:2`. Hence, `user:kevin` s ``` According to the schema, `user:george` can view `account:1`, because the account is public. Hence, `user:george` should be able to view `account:1`. The expected result is `'true'`. + -

-
- -
- - Can user:george view account:2? - - -

- -```perm + + ```perm entity account { relation owner @user relation following @user @@ -172,17 +143,12 @@ According to the schema, `user:george` can view `account:1`, because the account ``` According to the schema, `user:george` is the owner of `account:2`. Hence, `user:george` should be able to view `account:2`. The expected result is `'true'`. - -

-
+ ### Test 2: Checking Post Viewing Permissions -
Can user:george view post:1? - -

- -```perm + + ```perm entity post { relation account @account attribute restricted boolean @@ -191,13 +157,9 @@ entity post { ``` According to the schema, `post:1` is linked with `account:1`, and it does not have restricted access. Also, `user:george` is following `account:1`. Hence, `user:george` should be able to view `post:1`. The expected result is `'true'`. + -

-
- -
Can user:kevin view post:2? -

- + ```perm entity post { relation account @account @@ -207,14 +169,10 @@ entity post { ``` According to the schema, `post:2` is linked with `account:2`, and it has restricted access. Also, `user:george` is not following `account:1`. Hence, `user:kevin` should not be able to view `post:2`. The expected result is `'false'`. + -

-
- -
Can user:george view post:2? -

- -```perm + + ```perm entity post { relation account @account attribute restricted boolean @@ -223,15 +181,11 @@ entity post { ``` According to the schema, `post:2` is linked with `account:2`, and it is restricted access. Also, `user:george` can view his own `post:2`. The expected result is `'true'`. - -

-
+ ### Test 3: Checking Post Commenting Permissions -
Can user:george comment post:1? -

- + ```perm entity post { relation account @account @@ -241,14 +195,10 @@ entity post { ``` According to the schema, `post:1` is linked with `account:1`, and it is not restricted. Also, `user:george` can comment on `post:1`. The expected result is `'true'`. + -

-
- -
Can user:kevin comment post:2? -

- -```perm + + ```perm entity post { relation account @account attribute restricted boolean @@ -257,9 +207,7 @@ entity post { ``` According to the schema, `post:2` is linked with `account:2`, and it is restricted. `user:kevin` cannot comment on `post:2`. The expected result is `'false'`. - -

-
+ Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. diff --git a/docs/versioned_docs/version-0.6.x/examples.md b/docs/getting-started/examples/intro.mdx similarity index 67% rename from docs/versioned_docs/version-0.6.x/examples.md rename to docs/getting-started/examples/intro.mdx index ecc4d10a..2b76a3c6 100644 --- a/docs/versioned_docs/version-0.6.x/examples.md +++ b/docs/getting-started/examples/intro.mdx @@ -1,8 +1,7 @@ --- id: examples -title: Real World Examples +title: Introduction sidebar_label: Real World Examples -slug: /getting-started/examples --- * [Google Docs]: Explore how users can gain direct access to a document through **organizational roles** or through **inherited/nested permissions**. @@ -11,8 +10,8 @@ slug: /getting-started/examples * [Instagram]: Explore how **public/private attributes** play role in granting access to specific users. * [Mercury]: Explore how **attributes and rules interact within the hierarchical relationships**. -[Google Docs]:./getting-started/examples/google-docs.md -[Facebook Groups]:./getting-started/examples/facebook-groups.md -[Notion]:./getting-started/examples/notion.md -[Instagram]:./getting-started/examples/instagram.md -[Mercury]:./getting-started/examples/mercury.md \ No newline at end of file +[Google Docs]:./google-docs +[Facebook Groups]:./facebook-groups +[Notion]:./notion +[Instagram]:./instagram +[Mercury]:./mercury \ No newline at end of file diff --git a/docs/docs/getting-started/examples/mercury.md b/docs/getting-started/examples/mercury.mdx similarity index 94% rename from docs/docs/getting-started/examples/mercury.md rename to docs/getting-started/examples/mercury.mdx index 796c6211..85b88b13 100644 --- a/docs/docs/getting-started/examples/mercury.md +++ b/docs/getting-started/examples/mercury.mdx @@ -1,12 +1,12 @@ -# Mercury - Explore **Mercury's Authorization Schema** in this example, delving into the intricate interplay among **users**, **organizations**, and **accounts**. Uncover the defined user roles, approval workflows, and limits, providing a snapshot of the dynamic relationships within the Mercury ecosystem. For those who donโ€™t know, Mercury is a bank offering both checking and savings accounts, complete with debit and credit card features. Given the delicate nature of financial transactions, Mercury has built-in access control features to ensure security. But today weโ€™re going to focus on approvals. Mercury allows itโ€™s users to set a number amount for multiple user approval for any action. -For instance, an admin can decide that withdrawals above $1000 by members require approval from two designated approvers. This means, if a member wants to withdraw more than $1000, they need a green light from two admin. And if an admin tries to withdraw they need an approval form another admin. +For instance, an admin can decide that withdrawals above $1000 by members require approval from two designated approvers. + +This means, if a member wants to withdraw more than $1000, they need a green light from two admin. And if an admin tries to withdraw they need an approval form another admin. - Admin โ†’ Withdraw $1000 โ†’ needs an approver - Member โ†’ Withdraw $1000 โ†’ needs 2 approvers. diff --git a/docs/docs/getting-started/examples/notion.md b/docs/getting-started/examples/notion.mdx similarity index 98% rename from docs/docs/getting-started/examples/notion.md rename to docs/getting-started/examples/notion.mdx index 9095d464..d21851ce 100644 --- a/docs/docs/getting-started/examples/notion.md +++ b/docs/getting-started/examples/notion.mdx @@ -1,5 +1,3 @@ -# Notion - This is a schema definition of the authorization model for Notion, a popular productivity and organization tool. ### Schema | [Open in playground](https://play.permify.co/?s=BsCvLmd4g81sB20XJZI5p) @@ -261,10 +259,8 @@ comment:task_list_2_comment_2#author@user:charlie Since we have our schema and the sample relation tuples, let's check some permissions and test our authorization logic. -
can user:alice write database:task_list ? -

- -```perm + + ```perm entity database { // The workspace associated with the database relation workspace @workspace @@ -316,13 +312,10 @@ entity workspace { And as we mentioned the engineering team workspace is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`). Therefore, the `user:alice write database:task_list` check request should yield a **'true'** response. -

-
+ -
can user:charlie write page:product_spec ? -

- -```perm + + ```perm entity page { // The workspace associated with the page relation workspace @workspace @@ -355,9 +348,7 @@ entity workspace { ``` So that, `user:charlie` doesn't have a write relationship in the workspace. And ultimately, the `user:charlie write page:product_spec` check request should yield a **'false'** response. - -

-
+ Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. diff --git a/docs/docs/getting-started/modeling.md b/docs/getting-started/modeling.mdx similarity index 89% rename from docs/docs/getting-started/modeling.md rename to docs/getting-started/modeling.mdx index 2a5fbc60..06d45927 100644 --- a/docs/docs/getting-started/modeling.md +++ b/docs/getting-started/modeling.mdx @@ -1,12 +1,11 @@ --- -sidebar_position: 1 +icon: "code" +title: "Modeling Authorization" --- -# Modeling Authorization +Permify was designed and structured as a true ReBAC solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. -Permify was designed and structured as a true ReBAC solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -With Permify, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. +With Permify, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. @@ -28,9 +27,9 @@ Permify Schema can be created on our [playground](https://play.permify.co/) as w This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. -We'll follow a simplified version of the GitHub access control system, where teams and organizations have control over the viewing, editing, or deleting access rights of repositories. +We'll follow a simplified version of the GitHub access control system, where teams and organizations have control over the viewing, editing, or deleting access rights of repositories. -Before start I want to share the full implementation of simple Github access control example with using Permify Schema. +Before start I want to share the full implementation of simple Github access control example with using Permify Schema. ```perm entity user {} @@ -68,14 +67,14 @@ entity repository { } ``` -:::info + You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. [vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm -::: + -## Defining Entities +## Defining Entities The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. @@ -100,7 +99,7 @@ Entities has 2 different attributes. These are; ## Defining Relations -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. +Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. Keyword **_relation_** need to used to create a entity relation with name and type attributes. @@ -117,7 +116,7 @@ relation [name] @[type] Lets turn back to our example and define our relations inside our entities: -#### User Entity +### User Entity โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. @@ -127,7 +126,7 @@ entity user {} ### Roles and User Types -You can define user types and roles within the entity. If you specifically want to define a global role, such as `admin`, we advise defining it at the entity with the most global hierarchy, such as an organization. Then, spread it to the rest of the entities to include it within permissions. +You can define user types and roles within the entity. If you specifically want to define a global role, such as `admin`, we advise defining it at the entity with the most global hierarchy, such as an organization. Then, spread it to the rest of the entities to include it within permissions. For the sake of simplicity, let's define only 2 user types in an organization, these are administrators and direct members of the organization. @@ -189,7 +188,7 @@ As you can see we have new syntax above, When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. -:::info + You can use **#** to reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. Actual purpose of feature locking is to giving ability to specify the sets of users that can be assigned. @@ -217,19 +216,19 @@ You will then be able to specify not only individual users but also members of a - organization:1#viewer@user:U2 - organization:1#viewer@organization:O1#member - With `organization:1#viewer@organization:O1#member` all members of the organization O1 will have the right to perform the relevant action. In other words, all members in O1 now end up having the relevant `viewer` relation. You can think of these definitions as a precaution taken against creating undesired user set relationships. -::: + + Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. ## Defining Actions and Permissions -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. +Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. The basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. @@ -260,7 +259,7 @@ entity repository { โ†’ If we examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. -:::info Permission Keyword + The same `read` can also be defined using the **permission** keyword, as follows: ```perm @@ -268,15 +267,16 @@ The same `read` can also be defined using the **permission** keyword, as follows ``` Using `action` and `permission` will yield the same result for defining permissions in your authorization logic. See why we have 2 keywords for defining an permission from the [Nested Hierarchies](#nested-hierarchies) section. -::: + + #### Exclusion -After this point, we'll move beyond the GitHub example and explore more advanced abilities of Permify DSL. +After this point, we'll move beyond the GitHub example and explore more advanced abilities of Permify DSL. Before delving into details, let's examine the **`not`** operator and conclude [Intersection and Exclusion](#intersection-and-exclusion) section. -Here is the **post** entity from our sample [Instagram Authorization Structure](./examples/google-docs.md)example, +Here is the **post** entity from our sample [Instagram Authorization Structure](./examples/instagram) example, ```perm entity post { @@ -301,7 +301,7 @@ This is a simple example to demonstrate how you can exclude users, resources, or ### Permission Union -Permify allows you to set permissions that are effectively the union of multiple permission sets. +Permify allows you to set permissions that are effectively the union of multiple permission sets. You can define permissions as relations to union all rules that permissions have. Here is an simple demonstration how to achieve permission union in our DSL, you can use actions (or permissions) when defining another action (or permission) like relations, @@ -314,20 +314,20 @@ The `delete` action inherits the rules from the `edit` action. By doing that, we Permission union is super beneficial in scenarios where a user needs to have varied access across different departments or roles. -### Nested Hierarchies +### Nested Hierarchies The reason we have two keywords for defining permissions (`action` and `permission`) is that while most permissions are based on actions (such as view, read, edit, etc.), there are still cases where we need to define permissions based on roles or user types, such as admin or member. -Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. +Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. Here is a simple example to demonstrate inherited permissions. -Let's examine a small snippet from our [Facebook Groups](./examples/google-docs.md) real world example. Let's create a permission called 'view' in the comment entity (which represents the comments of the post in Facebook Groups) +Let's examine a small snippet from our [Facebook Groups](./examples/google-docs) real world example. Let's create a permission called 'view' in the comment entity (which represents the comments of the post in Facebook Groups) Users can only view a comment if: -- The user is the owner of that comment -**or** +- The user is the owner of that comment + **or** - The user is a member of the group to which the comment's post belongs. ```perm @@ -341,7 +341,7 @@ entity post { relation group @group // Permissions for the post entity - + .. .. permission group_member = group.member @@ -360,7 +360,7 @@ entity comment { .. .. - // Permissions + // Permissions action view = owner or post.group_member .. @@ -374,7 +374,7 @@ The `post.group_member` refers to the members of the group to which the post bel permission group_member = group.member ``` -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. +Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. @@ -384,7 +384,7 @@ With Permify DSL, you can define recursive relationship-based permissions within As an example, consider a system where there are multiple organizations within a company, some of which may have a parent-child relationship between them. -As expected, organization members are also granted permission to view their organization details. You can model that as follows: +As expected, organization members are also granted permission to view their organization details. You can model that as follows: ```perm entity user {} @@ -401,9 +401,9 @@ Let's extend the scenario by adding a rule allowing parent organization members ![modeling-authorization](https://user-images.githubusercontent.com/58391988/279456032-485a0aef-b83b-4257-af48-0fcbe6fa2e64.png) -First authorization schema that we provide won't solve this issue because `parent.member` accommodate single upward traversal in a hierarchy. +First authorization schema that we provide won't solve this issue because `parent.member` accommodate single upward traversal in a hierarchy. -Instead of `parent.member` we can call the parent view permission on the same entity - `parent.view` to achieve multiple levels of upward traversal, as follows: +Instead of `parent.member` we can call the parent view permission on the same entity - `parent.view` to achieve multiple levels of upward traversal, as follows: ```perm entity user {} @@ -418,9 +418,10 @@ entity organization { This way, we achieve a recursive relationship between parent-child organizations. -:::note -*Credits to [Lรฉo](https://github.com/LeoFVO) for the illustration and for [highlighting](https://github.com/Permify/permify/issues/790) this use case.* -::: + + *Credits to [Lรฉo](https://github.com/LeoFVO) for the illustration and for + [highlighting](https://github.com/Permify/permify/issues/790) this use case.* + ## Attribute Based Permissions (ABAC) @@ -450,7 +451,7 @@ string string[] // An integer attribute type. -integer +integer // An integer array attribute type. integer[] @@ -472,7 +473,7 @@ In the following example schema, a rule could be used to check if a given IP add entity user {} entity organization { - + relation admin @user attribute ip_range string[] @@ -485,11 +486,12 @@ rule check_ip_range(ip string, ip_range string[]) { } ``` -:::info Syntax -We design our schema language based on [Common Expression Language (CEL)](https://github.com/google/cel-go). So the syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. + +We design our schema language based on [Common Expression Language (CEL)](https://github.com/google/cel-go). So the syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. Please let us know via our [Discord channel](https://discord.gg/n6KfzYxhPp) if you have questions regarding syntax, definitions or any operator you identify not working as expected. -::: + + Let's examine some of common usage of ABAC with small schema examples. @@ -500,14 +502,15 @@ For attributes that represent a binary choice or state, such as a yes/no questio ```perm entity post { attribute is_public boolean - + permission view = is_public } ``` -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: + + โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as + `FALSE` + ### Text & Object Based Conditions @@ -522,7 +525,7 @@ String can be used as attribute data type in a variety of scenarios where text-b entity user {} entity organization { - + relation admin @user attribute location string[] @@ -535,15 +538,16 @@ rule check_location(current_location string, location string[]) { } ``` -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: + + โ›” If you donโ€™t create the related attribute data, Permify accounts string as + `""` + ### Numerical Conditions #### Integers -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: +Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. - **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). @@ -560,9 +564,10 @@ rule check_age(age integer) { } ``` -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: + + โ›” If you donโ€™t create the related attribute data, Permify accounts integer as + `0` + #### Double - Precise numerical information @@ -588,9 +593,10 @@ rule check_balance(amount double, balance double) { } ``` -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: + + โ›” If you donโ€™t create the related attribute data, Permify accounts double as + `0.0` + See more details on [Attribute Based Access Control](#attribute-based-permissions-abac) section to learn our approach on ABAC as well as how it operates in Permify. you can see more comprehensive ABAC examples in the [Example ABAC Use Cases](../use-cases/abac/#example-use-cases) section in related page. @@ -600,14 +606,14 @@ You can check out more comprehensive schema examples from the [Real World Exampl Here is what each example focuses on, -* [Google Docs]: how users can gain direct access to a document through **organizational roles** or through **inherited/nested permissions**. -* [Facebook Groups]: how users can perform various actions based on the **roles and permissions within the groups** they belong. -* [Notion]: how **one global entity (workspace) can manage access rights** in the child entities that belong to it. -* [Instagram]: how **public/private attributes** play role in granting access to specific users. -* [Mercury]: how **attributes and rules interact within the hierarchical relationships**. - -[Google Docs]:./examples/google-docs.md -[Facebook Groups]:./examples/facebook-groups.md -[Notion]:./examples/notion.md -[Instagram]:./examples/instagram.md -[Mercury]:./examples/mercury.md \ No newline at end of file +- [Google Docs]: how users can gain direct access to a document through **organizational roles** or through **inherited/nested permissions**. +- [Facebook Groups]: how users can perform various actions based on the **roles and permissions within the groups** they belong. +- [Notion]: how **one global entity (workspace) can manage access rights** in the child entities that belong to it. +- [Instagram]: how **public/private attributes** play role in granting access to specific users. +- [Mercury]: how **attributes and rules interact within the hierarchical relationships**. + +[Google Docs]: ./examples/google-docs +[Facebook Groups]: ./examples/facebook-groups +[Notion]: ./examples/notion +[Instagram]: ./examples/instagram +[Mercury]: ./examples/mercury diff --git a/docs/versioned_docs/version-0.6.x/installation/overview.md b/docs/getting-started/quickstart.mdx similarity index 81% rename from docs/versioned_docs/version-0.6.x/installation/overview.md rename to docs/getting-started/quickstart.mdx index 76fb8a56..b05c6674 100644 --- a/docs/versioned_docs/version-0.6.x/installation/overview.md +++ b/docs/getting-started/quickstart.mdx @@ -1,25 +1,23 @@ --- -sidebar_position: 1 +icon: "rocket" +title: Quickstart --- -# Guide - This guide shows you how to set up Permify in your servers and use it across your applications. -:::info Minimum Requirements +## Minimum Requirements PostgreSQL: Version 13.8 or higher -::: Please ensure your system meets these requirements before proceeding with the following steps: 1. [Set Up & Run Permify Service](#set-up-permify-service) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Manage and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) +2. [Model your Authorization with Permify Schema](#model-your-authorization-with-permify-schema) +3. [Store Authorization Data & Schema](#store-authorization-data) 4. [Perform Access Check](#perform-access-check) -:::info Talk to an Permify Engineer + Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: + ## Set Up Permify Service @@ -40,11 +38,11 @@ This will start Permify with the default configuration options: * Port 3478 is used to serve the GRPC Service. * Authorization data stored in memory. -:::info + You can examine [Deploy using Docker] section to get more about the configuration options and learn the full integration to run Permify Service from docker container. -[Deploy using Docker]: ../container -::: +[Deploy using Docker]: ../setting-up/installation/container + ### Test your connection @@ -56,10 +54,30 @@ localhost:3476/healthz You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core endpoints. -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) +[Using the API]: ../getting-started/enforcement + + ## Model your Authorization with Permify Schema @@ -71,14 +89,13 @@ You can define your entities, relations between them and access control decision Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. -:::caution Use Playground For Testing + If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. ![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: + Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. @@ -106,45 +123,44 @@ Lets roll back our example, - Organization member and admin can view files. - Only admins can edit files. -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. + +For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also you can check out [Real World Examples] section to better understand some familiar use cases modeled with Permify Schema. -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../use-cases/simple-rbac -::: +[Modeling Authorization with Permify]: ../getting-started/modeling +[Real World Examples]: ../getting-started/examples/intro + ### Configuring Schema via API -After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../api-overview/schema/write-schema.md) for configuration of your authorization model on Permify authorization service. +After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../../api-reference/schema/write-schema) for configuration of your authorization model on Permify authorization service. -:::caution Before Continue on Writing Schema + You'll see **tenant_id** parameter almost all Permify APIs including Write Schema. With version 0.3.x Permify became a tenancy based authorization infrastructure, and supports multi-tenancy by default so its a mandatory parameter when doing any operations. -We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. -::: +We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. + -#### Example HTTP Request on Postman: + +**Example HTTP Request on Postman:** | Required | Argument | Type | Default | Description | |----------|-------------------|--------|---------|-------------| | [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | [x] | schema | string | - | Permify Schema as string| -**POST /v1/tenants/{tenant_id}/schemas/write** +**POST** `/v1/tenants/{tenant_id}/schemas/write` ![permify-schema](https://user-images.githubusercontent.com/34595361/214457054-19b141ac-6bfa-4db4-aeab-f7b7149c3351.png) -## Store Authorization Data as Relational Tuples +## Store Authorization Data After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. -### Create Relational Tuples - -You can create relational tuples as authorization rules by using [Write Data API](../api-overview/data/write-data.md) +You can write relationships and attributes as ACLs by using [Write Data API](../../api-reference/data/write-data) For our guide let's grant one of the team members (Ashley) an admin role. -#### Example HTTP Request on Postman: +**Example HTTP Request on Postman:** | Required | Argument | Type | Default | Description | |----------|-------------------|--------|---------|-------------| @@ -155,7 +171,7 @@ For our guide let's grant one of the team members (Ashley) an admin role. | [x] | subject | string | - | User or user set who wants to take the action. | | [ ] | schema_version | string | 8 | Version of the schema | -**POST /v1/tenants/{tenant_id}/data/write** +**POST** `/v1/tenants/{tenant_id}/data/write` ```json { @@ -181,17 +197,17 @@ For our guide let's grant one of the team members (Ashley) an admin role. ![write-data](https://user-images.githubusercontent.com/34595361/214458203-8264e141-642d-48b0-9242-416bbf6f8795.png) -**Created relational tuple:** organization:1#admin@user:1 +**Created relationship:** organization:1#admin@user:1 **Semantics:** User 1 (Ashley) has admin role on organization 1. -:::tip + In ideal production usage Permify stores your authorization data in a database you prefer. You can configure the database with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) or CLI flag options. But in this tutorial Permify Service running default configurations on local, so authorization data will be stored in memory. You can find more detailed explanation how Permify stores authorization data in [Managing Authorization Data] section. -[Managing Authorization Data]: ../../getting-started/sync-data -::: +[Managing Authorization Data]: ../getting-started/sync-data + ## Perform Access Check @@ -199,14 +215,12 @@ Finally we're ready to control authorization. Access decision results computed a Lets get back to our example and perform an example access check via [Check API]. We want to check whether an specific user has an access to view files in a organization. -[Check API]: ../../api-overview/permission/check-api -[Permify Schema]: ../../getting-started/modeling - -#### Example HTTP Request: +[Check API]: ../../../api-reference/permission/check-api +[Permify Schema]: ../getting-started/modeling ***Can the user 45 view files on organization 1 ?*** -**POST /v1/tenants/{tenant_id}/permissions/check** +**POST** `/v1/tenants/{tenant_id}/permissions/check` | Required | Argument | Type | Default | Description | |----------|----------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------| @@ -217,7 +231,7 @@ Lets get back to our example and perform an example access check via [Check API] | [ ] | schema_version | string | - | get results according to given schema version | | [ ] | depth | integer | 8 | - | -### Request +**Request:** ```json { @@ -239,7 +253,7 @@ Lets get back to our example and perform an example access check via [Check API] } ``` -### Response +**Response** ```json { @@ -252,7 +266,7 @@ Lets get back to our example and perform an example access check via [Check API] See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify -[Access Control Check]: ../api-overview/permission/check-api.md +[Access Control Check]: ../../api-reference/permission/check-api ## Need any help ? diff --git a/docs/docs/getting-started/sync-data.md b/docs/getting-started/sync-data.mdx similarity index 89% rename from docs/docs/getting-started/sync-data.md rename to docs/getting-started/sync-data.mdx index 07f0759f..1fde3de0 100644 --- a/docs/docs/getting-started/sync-data.md +++ b/docs/getting-started/sync-data.mdx @@ -1,19 +1,16 @@ --- sidebar_position: 2 +title: Storing Data & Schema +icon: 'database' --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Managing Authorization Data - -Permify unifies your authorization data in a database of your preference, which serves as the single source of truth for all authorization queries and requests via the Permify API. +Permify unifies your authorization data and the authorization schemas you have in a database of your preference, which serves as the single source of truth for all authorization queries and requests via the Permify API. In Permify, you can store authorization data in two different forms: as **relationships** and as **attributes**. Let's examine relationships first. -## Access Control as Relationships +## Relationships In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). @@ -45,7 +42,7 @@ Here are some attributes with semantics, These relational tuples and attributes represents your authorization data. -Permify stores your these data in a database you prefer. You can configure the database when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). +Permify stores your these data in a database you prefer. You can configure the database when running Permify Service with using [configuration options](../setting-up/configuration). Stored data are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user's action is authorized. @@ -57,7 +54,7 @@ Relationships and attributes can be created with an simple API call, Since these Each relational tuple or attribute should be created according to its authorization model, [Permify Schema]. -[Permify Schema]: ../modeling +[Permify Schema]: ./modeling ![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) @@ -94,7 +91,7 @@ According to the schema above; when a user creates a document in an organization You can create relational tuples by using `Write Data API`. - + ```go rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { @@ -117,10 +114,9 @@ rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { }, }) ``` + - - - + ```javascript client.data.write({ @@ -144,8 +140,8 @@ client.data.write({ }) ``` - - + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -170,7 +166,7 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write ] }' ``` - + ### Snap Tokens @@ -183,7 +179,7 @@ In Write Data API response you'll get a snap token of the operation. } ``` -This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../reference/snap-tokens.md) +This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../operations/snap-tokens) ## More Examples @@ -196,7 +192,7 @@ Let's create more example data according to the schema we defined above. **Semantics:** User 3 is administrator in organization 1. - + ```go rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { @@ -220,9 +216,9 @@ rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { }) ``` - + - + ```javascript client.data.write({ @@ -246,8 +242,8 @@ client.data.write({ }) ``` - - + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -272,7 +268,7 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write ] }' ``` - + ### Parent Organization @@ -282,7 +278,7 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write **Semantics:** Organization 1 is parent of document 1. - + ```go rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { @@ -307,9 +303,9 @@ rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { }) ``` - + - + ```javascript client.data.write({ @@ -334,8 +330,8 @@ client.data.write({ }) ``` - - + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -360,14 +356,14 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write ] }' ``` - + -:::info + Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. Simply, the usage of ... is straightforward: if you're use user entity as an subject, you should not be using the `...` If you're using another subject rather than user entity then you need to use the `...` -::: + ### Organization Members Are Maintainers in specific Doc @@ -376,7 +372,7 @@ Simply, the usage of ... is straightforward: if you're use user entity as an sub **Definition:** Members of organization 2 are maintainers in document 1. - + ```go rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { @@ -401,9 +397,9 @@ rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { }) ``` - + - + ```javascript client.data.write({ @@ -428,8 +424,8 @@ client.data.write({ }) ``` - - + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ @@ -454,10 +450,10 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write ] }' ``` - + -#### Test this Example on [Playground](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_) +**Test this Example on [Playground](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_)** ## Audit Logs For Permission Changes @@ -473,8 +469,4 @@ We have a strong foundation for permission baselining and review, thanks to MVCC **Current State Review:** You can review the current state of permissions by examining the latest versions of each permission setting. -**Cleanup:** Your system incorporates a garbage collector for managing old data, which helps keep your permissions structure clean and optimized. - -## Next - -Let's now head over to the **Access Control Check** section and learn how to perform access control in Permify to ensure that only authorized users have the right level of access to our resources. +**Cleanup:** Your system incorporates a garbage collector for managing old data, which helps keep your permissions structure clean and optimized. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/getting-started/testing.md b/docs/getting-started/testing.mdx similarity index 97% rename from docs/versioned_docs/version-0.5.x/getting-started/testing.md rename to docs/getting-started/testing.mdx index 977fcf2d..6cad8524 100644 --- a/docs/versioned_docs/version-0.5.x/getting-started/testing.md +++ b/docs/getting-started/testing.mdx @@ -1,18 +1,18 @@ --- sidebar_position: 4 +icon: 'vial' +title: 'Testing & Validation' --- -# Testing & Validation - Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). -:::info + If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: + ## Adding Validate Action To Your Workflow @@ -34,9 +34,9 @@ steps: validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" ``` -:::info + If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: + ## Schema Validation File @@ -188,11 +188,11 @@ checks: Semantics for above check is: whether `user:1` can push to `repository:3`, additional to stored tuples take account that user:1 is owner of repository:3 (`repository:3#owner@user:1`). Expected result for that check it **true** - `push : true` -:::info Contextual Tuples + We use `context` (Contextual Tuples) with simple relational tuples for simplicity in this example. However, it is primarily used for dynamic access checks, such as those involving time, date, or IP address, etc. -To learn more about how `context` works, see the [Contextual Tuples](../../reference/contextual-tuples) section. -::: +To learn more about how `context` works, see the [Contextual Tuples](../operations/contextual-tuples) section. + ### Entity Filtering @@ -223,9 +223,9 @@ You can create `subject_filters` within `scenarios` to test your subject filteri edit : ["58"] ``` -:::info API Endpoints -You can find the related API endpoints for `check`, `entity_filters`, and `subject_filters` in the Permission service in the [Using The API](../../api-overview) section. -::: + +You can find the related API endpoints for `check`, `entity_filters`, and `subject_filters` in the Permission service in the [Using The API](../getting-started/enforcement) section. + ## Coverage Analysis @@ -268,7 +268,7 @@ For managing permission/relation changes, we suggest storing schema in an abstra We recommend adding ourย [schema validator](https://github.com/Permify/permify-validate-action)ย to the pipeline to ensure that any changes are automatically validated. -You can find more details about our suggested workflow to handle schema changes in [Write Schema](../../api-overview/schema/write-schema#suggested-workflow-for-schema-changes) section. +You can find more details about our suggested workflow to handle schema changes in following [FAQS page](../introduction/faqs#how-to-manage-schema-changes). ## Need any help ? diff --git a/docs/images/checks-passed.png b/docs/images/checks-passed.png new file mode 100644 index 00000000..3303c773 Binary files /dev/null and b/docs/images/checks-passed.png differ diff --git a/docs/images/hero-dark.svg b/docs/images/hero-dark.svg new file mode 100644 index 00000000..c6a30e88 --- /dev/null +++ b/docs/images/hero-dark.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/hero-light.svg b/docs/images/hero-light.svg new file mode 100644 index 00000000..297d68fb --- /dev/null +++ b/docs/images/hero-light.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/logo/.DS_Store b/docs/logo/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/docs/logo/.DS_Store differ diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg new file mode 100644 index 00000000..a784a21f --- /dev/null +++ b/docs/logo/dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 00000000..c4158bf8 --- /dev/null +++ b/docs/logo/light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/mint.json b/docs/mint.json new file mode 100644 index 00000000..4c5bbe79 --- /dev/null +++ b/docs/mint.json @@ -0,0 +1,189 @@ +{ + "$schema": "https://mintlify.com/schema.json", + "name": "Permify Docs", + "logo": { + "dark": "/logo/dark.svg", + "light": "/logo/light.svg" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#8246FF", + "light": "#8246FF", + "dark": "#6318FF", + "anchors": { + "from": "#8246FF", + "to": "#8246FF" + } + }, + "openapi": ["./api-reference/openapi.json"], + "topbarLinks": [ + { + "name": "Support", + "url": "https://discord.com/invite/n6KfzYxhPp" + } + ], + "modeToggle": { + "default": "dark" + }, + "analytics": { + "plausible": { + "domain": "docs.permify.co" + } + }, + "topbarCtaButton": { + "name": "Playground", + "url": "https://play.permify.co/?s=organizations-hierarchies" + }, + "tabs": [ + { + "name": "API Reference", + "url": "api-reference" + } + ], + "anchors": [ + { + "name": "Community", + "icon": "discord", + "url": "https://discord.com/invite/n6KfzYxhPp" + }, + { + "name": "Open Source", + "icon": "github", + "url": "https://github.com/Permify/permify" + } + ], + "navigation": [ + { + "group": "Introduction", + "pages": [ + "permify-overview/intro", + "permify-overview/authorization-service", + "permify-overview/faqs" + ] + }, + { + "group": "Getting Started", + "pages": [ + "getting-started/quickstart", + "getting-started/modeling", + "getting-started/sync-data", + "getting-started/enforcement", + "getting-started/testing", + { + "group": "Real World Examples", + "icon": "globe", + "pages": [ + "getting-started/examples/intro", + "getting-started/examples/facebook-groups", + "getting-started/examples/google-docs", + "getting-started/examples/instagram", + "getting-started/examples/mercury", + "getting-started/examples/notion" + ] + } + ] + }, + { + "group": "Setting Up", + "icon": "cloud-arrow-up", + "pages": [ + "setting-up/configuration", + { + "group": "Deployment", + "icon": "cloud-arrow-up", + "pages": [ + "setting-up/installation/intro", + "setting-up/installation/aws", + "setting-up/installation/brew", + "setting-up/installation/container", + "setting-up/installation/google", + "setting-up/installation/helm", + "setting-up/installation/kubernetes" + ] + } + ] + }, + { + "group": "Operations", + "pages": [ + "operations/bundle", + "operations/cache", + "operations/contextual-tuples", + "operations/tracing", + "operations/snap-tokens" + ] + }, + { + "group": "Use Cases", + "pages": [ + "use-cases/rbac", + "use-cases/rebac", + "use-cases/abac", + "use-cases/custom-roles", + "use-cases/multi-tenancy" + ] + }, + { + "group": "API Documentation", + "pages": [ + "api-reference/introduction" + ] + }, + { + "group": "Schema Service", + "pages": [ + "api-reference/schema/write-schema", + "api-reference/schema/list-schema", + "api-reference/schema/read-schema" + ] + }, + { + "group": "Data Service", + "pages": [ + "api-reference/data/write-data", + "api-reference/data/read-relationships", + "api-reference/data/read-attributes", + "api-reference/data/run-bundle", + "api-reference/data/delete-data" + ] + }, + { + "group": "Permission Service", + "pages": [ + "api-reference/permission/check-api", + "api-reference/permission/expand-api", + "api-reference/permission/lookup-subject", + "api-reference/permission/lookup-entity", + "api-reference/permission/lookup-entity-stream", + "api-reference/permission/subject-permission" + ] + }, + { + "group": "Tenancy Service", + "pages": [ + "api-reference/tenancy/list-tenants", + "api-reference/tenancy/create-tenant", + "api-reference/tenancy/delete-tenant" + ] + }, + { + "group": "Bundle Service", + "pages": [ + "api-reference/bundle/write-bundle", + "api-reference/bundle/read-bundle", + "api-reference/bundle/delete-bundle" + ] + }, + { + "group": "Watch Service", + "pages": [ + "api-reference/watch/watch-changes" + ] + } + ], + "footerSocials": { + "twitter": "https://twitter.com/getPermify", + "github": "https://github.com/Permify/permify", + "linkedin": "https://www.linkedin.com/company/permifyco" + } +} \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/bundle.md b/docs/operations/bundle.mdx similarity index 71% rename from docs/versioned_docs/version-0.6.x/bundle.md rename to docs/operations/bundle.mdx index ca7db9ec..fd23cb77 100644 --- a/docs/versioned_docs/version-0.6.x/bundle.md +++ b/docs/operations/bundle.mdx @@ -1,15 +1,13 @@ --- -id: bundle -title: Bundle Service -sidebar_label: Data Bundles -slug: /api-overview/bundle +icon: cube +title: Data Bundles --- ## What is Data Bundles Ensuring that authorization data remains in sync with the business model is an important practice when using Permify. -Prior to Data Bundles, it was the responsibility of the services (such as [WriteData](./api-overview/data/write-data.md) API) to structure how relations are created and deleted when actions occur on resources. +Prior to Data Bundles, it was the responsibility of the services (such as [WriteData](../api-reference/data/write-data) API) to structure how relations are created and deleted when actions occur on resources. With the Data Bundles, you be able to bundle and model the creation and deletion of relations and attributes when specific actions occur on resources in your applications. @@ -19,7 +17,7 @@ We believe this functionality will streamline managing authorization data as wel Let's examine how Bundles operates with basic example. -Let's say you want to model how data will be created when an organization created in your application. For this purpose, you can utilize the [WriteBundle](./api-overview/bundle/write-bundle.md) API endpoint. This API enables users to define or update data bundles, each distinguished by a unique name. +Let's say you want to model how data will be created when an organization created in your application. For this purpose, you can utilize the [WriteBundle](../../api-reference/bundle/write-bundle) API endpoint. This API enables users to define or update data bundles, each distinguished by a unique name. Here's an example body for WriteBundle in this scenario: @@ -54,9 +52,9 @@ Let's say user:564 creates an organization:789 in your application. According to - organization:789#manager@user:564 - organization:789$public|boolean:false -Instead of using the [WriteData](./api-overview/data/write-data.md) endpoint, you can utilize [RunBundle](./api-overview/data/run-bundle.md) to create this data by simply providing specific identifiers. +Instead of using the [WriteData](../../api-reference/data/write-data) endpoint, you can utilize [RunBundle](../../api-reference/data/run-bundle) to create this data by simply providing specific identifiers. -An example request of [RunBundle](./api-overview/data/run-bundle.md) for this scenario: +An example request of [RunBundle](../../api-reference/data/run-bundle) for this scenario: ```json POST /bundle @@ -78,7 +76,7 @@ This will result in the creation of the following data in Permify: ## Endpoints -- [WriteBundle](./api-overview/bundle/write-bundle.md) -- [RunBundle](./api-overview/data/run-bundle.md) -- [DeleteBundle](./api-overview/bundle/delete-bundle.md) -- [ReadBundle](./api-overview/bundle/read-bundle.md) +- [WriteBundle](../../api-reference/bundle/write-bundle) +- [RunBundle](../../api-reference/data/run-bundle) +- [DeleteBundle](../../api-reference/bundle/delete-bundle) +- [ReadBundle](../../api-reference/bundle/read-bundle) diff --git a/docs/versioned_docs/version-0.5.x/reference/cache.md b/docs/operations/cache.mdx similarity index 90% rename from docs/versioned_docs/version-0.5.x/reference/cache.md rename to docs/operations/cache.mdx index 1910705a..f97af926 100644 --- a/docs/versioned_docs/version-0.5.x/reference/cache.md +++ b/docs/operations/cache.mdx @@ -1,4 +1,7 @@ -# Cache Mechanisms +--- +icon: hard-drive +title: Cache Mechanisms +--- This section showcases the cache mechanisms that Permify uses. @@ -9,14 +12,14 @@ Schemas are stored in an in-memory cache based on their versions. If a version i The size of this can be determined through the Permify configuration. Here is an example configuration: service: -```yaml +```yaml โ€ฆ schema: cache: number_of_counters: 1_000 max_cost: 10MiB โ€ฆ -``` +``` The cache library used is: https://github.com/dgraph-io/ristretto @@ -25,14 +28,14 @@ The cache library used is: https://github.com/dgraph-io/ristretto Permify applies the MVCC (Multi Version Concurrency Control) pattern for Postgres, creating a separate database snapshot for each write and delete operation. This both enhances performance and provides a consistent cache. An example of a cache key is: -check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request} +`check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request}` Permify hashes each request and searches for the same key. If it cannot find it, it runs the check engine and writes to the cache, thus creating a consistently working hash. The size of this can also be determined via the Permify configuration. Hereโ€™s an example: service: -```yaml +```yaml โ€ฆ permission: bulk_limit: 100 @@ -41,7 +44,7 @@ service: number_of_counters: 10_000 max_cost: 10MiB โ€ฆ -``` +``` The cache library used is: https://github.com/dgraph-io/ristretto @@ -49,7 +52,7 @@ Note: Another advantage of the MVCC pattern is the ability to historically store ## Distributed Cache -Permify does provide a distributed cache across availability zones (within an AWS region) via **Consistent Hashing**. Permify uses Consistent Hashing across its distributed instances for more efficient use of their individual caches. +Permify does provide a distributed cache across availability zones (within an AWS region) via **Consistent Hashing**. Permify uses Consistent Hashing across its distributed instances for more efficient use of their individual caches. This would allow for high availability and resilience in the face of individual nodes or even entire availability zone failure, as well as improved performance due to data locality benefits. @@ -65,13 +68,16 @@ Using this consistent hashing approach, we can effectively utilize individual ca You can learn more about consistent hashing from the following blog post: [Introducing Consistent Hashing](https://itnext.io/introducing-consistent-hashing-9a289769052e) -:::info -Note, however, that while the consistent hashing approach will distribute keys evenly across the cache nodes, it's up to the application logic to ensure the cache is used effectively (i.e., that it reads from and writes to the cache appropriately). -::: + + Note that while the consistent hashing approach will distribute keys evenly + across the cache nodes, it's up to the application logic to ensure the cache + is used effectively (i.e., that it reads from and writes to the cache + appropriately). + Here is an example configuration: -```yaml +```yaml distributed: # Indicates whether the distributed mode is enabled or not enabled: true @@ -83,13 +89,10 @@ distributed: # The port on which the service is exposed port: "5000" -``` +``` Additional to that weโ€™re using a [circuit breaker](https://blog.bitsrc.io/circuit-breaker-pattern-in-microservices-26bf6e5b21ff) pattern to detect and handle failures when the underlying database is unavailable. It prevents unnecessary calls when the database is down and handles the process on the rebooting phase. ## Need any help ? Our team is happy help you to structure right architecture for your permission system. Feel free to [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - diff --git a/docs/versioned_docs/version-0.6.x/reference/contextual-tuples.md b/docs/operations/contextual-tuples.mdx similarity index 74% rename from docs/versioned_docs/version-0.6.x/reference/contextual-tuples.md rename to docs/operations/contextual-tuples.mdx index 3765e967..eea6415b 100644 --- a/docs/versioned_docs/version-0.6.x/reference/contextual-tuples.md +++ b/docs/operations/contextual-tuples.mdx @@ -1,7 +1,7 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Contextual Data (Dynamic Permissions) +--- +icon: tags +title: Contextual Permissions +--- Contextual tuples are relations that can be dynamically added to permission request operations. When you send these relations along with your requests, they get processed alongside existing relations in the database and will return a result. @@ -76,7 +76,7 @@ So let's say user Ashley trying to view employee X. And lets assume that, - She connected to VPN which connected to network 192.158.1.38 - which is Branch's internal network. - + ```go data, err := structpb.NewStruct(map[string]interface{}{ @@ -111,8 +111,8 @@ cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequ }) ``` - - + + ```javascript client.permission @@ -147,8 +147,57 @@ client.permission }); ``` - - + + + +```python +import permify +from permify.models.permission_check_request import PermissionCheckRequest +from permify.models.permission_check_response import PermissionCheckResponse +from permify.rest import ApiException +from pprint import pprint + +configuration = permify.Configuration(host="http://localhost") + +with permify.ApiClient(configuration) as api_client: + api_instance = permify.PermissionApi(api_client) + tenant_id = 't1' + + body = PermissionCheckRequest( + tenant_id=tenant_id, + metadata={ + "snapToken": "", + "schemaVersion": "", + "depth": 20 + }, + entity={ + "type": "organization", + "id": "1", + }, + permission="hr_manager", + subject={ + "type": "user", + "id": "1", + }, + context={ + "data": { + "ip_address": "192.158.1.38", + }, + }, + ) + + try: + api_response = api_instance.permissions_check(tenant_id, body) + if api_response.can == PermissionCheckResponse.Result.RESULT_ALLOWED: + print("RESULT_ALLOWED") + else: + print("RESULT_DENIED") + except ApiException as e: + print(f"Exception when calling PermissionApi->permissions_check: {e}") +``` + + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ @@ -177,12 +226,16 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permission }' ``` - + -:::info -Besides data, you can also provide relational tuples and attributes alongside the access check using contextual tuples. You can view the full parameters for the [permission check in our swagger docs](https://permify.github.io/permify-swagger/#/Permission/permissions.check). -::: + + Besides data, you can also provide relational tuples and attributes alongside + the access check using contextual tuples. You can view the full parameters for + the [permission check in our swagger + docs](https://permify.github.io/permify-swagger/#/Permission/permissions.check). + + ## Need any help ? diff --git a/docs/versioned_docs/version-0.6.x/reference/snap-tokens.md b/docs/operations/snap-tokens.mdx similarity index 93% rename from docs/versioned_docs/version-0.6.x/reference/snap-tokens.md rename to docs/operations/snap-tokens.mdx index 95eec606..b4ed3e7e 100644 --- a/docs/versioned_docs/version-0.6.x/reference/snap-tokens.md +++ b/docs/operations/snap-tokens.mdx @@ -1,5 +1,7 @@ - -# Snap Tokens & Zookies +--- +icon: equals +title: Consistency (Snap Tokens) +--- A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. @@ -39,10 +41,10 @@ Then this snap token can be used in endpoints. For example it can be used in acc } ``` -[Write API]: ../../api-overview/data/write-data/ -[Check API]: ../../api-overview/permission/check-api +[Write API]: ../../api-reference/data/write-data +[Check API]: ../../api-reference/permission/check-api -### When Snap Token is NOT Provided +## When Snap Token is NOT Provided In Permify, every transaction is recorded in the 'transactions' table, and when a Snap Token is not provided, it retrieves the ID of the latest transaction from this table. This ID represents the most current snapshot of the database. After a query is executed with this ID, the results are then cached using this ID. @@ -56,4 +58,4 @@ When the second request arrives, since a transaction ID was not provided, the la ## More on Cache Mechanism -Permify implements several cache mecnanisims in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](./cache.md) \ No newline at end of file +Permify implements several cache mecnanisims in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisms](./cache) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/reference/tracing.md b/docs/operations/tracing.mdx similarity index 97% rename from docs/versioned_docs/version-0.6.x/reference/tracing.md rename to docs/operations/tracing.mdx index 13fb142d..46224c7a 100644 --- a/docs/versioned_docs/version-0.6.x/reference/tracing.md +++ b/docs/operations/tracing.mdx @@ -1,5 +1,7 @@ - -# Tracing Tools +--- +icon: magnifying-glass +title: Observability +--- Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 4284b6d9..00000000 --- a/docs/package.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "docs", - "version": "0.0.0", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids", - "typecheck": "tsc" - }, - "dependencies": { - "@cmfcmf/docusaurus-search-local": "^1.1.0", - "@docusaurus/core": "^2.4.3", - "@docusaurus/plugin-client-redirects": "^2.4.3", - "@docusaurus/plugin-google-analytics": "^2.4.3", - "@docusaurus/plugin-google-gtag": "^2.4.3", - "@docusaurus/preset-classic": "^2.4.3", - "@glidejs/glide": "^3.6.0", - "@reach/accordion": "^0.18.0", - "@redq/reuse-modal": "^2.0.0", - "@styled-system/theme-get": "^5.1.2", - "animate.css": "^4.1.1", - "clsx": "^1.1.1", - "polished": "^4.2.2", - "prism-react-renderer": "^1.2.1", - "prop-types": "^15.8.1", - "query-string": "^8.1.0", - "rc-collapse": "^3.5.2", - "rc-drawer": "^6.1.3", - "rc-progress": "^3.4.1", - "rc-tabs": "12.5.7", - "react": "^17.0.1", - "react-accessible-accordion": "5.0.0", - "react-anchor-link-smooth-scroll": "^1.0.12", - "react-aria-menubutton": "^7.0.3", - "react-collapser": "^1.5.10", - "react-content-loader": "^6.2.0", - "react-countdown-now": "^2.1.2", - "react-countup": "^6.4.1", - "react-dom": "^17.0.1", - "react-github-btn": "^1.4.0", - "react-icons": "^4.7.1", - "react-icons-kit": "^2.0.0", - "react-id-swiper": "^4.0.0", - "react-image": "^4.0.3", - "react-image-gallery": "1.2.11", - "react-loader-spinner": "^5.3.4", - "react-masonry-component": "^6.3.0", - "react-parallax": "^3.5.1", - "react-parallax-component": "^1.0.6", - "react-phone-number-input": "^3.2.18", - "react-responsive": "^9.0.0", - "react-reveal": "^1.2.2", - "react-rnd": "^10.3.7", - "react-scroll-motion": "^0.3.0", - "react-scroll-parallax": "^3.3.1", - "react-scrollspy": "^3.4.3", - "react-select": "^5.6.0", - "react-slick": "^0.29.0", - "react-stickynode": "^4.1.0", - "react-tabs": "^6.0.0", - "react-tsparticles": "^2.9.3", - "react-waypoint": "10.3.0", - "styled-components": "^5.3.6", - "styled-system": "5.1.5", - "swiper": "^9.1.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "^2.4.0", - "@tsconfig/docusaurus": "^1.0.4", - "typescript": "^4.9.5" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/docs/docs/permify-overview/authorization-service.md b/docs/permify-overview/authorization-service.mdx similarity index 82% rename from docs/docs/permify-overview/authorization-service.md rename to docs/permify-overview/authorization-service.mdx index 91d0dc7f..9daa4247 100644 --- a/docs/docs/permify-overview/authorization-service.md +++ b/docs/permify-overview/authorization-service.mdx @@ -1,7 +1,9 @@ +--- +sidebar_position: 1 +title: Authorization As A Service +--- -# Authorization As A Service - -Getting authorization right is tough, no matter how you've set up your architecture. You're gonna need a solid plan to handle permissions between services, all while keeping it separate from your applications main code. +Getting authorization right is tough, no matter how you've set up your architecture. You're gonna need a solid plan to handle permissions between services, all while keeping it separate from your applications main code. In a monolithic app, you can abstract authorization from your app using libraries. This involves building a permission system for each individual application or service that is directly connected with the database. @@ -9,7 +11,7 @@ This approach works well until you have several applications with many services. So due to this, at some point, most companies tend to design these systems as abstract entities, such as a centralized engine, that cater apps that has many services. But its not an easy process for [several reasons](#building-an-authorization-service-is-hard). -Authorization as a service means outsourcing your app's permission management to streamline authorization in your applications. Beyond the clear advantage of saving valuable development time, [it also significantly enhances visibility, scalability, and flexibility](#benefits-of-using-an-authorization-service) within your authorization journey. +Authorization as a service means outsourcing your app's permission management to streamline authorization in your applications. Beyond the clear advantage of saving valuable development time, [it also significantly enhances visibility, scalability, and flexibility](#benefits-of-using-an-authorization-service-permify) within your authorization journey. [Permify] is an **centralized authorization service** that offers a variety of binding and crafting options to secure your applications. It works in run time and respond to all authorization questions from any of your apps. @@ -27,54 +29,61 @@ For instance; in order to make an access check and compute a decision, you need Loading and processing authorization data is especially painful for access checks which come from different environments and services. Also, the authorization service which will be accessed by nearly every other service must be at least as available as the rest of your stack. -So for a centralized authorization service to operate smoothly, this systems needs to have to be fast, consistent, and available all times. +So for a centralized authorization service to operate smoothly, this systems needs to have to be fast, consistent, and available all times. Another point is, you probably need to have an additional service to to store your authorization data model, which generally includes saving and updating essential permissions like roles, attributes or relationships. This service should manage the entirety of authorization policies, providing administrators the flexibility to adjust these policies when necessary. ## Benefits of using an Authorization Service - Permify -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. +### Move & Iterate Faster -You can get started quickly with a [simple API](../api-overview.md) that you can easily integrate into your application to move and iterate faster. +Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. -### Scale As You Wish -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. +You can get started quickly with a [simple API](../getting-started/enforcement) that you can easily integrate into your application to move and iterate faster. +### Scale As You Wish +Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. -Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. +Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services -:::success Metrics -Currently, Permify can achieve response times of up to **10ms** for access control checks, with handling up to **1 trillion access requests** per second. Thanks to our state-of-the-art [parallel graph engine](https://docs.permify.co/docs/api-overview/permission/check-api/#how-access-decisions-evaluated) and various [cache mechanisms](https://docs.permify.co/docs/reference/cache/) that we operate. -::: + + Currently, Permify can achieve response times of up to **10ms** for access + control checks, with handling up to **1 trillion access requests** per second. + Thanks to our state-of-the-art [parallel graph + engine](./faqs#how-access-decisions-evaluated) + and various [cache mechanisms](../operations/cache) + that we operate. + [Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell ### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. -Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. +Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. + +Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. -Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. +Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. ### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. -Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. +Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. -Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. +Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. + +Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. ### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. + +Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. ## Need any help on Authorization ? Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify or how it might fit into your authorization workflow, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/permify-overview/faqs.mdx b/docs/permify-overview/faqs.mdx new file mode 100644 index 00000000..224ec304 --- /dev/null +++ b/docs/permify-overview/faqs.mdx @@ -0,0 +1,129 @@ +--- +title: Permify FAQs +--- + +### Does Permify Supports Authentication? + +Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. + +To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. + +Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. + +### How Access Decisions Evaluated? + +Access decisions are evaluated by stored authorization data and your authorization model, Permify Schema. + +In high level, access of an subject related with the relationships or attributes created between the subject and the resource. You can define this data in Permify Schema then create and store them as relational tuples and attributes, which is basically forms your authorization data. + +Permify Engine to compute access decision in 2 steps, +1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. +2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. + +Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. + +[relational tuples]: ../../getting-started/sync-data.md +[Permify Schema]: ../../getting-started/modeling.md + +When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows + +```perm +entity user {} + +entity organization { + + // organizational roles + relation admin @user + relation member @user +} + +entity document { + + // represents documents parent organization + relation parent @organization + + // represents owner of this document + relation owner @user + + // permissions + action edit = parent.admin or owner + action delete = owner +} +``` + +Which has a directed graph as follows: + +![relational-tuples](https://github.com/Permify/permify/assets/39353278/cec9936c-f907-42c0-a419-032ebb45454e) + +As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: + +**Q1:** Get the owners of the `document:12`. + +**Q2:** Get admins of the organization where `document:12` belongs to. + +Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. + +Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. + +### How To Manage Schema Changes ? + +It's expected that your initial schema will eventually change as your product or system evolves + +As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. + +A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database unless you delete them with the [delete API](../data/delete-data.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. + +We recommend applying the following pattern to safely handle schema changes: + +- Set up a central git repository that includes the schema. +- Teams or individuals who need to update the schema should add new permissions or relations to this repository. +- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. +- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. + + +### What is Preferred Deployment Pattern For Permify? + +Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. + +Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. + +See the [What is Authorization Service] Section for a detailed explanation of those advantages. + +[What is Authorization Service]: ../authorization-service + +![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) + +Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. + +As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. + + +### Why should I use Permify instead of IAM solutions such as Cognito, Firebase Auth or Keycloak to handle authorization? + +There are some major differences between authorization-specific solutions and identity providers, or I might say IAMs + +While IAMs often offer some level of authorization capabilities, they are not as flexible or fine-grained as dedicated authorization systems like Permify. Therefore, customizing complex permission logic (such as hierarchical relationships, user groups, dynamic attributes, etc.) can be challenging in IAMs. + +Another point is that authorization as a service solutions are focused entirely on authorization. This means they provide not only fine-grained permissions but also tooling and functionality to ease testing and observability of the authorization system. + +Also Permify leveraging Googleโ€™s Zanzibar scalable data model and unified ACL (Access Control List) approach, enables the creation of a centralized authorization service capable of handling high volumes of data and access checks across your microservices stack. + +Still its worth mention that if you have a basic authorization system or need, it totally makes sense to use the solutions you mentioned for handling the authorization part as well. + +### How Permify Works With Identity Providers (IAMs)? + +Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. + +Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. + +But as a maker of this project management app. You need to build the permissions and then map to Okta. + +What we do is, help you build these permissions and eventually map anywhere you want. + +### Is Permify a true ReBAC solution? + +Permify was designed and structured as a true ReBAC solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. + +With Permify, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. + +This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. \ No newline at end of file diff --git a/docs/permify-overview/intro.mdx b/docs/permify-overview/intro.mdx new file mode 100644 index 00000000..0616bec0 --- /dev/null +++ b/docs/permify-overview/intro.mdx @@ -0,0 +1,109 @@ +--- +title: Explore Permify +description: 'Start building scalable authorization systems in mere minutes' +--- + +Hero Light +Hero Dark + +## What is Permify ? + +[Permify](https://github.com/Permify/permify) is an **open source authorization service** for creating fine-grained and scalable authorization systems. + +With Permify, you can easily structure your authorization model, store authorization data in your preferred database, and interact with the Permify API to handle all authorization queries from your applications or services. + +Permify is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://permify.co/post/google-zanzibar-in-a-nutshell/). + +## Motivation + +Building scalable authorization systems is hard and time-consuming, and we're here to overcome this! + +Our goal is to make Googleโ€™s Zanzibar available to everyone and help engineering teams build a robust, flexible, and easily auditable authorization system, which will centrally position itself in your environment, taking responsibility for access control among your applications and services. + +See the [Authorization As A Service](./authorization-service) section to learn why building authorization is challenging and how our approach significantly reduces engineering efforts and secures future-proof access control systems. + +## With Permify, you can: + +๐Ÿ”ฎ Create permissions and policies using [Permify's flexible authorization language](../getting-started/modeling) that is compatible with traditional roles and permissions (RBAC), arbitrary relations between users and objects (ReBAC), and attributes (ABAC). + +๐Ÿ” [Manage and store authorization data](../getting-started/sync-data) in your preferred database with high availability and consistency. + +โœ… [Interact with the Permify API](../getting-started/enforcement) to perform access checks, filter your resources with specific permissions, perform bulk permission checks for various resources, and more. + +๐Ÿงช Test your authorization logic with [Permify's schema testing](../getting-started/testing). You can conduct scenario-based testing, policy coverage analysis, and IDL parser integration to achieve end-to-end validations for your desired authorization schema. + +โš™๏ธ Create custom and isolated authorization models for different applications using Permify [Multi-Tenancy](../use-cases/multi-tenancy) support, all managed within a single place, Permify instance. + +## Getting Started + +In Permify, authorization is divided into 3 core aspects; **modeling**, **storing authorization data** and **interacting with the APIs**. + +- See how to [Model your Authorization] using Permify Schema. +- Learn how Permify will [Store Authorization Data] as relations. +- Perform [Access Checks] anywhere in your stack. + +[Model your Authorization]: ../getting-started/modeling +[Store Authorization Data]: ../getting-started/sync-data +[Access Checks]: ../getting-started/enforcement + +This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. + +For the ones that want to try it out and examine it instantly, use [Permify Playground](https://play.permify.co/) to get started! + + +
+
+ +
+
+
+ +## Community & Support + +We would love to hear from you! + +You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. + +For feature requests, bugs, or any improvements you can always open an [issue](https://github.com/permify/permify/issues). + +### Want to Contribute? Here are the ways to contribute to Permify + +* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. +* **Improve documentation:** Alongside our codebase, documentation is an important part of our open-source journey. We're trying to give the best DX possible to explain ourselves and Permify. And you can help with that by importing resources or adding new ones. +* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. + +You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). + +## Communication Channels + +If you like Permify, please consider giving us a star on [github](https://github.com/permify/permify) + + + + +## Roadmap + +You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! + +## Need any help on Authorization ? + +Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify or how it might fit into your authorization workflow, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). + diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx new file mode 100644 index 00000000..d7f34867 --- /dev/null +++ b/docs/quickstart.mdx @@ -0,0 +1,86 @@ +--- +title: 'Quickstart' +description: 'Start building awesome documentation in under 5 minutes' +--- + +## Setup your development + +Learn how to update your docs locally and and deploy them to the public. + +### Edit and preview + + + + During the onboarding process, we created a repository on your Github with + your docs content. You can find this repository on our + [dashboard](https://dashboard.mintlify.com). To clone the repository + locally, follow these + [instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) + in your terminal. + + + Previewing helps you make sure your changes look as intended. We built a + command line interface to render these changes locally. 1. Install the + [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the + documentation changes locally with this command: ``` npm i -g mintlify ``` + 2. Run the following command at the root of your documentation (where + `mint.json` is): ``` mintlify dev ``` + + + +### Deploy your changes + + + + + Our Github app automatically deploys your changes to your docs site, so you + don't need to manage deployments yourself. You can find the link to install on + your [dashboard](https://dashboard.mintlify.com). Once the bot has been + successfully installed, there should be a check mark next to the commit hash + of the repo. + + + [Commit and push your changes to + Git](https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository#about-git-push) + for your changes to update in your docs site. If you push and don't see that + the Github app successfully deployed your changes, you can also manually + update your docs through our [dashboard](https://dashboard.mintlify.com). + + + + +## Update your docs + +Add content directly in your files with MDX syntax and React components. You can use any of our components, or even build your own. + + + + + Add flair to your docs with personalized branding. + + + + Implement your OpenAPI spec and enable API user interaction. + + + + Draw insights from user interactions with your documentation. + + + + Keep your docs on your own website's subdomain. + + + diff --git a/docs/docs/reference/configuration.md b/docs/setting-up/configuration.mdx similarity index 94% rename from docs/docs/reference/configuration.md rename to docs/setting-up/configuration.mdx index e7f01746..d577170d 100644 --- a/docs/docs/reference/configuration.md +++ b/docs/setting-up/configuration.mdx @@ -1,11 +1,22 @@ -# Configuration File +--- +title: Configuration +icon: gear +--- -Permify offers various options for configuring your Permify API Server. +Permify offers various options for configuring your Permify Server. Here is the example configuration YAML file with glossary below. -Here is the example configuration YAML file with glossary below. You can also find +You can also find this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. -***Example config.yaml file*** +### Configure Using Flags + +Alternatively, you can set configuration options using flags when running the command. See all the configuration flags by running, + +```shell +docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -help +``` + +### Configuration Using YAML File ```yaml # The server section specifies the HTTP and gRPC server settings, @@ -108,10 +119,9 @@ distributed: ``` -## Options +## Configuration Glossary -
server | Server Configurations -

+ #### Definition @@ -142,7 +152,7 @@ Server options to run Permify. (`grpc` and `http` available for now.) | [x] | tls | - | transport layer security options. | | [ ] | enabled (for tls) | false | switch option for tls | | [ ] | cert | - | tls certificate path. | -| [ ] | key | - | tls key pat | +| [ ] | key | - | tls key path | #### ENV @@ -160,11 +170,9 @@ Server options to run Permify. (`grpc` and `http` available for now.) | http-cors-allowed-origins | PERMIFY_HTTP_CORS_ALLOWED_ORIGINS | string array | | http-cors-allowed-headers | PERMIFY_HTTP_CORS_ALLOWED_HEADERS | string array | -

-
+ -
logger | Logging Options -

+ #### Definition @@ -193,11 +201,9 @@ Real time logs of authorization. Permify uses [zerolog] as a logger. | log-level | PERMIFY_LOG_LEVEL | string | | log-output | PERMIFY_LOG_OUTPUT | string | -

-
+ -
authn | Server Authentication -

+ #### Definition @@ -205,8 +211,8 @@ You can choose to authenticate users to interact with Permify API. There are 2 authentication method you can choose: -* [Pre Shared Keys](#pre-shared-keys) -* [OpenID Connect](#openid-connect) +* Pre Shared Keys +* OpenID Connect #### Pre Shared Keys @@ -276,12 +282,9 @@ authentication. | authn-oidc-issuer | PERMIFY_AUTHN_OIDC_ISSUER | string | | authn-oidc-audience | PERMIFY_AUTHN_OIDC_AUDIENCE | string | -

-
- + -
tracer | Tracing Configurations -

+ #### Definition @@ -320,11 +323,9 @@ authorization when using Permify. | tracer-urlpath | PERMIFY_TRACER_URL_PATH | string | | tracer-insecure | PERMIFY_TRACER_INSECURE | boolean | -

-
+ -
meter | Meter Configurations -

+ #### Definition @@ -360,11 +361,9 @@ os, arch. | meter-urlpath | PERMIFY_METER_URL_PATH | string | | meter-insecure | PERMIFY_METER_INSECURE | boolean | -

-
+ -
database | Database Configurations -

+ #### Definition @@ -421,11 +420,9 @@ audits, decision logs, authorization model) | database-garbage-collection-timeout | PERMIFY_DATABASE_GARBAGE_COLLECTION_TIMEOUT | duration | | database-garbage-collection-window | PERMIFY_DATABASE_GARBAGE_COLLECTION_WINDOW | duration | -

-
+ -
service | Service Configurations -

+ #### Definition @@ -476,11 +473,9 @@ limiting, cache size). | service-permission-concurrency-limit | PERMIFY_SERVICE_PERMISSION_CONCURRENCY_LIMIT | int | | service-permission-cache-max-cost | PERMIFY_SERVICE_PERMISSION_CACHE_MAX_COST | int | -

-
+ -
profiler | Performance Profiler Configurations -

+ #### Definition @@ -509,11 +504,9 @@ characteristics of their code by generating detailed profiles of program executi | profiler-enabled | PERMIFY_PROFILER_ENABLED | boolean | | profiler-port | PERMIFY_PROFILER_PORT | string | -

-
+ -
Distributed | Consistent hashing Configurations -

+ #### Definition @@ -545,8 +538,7 @@ improving scalability and performance in distributed systems." | distributed-address | PERMIFY_DISTRIBUTED_ADDRESS | string | | distributed-port | PERMIFY_DISTRIBUTED_PORT | string | -

-
+ [jaeger]: https://www.jaegertracing.io/ diff --git a/docs/setting-up/installation/.DS_Store b/docs/setting-up/installation/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/docs/setting-up/installation/.DS_Store differ diff --git a/docs/versioned_docs/version-0.4.x/installation/aws.md b/docs/setting-up/installation/aws.mdx similarity index 98% rename from docs/versioned_docs/version-0.4.x/installation/aws.md rename to docs/setting-up/installation/aws.mdx index c1c204d9..a689cd97 100644 --- a/docs/versioned_docs/version-0.4.x/installation/aws.md +++ b/docs/setting-up/installation/aws.mdx @@ -1,9 +1,7 @@ --- -title: AWS ECS, ECR & EC2 +title: Deploy on AWS ECS, ECR & EC2 --- -# ย Deploy on AWS ECS, ECR & EC2 - AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). @@ -18,7 +16,7 @@ There is no prerequisite in this tutorial. You can simply deploy permify by foll At the end of this tutorial youโ€™ll be able to; -1. [Create a security group](#create-an-ec2-security-group) +1. [Create a security group](#1-create-an-ec2-security-group) 2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) 3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) 4. [Running our task definition](#4-running-our-task-definition) diff --git a/docs/docs/installation/brew.md b/docs/setting-up/installation/brew.mdx similarity index 66% rename from docs/docs/installation/brew.md rename to docs/setting-up/installation/brew.mdx index 0212df8b..de2cb0c5 100644 --- a/docs/docs/installation/brew.md +++ b/docs/setting-up/installation/brew.mdx @@ -1,9 +1,7 @@ --- -title: "Install with Brew" +title: Install with Brew --- -# Brew With Configurations - This section shows how to install and run Permify Service using brew. ### Install Permify @@ -28,9 +26,10 @@ See all the configuration flags by running, permify serve --help ``` -:::info Environment Variables + In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: + + ### Configure With Using Config File @@ -46,7 +45,7 @@ or permify serve --config=config.yaml ``` -### Test your connection. +### Test your connection You can test your connection by making an HTTP GET request, @@ -56,10 +55,30 @@ localhost:3476/healthz You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. -[Using the API]: ../../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) +[Using the API]: ../../getting-started/enforcement + + ### Need any help ? diff --git a/docs/docs/installation/container.md b/docs/setting-up/installation/container.mdx similarity index 53% rename from docs/docs/installation/container.md rename to docs/setting-up/installation/container.mdx index aa0b7686..7cbea8fa 100644 --- a/docs/docs/installation/container.md +++ b/docs/setting-up/installation/container.mdx @@ -1,9 +1,7 @@ --- -title: "Docker Container" +title: Run using Docker --- -# Run using Docker - This section shows how to run Permify using our docker container. You can run Permify using Docker with following command. ## Run in a terminal @@ -12,15 +10,15 @@ This section shows how to run Permify using our docker container. You can run Pe docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve ``` -This will start a Permify server with the configuration that is in **{YOUR-CONFIG-PATH}**. +This will start a Permify server with the configuration that is in `{YOUR-CONFIG-PATH}`. ### Configure with a YAML file -This config path - `{YOUR-CONFIG-PATH}` - should contain the [config yaml file](../reference/configuration.md), where you can configure the Permify Server as well as define the ***database*** to store your authorization related data in. +This config path - `{YOUR-CONFIG-PATH}` - should contain the [config yaml file](../configuration), where you can configure the Permify Server as well as define the ***database*** to store your authorization related data in. -:::info Talk to an Permify Engineer + By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. -::: + ### Configure Using Flags @@ -30,9 +28,11 @@ Alternatively, you can set configuration options using flags when running the co docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -help ``` -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: + +In addition to CLI flags, Permify also supports configuration via environment variables. + +You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). + ### Test your connection. @@ -44,11 +44,30 @@ localhost:3476/healthz You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - +[Using the API]: ../../getting-started/enforcement + + ### Need any help ? diff --git a/docs/docs/installation/google.md b/docs/setting-up/installation/google.mdx similarity index 100% rename from docs/docs/installation/google.md rename to docs/setting-up/installation/google.mdx diff --git a/docs/docs/installation/helm.md b/docs/setting-up/installation/helm.mdx similarity index 97% rename from docs/docs/installation/helm.md rename to docs/setting-up/installation/helm.mdx index 5345d05d..421b366b 100644 --- a/docs/docs/installation/helm.md +++ b/docs/setting-up/installation/helm.mdx @@ -1,11 +1,7 @@ --- -title: Helm Chart +title: Deploying Permify with Helm Charts --- -# Deploying Permify with Helm Charts - -## Introduction to Helm - Helm is a package manager for Kubernetes applications that simplifies the deployment and management of applications in a Kubernetes cluster. Using Helm, you can package and release your applications as charts, which are pre-configured Kubernetes resources. You can learn more about helm [here](https://helm.sh/docs/) diff --git a/docs/setting-up/installation/intro.mdx b/docs/setting-up/installation/intro.mdx new file mode 100644 index 00000000..5b48761f --- /dev/null +++ b/docs/setting-up/installation/intro.mdx @@ -0,0 +1,58 @@ +--- +id: examples +title: Deployment +sidebar_label: Installation +--- + +
+ + Set up Permify instance a with single docker command in your local. + +
+ +## Deployment Guides + +Here is some options that you can use to set up and deploy Permify in your servers. +If options your deployment preference is not listed below please let us know. + +
+
+ + Deploy Permify on a server using a configuration yaml file. + +
+
+ + Deploying Docker Container & Permify to AWS EC2 using ECS. + +
+
+ + Deploy Permify on a EKS Kubernetes cluster. + +
+
+ + Install and run Permify with Homebrew package manager. + +
+
+ + Deploy Permify with using Google Compute Engine. + +
+
+ + Deploying Permify with Helm Charts. + +
+
+ +If you have any questions, feel free to join our [Discord community](https://discord.gg/n6KfzYxhPp) and start a discussion! \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/installation/kubernetes.md b/docs/setting-up/installation/kubernetes.mdx similarity index 97% rename from docs/versioned_docs/version-0.5.x/installation/kubernetes.md rename to docs/setting-up/installation/kubernetes.mdx index f2a1e21b..fffa5c4b 100644 --- a/docs/versioned_docs/version-0.5.x/installation/kubernetes.md +++ b/docs/setting-up/installation/kubernetes.mdx @@ -1,16 +1,14 @@ --- -title: Kubernetes Cluster +title: Deploy on Kubernetes Cluster --- -# ย Deploy on Kubernetes Cluster - In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. Hereโ€™s what weโ€™re going to do step-by-step; 1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) 3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) +4. [Deploy Permify to nodes](#deploying-and-running-permify-in-nodes) There are a couple of small prerequisites for this tutorial. diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index c6a30175..00000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,202 +0,0 @@ -/** @type {import('@docusaurus/plugin-content-docs/src/sidebars/types').Sidebars} */ -module.exports = { - someSidebar: [ - { - type: "category", - label: "First Glance", - link: { - type: "generated-index", - title: "First Glance", - slug: "/permify-overview", - }, - items: [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - collapsed: false, - }, - { - type: "category", - label: "Getting Started", - link: { - type: "generated-index", - title: "Getting Started", - slug: "/getting-started", - }, - items: [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing", - { - type: "category", - label: "Real World Examples", - link: { - type: "doc", - id: "examples", - }, - items: [ - "getting-started/examples/google-docs", - "getting-started/examples/facebook-groups", - "getting-started/examples/notion", - "getting-started/examples/instagram", - "getting-started/examples/mercury" - ], - }, - ], - collapsed: false, - }, - { - type: "category", - label: "Set Up Permify", - link: { - type: "doc", - id: "installation", - }, - items: [ - "installation/overview", - "installation/brew", - "installation/container", - "installation/aws", - "installation/azure", - "installation/google", - "installation/helm", - "installation/kubernetes", - ], - collapsed: true, - }, - { - type: "category", - label: "Using the API", - link: { - type: "doc", - id: "api-overview", - }, - items: [ - { - type: 'category', - label: 'Schema Service', - link: { - type: "generated-index", - title: "Schema Service", - slug: "/api-overview/schema", - }, - items: [ - "api-overview/schema/write-schema" - ], - }, - { - type: 'category', - label: 'Data Service', - link: { - type: "generated-index", - title: "Data Service", - slug: "/api-overview/data", - }, - items: [ - "api-overview/data/write-data", - "api-overview/data/read-relationships", - "api-overview/data/read-attributes", - "api-overview/data/run-bundle", - "api-overview/data/delete-data" - ], - }, - { - type: 'category', - label: 'Bundle Service', - link: { - type: "doc", - id: "bundle", - }, - items: [ - "api-overview/bundle/write-bundle", - "api-overview/bundle/read-bundle", - "api-overview/bundle/delete-bundle" - ], - }, - { - type: 'category', - label: 'Permission Service', - link: { - type: "generated-index", - title: "Permission Service", - slug: "/api-overview/permission", - }, - items: [ - "api-overview/permission/check-api", - "api-overview/permission/lookup-entity", - "api-overview/permission/lookup-subject", - "api-overview/permission/expand-api", - "api-overview/permission/subject-permission" - ], - }, - { - type: 'category', - label: 'Tenancy Service', - link: { - type: "generated-index", - title: "Tenancy Service", - slug: "/api-overview/tenancy", - }, - items: [ - "api-overview/tenancy/create-tenant", - "api-overview/tenancy/delete-tenant", - ], - }, - { - type: 'category', - label: 'Watch Service', - link: { - type: "generated-index", - title: "Watch Service", - slug: "/api-overview/watch", - }, - items: [ - "api-overview/watch/watch-changes", - ], - }, - ], - collapsed: true - }, - { - type: "doc", - id: "playground", - label: "Permify Playground", - }, - { - type: "category", - label: "Common Use Cases", - link: { - type: "doc", - id: "use-cases", - }, - items: [ - "use-cases/simple-rbac", - "use-cases/abac", - "use-cases/custom-roles", - "use-cases/multi-tenancy", - "use-cases/rebac", - ], - collapsed: true, - }, - { - type: "category", - label: "Reference", - link: { - type: "generated-index", - title: "Reference", - slug: "/reference" - }, - items: [ - "reference/glossary", - "reference/configuration", - "reference/contextual-tuples", - "reference/snap-tokens", - "reference/cache", - "reference/tracing" - ], - collapsed: true - }, - ], -}; diff --git a/docs/snippets/snippet-intro.mdx b/docs/snippets/snippet-intro.mdx new file mode 100644 index 00000000..c57e7c75 --- /dev/null +++ b/docs/snippets/snippet-intro.mdx @@ -0,0 +1,4 @@ +One of the core principles of software development is DRY (Don't Repeat +Yourself). This is a principle that apply to documentation as +well. If you find yourself repeating the same content in multiple places, you +should consider creating a custom snippet to keep your content in sync. diff --git a/docs/src/components/Card/Card.jsx b/docs/src/components/Card/Card.jsx deleted file mode 100644 index 00218170..00000000 --- a/docs/src/components/Card/Card.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import styles from "./Card.module.css"; - -export const Card = ({ - title, - description, - imgSrc, - link, -}) => { - console.log('link:', link) - - return ( - - ); -}; diff --git a/docs/src/components/Card/Card.module.css b/docs/src/components/Card/Card.module.css deleted file mode 100644 index a0f02588..00000000 --- a/docs/src/components/Card/Card.module.css +++ /dev/null @@ -1,96 +0,0 @@ -html[data-theme='dark'] .card { - background-color: #242526; - border-radius: 5px; - display: flex; - flex-direction: column; - min-width: 0; - position: relative; - box-shadow: rgba(0, 0, 0, 0.35) 0 1px 1px; - cursor: pointer; -} - -.card { - border-radius: 5px; - display: flex; - flex-direction: column; - min-width: 0; - position: relative; - box-shadow: rgba(0, 0, 0, 0.35) 0 1px 1px; - cursor: pointer; - } - - .card:hover { - transition-delay: 1s; - box-shadow: rgba(0, 0, 0, 0.35) 0 5px 5px; - } - - .card-body { - display: flex; - padding: 2.5rem; - flex: 1 1 auto; - align-items: start; - } - - .card-info { - margin-left: 1.5rem; - width: 80%; - } - - .card-icon { - width: 20%; - } - - .card-container-setup { - display: grid; - grid-template-columns: 1fr 1fr !important; - grid-gap: 2rem; - padding: 1rem 0; - } - - @media all and (max-width: 768px) { - .card-container-setup { - grid-template-columns: 1fr !important; - } - - .card-body { - flex-direction: column; - align-items: center; - } - - .card-info { - padding: 0.25rem 0; - width: 100%; - margin: 0 auto; - } - - .card-info h3 { - text-align: center; - } - } - - @media all and (min-width: 820px) and (max-width: 1280px) { - .card-body { - flex-direction: column; - align-items: center; - } - - .card-info { - padding: 0.25rem 0; - width: 100%; - margin: 0 auto; - } - - .card-info h3 { - text-align: center; - } - - .card-icon img { - width: 100%; - margin: 0 auto; - } - - .card-icon { - display: flex; - padding-bottom: 0.5rem; - } - } \ No newline at end of file diff --git a/docs/src/components/Card/CardList.jsx b/docs/src/components/Card/CardList.jsx deleted file mode 100644 index e1beed50..00000000 --- a/docs/src/components/Card/CardList.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from "react"; -import {Card} from "./Card" -import styles from "./Card.module.css"; - -export const CardList = () => { - - const list = [ - { - id:1, - title:"Try Permify in Local", - description: "Set up Permify a with single docker command in your local", - imgSrc: "https://user-images.githubusercontent.com/34595361/212459030-7bd3ff7f-1538-4870-87cd-fbd0f4a21624.png", - link: "./overview" - }, - { - id:2, - title:"Docker", - description: "Deploy Permify on a server using a configuration yaml file", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458191-50464c53-3228-40bf-8e8c-66a021eac13a.svg", - link: "./container" - }, - { - id:3, - title:"AWS", - description: "Deploying Docker Container & Permify to AWS EC2 using ECS", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458359-e5472772-ce68-4c5a-a595-8ab123976202.svg", - link: "./aws" - }, - { - id:4, - title:"Kubernetes (EKS)", - description: "Deploy Permify on a EKS Kubernetes cluster", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458403-4ad18c86-6618-4df4-86f6-167553fcee87.png", - link: "./kubernetes" - }, - { - id:5, - title:"Brew", - description: "Install and run Permify with Brew", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458420-95a75de6-ac32-4958-87de-5e0848e6753c.png", - link: "./brew" - }, - { - id:6, - title:"Google Compute Engine", - description: "Deploy Permify with using Google Compute Engine", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458849-354849d8-cbdf-48de-9272-6e6d9ad5856e.svg", - link: "./google" - }, - { - id:7, - title:"Helm Charts", - description: "Deploying Permify with Helm Charts", - imgSrc: "https://user-images.githubusercontent.com/34595361/212458403-4ad18c86-6618-4df4-86f6-167553fcee87.png", - link: "./helm" - }, - ] - - return ( -
- {list.map((item) => ( - - ))} -
- ); -}; diff --git a/docs/src/components/Card/index.jsx b/docs/src/components/Card/index.jsx deleted file mode 100644 index 28faa850..00000000 --- a/docs/src/components/Card/index.jsx +++ /dev/null @@ -1,4 +0,0 @@ -import { Card } from "./Card"; -import { CardList } from "./CardList"; - -export { Card, CardList }; diff --git a/docs/src/components/Case/Case.jsx b/docs/src/components/Case/Case.jsx deleted file mode 100644 index 65da6a1d..00000000 --- a/docs/src/components/Case/Case.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import styles from "./Case.module.css"; - -export const Case = ({ - title, - description, - link, -}) => { - console.log('link:', link) - - return ( - - ); -}; diff --git a/docs/src/components/Case/Case.module.css b/docs/src/components/Case/Case.module.css deleted file mode 100644 index 3d8e6c1b..00000000 --- a/docs/src/components/Case/Case.module.css +++ /dev/null @@ -1,91 +0,0 @@ -html[data-theme='dark'] .card { - background-color: #242526; - border-radius: 5px; - display: flex; - flex-direction: column; - min-width: 0; - position: relative; - box-shadow: rgba(0, 0, 0, 0.35) 0 1px 1px; - cursor: pointer; -} - -.card { - border-radius: 5px; - display: flex; - flex-direction: column; - min-width: 0; - position: relative; - box-shadow: rgba(0, 0, 0, 0.35) 0 1px 1px; - cursor: pointer; - } - - .card:hover { - transition-delay: 1s; - box-shadow: rgba(0, 0, 0, 0.35) 0 5px 5px; - } - - .card-body { - align-items: start; - display: flex; - flex: 1 1 auto; - padding: 1.2rem; - } - - .card-info { - width: 100%; - } - - .card-container-setup { - display: grid; - grid-template-columns: 1fr 1fr !important; - grid-gap: 2rem; - padding: 1rem 0; - } - - @media all and (max-width: 768px) { - .card-container-setup { - grid-template-columns: 1fr !important; - } - - .card-body { - flex-direction: column; - align-items: center; - } - - .card-info { - padding: 0.25rem 0; - width: 100%; - margin: 0 auto; - } - - .card-info h3 { - text-align: center; - } - } - - @media all and (min-width: 820px) and (max-width: 1280px) { - .card-body { - flex-direction: column; - align-items: center; - } - - .card-info { - padding: 0.25rem 0; - width: 100%; - margin: 0 auto; - } - - .card-info h3 { - text-align: center; - } - - .card-icon img { - width: 100%; - margin: 0 auto; - } - - .card-icon { - display: flex; - padding-bottom: 0.5rem; - } - } \ No newline at end of file diff --git a/docs/src/components/Case/CaseList.jsx b/docs/src/components/Case/CaseList.jsx deleted file mode 100644 index 91217d77..00000000 --- a/docs/src/components/Case/CaseList.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { Case } from "./Case"; -import styles from "./Case.module.css"; - -export const CaseList = ({ list }) => { - return ( -
- {list && list.map((item) => ( - - ))} -
- ); -}; diff --git a/docs/src/components/Case/index.jsx b/docs/src/components/Case/index.jsx deleted file mode 100644 index 7acbffe7..00000000 --- a/docs/src/components/Case/index.jsx +++ /dev/null @@ -1,4 +0,0 @@ -import { Case } from "./Case"; -import { CaseList } from "./CaseList"; - -export { Case, CaseList }; diff --git a/docs/src/components/HomepageFeatures.module.css b/docs/src/components/HomepageFeatures.module.css deleted file mode 100644 index b248eb2e..00000000 --- a/docs/src/components/HomepageFeatures.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.featureSvg { - height: 200px; - width: 200px; -} diff --git a/docs/src/components/HomepageFeatures.tsx b/docs/src/components/HomepageFeatures.tsx deleted file mode 100644 index e1d1c790..00000000 --- a/docs/src/components/HomepageFeatures.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import useBaseUrl from '@docusaurus/useBaseUrl'; -import React from 'react'; -import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; - -type FeatureItem = { - title: string; - image: string; - description: JSX.Element; -}; - -const FeatureList: FeatureItem[] = [ - { - title: 'Easy to Use', - image: '/img/undraw_docusaurus_mountain.svg', - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), - }, - { - title: 'Focus on What Matters', - image: '/img/undraw_docusaurus_tree.svg', - description: ( - <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. - - ), - }, - { - title: 'Powered by React', - image: '/img/undraw_docusaurus_react.svg', - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), - }, -]; - -function Feature({title, image, description}: FeatureItem) { - return ( -
-
- {title} -
-
-

{title}

-

{description}

-
-
- ); -} - -export default function HomepageFeatures(): JSX.Element { - return ( -
-
-
- {FeatureList.map((props, idx) => ( - - ))} -
-
-
- ); -} diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index 64fc6e37..00000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #8246FF; - --ifm-color-primary-dark: #736986; - --ifm-color-primary-darker: #291A47; - --ifm-color-primary-darkest: #0C0025; - --ifm-color-primary-light: #A274FF; - --ifm-color-primary-lighter: #C1A2FF; - --ifm-color-primary-lightest: #E0D1FF; - --ifm-code-font-size: 95%; - --ifm-pagination-nav-border-radius: 2px; - --ifm-pagination-border-radius: 2px; - --ifm-pre-border-radius: 2px; - --ifm-alert-border-radius: 2px; - --ifm-badge-border-radius: 2px; - --ifm-breadcrumb-border-radius: 2px; -} - -.footer--dark { - --ifm-footer-background-color: #141517 !important; -} - -html[data-theme='dark'] { - --ifm-background-color: black !important; - --ifm-navbar-background-color: #141517 !important; -} - -html[data-theme='dark'] .card { - background-color: #141517; - border: 1px solid #303030 !important; -} - -html[data-theme='dark'] pre { - background-color: #141517 !important; -} - -html[data-theme='dark'] code { - background-color: #141517 !important; -} - -.card { - border-radius: 2px !important; -} - -html[data-theme='dark'] .card-body_src-components-Card-Card-module { - background-color: #141517 !important; -} - -html[data-theme='dark'] .card_src-components-Case-Case-module { - background-color: #141517 !important; -} - -html { - font-size: 16px; -} - -.aa-DetachedSearchButton { - width: 170px !important; - height: 2rem !important; - font-size: 14px !important; -} - - -/* For readability concerns, you should choose a lighter palette in dark mode. */ -html[data-theme='dark'] { - --ifm-color-primary: #A274FF; - --ifm-color-primary-dark: #A274FF; - --ifm-color-primary-darker: #A274FF; - --ifm-color-primary-darkest: #A274FF; - --ifm-color-primary-light: #A274FF; - --ifm-color-primary-lighter: #E0D1FF; - --ifm-color-primary-lightest: #E0D1FF; -} - -.docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.1); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -html[data-theme='dark'] .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.3); -} - -.header-discord-link:hover { - opacity: 0.6; -} - -.header-discord-link:before { - background: url("data:image/svg+xml,%3Csvg width='25px' height='20px' viewBox='0 0 256 199' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' preserveAspectRatio='xMidYMid'%3E%3Cg%3E%3Cpath d='M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z' fill-rule='nonzero'%3E%3C/path%3E%3C/g%3E%3C/svg%3E") no-repeat; - content: ""; - display: flex; - height: 20px; - width: 25px; -} - -html[data-theme='dark'] .header-discord-link:before { - background: url("data:image/svg+xml,%3Csvg width='25px' height='20px' viewBox='0 0 256 199' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' preserveAspectRatio='xMidYMid'%3E%3Cg%3E%3Cpath d='M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z' fill='%23FFF' fill-rule='nonzero'%3E%3C/path%3E%3C/g%3E%3C/svg%3E") no-repeat; -} - -.header-twitter-link:hover { - opacity: 0.6; -} - -.header-twitter-link:before { - background: url("data:image/svg+xml,%3Csvg width='25px' height='20px' viewBox='0 0 256 209' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' preserveAspectRatio='xMidYMid'%3E%3Cg%3E%3Cpath d='M256,25.4500259 C246.580841,29.6272672 236.458451,32.4504868 225.834156,33.7202333 C236.678503,27.2198053 245.00583,16.9269929 248.927437,4.66307685 C238.779765,10.6812633 227.539325,15.0523376 215.57599,17.408298 C205.994835,7.2006971 192.34506,0.822 177.239197,0.822 C148.232605,0.822 124.716076,24.3375931 124.716076,53.3423116 C124.716076,57.4586875 125.181462,61.4673784 126.076652,65.3112644 C82.4258385,63.1210453 43.7257252,42.211429 17.821398,10.4359288 C13.3005011,18.1929938 10.710443,27.2151234 10.710443,36.8402889 C10.710443,55.061526 19.9835254,71.1374907 34.0762135,80.5557137 C25.4660961,80.2832239 17.3681846,77.9207088 10.2862577,73.9869292 C10.2825122,74.2060448 10.2825122,74.4260967 10.2825122,74.647085 C10.2825122,100.094453 28.3867003,121.322443 52.413563,126.14673 C48.0059695,127.347184 43.3661509,127.988612 38.5755734,127.988612 C35.1914554,127.988612 31.9009766,127.659938 28.694773,127.046602 C35.3777973,147.913145 54.7742053,163.097665 77.7569918,163.52185 C59.7820257,177.607983 37.1354036,186.004604 12.5289147,186.004604 C8.28987161,186.004604 4.10888474,185.75646 0,185.271409 C23.2431033,200.173139 50.8507261,208.867532 80.5109185,208.867532 C177.116529,208.867532 229.943977,128.836982 229.943977,59.4326002 C229.943977,57.1552968 229.893412,54.8901664 229.792282,52.6381454 C240.053257,45.2331635 248.958338,35.9825545 256,25.4500259' %3E%3C/path%3E%3C/g%3E%3C/svg%3E") no-repeat; - content: ""; - display: flex; - height: 20px; - width: 27px; -} - -html[data-theme='dark'] .header-twitter-link:before { - background: url("data:image/svg+xml,%3Csvg width='25px' height='20px' viewBox='0 0 256 209' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' preserveAspectRatio='xMidYMid'%3E%3Cg%3E%3Cpath d='M256,25.4500259 C246.580841,29.6272672 236.458451,32.4504868 225.834156,33.7202333 C236.678503,27.2198053 245.00583,16.9269929 248.927437,4.66307685 C238.779765,10.6812633 227.539325,15.0523376 215.57599,17.408298 C205.994835,7.2006971 192.34506,0.822 177.239197,0.822 C148.232605,0.822 124.716076,24.3375931 124.716076,53.3423116 C124.716076,57.4586875 125.181462,61.4673784 126.076652,65.3112644 C82.4258385,63.1210453 43.7257252,42.211429 17.821398,10.4359288 C13.3005011,18.1929938 10.710443,27.2151234 10.710443,36.8402889 C10.710443,55.061526 19.9835254,71.1374907 34.0762135,80.5557137 C25.4660961,80.2832239 17.3681846,77.9207088 10.2862577,73.9869292 C10.2825122,74.2060448 10.2825122,74.4260967 10.2825122,74.647085 C10.2825122,100.094453 28.3867003,121.322443 52.413563,126.14673 C48.0059695,127.347184 43.3661509,127.988612 38.5755734,127.988612 C35.1914554,127.988612 31.9009766,127.659938 28.694773,127.046602 C35.3777973,147.913145 54.7742053,163.097665 77.7569918,163.52185 C59.7820257,177.607983 37.1354036,186.004604 12.5289147,186.004604 C8.28987161,186.004604 4.10888474,185.75646 0,185.271409 C23.2431033,200.173139 50.8507261,208.867532 80.5109185,208.867532 C177.116529,208.867532 229.943977,128.836982 229.943977,59.4326002 C229.943977,57.1552968 229.893412,54.8901664 229.792282,52.6381454 C240.053257,45.2331635 248.958338,35.9825545 256,25.4500259' fill='%23fff'%3E%3C/path%3E%3C/g%3E%3C/svg%3E") no-repeat; -} - -.header-github-link:hover { - opacity: 0.6; -} - -.header-github-link:before { - background: url("data:image/svg+xml;charset=utf-8, %3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat; - content: ""; - display: flex; - height: 24px; - width: 24px; -} - -html[data-theme='dark'] .header-github-link:before { - background: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12' fill='%23fff'/%3E%3C/svg%3E") no-repeat; -} - -.getting-started-grid { - display: inline-grid; - align-items: center; - grid-template-columns: 1fr 1fr 1fr; - column-gap: 10px; - row-gap: 10px; -} - -.btn:hover { - cursor: pointer; -} - -.btn-thumb { - display: flex; - flex-direction: column; - height: 100%; - padding: 0 0 10px 0 ; - align-items: center; - text-align: center; - - background-color: #291A47; - border-radius: 2px; - color: #ffffff; - vertical-align: middle; -} - -.btn-thumb .thumbnail { - height: 160px; - width: 100%; - margin-bottom: 10px; - position: relative; - border-radius: 0 !important; -} - -.btn-thumb .thumb-txt { - display: flex; - align-items: center; - height: inherit; - padding: 0 5px; -} - - -.btn-thumb img { - height: 160px; - width: 100%; - object-fit: cover; - border-radius: 2px 2px 0 0; -} \ No newline at end of file diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js deleted file mode 100644 index 9243d1cc..00000000 --- a/docs/src/pages/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { Redirect } from 'react-router-dom'; - -export default function Home() { - return ; -} diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css deleted file mode 100644 index 666feb6a..00000000 --- a/docs/src/pages/index.module.css +++ /dev/null @@ -1,23 +0,0 @@ -/** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. - */ - -.heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; -} - -@media screen and (max-width: 966px) { - .heroBanner { - padding: 2rem; - } -} - -.buttons { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/docs/src/theme/prism-include-languages.js b/docs/src/theme/prism-include-languages.js deleted file mode 100644 index e20261f4..00000000 --- a/docs/src/theme/prism-include-languages.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -import siteConfig from '@generated/docusaurus.config'; -export default function prismIncludeLanguages(PrismObject) { - const { - themeConfig: {prism}, - } = siteConfig; - const {additionalLanguages} = prism; // Prism components work on the Prism instance on the window, while prism- - // react-renderer uses its own Prism instance. We temporarily mount the - // instance onto window, import components to enhance it, then remove it to - // avoid polluting global namespace. - // You can mutate PrismObject: registering plugins, deleting languages... As - // long as you don't re-assign it - - globalThis.Prism = PrismObject; - additionalLanguages.forEach((lang) => { - // eslint-disable-next-line global-require, import/no-dynamic-require - require(`prismjs/components/prism-${lang}`); - }); - - require('./prism-perm-lang.js'); - - delete globalThis.Prism; -} diff --git a/docs/src/theme/prism-perm-lang.js b/docs/src/theme/prism-perm-lang.js deleted file mode 100644 index 1ecb8ee9..00000000 --- a/docs/src/theme/prism-perm-lang.js +++ /dev/null @@ -1,14 +0,0 @@ -(function (Prism) { - Prism.languages.perm = Prism.languages.extend('clike', { - - 'comment': { - pattern: /\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/, - greedy: true - }, - 'number':{ - pattern: /\\B@\\w+#?\\w+/, - greedy: true - }, - 'keyword': /\b(?:entity|permission|attribute|rule|relation|action|or|not|and)\b(?!\s*=\s*\d)/, - }); -}(Prism)); diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/static/img/docusaurus.png b/docs/static/img/docusaurus.png deleted file mode 100644 index f458149e..00000000 Binary files a/docs/static/img/docusaurus.png and /dev/null differ diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index 9b582bdd..00000000 Binary files a/docs/static/img/favicon.ico and /dev/null differ diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg deleted file mode 100644 index faff98f9..00000000 --- a/docs/static/img/logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/static/img/tutorial/docsVersionDropdown.png b/docs/static/img/tutorial/docsVersionDropdown.png deleted file mode 100644 index ff1cbe68..00000000 Binary files a/docs/static/img/tutorial/docsVersionDropdown.png and /dev/null differ diff --git a/docs/static/img/tutorial/localeDropdown.png b/docs/static/img/tutorial/localeDropdown.png deleted file mode 100644 index d7163f96..00000000 Binary files a/docs/static/img/tutorial/localeDropdown.png and /dev/null differ diff --git a/docs/static/img/undraw_docusaurus_mountain.svg b/docs/static/img/undraw_docusaurus_mountain.svg deleted file mode 100644 index 431cef2f..00000000 --- a/docs/static/img/undraw_docusaurus_mountain.svg +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/undraw_docusaurus_react.svg b/docs/static/img/undraw_docusaurus_react.svg deleted file mode 100644 index e4170504..00000000 --- a/docs/static/img/undraw_docusaurus_react.svg +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/undraw_docusaurus_tree.svg b/docs/static/img/undraw_docusaurus_tree.svg deleted file mode 100644 index a05cc03d..00000000 --- a/docs/static/img/undraw_docusaurus_tree.svg +++ /dev/null @@ -1 +0,0 @@ -docu_tree \ No newline at end of file diff --git a/docs/tsconfig.json b/docs/tsconfig.json deleted file mode 100644 index 6f475698..00000000 --- a/docs/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // This file is not used in compilation. It is here just for a nice editor experience. - "extends": "@tsconfig/docusaurus/tsconfig.json", - "compilerOptions": { - "baseUrl": "." - } -} diff --git a/docs/use-cases/.DS_Store b/docs/use-cases/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/docs/use-cases/.DS_Store differ diff --git a/docs/docs/use-cases/abac.md b/docs/use-cases/abac.mdx similarity index 92% rename from docs/docs/use-cases/abac.md rename to docs/use-cases/abac.mdx index 23a6cb4d..a005a3ef 100644 --- a/docs/docs/use-cases/abac.md +++ b/docs/use-cases/abac.mdx @@ -1,4 +1,6 @@ -# Attribute Based Access Control +--- +title: Attribute Based Access Control (ABAC) +--- This page explains the design approach of Permify's ABAC support as well as demonstrates how to create and use attribute based permissions in Permify. @@ -93,11 +95,11 @@ rule check_ip_range(ip string, ip_range string[]) { } ``` -:::info Syntax + We design our schema language based on [Common Expression Language (CEL)](https://github.com/google/cel-go). So the syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. Please let us know via our [Discord channel](https://discord.gg/n6KfzYxhPp) if you have questions regarding syntax, definitions or any operator you identify not working as expected. -::: + Let's examine some of common usage of ABAC with small schema examples. @@ -112,10 +114,7 @@ entity post { permission view = is_public } ``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: +If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` ### Text & Object Based Conditions @@ -143,9 +142,8 @@ rule check_location(current_location string, location string[]) { } ``` -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: +If you donโ€™t create the related attribute data, Permify accounts string as `""` + ### Numerical Conditions @@ -167,10 +165,7 @@ rule check_age(age integer) { age >= 18 } ``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: +If you donโ€™t create the related attribute data, Permify accounts integer as `0` #### Double - Precise numerical information @@ -195,10 +190,7 @@ rule check_balance(amount double, balance double) { (balance >= amount) && (amount <= 5000) } ``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: +If you donโ€™t create the related attribute data, Permify accounts double as `0.0` ## Example Use Cases @@ -242,8 +234,8 @@ This means that the 'view' permission is granted if either the repository is pub **Request keys before hash** -- check*{snapshot}*{schema*version}*{context}\_post:1$is_public โ†’ true -- check*{snapshot}*{schema*version}*{context}\_post:1#admin@user:1 โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_post:1$is_public` โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_post:1#admin@user:1` โ†’ true ### Example of Weekday @@ -283,8 +275,8 @@ The permissions in this model state that to 'view' the repository, the user must **Request keys before hash** -- check*{snapshot}*{schema*version}*{context}\_organization:1$is_weekday(context.day_of_week) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_post:1#member@user:1 โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_organization:1$is_weekday(context.day_of_week)` โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_post:1#member@user:1` โ†’ true ### Example of Banking System @@ -327,8 +319,8 @@ Both of these conditions need to be true for the **`withdraw`** permission to be **Request keys before hash** -- check*{snapshot}*{schema*version}*{context}\_account:1$check_balance(context.amount,balance) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_account:1#owner@user:1 โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_account:1$check_balance(context.amount,balance)` โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_account:1#owner@user:1` โ†’ true ### Hierarchical Usage @@ -376,16 +368,19 @@ rule check_budget(budget double) { - organization:1$organization|integer:2021 **Check Evolution Sub Queries For Department View** + โ†’ department:1$check_budget(budget) โ†’ true + โ†’ department:1#organization@user:1 โ†’ true โ†’ organization:2$check_founding_year(founding_year) โ†’ false + โ†’ organization:1$check_founding_year(founding_year) โ†’ true **Request keys before hash** -- check*{snapshot}*{schema*version}*{context}\_department:1$check_budget(budget) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_organization:2$check_founding_year(founding_year) โ†’ false -- check*{snapshot}*{schema*version}*{context}\_organization:1$check_founding_year(founding_year) โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_department:1$check_budget(budget)` โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_organization:2$check_founding_year(founding_year)` โ†’ false +- `check*{snapshot}*{schema*version}*{context}\_organization:1$check_founding_year(founding_year)` โ†’ true ## Evaluation of ABAC Access Checks @@ -459,8 +454,8 @@ The cache mechanism works by hashing the snapshot of the database, schema versio **Request keys before hash** -- check*{snapshot}*{schema*version}*{context}\_organization:1#admin@user:1 โ†’ true -- check*{snapshot}*{schema*version}*{context}\_organization:1$check_ip_range(ip_range) โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_organization:1#admin@user:1` โ†’ true +- `check*{snapshot}*{schema*version}*{context}\_organization:1$check_ip_range(ip_range)` โ†’ true ## How To Use ABAC diff --git a/docs/docs/use-cases/custom-roles.md b/docs/use-cases/custom-roles.mdx similarity index 99% rename from docs/docs/use-cases/custom-roles.md rename to docs/use-cases/custom-roles.mdx index 4ddf1658..95a620df 100644 --- a/docs/docs/use-cases/custom-roles.md +++ b/docs/use-cases/custom-roles.mdx @@ -1,5 +1,6 @@ - -# Custom Roles +--- +title: Custom Roles +--- This document highlights a solution for custom roles with the [Permify Schema]. In this tutorial, we will create custom **admin** and **member** roles in a project. Then set the permissions of these roles according to their capabilities on the dashboard and tasks. diff --git a/docs/versioned_docs/version-0.5.x/use-cases/multi-tenancy.md b/docs/use-cases/multi-tenancy.mdx similarity index 63% rename from docs/versioned_docs/version-0.5.x/use-cases/multi-tenancy.md rename to docs/use-cases/multi-tenancy.mdx index c8e5be9a..4f319113 100644 --- a/docs/versioned_docs/version-0.5.x/use-cases/multi-tenancy.md +++ b/docs/use-cases/multi-tenancy.mdx @@ -1,39 +1,21 @@ --- -title: "Multi Tenancy" +title: Multi Tenancy --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -With version 0.3.x Permify moved to a tenancy-based infrastructure, which affects almost all of the API operations. - -## Multi Tenancy on Permify - Multi-tenancy in Permify refers to an authorization architecture where a single Permify authorization service serves multiple applications/organizations (tenants). -This allows customization of the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create a custom authorization schema and relation tuples for the different tenants and manage them in a single place. +This allows customization of the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create a custom authorization schema and authorization data for the different tenants and manage them in a single place. For the users that don't have/need multi-tenancy in their authorization structure, we created a pre-inserted tenant (id: **t1**) that comes default when you serve a Permify service. -Several things changed when we moved to tenant based infrastructure, these are: - -- [Multi Tenancy on Permify](#multi-tenancy-on-permify) - - [API endpoints now have Tenant ID field](#api-endpoints-now-have-tenant-id-field) - - [Check API](#check-api) - - [Added Tenancy Service](#added-tenancy-service) - - [Permission Database Tenancy Table and Tenant Id column](#permission-database-tenancy-table-and-tenant-id-column) - - [Tenant Table](#tenant-table) - - [Tenant ID Column](#tenant-id-column) -- [Need any help ?](#need-any-help-) +### Tenancy Based APIs -### API endpoints now have Tenant ID field - -All API endpoints now have a `โ€tenant_id` mandatory field. Let's examine a check request below, +Almost all Permify API endpoints have a `โ€tenant_id` mandatory field. Let's examine a check request below, #### Check API - + ```go cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { @@ -61,8 +43,8 @@ cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckReq }) ``` - - + + ```javascript client.permission.check({ @@ -90,8 +72,51 @@ client.permission.check({ }) ``` - - + + + +```python +import permify +from permify.models.permission_check_request import PermissionCheckRequest +from permify.rest import ApiException +from pprint import pprint + +configuration = permify.Configuration(host="http://localhost") + +with permify.ApiClient(configuration) as api_client: + api_instance = permify.PermissionApi(api_client) + tenant_id = 't1' + + body = PermissionCheckRequest( + tenant_id=tenant_id, + metadata={ + "snapToken": "", + "schemaVersion": "", + "depth": 20 + }, + entity={ + "type": "repository", + "id": "1", + }, + permission="edit", + subject={ + "type": "user", + "id": "1", + } + ) + + try: + api_response = api_instance.permissions_check(tenant_id, body) + if api_response.can == PermissionCheckResponse.Result.RESULT_ALLOWED: + print("RESULT_ALLOWED") + else: + print("RESULT_DENIED") + except ApiException as e: + print(f"Exception when calling PermissionApi->permissions_check: {e}") +``` + + + ```curl curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ @@ -114,20 +139,21 @@ curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permission }, }' ``` - + -Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../api-overview.md). +Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../getting-started/enforcement). -### Added Tenancy Service +### Tenancy Service -To manage tenants we have added a Tenancy service; you can create, delete and list tenants. See the [Tenancy Service](../../api-overview/tenancy) in Using The API section. +To manage tenants we have added a Tenancy service; you can create, delete and list tenants. See the [Tenancy Service](../../api-reference/tenancy/create-tenant) in Using The API section. -### Permission Database Tenancy Table and Tenant Id column +### Permission Database #### Tenant Table -A tenants table has been added to the Permission database to store tenant's details. The new folder structure changed as follows: +A tenants table has been added to the Permissions database to store tenants' details. + ``` tables โ”œโ”€โ”€ migrations @@ -139,7 +165,7 @@ tables #### Tenant ID Column -Relation tuples and schema definition tables now have a tenant_id column, which stores the id of the tenant that the data belongs. +Authorization DATA and schema definition tables now have a tenant_id column, which stores the id of the tenant that the data belongs. Let's take a look at a snapshot of the demo table on an example Permission Database. diff --git a/docs/versioned_docs/version-0.6.x/use-cases/simple-rbac.md b/docs/use-cases/rbac.mdx similarity index 97% rename from docs/versioned_docs/version-0.6.x/use-cases/simple-rbac.md rename to docs/use-cases/rbac.mdx index 85d70b6b..9835ac40 100644 --- a/docs/versioned_docs/version-0.6.x/use-cases/simple-rbac.md +++ b/docs/use-cases/rbac.mdx @@ -1,9 +1,7 @@ --- -sidebar_position: 1 +title: Role Based Access Control (RBAC) --- -# Role Based Access Control - Want to implement roles and permissions in your application? Permify fully covers you at that point. The example below shows how to model simple role based access controls for organizational roles and permissions with our authorization language, [Permify Schema]. [Permify Schema]: ../../getting-started/modeling @@ -120,7 +118,7 @@ organization:21#agent@user:ege For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. -[Relational Tuples]: ../getting-started/sync-data.md +[Relational Tuples]: ../getting-started/sync-data ## Need any help ? diff --git a/docs/versioned_docs/version-0.6.x/use-cases/rebac.md b/docs/use-cases/rebac.mdx similarity index 96% rename from docs/versioned_docs/version-0.6.x/use-cases/rebac.md rename to docs/use-cases/rebac.mdx index 561b500e..f33c98b6 100644 --- a/docs/versioned_docs/version-0.6.x/use-cases/rebac.md +++ b/docs/use-cases/rebac.mdx @@ -1,5 +1,6 @@ - -# Relationship Based Access Control +--- +title: Relationship Based Access Control (ReBAC) +--- Permify was designed and structured as a true [Relationship Based Access Control(ReBAC)](https://permify.co/post/relationship-based-access-control-rebac/) solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. @@ -7,7 +8,7 @@ Here are some common use cases where you can benefit from using ReBAC models in - [Protecting Organizational-Wide Resources](#protecting-organizational-wide-resources) - [Deeply Nested Hierarchies](#deeply-nested-hierarchies) -- [User Groups & Team Permissions](#user-groups--team-permissions) +- [User Groups & Team Permissions](#user-groups-and-team-permissions) ## Protecting Organizational-Wide Resources @@ -208,9 +209,7 @@ team:1#org@organization:1#... project:1#team@team:1#... -Lets assume we created the above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** since the `user:1` is an admin of the `organization:1` and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md +Lets assume we created the above tuples. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** since the `user:1` is an admin of the `organization:1` and `project:1` belongs to `team:1`, which belongs to `organization:1`. Let's break down this case, diff --git a/docs/versioned_docs/version-0.2.x/api-overview.md b/docs/versioned_docs/version-0.2.x/api-overview.md deleted file mode 100644 index 762d98a5..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Overview - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 3 core parts; ***modeling authorization***, ***storing authorization data*** and ***enforcement***. Therefore, Permify API has sections that represent the functionalities of these core parts. - -- **Permission Section**: Consist enforcement requests and options. -- **Relationship Section**: Authorization data operations such as creating, deleting and reading relational tuples. -- **Schema Section**: Modeling and Permify Schema related functionalities including configuration and auditing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) and [REST](https://restfulapi.net/). - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de?action=collection%2Ffork&collection-url=entityId%3D16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de%26entityType%3Dcollection%26workspaceId%3Dd3a8746c-fa57-49c0-83a5-6fcf25a7fc05) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://app.swaggerhub.com/apis-docs/permify/permify/latest) - -## Core Paths - -- Check access with [Check API](./api-overview/check-api.md) -- Configure your authorization model with [Schema Write](./api-overview/write-schema.md) -- Write relational tuples with [Write API](./api-overview/write-relationships.md) -- Read relation tuples and filter them with [Read API](./api-overview/read-api.md) -- Delete relation tuples with [Delete Tuple](./api-overview/delete-relationships.md) -- Expand schema actions with [Expand API](./api-overview/expand-api.md) -- Get permissions of your resources with [Schema Lookup](./api-overview/schema-lookup.md) - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/api-overview/_category_.json b/docs/versioned_docs/version-0.2.x/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/check-api.md b/docs/versioned_docs/version-0.2.x/api-overview/check-api.md deleted file mode 100644 index b9d9b56c..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/check-api.md +++ /dev/null @@ -1,122 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control - -In Permify, you can perform two different types access checks, - -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **data filtering (coming soon)** authorization checks , in form of `Which records can user U edit ?` - -In this section we'll investigate proior check request of Permify: **resource based** authorization checks. You can find subject based access checks in [data filtering] section. - -**Path:** POST /v1/permissions/check - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It containes type and id of the subject. | -| [ ] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop| - -#### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -} -``` - -#### Response - -```json -{ - "can": "RESULT_ALLOW", - "remaining_depth": 0 -} -``` - -### Using gRPC Clients - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. See how [access decisions evaluated] in Permify. - -[access decisions evaluated]: ../../getting-started/enforcement#how-access-decisions-are-evaluated - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/delete-relationships.md b/docs/versioned_docs/version-0.2.x/api-overview/delete-relationships.md deleted file mode 100644 index 17474d7e..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/delete-relationships.md +++ /dev/null @@ -1,98 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Relational Tuples - -You can delete any stored relation tuples with following path - -**Path:** POST /v1/relationships/delete - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - -#### Request - -```json -{ - "filter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - } -} -``` - -### Using gRPC Clients - - - - -```go -rr, err: = client.Relationship.Delete(context.Background(), & v1.RelationshipDeleteRequest { - Metadata: &v1.RelationshipDeleteRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.delete({ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/expand-api.md b/docs/versioned_docs/version-0.2.x/api-overview/expand-api.md deleted file mode 100644 index ff4c9086..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/expand-api.md +++ /dev/null @@ -1,308 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API -We developed a expand API to see Permify Schema actions in a tree structure to improve observability and reasonability of access permissions. - -Expand API is represented by a user set tree whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs, and intermediate nodes represent union, intersection, or exclusion operators. - -Expand is crucial for our users to reason about the complete set of users and groups that have access to their objects, which allows them to build efficient search indices for access-controlled content. Unlike the Read API, Expand follows indirect references expressed through user set rewrite rules. - -## Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify's DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** POST /v1/permissions/expand - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../reference/snap-tokens) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. -| [x] | action | string | - | The action the user wants to perform on the resource | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "entity": { - "type": "repository", - "id": "1" - }, - "action": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": null, - "expand": { - "operation": "INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "parent.admin" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "organization", - "id": "1", - "relation": "admin" - } - ] - } - }, - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "parent.member" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "organization", - "id": "1", - "relation": "member" - } - ] - } - }, - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "exclusion": false, - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- -### Using gRPC Clients - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), & v1.PermissionExpandRequest { - Metadata: & v1.PermissionExpandRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -#### **Graph Representation of Expanding Read Action** - -![graph-of-relations](https://user-images.githubusercontent.com/34595361/186653899-7090feb5-8ef4-4a8c-991f-ed9475a5e1f7.png) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/read-api.md b/docs/versioned_docs/version-0.2.x/api-overview/read-api.md deleted file mode 100644 index 2044a616..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/read-api.md +++ /dev/null @@ -1,131 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -**Path:** POST /v1/relationship/read - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - -#### Request - -```json -{ - "metadata": { - "snap_token": "", - }, - "filter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "member", - "subject": { - "type": "", - "ids": [ - "" - ], - "relation": "" - } - } -} -``` - -#### Response - -```json -[ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member", - "subject": { - "type": "user", - "id": "1" - } - }, - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member", - "subject": { - "type": "user", - "id": "2" - } - } -] -``` - -### Using gRPC Clients - - - - -```go -rr, err: = client.Relationship.Read(context.Background(), & v1.RelationshipReadRequest { - Metadata: &v1.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.read({ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [ - "" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/schema-lookup.md b/docs/versioned_docs/version-0.2.x/api-overview/schema-lookup.md deleted file mode 100644 index 96b75d1a..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/schema-lookup.md +++ /dev/null @@ -1,90 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Schema Lookup - -You can use schema lookup API endpoint to retrieve all permissions associated with a resource relation. Basically, you can perform enforcement without checking stored authorization data. For example in given a Permify Schema like: - -``` -entity user {} - -entity document { - - relation assignee @user - relation manager @user - - action view = assignee or manager - action edit = manager - -} - -``` - -Let's say you have a user X with a manager role. If you want to check what user X can do on a documents ? You can use the schema lookup endpoint as follows, - -**Path:** POST /v1/permissions/lookup-schema - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [x] | entity_type | string | - | type of the entity. -| [x] | relation_names | string[] | - | string array that holds entity relations | - -#### Request - -```json -{ - "metadata": { - "schema_version": "" - }, - "entity_type": "document", - "relation_names": [ "manager" ] -} -``` - -#### Response - -```json -{ - "data": { - "action_names": [ - "view", - "edit" - ] - } -} -``` - - -### Using gRPC Clients - - - - -```go -cr, err: = client.Permission.LookupSchema(context.Background(), & v1.PermissionLookupSchemaRequest { - Metadata: & v1.PermissionLookupSchemaRequestMetadata { - SchemaVersion: "" - }, - EntityType: "document", - RelationNames: []string {"manager"}, -}) -``` - - - - -```javascript -client.permission.LookupSchema({ - metadata: { - schema_version: "" - }, - entity_type: "document", - relation_names: [ "manager" ] -}) -``` - - - - -The response will return all the possible actions that manager can perform on documents. Also you can extend relation lookup as much as you want by adding relations to the **"relation_names"** array. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/api-overview/write-relationships.md b/docs/versioned_docs/version-0.2.x/api-overview/write-relationships.md deleted file mode 100644 index 37eb812f..00000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/write-relationships.md +++ /dev/null @@ -1,190 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Relationships - -In Permify, relations between your entities, objects and users stored as [relational tuples] in [writeDB]. Since relations and authorization data's are live instances these relational tuples can be created with an simple API call in runtime. - -When using Permify, the application client should update writeDB about the changes happening in entities or resources that are related to the authorization structure. If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [writeDB] up-to-date. Besides, each relational tuple should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as relation tuples and store in [writeDB]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -[relational tuples]: ../../getting-started/sync-data -[writeDB]: ../../getting-started/sync-data#where-relational-tuples-used- - -## Example Request - -So if user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` must be created by using **/v1/relationships/write** endpoint. - -**Path:** POST /v1/relationships/write - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - -### Request - -```json -{ - "schema_version": "", - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -} -``` - -### Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../reference/snap-tokens) and how its avoiding stale cache. - -### Using gRPC Clients - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "admin", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -## Suggested Workflow - -The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? - -### Two Phase Commit Approach -In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. - -To give more concurrent example of this action, let's take a look at below createDocument function - -```go -func CreateDocuments(db *gorm.DB) error { - - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - } - }() - - if err := tx.Error; err != nil { - return err - } - - if err := tx.Create(docs).Error; err != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - return err - } - - // if transaction successful, write relation tuple to Permify - permify.WriteRelationships(...) - - return tx.Commit().Error -} -``` -The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. - -### Relationships that not stored in application database - -Although ownership generally stored in application databases, there are some relations that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. - -For example, you can model a simple project management authorization in Permify as follows, - -```perm -entity user {} - -entity team { - - relation owner @user - relation member @user -} - -entity project { - - relation team @team - relation owner @user - - action view = team.member or team.owner or project.owner - action edit = project.owner or team.owner - action delete = project.owner or team.owner - -} -``` - -This **team member** relation won't need to be stored in the application database. Storing it only in Permify - WriteDB - is enough. In that situation, `WriteRelationships` can be performed in any logical place in your stack. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/deployment/_category_.json b/docs/versioned_docs/version-0.2.x/deployment/_category_.json deleted file mode 100644 index 3bbc347f..00000000 --- a/docs/versioned_docs/version-0.2.x/deployment/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Deployment", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/deployment/aws.md b/docs/versioned_docs/version-0.2.x/deployment/aws.md deleted file mode 100644 index a1f5560d..00000000 --- a/docs/versioned_docs/version-0.2.x/deployment/aws.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -title: AWS ECS, ECR & EC2 ---- - -# ย Deploy on AWS ECS, ECR & EC2 - -AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. - -There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). - -ECS is a container management service. You can run your containers as task definitions, and Itโ€™s one of the easiest ways to deploy containers. - -If youโ€™d like to watch this tutorial rather than reading. Hereโ€™s the video version. - - - -There is no prerequisite in this tutorial. You can simply deploy permify by following this step-by-step guide. However, if you want to integrate more advanced AWS security & networking features, weโ€™ll follow up with a new tutorial guideline. - -At the end of this tutorial youโ€™ll be able to; - -1. [Create a security group](#create-an-ec2-security-group) -2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) -3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) -4. [Running our task definition](#4-running-our-task-definition) - -## 1. Create an EC2 Security Group - -So first thing first, letโ€™s go over into security groups and create our security group. Weโ€™ll need this security group while creating our cluster. - -![security-group-1](https://user-images.githubusercontent.com/34595361/208877994-e9461acc-4ffd-4591-b43e-db254366d25d.png) - -Search for โ€œSecurity Groupsโ€ in the search bar. And go to the EC2 security groups feature. - -![security-group-2](https://user-images.githubusercontent.com/34595361/208877493-ab11228c-1aa0-4bc5-b41d-4527737028e9.png) - -Then start creating a new security group. - -![security-group-3](https://user-images.githubusercontent.com/34595361/208877500-2c299883-6107-4b70-aa96-0f28eb00cf3d.png) - -You have to name your security group, and give a description. Also, you need to choose the same VPC that youโ€™ll going to use in EC2. So, I choose the default one. And Iโ€™m going to use same one while creating the ECS cluster. - -The next step is to configure our inbound rules. Hereโ€™s the configuration; - -```json -//for mapping HTTP request port. -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for mapping RPC request port. -type = "Custom TCP", protocol = "TCP", port_range = "3478",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for using SSH for connecting from your local computer. -type = "Custom TCP", protocol = "TCP", port_range = "22",source = "Anywhere", 0.0.0.0/0 -``` - -We have configured the HTTP and RPC ports for Permify. Also, we added port โ€œ22โ€ for SSH connection. So, we can connect to EC2 through our local terminal. - -Now, weโ€™re good to go. You can create the security group. And itโ€™s ready to use in our ECS. - -## 2. Creating an ECS cluster - -![create-ecs-cluster-1](https://user-images.githubusercontent.com/34595361/208878666-98c5d3ce-b079-444d-bc66-53f13038a08a.png) - -The next step is to create an ECS cluster. From your AWS console search for Elastic Container Service or ECS for short. - -![create-ecs-cluster-2](https://user-images.githubusercontent.com/34595361/208878675-2f266cfc-defb-4c7f-9186-b4de39f1743b.png) - -Then go over the clusters. As you can see there are 2 types of clusters. One is for ECS and another for EKS. We need to use ECS, EKS stands for Elastic Kubernetes Service. Today weโ€™re not going to cover Kubernetes. - -Click **โ€œCreate Clusterโ€** - -![create-ecs-cluster-3](https://user-images.githubusercontent.com/34595361/208878685-3edac67b-5b3d-4f0d-b2f7-70a5ec2e4870.png) - -Letโ€™s create our first Cluster. Simply you have 3 options; Serverless(Network Only), Linux, and Windows. Weโ€™re going to cover EC2 Linux + Networking option. - -![create-ecs-cluster-4](https://user-images.githubusercontent.com/34595361/208878681-d98a77db-16b1-42af-a697-3036cc604c85.png) - -The next step is to configure our Cluster, starting with your Cluster name. Since weโ€™re deploying Permify, Iโ€™ll call it โ€œpermifyโ€. - -Then choose your instance type. You can take a look at different instances and pricing from [here](https://aws.amazon.com/ec2/pricing/on-demand/). Iโ€™m going with the t4 large. For cost purposes, you can choose t2.micro if youโ€™re just trying out. Itโ€™s free tier eligible. - -Also, if you want to connect this EC2 instance from your local computer. You need to use SSH. Thus choose a key pair. If you have no such intention, leave it โ€œnoneโ€. - -![create-ecs-cluster-5](https://user-images.githubusercontent.com/34595361/208878989-801839f5-8fce-4410-99e0-0a2dcccb47fa.png) - -Now, we need to configure networking. First, choose your VPC, we use the default VPC as we did in the security groups. And choose any subnet on that VPC. - -You want to enable auto-assigned IP to make your app reachable from the internet. - -Choose the security group we have created previously. - -And voila, you can create your cluster. Now, we need to run our container in this cluster. To do that, letโ€™s go over task definitions. And create our container definition. - -## 3. Creating and running task definitions - -Go over to ECS, and click the task definitions. - -![create-run-task-1](https://user-images.githubusercontent.com/34595361/208879726-fe5aac07-16a8-4f8c-9cc9-1c95ca191a42.png) - -And create a new task definition. - -![create-run-task-2](https://user-images.githubusercontent.com/34595361/208879733-e9aa6fa4-9f66-44e4-8c70-dfa0e33c1b73.png) - -Again, youโ€™re going to ask to choose between; FARGATE, EC2, and EXTERNAL (On-premise). Weโ€™ll continue with EC2. - -Leave everything in default under the โ€œConfigure task and container definitionsโ€ section. - -![create-run-task-3](https://user-images.githubusercontent.com/34595361/208879735-789ec411-5829-47be-9634-c09c7b0c0320.png) - -Under the IAM role section you can choose โ€œecsTaskExecutionRoleโ€ if you want to use Cloud Watch later. - -You can leave task size in default since itโ€™s optional for EC2. - -The critical part over here is to add our container. Click on the โ€œAdd Containerโ€ button. - -![create-run-task-4](https://user-images.githubusercontent.com/34595361/208879740-4515e884-1efd-46fd-8e8c-cfa86634b673.png) - -Then we need to add our container details. First, give a name. And then the most important part is our image URI. Permify is registered on the Github Registry so our image is; - -```yaml -ghcr.io/permify/permify:latest -``` - -Then we need to define memory limit for the container, I went with 1024. You can define as much as your instance allows. - -Next step is to mapping our ports. As we mentioned in security groups, Permify by default listens; - -- `3476 for HTTP port` -- `3478 for RPC port` - -![create-run-task-5](https://user-images.githubusercontent.com/34595361/208879746-5991a04c-73d5-4e35-97b0-67aa9ebf61fc.png) - -Then we need to define command under the environment section. So, in order to start permify we first need to add โ€œserveโ€ command. - -For using properly we need a few other. Hereโ€™s the commands we need. - -```yaml -serve, --database-engine=postgres, --database-name=, --database-uri=postgres://:@:, --database-pool-max=20 -``` - -- `serve` โ‡’ for starting the Permify. -- `--database-engine=postgres` โ‡’ for defining the db we use. -- `--database-name=` โ‡’ name of the database you use. -- `--database-uri=postgres://:password@:` โ‡’ for connecting your database with URI. -- `--database-pool-max=20` โ‡’ the depth for running in graph. - -Weโ€™re nice and clear, add the container and then just create your task definition. Weโ€™ll use this definition to run in our cluster. - -So, letโ€™s go over and run our task definition. - -## 4. Running our task definition - -![run-task-definition-1](https://user-images.githubusercontent.com/34595361/208880326-c5ecb48c-e210-47f8-bd92-d1f789be24ff.png) - -Letโ€™s go to ECS and enter into our cluster. And go over into the tasks to run our task. - -![run-task-definition-2](https://user-images.githubusercontent.com/34595361/208880332-97a5732d-bc7d-401e-bae9-216d4273c5bf.png) - -Click to โ€œRun new Taskโ€ - -![run-task-definition-3](https://user-images.githubusercontent.com/34595361/208880335-b3ce229f-33ff-4f03-90e7-6d6a306928ae.png) - -Choose EC2 as a launch type. Then pick the task definition we just created. And leave everything else in the default. You can run your task now. - -We have just deployed our container into EC2 instance with ECS. Letโ€™s test it. - -Now you can go over into EC2, and click on the running instances. Find the instance named `ECS Instance - EC2ContainerService-` in the running instances. - -![run-task-definition-4](https://user-images.githubusercontent.com/34595361/208880339-a508354c-99ee-4219-8ace-1c7fdbbe90ed.png) - -Copy the Public IPv4 DNS from the right corner, and paste it into your browser. But you need to add `:3476` to access our http endpoint. So it should be like this; - -`:3476` - -and if you add healthz at the end like this; - -`:3476/healthz` - -you should get Serving status :) - -![run-task-definition-5](https://user-images.githubusercontent.com/34595361/208880346-d19a6877-3013-4347-86c9-9f865b8a3e3c.png) - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/deployment/azure.md b/docs/versioned_docs/version-0.2.x/deployment/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/versioned_docs/version-0.2.x/deployment/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/deployment/google.md b/docs/versioned_docs/version-0.2.x/deployment/google.md deleted file mode 100644 index dcb3614a..00000000 --- a/docs/versioned_docs/version-0.2.x/deployment/google.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Google Compute Engine ---- - -# Deploy on Google Compute Engine - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/deployment/kubernetes.md b/docs/versioned_docs/version-0.2.x/deployment/kubernetes.md deleted file mode 100644 index 997c8942..00000000 --- a/docs/versioned_docs/version-0.2.x/deployment/kubernetes.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -title: Deploy on Kubernetes Cluster ---- - -# ย Deploy on Kubernetes Cluster - -In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. - -Hereโ€™s what weโ€™re going to do step-by-step; - -1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) -3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) - -There are a couple of small prerequisites for this tutorial. - -### Pre-requisites - -- An AWS account. -- The AWS Command Line Interface (CLI) is installed and configured on your local machine. โ€” [Click here](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/home) to go to IAM -- The AWS IAM Authenticator for Kubernetes is installed and configured on your local machine. - -## Configure AWS CLI with your IAM account. - -The first step is to configure our AWS IAM account into our local terminal so that we can run commands. Most of you probably have a configured AWS account if you ever set up anything into AWS programmatically, so you can skip this. If you donโ€™t follow these steps. - -### Create an AWS IAM Programmatic Access Account - -First, letโ€™s create IAM credentials for ourselves. Search IAM from the AWS console. You need to write down the account ID if you want to log in AWS console with this account as well. Letโ€™s go over users and start creating our credentials. - -![kubernetes-1](https://user-images.githubusercontent.com/34595361/211697636-6e106115-bd68-4909-aea0-5a7b6f8d5e18.png) - -At Users screen click to โ€œAdd usersโ€ โ€” and youโ€™ll end up in your first screen creating user credentials. Here you can define the name of the user. Also there 2 options that you can choose simultaneously. - -But you must choose โ€œAccess key - Programmatic accessโ€ option. Itโ€™ll allow us to configure our AWS CLI on our local machine. - -You can also choose โ€œPassword - AWS Management Console accessโ€ if you want to log in to this account through the console. But youโ€™ll need the Account ID that I mentioned in the IAM console screen. - -In the next screen, youโ€™ll be asked to create or copy the user-set permissions. For this tutorial, youโ€™ll only need to access EKS resources and features. So lets create group by clicking the โ€œCreate groupโ€ โ€” and then at pop-up screen search for EKS. - -![kubernetes-2](https://user-images.githubusercontent.com/34595361/211697647-f39d73e7-b6e2-40ae-8c3b-ad68032d6b21.png) - -Iโ€™ll choose all EKS permissions but if you have certain policies internally, just stick with them. Youโ€™ll only need following permission to; - -- `AmazonEKSClusterPolicy` -- `AmazonEKSServicePolicy` -- `AmazonEKSVPCResourceController` -- `AmazonEKSWorkerNodePolicy` - -Then simply you can review and create the user. - -![kubernetes-4](https://user-images.githubusercontent.com/34595361/211697655-1b75d4f9-a2ee-4b7e-9e1e-0be0b5aaad7d.png) - -Once you created the credentials youโ€™ll prompt the โ€œAccess key IDโ€ and โ€œSecret access keyโ€, you should save this down somewhere. Weโ€™re going the use these to configure our local machine with AWS CLI. - -### **Configure AWS CLI with your IAM account** - -Letโ€™s open our local terminal - -```jsx -aws configure -``` - -Next youโ€™ll ask for the following credentials; - -- `AWS Access Key ID` -- `AWS Secret Access Key` -- `Default region name` -- `Default output format` (leave it empty) - -## Creating an AWS EKS Cluster - -For the first step, we need to install [eksctl](https://eksctl.io/) โ€” which is like kubectl but for AWS EKS. It helps us to set up and deploy our cluster and nodes within a fraction of the time. - -Letโ€™s download eksctl using brew. - - -```jsx -brew tap weaveworks/tap -``` - -While installing the eksctl, weโ€™ll end up getting kubectl and other dependencies. - -```jsx -brew install weaveworks/tap/eksctl -``` - -Now, weโ€™re ready to create our EKS cluster. You can define certain things while deploying standard the cluster beside the name and version like; the region you want to deploy, the EC2 instance type of each node, and the number of nodes you want to run. - -```bash -eksctl create cluster \ ---name \ ---version 1.24 \ ---region ย \ ---nodegroup-name permify \ ---node-type t2.small \ ---nodes 2 -``` - -## Deploying & Running Permify in Nodes - -The next stop is applying our manifests which will help us to deploy and configure our container/Permify. - -Letโ€™s create our deployment manifest first. - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: permify - name: permify -spec: - replicas: 2 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-name=demo" - - "--database-uri=postgres://postgres:nOcodeSTIAnLAba@permify-test.ceuo5kqsxyea.us-east-1.rds.amazonaws.com:5432" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always -status: {} -``` - -Now letโ€™s apply our deployment manifest - -```jsx -kubectl apply -f deployment.yaml -``` - -The next step is to create a service manifest, this will allow us to configure our container app. - -```jsx -apiVersion: v1 -kind: Service -metadata: - name: permify -spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer -status: - loadBalancer: {} -``` - -Letโ€™s apply service.yaml to our nodes. - -```jsx -kubectl apply -f service.yaml -``` - -Last but not least, we can check our pods & nodes. And we can start using the container with load balancer \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/_category_.json b/docs/versioned_docs/version-0.2.x/example-use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/organizational.md b/docs/versioned_docs/version-0.2.x/example-use-cases/organizational.md deleted file mode 100644 index 7c4f94df..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/organizational.md +++ /dev/null @@ -1,151 +0,0 @@ - - -# Organizations & Hierarchies - -Group your users by organization with giving them access organzational-wide resources. In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -------- - -## Full Schema - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 3 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -### Relations - -To define relation, **relations** needed to be created as entity attributes. - -#### organization entity - -In our schema we defined 2 relation in organization entity, respectively; ``admin`` and ``member`` - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents the default user that belongs to that organization. - -#### repository entity - -Repository entities have 2 relations, these are ``parent`` and ``owner``. Both of these relations represents actual database relations with other entities rather than a role-based approach likewise to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -``parent`` relation represents the parent organization with a repository. And ``owner`` represents the specific user, the repository's owner. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### repository actions - -In our schema, we examined one of the main functionalities can the user make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who also need to have an administrative role or be an owner of the parent organization, can ``read``. -- Repository owners or administrative roles in an organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since ``parent` represents the parent organization of repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -## Example Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/ownership.md b/docs/versioned_docs/version-0.2.x/example-use-cases/ownership.md deleted file mode 100644 index 0891e84b..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/ownership.md +++ /dev/null @@ -1,42 +0,0 @@ - -# Ownership - -Granting privileges to the owner of the resource is a common pattern that many applications follow. Generally we want creators of the resource - document, post, comment etc - have superior power on that resource. Check out the below model see how ownership can be modeled with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -------- - -```perm -entity user {} - -entity comment { - - // represents comment's owner - relation owner @user - - // permissions - action edit = owner - action delete = owner -} - -``` - -## Sample Relational Tuples - -comment:2#owner@user:1 - -comment:3#owner@user:51 - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/parent-child.md b/docs/versioned_docs/version-0.2.x/example-use-cases/parent-child.md deleted file mode 100644 index 88ecd8b0..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/parent-child.md +++ /dev/null @@ -1,141 +0,0 @@ - -# Parent Child Relationships - -See how parent child relations can model in Permify. In this use case we'll follow a simple student-calendar management system with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -------- - -## Full Schema - -```perm -entity user {} - -entity student { - - // refers student itself - relation self @user - - // teacher of the student - relation teacher @user -} - -entity class { - - // refers class member - relation member @student - - // calender view permission - action view_calendar = member.self or member.teacher -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 3 entity, - -- `user`, represents users. This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `student`, representing students. student and class entities have many to many relation. - -- `class`, representing class that students belongs. - -### Relations - -To define relation, **relations** needed to be created as entity attributes. - -#### student entity - -In our schema above, we defined 2 relation in user entity, respectively; ``self`` and ``student`` - -```perm -entity student { - - relation self @user - relation teacher @user - -} - -``` - -**self** describes student itself, and **teacher** represents the teacher that students take a class from. - -#### class entity - -The class entity has only one relation, which is ``member``. It represents the member of the class. Basically, students whom taking that specific class. - -```perm -entity class { - - relation member @student - -} -``` - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### class actions - -Think each class has a calendar that shows necessary times and dates of lectures, deadlines etc. - -We want only, - -- Students that take that class -- Teachers, whom is teacher of the student that takes that specific class (class member). - -can access to calendar of that spesific class. - -```perm -entity class { - - action view_calendar = member.self or member.teacher - -} -``` - -Since ``member` represents the relation with student entitiy. It can reach its relations with comma. So, - -- ``member.self`` -indicates student itself, whom takes that class. - -- ``member.teacher`` -indicates teacher of student, whom takes that class. - -## Example Relational Tuples - -student:2#self@user:daniel - -student:54#self@user:daniel - -student:12#teacher@user:jack - -student:34#teacher@user:jack - -class:68#member@student:54 - -class:12#member@student:34 - - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/sharing.md b/docs/versioned_docs/version-0.2.x/example-use-cases/sharing.md deleted file mode 100644 index 0850cbc6..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/sharing.md +++ /dev/null @@ -1,40 +0,0 @@ - -# Sharing & Collaboration - -Inviting a team member to a document, project or repository should be hassle free to model. In Permify you can achieve this with simply defining a invite action. Check out the below model block see how sharing can be modeled with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -------- - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - relation manager @user - -} - -entity project { - - // represents project's parent organization - relation org @organization - - // represents owner of this project - relation owner @user - - // invite permission - action invite = org.admin or owner - -} - -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/simple-rbac.md b/docs/versioned_docs/version-0.2.x/example-use-cases/simple-rbac.md deleted file mode 100644 index 17933ead..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/simple-rbac.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Role Based Access Control - -Want to implement role and permissions to your application ? Permify fully covers you at that point. Below example shows how to model simple role based access control for organizational roles and permissions with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -------- - -## Full Schema - -```perm -entity user {} - -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - - //organization files access permissions - action view_files = admin or manager or (member and not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 2 entities, - -- `user`, represents users (maybe corresponds as employees). This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, representing the organization the user (employees) belongs. It has several roles and permissions related to the specific resources such as organization files and vendor files. - -### Relations - -#### organization entity - -We can use **relations** to define roles. In this example, we have 4 organizational wide roles, respectively; admin, manager, member, and agent. - -```perm -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - -} -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### organization actions - -In our schema, we define several actions for controlling access permissions on organization files and organization vendor's files. - -```perm -entity organization { - - //organization files access permissions - action view_files = admin or manager or (member and not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -let's take a look at some of the actions: - -- ``action edit_files = admin or manager`` -indicates that only the admin or manager has permission to edit files in the organization. - -- ``action view_files = admin or manager or (member and not agent)`` -indicates that the admin, manager, or members (without having the agent role) can view organization files. - - - -## Example Relational Tuples for this case - -organization:2#admin@user:daniel - -organization:5#member@user:ashley - -organization:17#manager@user:mert - -organization:21#agent@user:ege - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/example-use-cases/user-groups.md b/docs/versioned_docs/version-0.2.x/example-use-cases/user-groups.md deleted file mode 100644 index ed259649..00000000 --- a/docs/versioned_docs/version-0.2.x/example-use-cases/user-groups.md +++ /dev/null @@ -1,203 +0,0 @@ - -# User Groups - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with Permify's DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -------- - -## Full Schema - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // reference for organization that team belong - relation org @organization - - // organization admins or owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be admin and either owner or member of this team - action invite = org.admin and (owner or member) - - // only owners can remove users - action remove_user = owner - -} - -entity project { - - // references for team and organization that project belongs - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 4 entity, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that contain teams. - -- `team`, represents teams, which belongs to a organization. - -- `project`, represents projects that belongs teams. - -### Relations - -#### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -#### team entity - -The eeam entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -#### project entity - -Project entity has ``team`` and ``org`` relations. Both these relations represents parent relationship with other entites, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can perform editing and deleting team spesific resources. - -- Moreover, for inviting a colleague to a team you must have ***admin role*** and either be a ***owner*** or ***member*** on that team. - -- To remove users in team you must be a ***owner*** of that team. - -And these rules reflects Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -#### project actions - -And there are the project actions below. It consists of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Example Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organiation:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 also members in organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/getting-started/_category_.json b/docs/versioned_docs/version-0.2.x/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/versioned_docs/version-0.2.x/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.2.x/getting-started/enforcement.md b/docs/versioned_docs/version-0.2.x/getting-started/enforcement.md deleted file mode 100644 index c2f807ac..00000000 --- a/docs/versioned_docs/version-0.2.x/getting-started/enforcement.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Access Control Check - -In Permify, you can perform access control checks as both [resource specific] and [subject specific] (data filtering) with single API calls. - -A simple [resource specific] access check takes form of ***Can the subject U perform action X on a resource Y ?***. A real world example would be: can user:1 edit document:2 where the right side of the ":" represents identifier of the entity. - -On the other hand [subject specific] access check takes form of ***Which resources does subject U perform an action X ?*** This option is best for filtering data or bulk permission checks. - -[resource specific]: #resource-specific-check-request -[subject specific]: #subject-specific-data-filtering-check-request - -## Performance & Availability - -Permify designed to answer these authorization questions efficiently and with minimal complexity while providing low latency with: -- Using its parallel graph engine. -- Storing the relationships between resources beforehand in Permify data store: [writeDB], rather than providing these relationships at โ€œcheckโ€ time. -- Implementing permission caching to not recompute repeated permission checks, and in memory cache to store authorization schema. -- Using [Snap Tokens](../../reference/snap-tokens) to achieve consistency and high performance in cache. - -Performance and availability of the API calls - especially access checks - are crucial for us and we're ongoingly improving and testing it with various methods. - -:::info -We would love to create a test environment for you in order to test Permify API and see performance and availability of it. [Schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -[writeDB]: ../getting-started/sync-data.md - -## Resource Specific Check Request - -Let's follow an simplified access control decision for examining the resource specific [check] request. - -[check]: ../api-overview/check-api.md - -***Can the user 3 edit document 12 ?*** - -#### Path: - -POST /v1/permissions/check - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop| - -#### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - }, - "entity": { - "type": "document", - "id": "12" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -} -``` - -#### Response - -```json -{ - "can": "RESULT_ALLOW", - "remaining_depth": 0 -} -``` - -### How Access Decisions Evaluated? - -Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. - -In high level, access of an subject related with the relationships created between the subject and the resource. You can define this relationships in Permify Schema then create and store them as relational tuples, which is basically your authorization data. - -Permify Engine to compute access decision in 2 steps, -1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. -2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. - -Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. - -[relational tuples]: ../../getting-started/sync-data -[Permify Schema]: ../../getting-started/modeling - -When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity document { - - // represents documents parent organization - relation parent @organization - - // represents owner of this document - relation owner @user - - // permissions - action edit = parent.admin or owner - action delete = owner -} -``` - -Which has a directed graph as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193418063-af33fe81-95ed-4615-9d86-b50d4094ad8e.png) - -As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: - -**Q1:** Get the owners of the `document:12`. - -**Q2:** Get admins of the organization where `document:12` belongs to. - -Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. - -Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. - -## Subject Specific (Data Filtering) Check Request - -For this access check you can ask questions in form of โ€œWhich resources can user:X do action Y?โ€ And youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So we have a 2 seperate endpoints for data filtering check request, - -- [/v1/permissions/lookup-entity](#lookup-entity) -- [/v1/permissions/lookup-entity-stream](#lookup-entity-streaming) - -### Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array - -#### Path: - -POST /v1/permissions/lookup-entity - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | - -#### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - } -} -``` - -#### Response - -```json -{ - "entity_ids": [ - "15","142","93214","312","612" - ] -} -``` - -### Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -#### Path: - -POST /v1/permissions/lookup-entity-stream - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | - -#### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - } -} -``` - -#### Response - -```json -{ - "(streaming entity IDs')" -} -``` - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/getting-started/modeling.md b/docs/versioned_docs/version-0.2.x/getting-started/modeling.md deleted file mode 100644 index 4be6f25d..00000000 --- a/docs/versioned_docs/version-0.2.x/getting-started/modeling.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Modeling Authorization - -![modeling-authorization](https://raw.githubusercontent.com/Permify/permify/master/assets/permify-dsl.gif) - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like user types such as admin, manager, member, etc. - -You can define your entities, relations between them and access control decisions with using Permify Schema. It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -Hereโ€™s a simple breakdown of our schema. - -![permify-schema](https://user-images.githubusercontent.com/34595361/183866396-9d2850fc-043f-4254-aa4c-ee2c4172afb8.png) - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is **_".perm"_**. - -## Developing a Schema - -This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. - -We'll follow a simplified version of github access control system. To see completed model you can jump directly to [Github Example](#github-example). - -:::info -You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. - -[vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm - -::: - -### Entities - -The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. - -Think of entities as tables in your relationship database. We are strongly advice to name entities same as your database table name that its corresponds. In that way you can easily model and reason your authorization as well as eliminating the error possibility. - -You can create entities using `entity` keyword. Since we're following example of simplified github access control, lets create some of our entities as follows. - -```perm -entity user {} - -entity organization {} - -entity team {} - -entity repository {} -``` - -Entities has 2 different attributes. These are; - -- **relations** -- **actions** - -### Relations - -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. Keyword **_relation_** need to used to create a entity relation with name and type attributes. - -**Relation Attributes:** - -- **name:** relation name. -- **type:** relation type, basically the entity itโ€™s related to (e.g. user, organization, document, etc.) - -An example relation takes form of, - -```perm -relation [name] @[type] -``` - -Lets turn back to our example and define our relations inside our entities: - -#### User Entity - -โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. - -```perm -entity user {} -``` - -#### Organization Entity - -โ†’ For the sake of simplicity let's define only 2 user types in an organization, these are administrators and direct members of the organization. - -```perm -entity organization { - - relation admin @user - relation member @user - -} -``` - -#### Team Entity - -โ†’ Let's say teams can belong organizations and can have a member inside of it as follows, - -```perm -entity team { - - relation parent @organization - relation member @user - -} -``` - -The parent relation is indicating the organization the team belongs to. This way we can achieve **parent-child relationship** inside this entity. - -#### Repository Entity - -โ†’ Organizations and users can have multiple repositories, so each repository is related with an organization and with users. We can define repository relations as as follows. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - -} -``` - -The owner relation indicates the creator of the repository, that way we can achieve **ownership** in Permify. - -**Defining Multiple Relation Types** - -As you can see we have new syntax above, - -```perm - relation maintainer @user @team#member -``` - -When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. - -**_Quick note here:_** with using # you can reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. - -Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. So, the basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. - -Permify Schema supports `and`, `or`, `and not` and `or not` operators to define actions. Keyword **_action_** need to used with these operators to form an action. - -Lets get back to our github example and create some actions on repository entity, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - .. - .. - - action push = owner - -} -``` - -โ†’ `action push = owner or maintainer` indicates only the repository owner or maintainers can push to -repository. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - .. - .. - - action read = (owner or maintainer or org.member) and org.admin - -} -``` - -โ†’ For more fine grained permission let's examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. - - -:::info -You can add actions to another action like relations, see below. - -```perm - action edit = member or manager - action delete = edit or org.admin -``` - -delete action can inherit the edit action rules like above. To sum up, only organization administrators and any relation that can perform edit action (member or manager) can perform delete action. - -::: - -## Github Example - -Here is full implementation of simple Github access control example with using Permify Schema. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity team { - - relation parent @organization - relation member @user - - action edit_team = member or parent.admin - -} - -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - action push = owner or maintainer - action read = (owner or maintainer or parent.member) and parent.admin - action delete = parent.admin or owner - -} -``` - -See more schema examples from the [Example Use Cases](../../use-cases) section with their detailed examination. diff --git a/docs/versioned_docs/version-0.2.x/getting-started/sync-data.md b/docs/versioned_docs/version-0.2.x/getting-started/sync-data.md deleted file mode 100644 index 597754dd..00000000 --- a/docs/versioned_docs/version-0.2.x/getting-started/sync-data.md +++ /dev/null @@ -1,179 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Centralizing Authorization Data - -Permify unifies your authorization data in a database you prefer. We named that database as Write Database, shortly **WriteDB**. - -Permify API provides various functionalities - checking access, reasoning permissions, etc - to maintain separate access control mechanisms for individual applications. And **WriteDB** stands as a source of truth for these authorization functionalities. - -## Access Control as Relations - Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -In Permify, the simplest form of relational tuple structured as: `entity # relation @ user`. Here are some relational tuples with semantics, - -![relational-tuples](https://user-images.githubusercontent.com/34595361/183959294-149fcbb9-7f10-4c1e-8d66-20a839893909.png) - -## Where Relational Tuples Used ? - -In Permify, these relational tuples represents your authorization data. - -Permify stores your relational tuples (authorization data) in **WriteDB**. You can configure it **WriteDB** when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -Stored relational tuples are queried on access control check requests to decide whether user action is authorized. - -As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, or even a role such as an administrator or manager. -[WriteDB]: #write-database - -## Creating Relational Tuples - -Relational tuples can be created with an simple API call in runtime, since relations and authorization data's are live instances. Each relational tuple should be created according to its authorization model, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -Let's follow a real example to see how to create relation tuples. Lets say we have a document management system with the following Permify Schema. - -```perm -entity user {} - -entity organization { - - relation member @user - -} - -entity document { - - relation owner @user - relation org @organization - - action view = owner or org.member - action edit = owner - action delete = owner -} -``` - - According to the schema above; when a user creates a document in an organization, more specifically let's say, when user:1 in organization:2 create a document:4 we need to create the following relational tuple, - -- `document:4#owner@user:1` - -[WriteDB]: #write-database - -### API endpoint - -You can create relational tuples by using `/v1/relationships/write` endpoint. - -Send a request to POST - `/v1/relationships/write` - -**Request** - -```json -{ - "schema_version": "", - "tuples": [ - { - "entity": { - "type": "document", - "id": "4" - }, - "relation": "owner", - "subject": { - "type": "user", - "id": "1", - "relation": "" - } - } - ] -} -``` - -## More Relation Tuple Examples - -### Organization Admin - -Request - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -**Created relational tuple:** organization:1#admin@1 - -**Definition:** User 1 has admin role on organization 1. - -### Organization Members are Viewer of Repo - -Request - -```json -{ - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "viewer", - "subject": { - "type": "organization", - "id": "2", - "relation": "member" - } -} -``` - -**Created relational tuple:** repository:1#admin@organization:2#member - -**Definition:** Members of organization 2 are viewers of repository 1. - -### Parent Organization - -Request - -```json -{ - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "parent", - "subject": { - "type": "organization", - "id": "1", - "relation": "..." - } -} -``` - -**Relational Tuple:** repository:1#parent@organization:1#โ€ฆ - -**Definition:** Organization 1 is parent of repository 1. - -:::info -Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. -::: - -## Write Database - -But how authorization data stored in WriteDB ? Let's take a look at a snap shot of demo table on example Write Database. - -![demo-table](https://user-images.githubusercontent.com/34595361/180988784-a9424088-2d4f-4cee-8db4-96adde40d27d.png) - -Each row represents object-user or object-object relations, which we call relational tuples. Each row (tuple) behave as ACL and takes the form of โ€œuser U has relation R to object Oโ€ - -โ†’ Considering table above, semantics of second row (id:8) is **user 1 is owner of repository 1** - -Alternatively user U can behave as "set of users". -More specifically, โ€œset of users S has relation R to object Oโ€, where S is itself specified in terms of another object-relation pair. - - โ†’ First row in our table (id:7), we can see that **organization 1 (set of users in organization) is parent of repository 1** - -:::info -These relational tuples data form is inspired by Googleโ€™s Consistent, Global Authorization System, [Google Zanzibar White Paper](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) -::: \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/getting-started/testing.md b/docs/versioned_docs/version-0.2.x/getting-started/testing.md deleted file mode 100644 index 1d01611a..00000000 --- a/docs/versioned_docs/version-0.2.x/getting-started/testing.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Testing & Validation - -Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. - -Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. - -We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Usage - -### Adding action to your workflow - -After adding [permify-validate-action] to your Github Action workflow, you need to define the schema validation yaml file as, - -- **With local file:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1" - with: - validationFile: "test.yaml" -``` - -- **With external url:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1" - with: - validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" -``` - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -### Creating Schema Validation File - -Below you can examine an example schema validation yaml file. It consists 3 parts; `schema` which is your new authorization model, sample `relationships` to test your model and lastly `assertions` which is the test access check questions and expected response - true or false. - -```yaml -schema: >- - entity user {} - - entity organization { - - relation admin @user - relation member @user - - action create_repository = (admin or member) - action delete = admin - } - - entity repository { - - relation owner @user @organization#member - relation parent @organization - - action push = owner - action read = (owner and (parent.admin and parent.member)) - action delete = (parent.member and (parent.admin or owner)) - action edit = parent.member and not owner - } - -relationships: - - "organization:1#admin@user:1" - - "organization:1#member@user:1" - - "repository:1#owner@user:1" - - "repository:2#owner@user:2" - - "repository:2#owner@user:3" - - "repository:1#parent@organization:1#..." - - "organization:1#member@user:43" - - "repository:1#owner@user:43" - -assertions: - - "can user:1 push repository:1": true - - "can user:1 push repository:2": false - - "can user:1 push repository:3": false - - "can user:43 edit repository:1": false -``` - -You can also test your new authorization model in your local (Permify clone) without using [permify-validate-action] at all. - -For that open up a new file and add a schema yaml file inside. Then build your project with, run `make run` command and run `./permify validate {path of your schema validation file}`. - -If we use the above example schema validation file, after running `./permify validate {path of your schema validation file}` it gives a result on the terminal as: - -![schema-validation](https://user-images.githubusercontent.com/34595361/207110538-9837b09d-26b4-409a-a309-a21f66e6677a.png) - -[permify-validate-action]: https://github.com/Permify/permify-validate-action - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - - diff --git a/docs/versioned_docs/version-0.2.x/installation/_category_.json b/docs/versioned_docs/version-0.2.x/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/versioned_docs/version-0.2.x/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.2.x/installation/brew.md b/docs/versioned_docs/version-0.2.x/installation/brew.md deleted file mode 100644 index 8e81d855..00000000 --- a/docs/versioned_docs/version-0.2.x/installation/brew.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "Brew (With Conf)" ---- - -# Brew With Configurations - -This section shows how to intall and run Permify Service with using brew. - -### Install Permify - -Open terminal and run following line, - -```shell -brew install permify/tap/permify -``` - -### Run Permify Service - -To run the Permify Service, `permify serve` command should be run with configurations. - -By default, the service is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather then an actual database. You can override these with running the command with configuration flags. See all configuration options with running `permify serve --help` on terminal. - -Check out the [Centralize Authorization Data] section to learn how to organize this config YAML file and get more details about the configuration options. - -[Centralize Authorization Data]: ../getting-started/sync-data - -### Configuration Flags - -| Flag | Description | Default | -|--------------------------|----------| ----------| -| --authn-enabled | Enable server authentication | false | -| --authn-preshared-keys | Preshared key/keys for server authentication. | - | -| --database-engine | Data source. Permify supports PostgreSQL('postgres') for now. | memory | -| --database-name | Custom database name | - | -| --max_open_connections | Max connection pool size | 20 | -| --database-uri | Uri of your data source to store relation tuples | - | -| --grpc-port | Port that server run on | 3478 | -| --grpc-tls-config-cert-path | GRPC tls certificate path | - | -| --grpc-tls-config-key-path | GRPC tls key path | - | -| -h or --help | Help for serve | no value | -| --http-cors-allowed-headers | CORS allowed headers for http gateway | [*] | -| --http-cors-allowed-origins | CORS allowed origins for http gateway | [*] | -| --http-enabled | Switch option for HTTP server | true | -| --http-port | HTTP port address | 3476 | -| --http-tls-config-cert-path | HTTP tls certificate path | - | -| --http-tls-config-key-path | HTTP tls key path | - | -| --log-level | Real time logs of authorization. Permify uses zerolog as a logger | debug| -| --tracer-enabled | Switch option for tracing | false | -| --tracer-endpoint | Export uri for tracing data | - | -| --tracer-exporter | Can be; jaeger, signoz or zipkin. (integrated tracing tools) | - | - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de?action=collection%2Ffork&collection-url=entityId%3D16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de%26entityType%3Dcollection%26workspaceId%3Dd3a8746c-fa57-49c0-83a5-6fcf25a7fc05) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://app.swaggerhub.com/apis-docs/permify/permify/latest) - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.2.x/installation/container.md b/docs/versioned_docs/version-0.2.x/installation/container.md deleted file mode 100644 index 621d67fc..00000000 --- a/docs/versioned_docs/version-0.2.x/installation/container.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: "Container (With Conf)" ---- - -# Container With Configurations - -This section shows how to run Permify Service from a container with configurations. You can run Permify service from a container with following steps. - -### Run following line on Terminal - -```shell -docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve -``` - -This will start an API server with the configuration options that pointed out on the **{YOUR-CONFIG-PATH}**. - -This config path - `{YOUR-CONFIG-PATH}:/config` - addresses **config.yaml** file, where you can configure running options of the server as well as define the ***database to store your authorization data***. - -By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. You can override these by creating and and point out a new configuration file when running the command. - -Permify designed to be store authorization data in a database you prefer as relation tuples. We called that database **โ€˜writeDBโ€™**. Additional to other configuration options, you can also define (point out) your **โ€˜writeDBโ€™** on the configuration YAML file as well. - -### Configuration File - -Here is the example configuration YAML file with descriptions below. You can also find this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. - -***Example config.yaml file*** - -```yaml -server: - http: - enabled: true - port: 3476 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - grpc: - port: 3478 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - -logger: - level: 'debug' - -authn: - enabled: false - keys: [] - -tracer: - exporter: 'zipkin' - endpoint: 'http://localhost:9411/api/v2/spans' - enabled: false - -meter: - exporter: 'otlp' - endpoint: 'localhost:4318' - enabled: true - -service: - circuit_breaker: false - concurrency_limit: 100 - -database: - engine: 'postgres' - database: 'db_name' - uri: 'postgres://user:password@host:5432' - max_open_connections: 20 -``` -* **server:** Server options to run Permify. (`grpc` and `http` available for now.) - * **grpc:** example, same configurations for `http` option. - * **port:** port that server run on. - * **tls:** transport layer security options. - * **enabled** switch option for tls, *(default: false)*. - * **cert** tls certificate path. - * **key** tls key path. - -* **logger** - * **level:** Real time logs of authorization. Permify uses [zerolog] as a logger. - -[zerolog]: https://github.com/rs/zerolog - -* **authn** - * **enabled:** switch option for server authentication, *(default: false)* - * **keys:** Private key/keys for server authentication. Permify does not provide this key, so it must be generated by the users. - -* **tracer** (optional) - * **exporter:** Permify integrated with [jaeger] , [signoz] and [zipkin] tracing tools. See our [change log] about tracing performance of your authorization. - * **endpoint:** export uri for tracing data. - * **enabled:** switch option for tracing. *(default: false)* - -* **meter** (optional) - * **exporter:** [otpl](https://opentelemetry.io/docs/collector/) is default. - * **endpoint:** export uri to observe metrics; check count, cache check count and session information; Permify version, hostname, os, arch. - * **enabled:** switch option for meter tracing. *(default: true)* - -* **database** : Points out where your want to store your authorization data (relation tuples, audits, decision logs, authorization model ) - * **engine:** Data source. Permify supports **PostgreSQL**(`'postgres'`) for now. Contact with us for your preferred database. *(default: memory)* - * **database:** Custom database name. - * **uri:** Uri of your data source. - * **max_open_connections:** Max connection pool size, *(default: 20)* - -[jaeger]: https://www.jaegertracing.io/ -[zipkin]: https://zipkin.io/ -[signoz]: https://signoz.io/ -[change log]: https://permify.co/changelog/ - -### Configure With Using Flags - -Alternatively, you can set configuration options with the respected flags when running the command. See all configuration flags with running, - -```shell -docker run -p 8080:8080 ghcr.io/permify/permify serve -help -``` - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de?action=collection%2Ffork&collection-url=entityId%3D16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de%26entityType%3Dcollection%26workspaceId%3Dd3a8746c-fa57-49c0-83a5-6fcf25a7fc05) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://app.swaggerhub.com/apis-docs/permify/permify/latest) - - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.2.x/installation/overview.md b/docs/versioned_docs/version-0.2.x/installation/overview.md deleted file mode 100644 index 194c1247..00000000 --- a/docs/versioned_docs/version-0.2.x/installation/overview.md +++ /dev/null @@ -1,266 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Guide - -Permify is an open-source authorization service that you can run in your environment and works as an API. This guide shows how to set up Permify in your servers and use it accross your applications. Set up and implementation consists of 4 steps, - -1. [Set Up & Run Permify Service](#run-permify-api) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Migrate and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) -4. [Perform Access Check](#perform-access-check) - -:::info Talk to an Permify Engineer -Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -## Set Up Permify Service - -You can run Permify Service with two options: - -- [Run From Container](#run-from-container) -- [Install With Brew](#install-with-brew). - -### Run From Container - -Installation needs some configuration such as defining running options, selecting datastore to store authorization data and more. - -However, If you want to play around with Permify without doing any configurations, you can quickly start Permify on your local with running the command below: - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -See [Container With Configurations] section to get more details about the configuration options and learn the full integration to run Permify Service from container. - -[Container With Configurations]: ../container - -### Install With Brew - -Firstly, open terminal and run following line, - -```shell -brew install permify/tap/permify -``` - -After the brew installation, the `serve` command should be used to run Permify. However, if you want to start Permify without doing any configurations, you can run the command without config flags as follows: - -```shell -permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -You can override these configurations with running the command with configuration flags. See all configuration options with running `permify serve --help` on terminal. - -Check out the [Brew With Configurations] section to learn full implementation with configurations. - -[Brew With Configurations]: ../brew - -### Test your connection - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de?action=collection%2Ffork&collection-url=entityId%3D16122080-54b1e316-8105-4440-b5bf-f27a05a8b4de%26entityType%3Dcollection%26workspaceId%3Dd3a8746c-fa57-49c0-83a5-6fcf25a7fc05) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://app.swaggerhub.com/apis-docs/permify/permify/latest) - - -## Model your Authorization with Permify Schema - -After installation completed and Permify server is running, next step is modeling authorization with Permify's authorization language - [Permify Schema]- and condigure it to Permify API. - -You can define your entities, relations between them and access control decisions of each actions with using [Permify Schema]. - -### Creating your authorization model - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: - -Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action view_files = admin or member - action edit_files = admin - -} -``` - -We have 2 entities these are **"user"** and **"organization"**. Entities represents your main tables. We strongly advise naming entities the same as your original database entities. - -Lets roll back our example, - -- The `user` entity represents users. This entity is empty because it's only responsible for referencing users. - -- The `organization` entity has its own relations (`admin` and `member`) which related with user entity. This entity also has 2 actions, respectively: - - Organization member and admin can view files. - - Only admins can edit files. - -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. - -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../example-use-cases/simple-rbac -::: - -### Configuring Permify Schema to API - -After modeling completed, you need to send Permify Schema - authorization model - to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -#### Path : ** POST "/v1/schemas/write"** -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | schema | string | - | Permify Schema as string| - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - -## Store Authorization Data as Relational Tuples - -After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. Permify stores your authorization data in a database you prefer. We called that database as WriteDB, and you can configure it when running Permify Service. - -:::info -If your Permify Service running default configurations, authorization data will be stored in memory. -::: - -If you set up Permify Service from container you can both configure WriteDB with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) and configuration flags. On the other hand, If you're using brew to install and run Permify you can only use the configuration flags. - -### Create Relational Tuples - -You can create relational tuples as authorization rules at this writeDB by using `/v1/relationships/write` endpoint. - -For our guide let's grant one of the team members (Ashley) an admin role. - -**Request:** POST - `/v1/relationships/write` - -```json -{ - "schema_version": "", - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" //Organization identifier - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1", //Ashley's identifier - "relation": "" - } - } - ] -} -``` - -**Created relational tuple:** organization:1#admin@1 - -**Semantics:** User 1 (Ashley) has admin role on organization 1. - -:::info -You can find more detailed explanation from [Move & Synchronize Authorization Data] section. - -[Move & Synchronize Authorization Data]: ../../getting-started/sync-data -::: - -### Performing Access Control Check - -You can check authorization with -single API call. This check request returns a decision about whether user can perform an action on a certain resource. - -Access decisions generated according to relational tuples, which stored in your database (writeDB) and [Permify Schema] action conditions. - -[Permify Schema]: ../getting-started/modeling - -## Perform Access Check - -Finally we're ready to control authorization. Lets perform an example access check via [check] API. - -[check]: ../../api-overview/check-api - -***Can the user 45 view files on organization 1 ?*** - -### Path: - -POST /v1/permissions/check - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | entity | object | - | name and id of the entity. Example: organization:1โ€. -| [x] | action | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action | -| [ ] | schema_version | string | - | get results according to given schema version| -| [ ] | depth | integer | 8 | | - -### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_files", - "subject": { - "type": "user", - "id": "45", - "relation": "" - }, -} -``` - -### Response - -```json -{ - "can": "RESULT_ALLOW", - "metadata": { - "check_count": 0 - } -} -``` - -See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify - -[Access Control Check]: ../../getting-started/enforcement - -## Need any help ? - -Our team is happy to help you get started with Permify. If you struggle with installation or have any questions, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.2.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/versioned_docs/version-0.2.x/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.2.x/permify-overview/authorization-service.md b/docs/versioned_docs/version-0.2.x/permify-overview/authorization-service.md deleted file mode 100644 index 0810c6c2..00000000 --- a/docs/versioned_docs/version-0.2.x/permify-overview/authorization-service.md +++ /dev/null @@ -1,42 +0,0 @@ - -# What is Authorization Service? - -Authorization is an important part of software development. There are many different ways to implement authorization, but it's important for all apps to have some form of it in order to protect the user from malicious actors and unauthorized access attempts. - -An authorization service is a module that allows you to manage access to your application and ease the development and maintenance of your authorization system. It works in run time and respond to all authorization questions from any of your apps. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify] is a fully open source authorization service that offers a variety of binding and crafting options to secure your applications. - -[Permify]: https://github.com/Permify/permify - -## Why should I use Authorization Service instead of doing from scratch? - -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. You can started quickly with a simple API that you can easily integrate into your application to move and iterate faster. - -### Do Not Reinvent The Wheel -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. Building a scalable and robust authorization system is hard and needs a quite engineering time. Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services. - -[Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell/ - -### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. - -### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. - -### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. - -## Cases that can benefit from An Authorization Service: - -- If you already have an identity/auth solution and want to plug in fine-grained authorization on top of that. -- If you want to create a unified access control mechanism to use across your individual applications. -- If you want to make future-proof authorization system and don't want to spend engineering effort for it. -- If youโ€™re managing authorization for growing micro-service infrastructure. -- If your authorization logic is cluttering your code base. -- If your data model is getting too complicated to handle your authorization within the service. -- If your authorization is growing too complex to handle within code or API gateway. - diff --git a/docs/versioned_docs/version-0.2.x/permify-overview/infrastructure.md b/docs/versioned_docs/version-0.2.x/permify-overview/infrastructure.md deleted file mode 100644 index 9cc9733c..00000000 --- a/docs/versioned_docs/version-0.2.x/permify-overview/infrastructure.md +++ /dev/null @@ -1,50 +0,0 @@ - -# Where does Permify fit into your environment? - -Permify is a simply GRPC service that responsible for managing and authorization in your environment. This section shows where and how does Permify fit into your environment with examining Permify's architecture, deployment patterns and the usage with the authentication and identity providers. - -## Architecture - -Permify stores access control relations on a database you choose and performs authorization checks based on the stored relations. We called that database Write Database - **WriteDB** - and it behaves as a centralized data source for your authorization system. - -You can model your authorization with Permify's DSL - Permify Schema and your applications can talk to Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data, or make changes to data stored in WriteDb. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify with Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/versioned_docs/version-0.2.x/permify-overview/intro.md b/docs/versioned_docs/version-0.2.x/permify-overview/intro.md deleted file mode 100644 index 3b889d41..00000000 --- a/docs/versioned_docs/version-0.2.x/permify-overview/intro.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -sidebar_position: 1 -slug: / ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is an **open-source authorization service** for creating and maintaining fine-grained authorizations in your applications. - -With Permify you can easily structure your authorization model, store authorization data in your own servers securely, and interact with Permify API to handle all authorization questions from any of your applications. - -Permify's data model is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar Paper](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -## Key Features - -โš™๏ธ **Production ready** authorization API that serve as **gRPC** and **REST** - -๐Ÿ”ฎ Domain Specific Authorization Language - Permify Schema - to **easily model** your authorization - -๐Ÿ” Database Configuration to store your permissions **in house** with **high availability** - -โœ… Ask authorization questions and get answers **down to 10ms** with **parallel graph engine** - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) - -๐Ÿ“ **Audit & Reason** your access control hassle-free with various functionalities via API - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin] - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Getting Started - -In Permify, authorization divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify [Store Authorization Data] as relations. -- Perform an [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../getting-started/modeling -[Store Authorization Data]: ../getting-started/sync-data -[Access Checks]: ../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want trying out and examine it instantly, - - - -## Community & Support - -We love to talk about authorization also we would love to hear from you :heart: - -You can get immediate help on our [Discord](https://discord.gg/n6KfzYxhPp) channel. This can be any kind of questions related to Permify, authorization, or even from authentication or identity access control. We'd love to discuss anything related with access control space. - -For feature requests, bugs or any improvements you can always open an [issue] on Github. If you like Permify, please consider giving us a :star:๏ธ on [Github](https://github.com/Permify/permify) - -[issue]: https://github.com/Permify/permify/issues - -

Let's get connected

- -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Need any help on Authorization ? - -Our team is happy to help you anything about authorization. Moreover, if you'd like to learn more about using Permify in your app or have any questions, [schedule a call with one of our founders](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/playground.md b/docs/versioned_docs/version-0.2.x/playground.md deleted file mode 100644 index 00947b27..00000000 --- a/docs/versioned_docs/version-0.2.x/playground.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization in a browser. Our playground consists 4 sections; Authorization Model, Visualizer, Authorization Data and Enforcement. Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Authorization Model - -You can create your authorization model in this section with using Permify authorization language, Permify Schema. You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured with Permify Schema. Also, you can check our docs to learn more about how to model authorization in Permify. - -To demonstrate how playground works, let's choose the "empty" option from our dropdown to create a simple authorization model as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193245391-6ff7cd21-69e3-4b8e-9fa8-d28c9045fe16.png) - -We have 2 permissions these are editing repository and deleting repository. Repository has parent child relation with organizations. Lastly organizations can have organizational wide roles such as admin and member. After completing your authorization model you can just save it with hitting the save button and start testing it. - -## Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. Here is a visualization for example model that we created above. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193245587-ff794d53-c142-44fb-959b-5c4546dd73c1.png) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as relation tuples and these tuples stored in a database that you preferred. The basic relation tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. Lastly, the user is basically the user or user set in our system. Let's say we want make user 1 admin in organization 1 then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the "new" button and a pop up will open. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246047-a6c425bd-b417-4054-b1a0-9352e8f30ded.png) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246036-691cb4ab-a589-4856-887e-7f412a2bb32d.png) - -And this created tuple shown in the Authorization Data section as follows. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246251-ffbb5c8d-944b-4b87-ae50-82a7c2d575e2.png) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246717-cce0dc69-f10b-4e3a-8a85-ed846373a154.png) - -Created relational tuple after this will be: "repository:1#parent@organization:1#..." We used โ€œ...โ€ when subject type is different from user entity. #โ€ฆ represents a relation that does not affect the semantics of the tuple. - -## Enforcement ( Access Checks) -Finally as we have a sample data lets perform an access check from the right below. Let's check whether user:1 can edit the repository:1. Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission action as: - -`โ€action edit = owner or parent.admin` - -which parent.admin indicates admin in the organization that repository belongs to. So let's type **"can user:1 edit repository:1"** and hit the check button to get result. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246742-4df97b34-5e94-4132-9c7c-8d184ccc32f4.png) - - -Let's try to get unauthorized result. Type "can user:1 delete repository:1" on the question input. Since only owners can delete the repository this access check will result as unauthorized. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246754-86332f18-a483-479b-a0cf-62703c38a2f4.png) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. Check out our docs for different modeling use cases, creating and storing relational tuples and more. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.2.x/reference/_category_.json b/docs/versioned_docs/version-0.2.x/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/versioned_docs/version-0.2.x/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.2.x/reference/glossary.md b/docs/versioned_docs/version-0.2.x/reference/glossary.md deleted file mode 100644 index 58c57b51..00000000 --- a/docs/versioned_docs/version-0.2.x/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Write Database - WriteDB - -Permify stores your relational tuples (authorization data) in **WriteDB**. You can configure it **WriteDB** when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control Models](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/reference/snap-tokens.md b/docs/versioned_docs/version-0.2.x/reference/snap-tokens.md deleted file mode 100644 index d9d734f5..00000000 --- a/docs/versioned_docs/version-0.2.x/reference/snap-tokens.md +++ /dev/null @@ -1,50 +0,0 @@ - -# Snap Tokens & Zookies - -A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. - -## Why you should use Snap Tokens ? - -Basically, you should use snap tokens both for consistency and performance. The main goal of Permify is to provide an authorization system that ensures excellent performance that can handle millions of requests from different environments while ensuring data consistency. - -Performance standards can be achievable with caching. In Permify, the cache mechanism eliminates re-computing of access control checks that once occurred, unless any relationships of resources don't change. - -Still, all caches suffer from the risk of becoming stale. If some schema update happens, or relations change then all of the caches should be updated according to it to prevent false positive or false negative results. - -Permify avoids this problem with an approach of snapshot reads. Simply, it ensures that access control is evaluated at a consistent point in time to prevent inconsistency. - -To achieve this, we developed tokens called Snap Tokens that consist of a timestamp that is compared in access checks to ensure that the snapshot of the access control is at least as fresh as the resource timestamp - basically its stored snap token. - -## How to use Snap Tokens - -Snap Tokens used in endpoints to represent the snapshot and get fresh results of the API's. It mainly used in [Write API] and [Check API]. - -The general workflow for using snap token is getting the snap token from the reponse of Write API request - basically when writing a relational tuple - then mapped it with the resource. One way of doing that is storing snap token in the additioanl column in your relational database. - -Then this snap token can be used in endpoints. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -```json -{ - "schema_version": "ce8siqtmmud16etrelag", - "snap_token": "gp/twGSvLBc=", - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - }, -} -``` - -[Write API]: ../../api-overview/write-relationships -[Check API]: ../../api-overview/check-api - -#### All endpoints that used snap token - -- [Write API](../../api-overview/write-relationships) -- [Check API](../../api-overview/check-api) -- [Expand API](../../api-overview/expand-api) -- [Schema Lookup](../../api-overview/schema-lookup) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.2.x/reference/tracing.md b/docs/versioned_docs/version-0.2.x/reference/tracing.md deleted file mode 100644 index 9f6a3193..00000000 --- a/docs/versioned_docs/version-0.2.x/reference/tracing.md +++ /dev/null @@ -1,52 +0,0 @@ - -# Tracing Tools - -Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: - -- [Jaeger](https://www.jaegertracing.io/) -- [Signoz](https://signoz.io/) -- [Zipkin](https://zipkin.io/) - -## Usage - -### Set Up - -Adding one of these tracing tools to your authorization system is quite simple, you just need to define it in the Permify configuration file as **tracer**. - -```yaml -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -``` -- ***exporter***: enter the tool name that you want to use. `jaeger` , `signoz` and `zipkin`. -- ***endpoint***: export url for tracing data. -- ***disabled***: switch option for tracing. - -**Example YAML configuration file** - -```yaml -app: - name: โ€˜permifyโ€™ -http: - port: 3476 -logger: - log_level: โ€˜debugโ€™ - rollbar_env: โ€˜permifyโ€™ -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -database: - write: - connection: 'postgres' - database: 'morf-health-demo' - uri: 'postgres://postgres:SphU4Uf3QXNntT@permify.us-east-1.rds.amazonaws.com:5432' - pool_max: 2 -``` - -After running Permify in your server, you should run Zipkin as well. If you're using docker here is the docker pull request for Zipkin: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` diff --git a/docs/versioned_docs/version-0.3.x/api-overview.md b/docs/versioned_docs/version-0.3.x/api-overview.md deleted file mode 100644 index af2094da..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Overview - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 3 core parts: - -- [PermissionService]: Consists access control requests and options. -- [RelationshipService]: Authorization data operations such as creating, deleting and reading relational tuples. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ./permission -[RelationshipService]: ./relationship -[SchemaService]: ./schema -[TenancyService]: ./tenancy - -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -## Core Paths - -- Configure your authorization model with [Schema Write](./api-overview/schema/write-schema.md) -- Write relational tuples with [Write Relationships](./api-overview/relationship/write-relationships.md) -- Read relation tuples and filter them with [Read API](./api-overview/relationship/read-api.md) -- Check access with [Check API](./api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](./api-overview/permission/lookup-entity.md) -- Delete relation tuples with [Delete Tuple](./api-overview/relationship/delete-relationships.md) -- Expand schema actions with [Expand API](./api-overview/permission/expand-api.md) -- Get permissions of your resources with [Schema Lookup](./api-overview/permission/schema-lookup.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/api-overview/_category_.json b/docs/versioned_docs/version-0.3.x/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/permission/_category_.json b/docs/versioned_docs/version-0.3.x/api-overview/permission/_category_.json deleted file mode 100644 index f91d5b46..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/permission/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Permission Service", - "position": 3, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/permission/check-api.md b/docs/versioned_docs/version-0.3.x/api-overview/permission/check-api.md deleted file mode 100644 index fff42fdb..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/permission/check-api.md +++ /dev/null @@ -1,130 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control - -In Permify, you can perform two different types access checks, - -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **subject based** authorization checks, in form of `Which resources can user U edit ?` - -In this section we'll look at the resource based check request of Permify. You can find subject based access checks in [Check Entities' Permissions] section. - -[Check Entities' Permissions]: ../lookup-entity - -## Request - -**Path:** POST /v1/permissions/check - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop| - - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "can": "RESULT_ALLOW", - "remaining_depth": 0 -} -``` - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. See how [access decisions evaluated] in Permify. - -[access decisions evaluated]: ../../../getting-started/enforcement#how-access-decisions-evaluated - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/permission/expand-api.md b/docs/versioned_docs/version-0.3.x/api-overview/permission/expand-api.md deleted file mode 100644 index df568adb..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/permission/expand-api.md +++ /dev/null @@ -1,329 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API - -Retrieve all subjects (users and user sets) that have a relationship with given entity and permission - -Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. - -:::caution When To Use ? -Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. - -It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. -::: - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), & v1.PermissionExpandRequest { - TenantId: "t1", - Metadata: & v1.PermissionExpandRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -}' -``` - - - -## Example Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** POST /v1/tenants/{tenant_id}/permissions/expand - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. -| [x] | action | string | - | The action the user wants to perform on the resource | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "entity": { - "type": "repository", - "id": "1" - }, - "action": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": null, - "expand": { - "operation": "INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "parent.admin" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "organization", - "id": "1", - "relation": "admin" - } - ] - } - }, - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": null, - "expand": { - "operation": "UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "parent.member" - }, - "leaf": { - "exclusion": false, - "subjects": [ - { - "type": "organization", - "id": "1", - "relation": "member" - } - ] - } - }, - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "exclusion": false, - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- diff --git a/docs/versioned_docs/version-0.3.x/api-overview/permission/lookup-entity.md b/docs/versioned_docs/version-0.3.x/api-overview/permission/lookup-entity.md deleted file mode 100644 index 33ed99a8..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/permission/lookup-entity.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Check Entities' Permissions ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Entities' Permissions (Data Filtering) - -Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So, we provide 2 separate endpoints for data filtering check request, - -- [/v1/permissions/lookup-entity](#lookup-entity) -- [/v1/permissions/lookup-entity-stream](#lookup-entity-streaming) - -## Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. - -**POST** /v1/permissions/lookup-entity - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | - - - - -```go -cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - EntityType: "document", - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - } -}) -``` - - - - -```javascript -client.permission.lookupEntity({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity_type: "document", - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response.entity_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type":"user", - "id":"1" - } -}' -``` - - - -### Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -**POST** /v1/permissions/lookup-entity-stream - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | - - - - -```go -str, err: = client.Permission.LookupEntityStream(context.Background(), & v1.PermissionLookupEntityRequest { - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "", - SchemaVersion: "" - Depth: 50, - }, - EntityType: "document", - Permission: "view", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, -}) - -// handle stream response -for { - res, err: = str.Recv() - - if err == io.EOF { - break - } - - // res.EntityId -} -``` - - - - -```javascript -const permify = require("@permify/permify-node"); -const {PermissionLookupEntityStreamResponse} = require("@permify/permify-node/dist/src/grpc/generated/base/v1/service"); - -function main() { - const client = new permify.grpc.newClient({ - endpoint: "localhost:3478", - }) - - let res = client.permission.lookupEntityStream({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entityType: "document", - permission: "view", - subject: { - type: "user", - id: "1" - } - }) - - handle(res) -} - -async function handle(res: AsyncIterable) { - for await (const response of res) { - // response.entityId - } -} -``` - - - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/permission/schema-lookup.md b/docs/versioned_docs/version-0.3.x/api-overview/permission/schema-lookup.md deleted file mode 100644 index 5d660117..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/permission/schema-lookup.md +++ /dev/null @@ -1,95 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Schema Lookup - -You can use schema lookup API endpoint to retrieve all permissions associated with a resource relation. Basically, you can perform enforcement without checking stored authorization data. For example in given a Permify Schema like: - -``` -entity user {} - -entity document { - - relation assignee @user - relation manager @user - - action view = assignee or manager - action edit = manager - -} - -``` - -Let's say you have a user X with a manager role. If you want to check what user X can do on a documents ? You can use the schema lookup endpoint as follows, - -## Request - -**Path:** POST /v1/permissions/lookup-schema - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | schema_version | string | 8 | Version of the schema | -| [x] | entity_type | string | - | type of the entity. -| [x] | relation_names | string[] | - | string array that holds entity relations | - - - - -```go -cr, err: = client.Permission.LookupSchema(context.Background(), & v1.PermissionLookupSchemaRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupSchemaRequestMetadata { - SchemaVersion: "" - }, - EntityType: "document", - RelationNames: []string {"manager"}, -}) -``` - - - - -```javascript -client.permission.lookupSchema({ - tenantId: "t1", - metadata: { - schema_version: "" - }, - entity_type: "document", - relation_names: [ "manager" ] -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-schema' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "entity_type": "document", - "relation_names": [ "manager" ] -}' -``` - - - -## Response - -```json -{ - "data": { - "action_names": [ - "view", - "edit" - ] - } -} -``` - - -The response will return all the possible actions that manager can perform on documents. Also you can extend relation lookup as much as you want by adding relations to the **"relation_names"** array. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/relationship/_category_.json b/docs/versioned_docs/version-0.3.x/api-overview/relationship/_category_.json deleted file mode 100644 index bca5fd5e..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/relationship/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Relationship Service", - "position": 2, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/relationship/delete-relationships.md b/docs/versioned_docs/version-0.3.x/api-overview/relationship/delete-relationships.md deleted file mode 100644 index d63b7e68..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/relationship/delete-relationships.md +++ /dev/null @@ -1,103 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Relational Tuples - -You can delete any stored relation tuples with following API - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/relationships/delete - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Relationship.Delete(context.Background(), & v1.RelationshipDeleteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipDeleteRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.delete({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "filter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/relationship/read-api.md b/docs/versioned_docs/version-0.3.x/api-overview/relationship/read-api.md deleted file mode 100644 index eb70b30c..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/relationship/read-api.md +++ /dev/null @@ -1,103 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -## Request - -**Path:** POST /v1/tenants/{tenant_id/relationships/read - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Relationship.Read(context.Background(), & v1.RelationshipReadRequest { - TenantId: "t1", - Metadata: &v1.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.read({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.3.x/api-overview/relationship/write-relationships.md b/docs/versioned_docs/version-0.3.x/api-overview/relationship/write-relationships.md deleted file mode 100644 index 0646ff4e..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/relationship/write-relationships.md +++ /dev/null @@ -1,196 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Relationships - -In Permify, relations between your entities, objects and users stored as [relational tuples] in [writeDB]. Since relations and authorization data's are live instances these relational tuples can be created with an simple API call in runtime. - -When using Permify, the application client should update writeDB about the changes happening in entities or resources that are related to the authorization structure. If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [writeDB] up-to-date. Besides, each relational tuple should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as relation tuples and store in [writeDB]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -[relational tuples]: ../../../getting-started/sync-data -[writeDB]: ../../../getting-started/sync-data#where-relational-tuples-used - -## Request - -So if user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` must be created by using **/v1/relationships/write** endpoint. - -**Path:** POST /v1/tenants/{tenant_id}/relationships/write - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "admin", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -## Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../../reference/snap-tokens) and how its avoiding stale cache. - -## Suggested Workflow - -The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? - -### Two Phase Commit Approach -In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. - -To give more concurrent example of this action, let's take a look at below createDocument function - -```go -func CreateDocuments(db *gorm.DB) error { - - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - } - }() - - if err := tx.Error; err != nil { - return err - } - - if err := tx.Create(docs).Error; err != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - return err - } - - // if transaction successful, write relation tuple to Permify - permify.WriteRelationships(...) - - return tx.Commit().Error -} -``` -The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. - -### Relationships that not stored in application database - -Although ownership generally stored in application databases, there are some relations that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. - -For example, you can model a simple project management authorization in Permify as follows, - -```perm -entity user {} - -entity team { - - relation owner @user - relation member @user -} - -entity project { - - relation team @team - relation owner @user - - action view = team.member or team.owner or project.owner - action edit = project.owner or team.owner - action delete = project.owner or team.owner - -} -``` - -This **team member** relation won't need to be stored in the application database. Storing it only in Permify - WriteDB - is enough. In that situation, `WriteRelationships` can be performed in any logical place in your stack. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/schema/_category_.json b/docs/versioned_docs/version-0.3.x/api-overview/schema/_category_.json deleted file mode 100644 index 8fd1e959..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/schema/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Schema Service", - "position": 1, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/schema/write-schema.md b/docs/versioned_docs/version-0.3.x/api-overview/schema/write-schema.md deleted file mode 100644 index 47a0e1ff..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/schema/write-schema.md +++ /dev/null @@ -1,93 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema - -Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. - -We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: - -Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -## Request - -**POST** /v1/tenants/{tenant_id}/schemas/write - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - TenantId: "t1", - Schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - `, -}) -``` - - - - -```javascript -client.schema.write({ - tenantId: "t1", - schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - ` -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "schema": "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/write"** - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - - -## Suggested Workflow For Schema Changes - -It's expected that your initial schema will eventually change as your product or system evolves - -As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. - -A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database (your writeDB) unless you delete them with the [delete API](../relationship/delete-relationships.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. - -We recommend applying the following pattern to safely handle schema changes: - -- Set up a central git repository that includes the schema. -- Teams or individuals who need to update the schema should add new permissions or relations to this repository. -- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. -- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. - -## Need any help ? - -Depending on the frequency and the type of the changes that you made on the schemas, this method may not be optimal for you - In such cases, we are open to exploring alternative solutions. Please feel free to [schedule a call with one of our engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/_category_.json b/docs/versioned_docs/version-0.3.x/api-overview/tenancy/_category_.json deleted file mode 100644 index 9771416d..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Tenancy Service", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/create-tenant.md b/docs/versioned_docs/version-0.3.x/api-overview/tenancy/create-tenant.md deleted file mode 100644 index 412ddaec..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/create-tenant.md +++ /dev/null @@ -1,55 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Tenant - -Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. - -:::caution -We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. -::: - -## Request - -**POST /v1/tenants/create** - - - - -```go -rr, err: = client.Tenancy.Create(context.Background(), & v1.TenantCreateRequest { - Id: "" - Name: "" -}) -``` - - - - - -```javascript -client.tenancy.create({ - id: "", - name: "" -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'http://localhost:3476/v1/tenants/create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "id": "", - "name": "" -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/delete-tenant.md b/docs/versioned_docs/version-0.3.x/api-overview/tenancy/delete-tenant.md deleted file mode 100644 index d8bd4a43..00000000 --- a/docs/versioned_docs/version-0.3.x/api-overview/tenancy/delete-tenant.md +++ /dev/null @@ -1,44 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Tenant - -You can delete a tenant with following API. - -## Request - -**DELETE /v1/tenants/{id}** - - - - -```go -rr, err: = client.Tenancy.Delete(context.Background(), & v1.TenantDeleteRequest { - Id: "" -}) -``` - - - - - -```javascript -client.tenancy.delete({ - id: "", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/comparision.md b/docs/versioned_docs/version-0.3.x/comparision.md deleted file mode 100644 index 6df7749f..00000000 --- a/docs/versioned_docs/version-0.3.x/comparision.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: comparison -title: Comparison Between Other Zanzibar implementations ---- - -:::caution Note -This comparison table shows the differentiation between authorization solutions based or inspired by Google Zanzibar paper. If you use any of these solutions and feel the information could be improved, feel free to reach out. -::: - -## General Aspects - -| | Ory/Keto | OpenFGA | SpiceDB | Permify -|---|---|---|---|---| -| **Zanzibar Paper Faithfulness** | Medium | High | High | High -| **Scalability** | Medium | Medium | High | High -| **Consistency & Cache** | No Zookies | No Zookies | Supported | Supported | -| **Dev UX** | Average | Average | High | High | - -## Feature Set - -- โœ…  Supported, and ready to use with no added configuration or code -- ๐ŸŸก  Limited support and requires extra user-code to implement. -- โ›”  Not officially supported or documented. - -| | Ory/Keto | OpenFGA | SpiceDB | Permify -|---|---|---|---|---| -| **Check API** |โœ… | โœ… | โœ… | โœ… | -| **Write API** | โœ… | โœ… | โœ… | โœ… | -| **Read API** | โœ… | โœ… | โœ… | โœ… | -| **Expand API** | โœ… | โœ… | โœ… | โœ… | -| **Watch API** | โœ… | โœ… | โœ… | โ›” | -| **RBAC** | โœ… | โœ… | โœ… | โœ… | -| **ReBAC** | โœ… | โœ… | โœ… | โœ… | -| **ABAC** | โ›” | ๐ŸŸก | โœ… | โ›” | -| **Data Filtering** | โ›” | โœ… | โœ… | โœ… | -| **Multi Tenancy** | โ›” | โœ… | โ›” | โœ… | -| **Testing & Validation** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Logging & Tracing** | ๐ŸŸก | โœ… | โœ… | โœ… | diff --git a/docs/versioned_docs/version-0.3.x/getting-started/_category_.json b/docs/versioned_docs/version-0.3.x/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.3.x/getting-started/enforcement.md b/docs/versioned_docs/version-0.3.x/getting-started/enforcement.md deleted file mode 100644 index d7ee5f12..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/enforcement.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Access Control Check - -In Permify, you can perform access control checks as both resource specific and subject specific (data filtering) with single API calls. - -A simple [resource based] access check takes form of ***Can the subject U perform action X on a resource Y ?***. A real world example would be: *can user:1 edit document:2* where the right side of the ":" represents identifier of the entity. - -On the other hand [subject based] access check takes form of ***Which resources does subject U perform an action X ?*** This option is best for filtering data or bulk permission checks. - -[resource based]: ../api-overview/permission/check-api.md -[subject based]: ../api-overview/permission/lookup-entity.md - -## Performance & Availability - -Permify designed to answer these authorization questions efficiently and with minimal complexity while providing low latency with: -- Using its parallel graph engine. -- Storing the relationships between resources beforehand in Permify data store: [writeDB], rather than providing these relationships at โ€œcheckโ€ time. -- Implementing permission caching to not recompute repeated permission checks, and in memory cache to store authorization schema. -- Using [Snap Tokens](../../reference/snap-tokens) to achieve consistency and high performance in cache. - -Performance and availability of the API calls - especially access checks - are crucial for us and we're ongoingly improving and testing it with various methods. - -:::info -We would love to create a test environment for you in order to test Permify API and see performance and availability of it. [Schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -[writeDB]: ../getting-started/sync-data.md - -## How Access Decisions Evaluated? - -Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. - -In high level, access of an subject related with the relationships created between the subject and the resource. You can define this relationships in Permify Schema then create and store them as relational tuples, which is basically your authorization data. - -Permify Engine to compute access decision in 2 steps, -1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. -2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. - -Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. - -[relational tuples]: ../../getting-started/sync-data -[Permify Schema]: ../../getting-started/modeling - -When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity document { - - // represents documents parent organization - relation parent @organization - - // represents owner of this document - relation owner @user - - // permissions - action edit = parent.admin or owner - action delete = owner -} -``` - -Which has a directed graph as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193418063-af33fe81-95ed-4615-9d86-b50d4094ad8e.png) - -As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: - -**Q1:** Get the owners of the `document:12`. - -**Q2:** Get admins of the organization where `document:12` belongs to. - -Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. - -Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/getting-started/examples/_category_.json b/docs/versioned_docs/version-0.3.x/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f801..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/examples/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Example Permission Structures", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/getting-started/examples/facebook-groups.md b/docs/versioned_docs/version-0.3.x/getting-started/examples/facebook-groups.md deleted file mode 100644 index 53a309a6..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/examples/facebook-groups.md +++ /dev/null @@ -1,537 +0,0 @@ -# Facebook Groups - -This example demonstrate the authorization structure for Facebook groups, which enables users to perform various actions based on their roles and permissions within the group. - -### Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) - -```perm -// Represents a user -entity user {} - -// Represents a Facebook group -entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator -} - -// Represents a post in a Facebook group -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner -} - -// Represents a comment like on a post in a Facebook group -entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member -} - -// Definition of poll entity -entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin -} - -// Definition of file entity -entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin -} - -// Definition of event entity -entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member -} -``` - -## Brief Examination of the Model - -The model defines several entities and relations, as well as actions and permissions that can be taken by users within the group. Let's examine them shortly; - -### Entities & Relations - -* **`user`** entity represents a user in the Facebook. - -* **`group`** entity represents the Facebook group, and it has several relations including member, admin, and moderator to represent the members, admins, and moderators of the group. Additionally, there are relations to represent the posts and comments in the group. - -* **`post`** entity represents a post in the Facebook group, and it has relations to represent the owner of the post and the group that the post belongs to. - -* **`comment`** entity represents a comment on a post in the Facebook group, and it has relations to represent the owner of the comment, the post that the comment belongs to, and the comment itself. - -* **`like`** entity represents a like on a post in the Facebook group, and it has relations to represent the owner of the like and the post that the like belongs to. - -* **`poll`** entity represents a poll in the Facebook group, and it has relations to represent the owner of the poll and the group that the poll belongs to. - -* **`file`** entity represents a file in the Facebook group, and it has relations to represent the owner of the file and the group that the file belongs to. - -* **`event`** entity represents an event in the Facebook group, and it has relations to represent the owner of the event and the group that the event belongs to. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. - -For example, the `create_group` action can only be performed by a `member`, as follows: - -#### Creating a group permission - -```perm -entity group { - - // Relation to represent the members of the group - relation member @user - - .. - - // Create group permission - action create_group = member - - .. - .. -} -``` - -Another example would be given from the `edit_post` action in the post entity, which specifies the permissions required to edit a post in a Facebook group. - -#### Editing a post permission - -```perm -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - .. - - action edit_post = owner or group.admin - - .. - .. -} -``` - -An **owner** of a post can always edit their own post. In addition, members who are defined as **admin** of the group - which the post belongs to - can also edit the post. - -Since most entities are deeply nested together, we also have multiple hierarchical permissions. - -#### Nested Hierarchies - -For example, we can define a permission "view_comment" if only user is owner of that comment or user is a member of the group which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -//group relationships -group:1#member@user:1 -group:1#admin@user:2 -group:2#moderator@user:3 -group:2#member@user:4 -group:1#member@user:5 - -//post relationships -post:1#owner@user:1 -post:1#group@group:1 -post:2#owner@user:4 -post:2#group@group:1 - -//comment relationships -comment:1#owner@user:2 -comment:1#post@post:1 -comment:2#owner@user:5 -comment:2#post@post:2 - -//like relationships -like:1#owner@user:3 -like:1#post@post:1 -like:2#owner@user:4 -like:2#post@post:2 - -//poll relationships -poll:1#owner@user:2 -poll:1#group@group:1 -poll:2#owner@user:5 -poll:2#group@group:1 - -//like relationships -file:1#owner@user:1 -file:1#group@group:1 - -//event relationships -event:1#owner@user:3 -event:1#group@group:1 -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:4 RSVP_to_event event:1 ? -

- -```perm - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - - .. - .. - - action RSVP_to_event = owner or group.member - } -``` - -According to what we have defined for the **'RSVP_to_event'** action, users who are either the owner of `event:1` or a member of the group that belongs to `event:1` can grant access to RSVP to the event. - -According to the relation tuples we created, `user:4` is not the **owner** of the event. Furthermore, when we check whether `user:4` is a **member** of the only group (`group:1`) that `event:1` is part of (`event:1#group@group:1`), we see that there is no **member** relation for `user:4` in that group. - -Therefore, the `user:4 RSVP_to_event event:1` check request should yield a **'false'** response. - -

-
- -
can user:5 view_comment comment:1 ? -

- -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -According to the relation tuples we created, `user:5` is not the **owner** of the comment. But member of the `group:1` and thats grant `user:5` (`group:1#member@user:5`) access to perform view the comment:1. In particularly, `comment:1` is part of the `post:1` (`comment:1#post@post:1`) and `post:1` is part of the group:1 (`post:1#group@group:1`). And from the action definition on above model group:1 members can view the `comment:1`. - -Therefore, the `user:5 view_comment comment:1` check request should yield a **'true'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator - } - - entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member - } - - entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner - } - - entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member - } - - entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin - } - - entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin - } - - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member - } - -relationships: - - group:1#member@user:1 - - group:1#admin@user:2 - - group:2#moderator@user:3 - - group:2#member@user:4 - - group:1#member@user:5 - - post:1#owner@user:1 - - post:1#group@group:1 - - post:2#owner@user:4 - - post:2#group@group:1 - - comment:1#owner@user:2 - - comment:1#post@post:1 - - comment:2#owner@user:5 - - comment:2#post@post:2 - - like:1#owner@user:3 - - like:1#post@post:1 - - like:2#owner@user:4 - - like:2#post@post:2 - - poll:1#owner@user:2 - - poll:1#group@group:1 - - poll:2#owner@user:5 - - poll:2#group@group:1 - - file:1#owner@user:1 - - file:1#group@group:1 - - event:1#owner@user:3 - - event:1#group@group:1 - -assertions: - - "can user:4 RSVP_to_event event:1": false - - "can user:5 view_comment comment:1": true -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make run`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233152003-1fbaf2af-d208-4290-af1f-359870b0de49.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/getting-started/examples/google-docs.md b/docs/versioned_docs/version-0.3.x/getting-started/examples/google-docs.md deleted file mode 100644 index 9e66f806..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/examples/google-docs.md +++ /dev/null @@ -1,304 +0,0 @@ -# Google Docs Simplified - -This example models a simplified version of Google Docs style permission system where users can be granted direct access to a resource, or access via organizations and nested groups. - -### Schema | [Open in playground](https://play.permify.co/?s=iuRic3nR1HeZJcFyRNKPo) - -```perm -entity user {} - -entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager -} - -entity group { - relation manager @user @group#member @group#manager - relation member @user @group#member @group#manager -} - -entity organization { - relation group @group - relation resource @resource - - relation administrator @user @group#member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} -``` - -## Breakdown of the Model - -### User - -```perm -entity user {} -``` -Represents a user who can be granted permission to access a resource directly, or through their membership in a group or organization. - -### Resource - -```perm -entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager -} -``` - -Represents a resource that users can be granted permission to access. The resource entity has two relationships: - -#### Relations - -**manager:** A relationship between users who are authorized to manage the resource. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group member and manager relations. - -**viewer:** A relationship between users who are authorized to view the resource. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager relations. - -The resource entity has two actions defined: - -#### Actions - -**manage:**: An action that can be performed by users who are authorized to manage the resource, as determined by the manager relationship. - -**view:** An action that can be performed by users who are authorized to view the resource, as determined by the viewer and manager relationships. - -### group - -```perm -entity group { - relation manager @user @group#member @group#manager - relation member @user @group#member @group#manager -} -``` - -Represents a group of users who can be granted permission to access a resource. The group entity has two relationships: - -#### Relations - -**manager:** A relationship between users who are authorized to manage the group. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**member:** A relationship between users who are members of the group. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager. - -The group entity has one action defined: - -### Organization - -```perm -entity organization { - relation group @group - relation resource @resource - - relation administrator @user @group#member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} -``` - -Represents an organization that can contain groups, users, and resources. The organization entity has several relationships: - -#### Relations - -**group:** A relationship between the organization and its groups. This relationship is defined by the `@group` annotation on the end corresponding to the group entity. - -**administrator:** A relationship between users who are authorized to manage the organization. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are directly members of the organization. This relationship is defined by the `@user` annotation on the end corresponding to the user entity. - -**resource:** A relationship between the organization and its resources. This relationship is defined by the `@resource` annotation on the end corresponding to the resource entity. - -The organization entity has two permissions defined: - -#### Permissions - -**admin:** An permission that can be performed by users who are authorized to manage the organization, as determined by the administrator relationship. - -**member:** An permission that can be performed by users who are directly members of the organization, or who have administrator relationship, or who are members of groups that are part of the organization, - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different groups -group:tech#manager@user:ashley -group:tech#member@user:david -group:marketing#manager@user:john -group:marketing#member@user:jenny -group:hr#manager@user:josh -group:hr#member@user:joe - -// Assign groups to other groups -group:tech#member@group:marketing#member -group:tech#member@group:hr#member - -// Connect groups to organization. -organization:acme#group@group:tech -organization:acme#group@group:marketing -organization:acme#group@group:hr - -// Add some resources under the organization -organization:acme#resource@resource:product_database -organization:acme#resource@resource:marketing_materials -organization:acme#resource@resource:hr_documents - -// Assign a user and members of a group as administrators for the organization -organization:acme#administrator@group:tech#manager -organization:acme#administrator@user:jenny - -// Set the permissions on some resources -resource:product_database#manager@group:tech#manager -resource:product_database#viewer@group:tech#member -resource:marketing_materials#viewer@group:marketing#member -resource:hr_documents#manager@group:hr#manager -resource:hr_documents#viewer@group:hr#member -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:ashley edit resource:product_database ? -

- -```perm - entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager - } -``` - -According what we have defined for the edit action only managers of can edit product database. In this context, Permify engine will check does subject `user:ashley` has any direct or indirect manager relation within `resource:product_database`. - -Ashley is manager in group tech (`group:tech#manager@user:ashley`) and we have defined that manager of group tech is manager of product_database with the tuple (`resource:product_database#manager@group:tech#manager`). Therefore, the **user:ashley edit resource:product_database** check request should yield **true** response. - -

-
- -
can user:joe view resource:hr_documents ? -

- -```perm - entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager - } -``` - -According what we have defined for the view action viewers or managers of can view hr documents. In this context, Permify engine will check does subject `user:joe` has any direct or indirect manager or viewer relation within `resource:hr_documents`. - -Joe doesn't have manager relationship in that resource or within any entity but he is member in the hr group (`group:hr#member@user:joe`) and we defined hr members have viewer relationship in hr documents (`resource:hr_documents#viewer@group:hr#member`). So that, this enforcement should yield **true** response. - -

-
- -
can user:david view resource:marketing_materials ? -

- -```perm - entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager - } -``` - -According what we have defined for the view action viewers or managers of can view hr documents. In this context, Permify engine will check does subject `user:david` has any direct or indirect manager or viewer relation within `resource:marketing_materials`. - -David doesn't have member or manager relationship related with marketing group or with the `resource:marketing_materials`. So that, this enforcement should yield **false** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity resource { - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager - action view = viewer or manager - } - - entity group { - relation manager @user @group#member @group#manager - relation member @user @group#member @group#manager - } - - entity organization { - relation group @group - relation resource @resource - - relation administrator @user @group#member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member - } - -relationships: - - group:tech#manager@user:ashley - - group:tech#member@user:david - - group:marketing#manager@user:john - - group:marketing#member@user:jenny - - group:hr#manager@user:josh - - group:hr#member@user:joe - - group:tech#member@group:marketing#member - - group:tech#member@group:hr#member - - organization:acme#group@group:tech - - organization:acme#group@group:marketing - - organization:acme#group@group:hr - - organization:acme#resource@resource:product_database - - organization:acme#resource@resource:marketing_materials - - organization:acme#resource@resource:hr_documents - - organization:acme#administrator@group:tech#manager - - organization:acme#administrator@user:jenny - - resource:product_database#manager@group:tech#manager - - resource:product_database#viewer@group:tech#member - - resource:marketing_materials#viewer@group:marketing#member - - resource:hr_documents#manager@group:hr#manager - - resource:hr_documents#viewer@group:hr#member - -assertions: - - "can user:ashley edit resource:product_database": true - - "can user:joe view resource:hr_documents": true - - "can user:david view resource:marketing_materials": false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make run`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://user-images.githubusercontent.com/34595361/233152224-e46850f2-8f92-4bd3-811d-54232d79a777.png) - -## Need any help ? - -This is the end of modeling Google Docs style permission system. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.3.x/getting-started/examples/notion.md b/docs/versioned_docs/version-0.3.x/getting-started/examples/notion.md deleted file mode 100644 index f3ed3d8d..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/examples/notion.md +++ /dev/null @@ -1,540 +0,0 @@ -# Notion - -This is a schema definition of the authorization model for Notion, a popular productivity and organization tool. - -### Schema | [Open in playground](https://play.permify.co/?s=BsCvLmd4g81sB20XJZI5p) - -```perm -entity user {} - -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin -} - -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write -} - -entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write -} - -entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter -} - -entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author -} - -entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write -} - -entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write -} -``` - -## Brief Examination of the Model - -The model defines several entities, including users, workspaces, pages, databases, blocks, and integrations. It also includes several default roles, such as Admin, Bot, Guest, and Member. Here's a breakdown of the entities: - -### Entities & Relations - -* **`user`**: Represents a user in the system. - -* **`workspace`**: Represents a workspace in which users can collaborate. Each workspace has an owner, members, guests, and bots associated with it. The owner and admin users have permission to manage the workspace. Permissions are defined for creating pages, inviting members, viewing the workspace, and managing the workspace. The read and write permissions can be inherited by child entities. - -* **`page`**: Represents a page within a workspace. Each page is associated with a workspace and has a writer and readers. The read and write permissions are defined based on the writer and readers of the page and can be inherited from the workspace. - -* **`database`**: Represents a database within a workspace. Each database is associated with a workspace and has an editor and viewers. The read and write permissions are defined based on the editor and viewers of the database and can be inherited from the workspace. Permissions are also defined for creating and deleting databases. - -* **`block`**: Represents a block within a page or database. Each block is associated with a page or database and has an editor and commenters. The read and write permissions are defined based on the editor and commenters of the block and can be inherited from the database. Commenters are users who have permission to comment on the block. - -* **`comment`**: Represents a comment on a block. Each comment is associated with a block and has an author. The read and write permissions are defined based on the author of the comment and can be inherited from the block. - -* **`template`**: Represents a template within a workspace. Each template is associated with a workspace and has a creator and viewers. The read and write permissions are defined based on the creator and viewers of the template and can be inherited from the workspace. Permissions are also defined for creating and deleting templates. - -* **`integration`**: Represents an integration within a workspace. Each integration is associated with a workspace and has an owner. Permissions are defined for reading and writing to the integration. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. Let's examine the **read** permission of the page entity. - -#### Page Read Permission - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - - .. - .. - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - .. -} - -entity page { - - // The workspace associated with the page - relation workspace @workspace - - .. - .. - - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - .. - .. - - // Define permissions for page actions - permission read = reader or workspace.read - - .. - .. -} -``` - -This permission specifies who can read the contents of the page at Notion. - -The `reader` relation specifies the users who are members of the workspace associated with the page (`workspace#member`) or guests of the workspace (`workspace#guest`). - -Read permission of the workspace inherited as `workspace.read` in the page entity. THis permission specifies that any user who has been granted read access to the workspace object (i.e., the workspace that the page belongs to) can also read the page. - -In summary, any user who is a member or guest of the workspace and has been granted read access to the page through the reader relation, as well as any user who has been granted read access to the workspace itself, can read the contents of the page. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different workspaces: -workspace:engineering_team#owner@user:alice -workspace:engineering_team#member@user:bob -workspace:engineering_team#guest@user:charlie -workspace:engineering_team#admin@user:alice -workspace:sales_team#owner@user:david -workspace:sales_team#member@user:eve -workspace:sales_team#guest@user:frank -workspace:sales_team#admin@user:david - -// Connect pages, databases, and templates to workspaces: -page:project_plan#workspace@workspace:engineering_team -page:product_spec#workspace@workspace:engineering_team -database:task_list#workspace@workspace:engineering_team -template:weekly_report#workspace@workspace:sales_team -database:customer_list#workspace@workspace:sales_team -template:marketing_campaign#workspace@workspace:sales_team - -// Set permissions for pages, databases, and templates: -page:project_plan#writer@user:frank -page:project_plan#reader@user:bob - -database:task_list#editor@user:alice -database:task_list#viewer@user:bob - -template:weekly_report#creator@user:alice -template:weekly_report#viewer@user:bob - -page:product_spec#writer@user:david -page:product_spec#reader@user:eve - -database:customer_list#editor@user:david -database:customer_list#viewer@user:eve - -template:marketing_campaign#creator@user:david -template:marketing_campaign#viewer@user:eve - -// Set relationships for blocks and comments: -block:task_list_1#database@database:task_list -block:task_list_1#editor@user:alice -block:task_list_1#commenter@user:bob -block:task_list_2#database@database:task_list -block:task_list_2#editor@user:alice -block:task_list_2#commenter@user:bob - -comment:task_list_1_comment_1#block@block:task_list_1 -comment:task_list_1_comment_1#author@user:bob -comment:task_list_1_comment_2#block@block:task_list_1 -comment:task_list_1_comment_2#author@user:charlie -comment:task_list_2_comment_1#block@block:task_list_2 -comment:task_list_2_comment_1#author@user:bob -comment:task_list_2_comment_2#block@block:task_list_2 -comment:task_list_2_comment_2#author@user:charlie -``` - -## Test & Validation - -Since we have our schema and the sample relation tuples, let's check some permissions and test our authorization logic. - -
can user:alice write database:task_list ? -

- -```perm - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - - .. - .. - - // Define permissions for database actions - .. - .. - - permission write = editor or workspace.write - - .. - .. - } -``` - -According to what we have defined for the **'write'** permission, users who are either; - -* The editor in task list database (`database:task_list`) -* Have a write permission in the engineering team workspace, which is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`) - -can edit the task list database (`database:task_list`) - -Based on the relation tuples we created, `user:alice` doesn't have the **editor** relationship with the `database:task_list`. - -Since `user:alice` is the owner and admin in the engineering team workspace (`workspace:engineering_team#admin@user:alice`) it has a write permission defined in the workspace entity, as you can see below: - -``` -entity workspace { - // The owner of the workspace - relation owner @user - .. - .. - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - // Define permissions that can be inherited by child entities - .. - permission write = owner or admin -} -``` - -And as we mentioned the engineering team workspace is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`). Therefore, the `user:alice write database:task_list` check request should yield a **'true'** response. - -

-
- -
can user:charlie write page:product_spec ? -

- -```perm - entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - - .. - .. - - permission write = writer or workspace.write - } -``` - -`user:charlie` is guest in the workspace (`workspace:engineering_team#guest@user:charlie`) and the engineering team workspace is the only workspace that `page:product_spec` belongs to. - -As we defined, guests doesn't have write permission in a workspace. - -```perm - entity workspace { - // The owner of the workspace - relation owner @user - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - permission write = owner or admin -} -``` - -So that, `user:charlie` doesn't have a write relationship in the workspace. And ultimately, the `user:charlie write page:product_spec` check request should yield a **'false'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin - } - - entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write - } - - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write - } - - entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter - } - - entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author - } - - entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write - } - - entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write - } - -relationships: - - workspace:engineering_team#owner@user:alice - - workspace:engineering_team#member@user:bob - - workspace:engineering_team#guest@user:charlie - - workspace:engineering_team#admin@user:alice - - workspace:sales_team#owner@user:david - - workspace:sales_team#member@user:eve - - workspace:sales_team#guest@user:frank - - workspace:sales_team#admin@user:david - - page:project_plan#workspace@workspace:engineering_team - - page:product_spec#workspace@workspace:engineering_team - - database:task_list#workspace@workspace:engineering_team - - template:weekly_report#workspace@workspace:sales_team - - database:customer_list#workspace@workspace:sales_team - - template:marketing_campaign#workspace@workspace:sales_team - - page:project_plan#writer@user:frank - - page:project_plan#reader@user:bob - - database:task_list#editor@user:alice - - database:task_list#viewer@user:bob - - template:weekly_report#creator@user:alice - - template:weekly_report#viewer@user:bob - - page:product_spec#writer@user:david - - page:product_spec#reader@user:eve - - database:customer_list#editor@user:david - - database:customer_list#viewer@user:eve - - template:marketing_campaign#creator@user:david - - template:marketing_campaign#viewer@user:eve - - block:task_list_1#database@database:task_list - - block:task_list_1#editor@user:alice - - block:task_list_1#commenter@user:bob - - block:task_list_2#database@database:task_list - - block:task_list_2#editor@user:alice - - block:task_list_2#commenter@user:bob - - comment:task_list_1_comment_1#block@block:task_list_1 - - comment:task_list_1_comment_1#author@user:bob - - comment:task_list_1_comment_2#block@block:task_list_1 - - comment:task_list_1_comment_2#author@user:charlie - - comment:task_list_2_comment_1#block@block:task_list_2 - - comment:task_list_2_comment_1#author@user:bob - - comment:task_list_2_comment_2#block@block:task_list_2 - - comment:task_list_2_comment_2#author@user:charlie - -assertions: - - "can user:alice write database:task_list": true - - "user:charlie write page:product_spec": false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make run`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233154924-c31a76f4-86f5-4ed3-a1ec-750b642927e6.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/getting-started/modeling.md b/docs/versioned_docs/version-0.3.x/getting-started/modeling.md deleted file mode 100644 index ba4b42c3..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/modeling.md +++ /dev/null @@ -1,287 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Modeling Authorization - -Permify has its own language that you can model your authorization logic with it. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like user types such as admin, manager, member, etc. - -![modeling-authorization](https://raw.githubusercontent.com/Permify/permify/master/assets/permify-dsl.gif) - -## Permify Schema - -You can define your entities, relations between them and access control decisions with using Permify Schema. It includes set-algebraic operators such as intersection and union for specifying potentially complex access control policies in terms of those user-object relations. - -Hereโ€™s a simple breakdown of our schema. - -![permify-schema](https://user-images.githubusercontent.com/34595361/183866396-9d2850fc-043f-4254-aa4c-ee2c4172afb8.png) - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is **_".perm"_**. - -## Developing a Schema - -This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. - -We'll follow a simplified version of github access control system. To see completed model you can jump directly to [Github Example](#github-example). - -:::info -You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. - -[vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm -::: - -### Defining Entities - -The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. - -Think of entities as tables in your relationship database. We are strongly advice to name entities same as your database table name that its corresponds. In that way you can easily model and reason your authorization as well as eliminating the error possibility. - -You can create entities using `entity` keyword. Since we're following example of simplified github access control, lets create some of our entities as follows. - -```perm -entity user {} - -entity organization {} - -entity team {} - -entity repository {} -``` - -Entities has 2 different attributes. These are; - -- **relations** -- **actions (or permissions)** - -### Defining Relations - -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. Keyword **_relation_** need to used to create a entity relation with name and type attributes. - -**Relation Attributes:** - -- **name:** relation name. -- **type:** relation type, basically the entity itโ€™s related to (e.g. user, organization, document, etc.) - -An example relation takes form of, - -```perm -relation [name] @[type] -``` - -Lets turn back to our example and define our relations inside our entities: - -#### User Entity - -โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. - -```perm -entity user {} -``` - -#### Organization Entity - -โ†’ For the sake of simplicity let's define only 2 user types in an organization, these are administrators and direct members of the organization. - -```perm -entity organization { - - relation admin @user - relation member @user - -} -``` - -#### Team Entity - -โ†’ Let's say teams can belong organizations and can have a member inside of it as follows, - -```perm -entity team { - - relation parent @organization - relation member @user - -} -``` - -The parent relation is indicating the organization the team belongs to. This way we can achieve **parent-child relationship** inside this entity. - -#### Repository Entity - -โ†’ Organizations and users can have multiple repositories, so each repository is related with an organization and with users. We can define repository relations as as follows. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - -} -``` - -The owner relation indicates the creator of the repository, that way we can achieve **ownership** in Permify. - -**Defining Multiple Relation Types** - -As you can see we have new syntax above, - -```perm - relation maintainer @user @team#member -``` - -When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. - -:::info -You can use **#** to reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. - -Actual purpose of feature locking is to giving ability to specify the sets of users that can be assigned. - -For example: - -```perm - relation viewer @user -``` - -When you define it like this, you can only add users directly as tuples (you can find out what relation tuples is in next section): - -* organization:1#viewer@user:U1 -* organization:1#viewer@user:U2 - -However, if you define it as: - -```perm - relation viewer @user @organization#member -``` - -You will then be able to specify not only individual users but also members of an organization: -* organization:1#viewer@user:U1 -* organization:1#viewer@user:U2 -* organization:1#viewer@organization:O1#member - -You can think of these definitions as a precaution taken against creating undesired user set relationships. -::: - -Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. - -### Defining Actions and Permissions - -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. So, the basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. - -The Permify Schema supports `and`, `or`, `and not` and `or not` operators for defining actions. The keywords **_action_** or **_permission_** can be used with those operators to form rules for your authorization logic. - -Lets get back to our github example and create some actions on repository entity, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - .. - .. - - action push = owner - -} -``` - -โ†’ `action push = owner or maintainer` indicates only the repository owner or maintainers can push to -repository. - -:::info -The same `push` can also be defined using the **permission** keyword, as follows: - -```perm -permission push = owner -``` - -Using action or permission will yield the same result for defining permissions in your authorization logic. - -The reason we have two keywords (`action` and `permission`) for defining permissions is that while most permissions are based on actions (such as view, read, edit, etc.), there are still cases where we need to define permissions based on roles or user types, such as admin or member. - -Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. - -See Real World Examples Section for examining the contextualize difference between using permission and action keyword. You can start with observing [Google Docs Simplified](./examples/google-docs.md) example. -::: - -For this tutorial we'll continue with `action` keyword, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - .. - .. - - action read = (owner or maintainer or org.member) and org.admin - -} -``` - -โ†’ For more fine grained permission let's examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. - -:::info -You can add actions to another action like relations, see below. - -```perm - action edit = member or manager - action delete = edit or org.admin -``` - -delete action can inherit the edit action rules like above. To sum up, only organization administrators and any relation that can perform edit action (member or manager) can perform delete action. -::: - -### Full Schema - -Here is full implementation of simple Github access control example with using Permify Schema. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity team { - - relation parent @organization - relation member @user - - action edit = member or parent.admin - -} - -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - action push = owner or maintainer - action read = (owner or maintainer or parent.member) and parent.admin - action delete = parent.admin or owner - -} -``` - -## Real World Examples - -This example shows almost all aspects of the Permify Schema. - -You can check out more schema examples from the [Real World Examples](../examples) section with their detailed examination. diff --git a/docs/versioned_docs/version-0.3.x/getting-started/sync-data.md b/docs/versioned_docs/version-0.3.x/getting-started/sync-data.md deleted file mode 100644 index 4f0243a2..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/sync-data.md +++ /dev/null @@ -1,445 +0,0 @@ ---- -sidebar_position: 2 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Managing Authorization Data - -Permify unifies your authorization data in a database you prefer. We named that database as Write Database, shortly **WriteDB**. - -Permify API provides various functionalities - checking access, reasoning permissions, etc - to maintain separate access control mechanisms for individual applications. And **WriteDB** stands as a source of truth for these authorization functionalities. - -## Access Control as Relations - Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -In Permify, the simplest form of relational tuple structured as: `entity # relation @ user`. Here are some relational tuples with semantics, - -![relational-tuples](https://user-images.githubusercontent.com/34595361/183959294-149fcbb9-7f10-4c1e-8d66-20a839893909.png) - -## Where Relational Tuples Used ? - -In Permify, these relational tuples represents your authorization data. - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure the database when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -Stored relational tuples are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user's action is authorized. - -As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, or even a role such as an admin or manager. -[WriteDB]: #write-database - -## Creating Relational Tuples - -Relational tuples can be created with an simple API call in runtime, since relations and authorization data's are live instances. Each relational tuple should be created according to its authorization model, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -Let's follow a simple document management system example with the following Permify Schema to see how to create relation tuples. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - -} - -entity document { - - relation owner @user - relation parent @organization - relation maintainer @user @organization#member - - action view = owner or parent.member or maintainer or parent.admin - action edit = owner or maintainer or parent.admin - action delete = owner or parent.admin -} -``` - -According to the schema above; when a user creates a document in an organization, more specifically let's say, when user:1 create a document:2 we need to create the following relational tuple, - -- `document:2#owner@user:1` - -[WriteDB]: #write-database - -### Write Relationships API - -You can create relational tuples by using `Write Relationships API`. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "2", - }, - Relation: "owner", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "2" - }, - relation: "owner", - subject: { - type: "user", - id: "1" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "2s" - }, - "relation": "owner", - "subject":{ - "type": "user", - "id": "1", - "relation": "" - } - } - ] -}' -``` - - - -### Snap Tokens: - -In Write Relationships API response you'll get a snap token of the operation. This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../reference/snap-tokens.md) - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -## More Relationships - -Let's create more relationships according to the schema we defined above. - -### Organization Admin - -**relational tuple:** organization:1#admin@user:3 - -**Semantics:** User 3 is administrator in organization 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "user", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -### Parent Organization - -**Relational Tuple:** document:1#parent@organization:1#โ€ฆ - -**Semantics:** Organization 1 is parent of document 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "parent", - Subject: & v1.Subject { - Type: "organization", - Id: "1", - Relation: "..." - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "parent", - subject: { - type: "organization", - id: "1", - relation: "..." - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "parent", - "subject":{ - "type": "organization", - "id": "1", - "relation": "..." - } - } - ] -}' -``` - - - -:::info -Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. - -Simply, the usage of ... is straightforward: if you're use user entity as an subject, you should not be using the `...` If you're using another subject rather than user entity then you need to use the `...` -::: - -### Organization Members Are Maintainers in specific Doc - -**Created relational tuple:** document:1#maintainer@organization:2#member - -**Definition:** Members of organization 2 are maintainers in document 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "maintainer", - Subject: & v1.Subject { - Type: "organization", - Id: "2", - Relation: "member" - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "maintainer", - subject: { - type: "organization", - id: "2", - relation: "member" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "maintainer", - "subject":{ - "type": "organization", - "id": "2", - "relation": "member" - } - } - ] -}' -``` - - - -## Test this Example on Playground - -You can test and examine the above schema and relational tuples that we created in our playground using this [link](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_). - -## Next - -Let's now head over to the **Access Control Check** section and learn how to perform access control in Permify to ensure that only authorized users have the right level of access to our resources. diff --git a/docs/versioned_docs/version-0.3.x/getting-started/testing.md b/docs/versioned_docs/version-0.3.x/getting-started/testing.md deleted file mode 100644 index 124932d5..00000000 --- a/docs/versioned_docs/version-0.3.x/getting-started/testing.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Testing & Validation - -Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. - -Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. - -We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Usage - -### Adding action to your workflow - -After adding [permify-validate-action] to your Github Action workflow, you need to define the schema validation yaml file as, - -- **With local file:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "test.yaml" -``` - -- **With external url:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" -``` - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -### Creating Schema Validation File - -Below you can examine an example schema validation yaml file. It consists 3 parts; `schema` which is your new authorization model, sample `relationships` to test your model and lastly `assertions` which is the test access check questions and expected response - true or false. - -```yaml -schema: >- - entity user {} - - entity organization { - - relation admin @user - relation member @user - - action create_repository = (admin or member) - action delete = admin - } - - entity repository { - - relation owner @user @organization#member - relation parent @organization - - action push = owner - action read = (owner and (parent.admin and parent.member)) - action delete = (parent.member and (parent.admin or owner)) - action edit = parent.member and not owner - } - -relationships: - - "organization:1#admin@user:1" - - "organization:1#member@user:1" - - "repository:1#owner@user:1" - - "repository:2#owner@user:2" - - "repository:2#owner@user:3" - - "repository:1#parent@organization:1#..." - - "organization:1#member@user:43" - - "repository:1#owner@user:43" - -assertions: - - "can user:1 push repository:1": true - - "can user:1 push repository:2": false - - "can user:1 push repository:3": false - - "can user:43 edit repository:1": false -``` - -## Testing in Local - -You can also test your new authorization model in your local (Permify clone) without using [permify-validate-action] at all. - -For that open up a new file and add a schema yaml file inside. Then build your project with, run `make run` command and run `./permify validate {path of your schema validation file}`. - -If we use the above example schema validation file, after running `./permify validate {path of your schema validation file}` it gives a result on the terminal as: - -![schema-validation](https://user-images.githubusercontent.com/34595361/207110538-9837b09d-26b4-409a-a309-a21f66e6677a.png) - -[permify-validate-action]: https://github.com/Permify/permify-validate-action - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - - diff --git a/docs/versioned_docs/version-0.3.x/installation.md b/docs/versioned_docs/version-0.3.x/installation.md deleted file mode 100644 index c855058f..00000000 --- a/docs/versioned_docs/version-0.3.x/installation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: installation -title: Setup Permify -slug: /installation ---- - -# Setup Permify - -Here is some options that you can use to set up and deploy Permify in your servers. - -```mdx-code-block -import {CardList} from '../../src/components/Card'; - - -``` - -If options your deployment preference is not listed below please let us know Also if you have any questions join our [Discord community](https://discord.gg/n6KfzYxhPp) or send us an email at support@permify.co. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/installation/_category_.json b/docs/versioned_docs/version-0.3.x/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.3.x/installation/aws.md b/docs/versioned_docs/version-0.3.x/installation/aws.md deleted file mode 100644 index c1c204d9..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/aws.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: AWS ECS, ECR & EC2 ---- - -# ย Deploy on AWS ECS, ECR & EC2 - -AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. - -There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). - -ECS is a container management service. You can run your containers as task definitions, and Itโ€™s one of the easiest ways to deploy containers. - -If youโ€™d like to watch this tutorial rather than reading. Hereโ€™s the video version. - - - -There is no prerequisite in this tutorial. You can simply deploy permify by following this step-by-step guide. However, if you want to integrate more advanced AWS security & networking features, weโ€™ll follow up with a new tutorial guideline. - -At the end of this tutorial youโ€™ll be able to; - -1. [Create a security group](#create-an-ec2-security-group) -2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) -3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) -4. [Running our task definition](#4-running-our-task-definition) - -## 1. Create an EC2 Security Group - -So first thing first, letโ€™s go over into security groups and create our security group. Weโ€™ll need this security group while creating our cluster. - -![security-group-1](https://user-images.githubusercontent.com/34595361/208877994-e9461acc-4ffd-4591-b43e-db254366d25d.png) - -Search for โ€œSecurity Groupsโ€ in the search bar. And go to the EC2 security groups feature. - -![security-group-2](https://user-images.githubusercontent.com/34595361/208877493-ab11228c-1aa0-4bc5-b41d-4527737028e9.png) - -Then start creating a new security group. - -![security-group-3](https://user-images.githubusercontent.com/34595361/208877500-2c299883-6107-4b70-aa96-0f28eb00cf3d.png) - -You have to name your security group, and give a description. Also, you need to choose the same VPC that youโ€™ll going to use in EC2. So, I choose the default one. And Iโ€™m going to use same one while creating the ECS cluster. - -The next step is to configure our inbound rules. Hereโ€™s the configuration; - -```json -//for mapping HTTP request port. -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for mapping RPC request port. -type = "Custom TCP", protocol = "TCP", port_range = "3478",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for using SSH for connecting from your local computer. -type = "Custom TCP", protocol = "TCP", port_range = "22",source = "Anywhere", 0.0.0.0/0 -``` - -We have configured the HTTP and RPC ports for Permify. Also, we added port โ€œ22โ€ for SSH connection. So, we can connect to EC2 through our local terminal. - -Now, weโ€™re good to go. You can create the security group. And itโ€™s ready to use in our ECS. - -## 2. Creating an ECS cluster - -![create-ecs-cluster-1](https://user-images.githubusercontent.com/34595361/208878666-98c5d3ce-b079-444d-bc66-53f13038a08a.png) - -The next step is to create an ECS cluster. From your AWS console search for Elastic Container Service or ECS for short. - -![create-ecs-cluster-2](https://user-images.githubusercontent.com/34595361/208878675-2f266cfc-defb-4c7f-9186-b4de39f1743b.png) - -Then go over the clusters. As you can see there are 2 types of clusters. One is for ECS and another for EKS. We need to use ECS, EKS stands for Elastic Kubernetes Service. Today weโ€™re not going to cover Kubernetes. - -Click **โ€œCreate Clusterโ€** - -![create-ecs-cluster-3](https://user-images.githubusercontent.com/34595361/208878685-3edac67b-5b3d-4f0d-b2f7-70a5ec2e4870.png) - -Letโ€™s create our first Cluster. Simply you have 3 options; Serverless(Network Only), Linux, and Windows. Weโ€™re going to cover EC2 Linux + Networking option. - -![create-ecs-cluster-4](https://user-images.githubusercontent.com/34595361/208878681-d98a77db-16b1-42af-a697-3036cc604c85.png) - -The next step is to configure our Cluster, starting with your Cluster name. Since weโ€™re deploying Permify, Iโ€™ll call it โ€œpermifyโ€. - -Then choose your instance type. You can take a look at different instances and pricing from [here](https://aws.amazon.com/ec2/pricing/on-demand/). Iโ€™m going with the t4 large. For cost purposes, you can choose t2.micro if youโ€™re just trying out. Itโ€™s free tier eligible. - -Also, if you want to connect this EC2 instance from your local computer. You need to use SSH. Thus choose a key pair. If you have no such intention, leave it โ€œnoneโ€. - -![create-ecs-cluster-5](https://user-images.githubusercontent.com/34595361/208878989-801839f5-8fce-4410-99e0-0a2dcccb47fa.png) - -Now, we need to configure networking. First, choose your VPC, we use the default VPC as we did in the security groups. And choose any subnet on that VPC. - -You want to enable auto-assigned IP to make your app reachable from the internet. - -Choose the security group we have created previously. - -And voila, you can create your cluster. Now, we need to run our container in this cluster. To do that, letโ€™s go over task definitions. And create our container definition. - -## 3. Creating and running task definitions - -Go over to ECS, and click the task definitions. - -![create-run-task-1](https://user-images.githubusercontent.com/34595361/208879726-fe5aac07-16a8-4f8c-9cc9-1c95ca191a42.png) - -And create a new task definition. - -![create-run-task-2](https://user-images.githubusercontent.com/34595361/208879733-e9aa6fa4-9f66-44e4-8c70-dfa0e33c1b73.png) - -Again, youโ€™re going to ask to choose between; FARGATE, EC2, and EXTERNAL (On-premise). Weโ€™ll continue with EC2. - -Leave everything in default under the โ€œConfigure task and container definitionsโ€ section. - -![create-run-task-3](https://user-images.githubusercontent.com/34595361/208879735-789ec411-5829-47be-9634-c09c7b0c0320.png) - -Under the IAM role section you can choose โ€œecsTaskExecutionRoleโ€ if you want to use Cloud Watch later. - -You can leave task size in default since itโ€™s optional for EC2. - -The critical part over here is to add our container. Click on the โ€œAdd Containerโ€ button. - -![create-run-task-4](https://user-images.githubusercontent.com/34595361/208879740-4515e884-1efd-46fd-8e8c-cfa86634b673.png) - -Then we need to add our container details. First, give a name. And then the most important part is our image URI. Permify is registered on the Github Registry so our image is; - -```yaml -ghcr.io/permify/permify:latest -``` - -Then we need to define memory limit for the container, I went with 1024. You can define as much as your instance allows. - -Next step is to mapping our ports. As we mentioned in security groups, Permify by default listens; - -- `3476 for HTTP port` -- `3478 for RPC port` - -![create-run-task-5](https://user-images.githubusercontent.com/34595361/208879746-5991a04c-73d5-4e35-97b0-67aa9ebf61fc.png) - -Then we need to define command under the environment section. So, in order to start permify we first need to add โ€œserveโ€ command. - -For using properly we need a few other. Hereโ€™s the commands we need. - -```yaml -serve, --database-engine=postgres, --database-uri=postgres://:@:/, --database-pool-max=20 -``` - -- `serve` โ‡’ for starting the Permify. -- `--database-engine=postgres` โ‡’ for defining the db we use. -- `--database-uri=postgres://:password@:/` โ‡’ for connecting your database with URI. -- `--database-pool-max=20` โ‡’ the depth for running in graph. - -Weโ€™re nice and clear, add the container and then just create your task definition. Weโ€™ll use this definition to run in our cluster. - -So, letโ€™s go over and run our task definition. - -## 4. Running our task definition - -![run-task-definition-1](https://user-images.githubusercontent.com/34595361/208880326-c5ecb48c-e210-47f8-bd92-d1f789be24ff.png) - -Letโ€™s go to ECS and enter into our cluster. And go over into the tasks to run our task. - -![run-task-definition-2](https://user-images.githubusercontent.com/34595361/208880332-97a5732d-bc7d-401e-bae9-216d4273c5bf.png) - -Click to โ€œRun new Taskโ€ - -![run-task-definition-3](https://user-images.githubusercontent.com/34595361/208880335-b3ce229f-33ff-4f03-90e7-6d6a306928ae.png) - -Choose EC2 as a launch type. Then pick the task definition we just created. And leave everything else in the default. You can run your task now. - -We have just deployed our container into EC2 instance with ECS. Letโ€™s test it. - -Now you can go over into EC2, and click on the running instances. Find the instance named `ECS Instance - EC2ContainerService-` in the running instances. - -![run-task-definition-4](https://user-images.githubusercontent.com/34595361/208880339-a508354c-99ee-4219-8ace-1c7fdbbe90ed.png) - -Copy the Public IPv4 DNS from the right corner, and paste it into your browser. But you need to add `:3476` to access our http endpoint. So it should be like this; - -`:3476` - -and if you add healthz at the end like this; - -`:3476/healthz` - -you should get Serving status :) - -![run-task-definition-5](https://user-images.githubusercontent.com/34595361/208880346-d19a6877-3013-4347-86c9-9f865b8a3e3c.png) - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/installation/azure.md b/docs/versioned_docs/version-0.3.x/installation/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/installation/brew.md b/docs/versioned_docs/version-0.3.x/installation/brew.md deleted file mode 100644 index e7388076..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/brew.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "Install with Brew" ---- - -# Brew With Configurations - -This section shows how to intall and run Permify Service with using brew. - -### Install Permify - -Open terminal and run following line, - -```shell -brew install permify/tap/permify -``` - -### Run Permify Service - -To run the Permify Service, `permify serve` command should be run with configurations. - -By default, the service is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather then an actual database. You can override these with running the command with configuration flags. - -### Configure With Using Flags - -See all configuration flags with running, - -```shell -permify serve --help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flags' argument with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.3.x/installation/container.md b/docs/versioned_docs/version-0.3.x/installation/container.md deleted file mode 100644 index 4802b078..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/container.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: "Docker Container" ---- - -# Deploy using Docker - -This section shows how to run Permify Service from a docker container. You can run Permify service from a container with following steps. - -## Run following line on Terminal - -```shell -docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve -``` - -This will start an API server with the configuration options that pointed out on the **{YOUR-CONFIG-PATH}**. - -### Configure With a YAML file - -This config path - `{YOUR-CONFIG-PATH}:/config` - addresses the [config yaml file](../reference/configuration.md), where you can configure running options of the Permify Server as well as define the ***database*** to store your authorization related data. - -:::info Talk to an Permify Engineer -By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. -::: - -### Configure With Using Flags - -Alternatively, you can set configuration options with the respected flags when running the command. See all configuration flags with running, - -```shell -docker run -p 8080:8080 ghcr.io/permify/permify serve -help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flags' argument with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.3.x/installation/google.md b/docs/versioned_docs/version-0.3.x/installation/google.md deleted file mode 100644 index dcb3614a..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/google.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Google Compute Engine ---- - -# Deploy on Google Compute Engine - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/installation/kubernetes.md b/docs/versioned_docs/version-0.3.x/installation/kubernetes.md deleted file mode 100644 index f2a1e21b..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/kubernetes.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Kubernetes Cluster ---- - -# ย Deploy on Kubernetes Cluster - -In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. - -Hereโ€™s what weโ€™re going to do step-by-step; - -1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) -3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) - -There are a couple of small prerequisites for this tutorial. - -### Pre-requisites - -- An AWS account. -- The AWS Command Line Interface (CLI) is installed and configured on your local machine. โ€” [Click here](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/home) to go to IAM -- The AWS IAM Authenticator for Kubernetes is installed and configured on your local machine. - -## Configure AWS CLI with your IAM account. - -The first step is to configure our AWS IAM account into our local terminal so that we can run commands. Most of you probably have a configured AWS account if you ever set up anything into AWS programmatically, so you can skip this. If you donโ€™t follow these steps. - -### Create an AWS IAM Programmatic Access Account - -First, letโ€™s create IAM credentials for ourselves. Search IAM from the AWS console. You need to write down the account ID if you want to log in AWS console with this account as well. Letโ€™s go over users and start creating our credentials. - -![kubernetes-1](https://user-images.githubusercontent.com/34595361/211697636-6e106115-bd68-4909-aea0-5a7b6f8d5e18.png) - -At Users screen click to โ€œAdd usersโ€ โ€” and youโ€™ll end up in your first screen creating user credentials. Here you can define the name of the user. Also there 2 options that you can choose simultaneously. - -But you must choose โ€œAccess key - Programmatic accessโ€ option. Itโ€™ll allow us to configure our AWS CLI on our local machine. - -You can also choose โ€œPassword - AWS Management Console accessโ€ if you want to log in to this account through the console. But youโ€™ll need the Account ID that I mentioned in the IAM console screen. - -In the next screen, youโ€™ll be asked to create or copy the user-set permissions. For this tutorial, youโ€™ll only need to access EKS resources and features. So lets create group by clicking the โ€œCreate groupโ€ โ€” and then at pop-up screen search for EKS. - -![kubernetes-2](https://user-images.githubusercontent.com/34595361/211697647-f39d73e7-b6e2-40ae-8c3b-ad68032d6b21.png) - -Iโ€™ll choose all EKS permissions but if you have certain policies internally, just stick with them. Youโ€™ll only need following permission to; - -- `AmazonEKSClusterPolicy` -- `AmazonEKSServicePolicy` -- `AmazonEKSVPCResourceController` -- `AmazonEKSWorkerNodePolicy` - -Then simply you can review and create the user. - -![kubernetes-4](https://user-images.githubusercontent.com/34595361/211697655-1b75d4f9-a2ee-4b7e-9e1e-0be0b5aaad7d.png) - -Once you created the credentials youโ€™ll prompt the โ€œAccess key IDโ€ and โ€œSecret access keyโ€, you should save this down somewhere. Weโ€™re going the use these to configure our local machine with AWS CLI. - -### **Configure AWS CLI with your IAM account** - -Letโ€™s open our local terminal - -```jsx -aws configure -``` - -Next youโ€™ll ask for the following credentials; - -- `AWS Access Key ID` -- `AWS Secret Access Key` -- `Default region name` -- `Default output format` (leave it empty) - -## Creating an AWS EKS Cluster - -For the first step, we need to install [eksctl](https://eksctl.io/) โ€” which is like kubectl but for AWS EKS. It helps us to set up and deploy our cluster and nodes within a fraction of the time. - -Letโ€™s download eksctl using brew. - - -```jsx -brew tap weaveworks/tap -``` - -While installing the eksctl, weโ€™ll end up getting kubectl and other dependencies. - -```jsx -brew install weaveworks/tap/eksctl -``` - -Now, weโ€™re ready to create our EKS cluster. You can define certain things while deploying standard the cluster beside the name and version like; the region you want to deploy, the EC2 instance type of each node, and the number of nodes you want to run. - -```bash -eksctl create cluster \ ---name \ ---version 1.24 \ ---region ย \ ---nodegroup-name permify \ ---node-type t2.small \ ---nodes 2 -``` - -## Deploying & Running Permify in Nodes - -The next stop is applying our manifests which will help us to deploy and configure our container/Permify. - -Letโ€™s create our deployment manifest first. - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: permify - name: permify -spec: - replicas: 2 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://postgres:nOcodeSTIAnLAba@permify-test.ceuo5kqsxyea.us-east-1.rds.amazonaws.com:5432/demo" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always -status: {} -``` - -Now letโ€™s apply our deployment manifest - -```jsx -kubectl apply -f deployment.yaml -``` - -The next step is to create a service manifest, this will allow us to configure our container app. - -```jsx -apiVersion: v1 -kind: Service -metadata: - name: permify -spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer -status: - loadBalancer: {} -``` - -Letโ€™s apply service.yaml to our nodes. - -```jsx -kubectl apply -f service.yaml -``` - -Last but not least, we can check our pods & nodes. And we can start using the container with load balancer \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/installation/overview.md b/docs/versioned_docs/version-0.3.x/installation/overview.md deleted file mode 100644 index 330db2e2..00000000 --- a/docs/versioned_docs/version-0.3.x/installation/overview.md +++ /dev/null @@ -1,253 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Guide - -This guide shows how to set up Permify in your servers and use it across your applications. Set up and implementation consists of 4 steps, - -1. [Set Up & Run Permify Service](#set-up-permify-service) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Manage and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) -4. [Perform Access Check](#perform-access-check) - -:::info Talk to an Permify Engineer -Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -## Set Up Permify Service - -You can run Permify Service with various options but in that tutorial we'll run it via docker container. - -### Run From Docker Container - -Production usage of Permify needs some configurations such as defining running options, selecting datastore to store authorization data and more. - -However, for the sake of this tutorial we'll not do any configurations and quickly start Permify on your local with running the docker command below: - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -:::info -You can examine [Deploy using Docker] section to get more about the configuration options and learn the full integration to run Permify Service from docker container. - -[Deploy using Docker]: ../container -::: - -### Test your connection - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core endpoints. - -[Using the API]: ../../api-overview - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -## Model your Authorization with Permify Schema - -After installation completed and Permify server is running, next step is modeling authorization with Permify authorization language - [Permify Schema] - and configure it to Permify API. - -You can define your entities, relations between them and access control decisions of each actions with using [Permify Schema]. - -### Creating your authorization model - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: - -Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action view_files = admin or member - action edit_files = admin - -} -``` - -We have 2 entities these are **"user"** and **"organization"**. Entities represents your main tables. We strongly advise naming entities the same as your original database entities. - -Lets roll back our example, - -- The `user` entity represents users. This entity is empty because it's only responsible for referencing users. - -- The `organization` entity has its own relations (`admin` and `member`) which related with user entity. This entity also has 2 actions, respectively: - - Organization member and admin can view files. - - Only admins can edit files. - -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. - -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../use-cases/simple-rbac -::: - -### Configuring Schema via API - -After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../api-overview/schema/write-schema.md) for configuration of your authorization model on Permify authorization service. - -:::caution Before Continue on Writing Schema -You'll see **tenant_id** parameter almost all Permify APIs including Write Schema. With version 0.3.x Permify became a tenancy based authorization infrastructure, and supports multi-tenancy by default so its a mandatory parameter when doing any operations. - -We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. -::: - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - -**POST /v1/tenants/{tenant_id}/schemas/write** - -![permify-schema](https://user-images.githubusercontent.com/34595361/214457054-19b141ac-6bfa-4db4-aeab-f7b7149c3351.png) - -## Store Authorization Data as Relational Tuples - -After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. - -### Create Relational Tuples - -You can create relational tuples as authorization rules at this writeDB by using [Write Relationships API](../api-overview/relationship/write-relationships.md) - -For our guide let's grant one of the team members (Ashley) an admin role. - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - -**POST /v1/tenants/{tenant_id}relationships/write** - -```json -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" //Organization identifier - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1", //Ashley's identifier - "relation": "" - } - } - ] -} -``` - -![write-relationships](https://user-images.githubusercontent.com/34595361/214458203-8264e141-642d-48b0-9242-416bbf6f8795.png) - -**Created relational tuple:** organization:1#admin@user:1 - -**Semantics:** User 1 (Ashley) has admin role on organization 1. - -:::tip -In ideal production usage Permify stores your authorization data in a database you prefer. We called that database as WriteDB, and you can configure it with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) or CLI flag options. - -But in this tutorial Permify Service running default configurations on local, so authorization data will be stored in memory. You can find more detailed explanation how Permify stores authorization data in [Managing Authorization Data] section. - -[Managing Authorization Data]: ../../getting-started/sync-data -::: - -## Perform Access Check - -Finally we're ready to control authorization. Access decision results computed according to relational tuples and the stored model, [Permify Schema] action conditions. - -Lets get back to our example and perform an example access check via [Check API]. We want to check whether an specific user has an access to view files in a organization. - -[Check API]: ../../api-overview/permission/check-api -[Permify Schema]: ../../getting-started/modeling - -#### Example HTTP Request: - -***Can the user 45 view files on organization 1 ?*** - -**POST /v1/tenants/{tenant_id}/permissions/check** - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | entity | object | - | name and id of the entity. Example: organization:1. -| [x] | action | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action | -| [ ] | schema_version | string | - | get results according to given schema version| -| [ ] | depth | integer | 8 | | - -### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_files", - "subject": { - "type": "user", - "id": "45", - "relation": "" - }, -} -``` - -### Response - -```json -{ - "can": "RESULT_ALLOW", - "metadata": { - "check_count": 0 - } -} -``` - -See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify - -[Access Control Check]: ../getting-started/enforcement.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you struggle with installation or have any questions, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/migrating.md b/docs/versioned_docs/version-0.3.x/migrating.md deleted file mode 100644 index c6906468..00000000 --- a/docs/versioned_docs/version-0.3.x/migrating.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: "Migrating to 0.3.x (Multi Tenancy)" ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Migration overview - -This doc guides you through migrating an existing Permify **0.2.x** authorization service to **0.3.x**. With version 0.3.x Permify moved to a tenancy-based infrastructure, which affects almost all of the API operations. - -## Multi Tenancy on Permify - -Multi-tenancy in Permify refers to an authorization architecture, where a single Permify authorization service serves multiple applications/organizations (tenants). - -This allows the ability to customize the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create custom authorization schema and relation tuples accordingly for the different tenants and manage them in a single place - in [WriteDB](./getting-started/sync-data.md). - -For the users that don't have/need multi-tenancy in their authorization structure, we created a pre-inserted tenant (id: **t1**) that comes default when you serve a Permify service. - -## What have changed ? - -Several things changed when we moved to tenant based infrastructure, these are: - -* [API endpoints now have Tenant ID field](#api-endpoints-now-have-tenant-id-field) -* [Added Tenancy Service](#added-tenancy-service) -* [WriteDB tables and tenant id column](#writedb-tables-and-tenant-id-column) - -### API endpoints now have Tenant ID field - -All API endpoints that cover in 0.2.x now have a `โ€tenant_id` mandatory field. Let's examine a check request below, - -#### Check API - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../api-overview/). - -### Added Tenancy Service - -To manage tenants we have added a Tenancy service; you can create, delete and list tenants accordingly. See the [Tenancy Service](../api-overview/tenancy/) on Using The API section. - -### WriteDB tenancy table and tenant id column - -#### Tenant Table - -Tenants table have added the Write DB to store tenant's details. The new WriteDB folder structure changed as follows: -``` -tables -โ”œโ”€โ”€ migrations -โ”œโ”€โ”€ relation_tuples -โ”œโ”€โ”€ schema_definitions -โ”œโ”€โ”€ tenants -โ”œโ”€โ”€ transactions -``` - -#### Tenant ID Column - -Relation tuples and schema definition tables now have a tenant_id column, which stores the id of the tenant that data belongs. - -Let's take a look at a snapshot of the demo table on an example WriteDB. - -Example Relation Tuples data table: -![tenant-id-tuples](https://user-images.githubusercontent.com/34595361/214724165-a3775756-0649-4869-b994-d837fadd271d.png) - -Example Schema Definitions data table -![tenant-id-schema](https://user-images.githubusercontent.com/34595361/214724727-01eadad3-720c-4c10-a88d-6ee293ecf4a8.png) - -## Need any help ? - -Our team is happy to help! If you struggle with migration or need help on using the multi-tenancy, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. diff --git a/docs/versioned_docs/version-0.3.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.3.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/versioned_docs/version-0.3.x/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.3.x/permify-overview/authorization-service.md b/docs/versioned_docs/version-0.3.x/permify-overview/authorization-service.md deleted file mode 100644 index 0810c6c2..00000000 --- a/docs/versioned_docs/version-0.3.x/permify-overview/authorization-service.md +++ /dev/null @@ -1,42 +0,0 @@ - -# What is Authorization Service? - -Authorization is an important part of software development. There are many different ways to implement authorization, but it's important for all apps to have some form of it in order to protect the user from malicious actors and unauthorized access attempts. - -An authorization service is a module that allows you to manage access to your application and ease the development and maintenance of your authorization system. It works in run time and respond to all authorization questions from any of your apps. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify] is a fully open source authorization service that offers a variety of binding and crafting options to secure your applications. - -[Permify]: https://github.com/Permify/permify - -## Why should I use Authorization Service instead of doing from scratch? - -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. You can started quickly with a simple API that you can easily integrate into your application to move and iterate faster. - -### Do Not Reinvent The Wheel -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. Building a scalable and robust authorization system is hard and needs a quite engineering time. Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services. - -[Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell/ - -### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. - -### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. - -### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. - -## Cases that can benefit from An Authorization Service: - -- If you already have an identity/auth solution and want to plug in fine-grained authorization on top of that. -- If you want to create a unified access control mechanism to use across your individual applications. -- If you want to make future-proof authorization system and don't want to spend engineering effort for it. -- If youโ€™re managing authorization for growing micro-service infrastructure. -- If your authorization logic is cluttering your code base. -- If your data model is getting too complicated to handle your authorization within the service. -- If your authorization is growing too complex to handle within code or API gateway. - diff --git a/docs/versioned_docs/version-0.3.x/permify-overview/infrastructure.md b/docs/versioned_docs/version-0.3.x/permify-overview/infrastructure.md deleted file mode 100644 index 9cc9733c..00000000 --- a/docs/versioned_docs/version-0.3.x/permify-overview/infrastructure.md +++ /dev/null @@ -1,50 +0,0 @@ - -# Where does Permify fit into your environment? - -Permify is a simply GRPC service that responsible for managing and authorization in your environment. This section shows where and how does Permify fit into your environment with examining Permify's architecture, deployment patterns and the usage with the authentication and identity providers. - -## Architecture - -Permify stores access control relations on a database you choose and performs authorization checks based on the stored relations. We called that database Write Database - **WriteDB** - and it behaves as a centralized data source for your authorization system. - -You can model your authorization with Permify's DSL - Permify Schema and your applications can talk to Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data, or make changes to data stored in WriteDb. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify with Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/versioned_docs/version-0.3.x/permify-overview/intro.md b/docs/versioned_docs/version-0.3.x/permify-overview/intro.md deleted file mode 100644 index 8c58eaac..00000000 --- a/docs/versioned_docs/version-0.3.x/permify-overview/intro.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -sidebar_position: 1 ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is an **open-source authorization service** for creating and maintaining fine-grained authorizations in your applications. - -With Permify you can easily structure your authorization model, store authorization data in a database you prefer, and interact with Permify API to handle all authorization queries from any of your applications. - -Permify is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -## Key Features - -๐Ÿ›ก๏ธ **Production ready** authorization API that serve as **gRPC** and **REST** - -๐Ÿ”ฎ Domain Specific Authorization Language - Permify Schema - to **easily model** your authorization - -๐Ÿ” Database Configuration to store your permissions **in house** with **high availability** - -โœ… Perform access control checks and get answers **down to 10ms** with **parallel graph engine** - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) - -โš™๏ธ Create custom permissions for your **tenants**, and manage them in single place with **Multi Tenancy** - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin] - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Getting Started - -In Permify, authorization divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify [Store Authorization Data] as relations. -- Perform an [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../../getting-started/modeling -[Store Authorization Data]: ../../getting-started/sync-data -[Access Checks]: ../../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want trying out and examine it instantly, - -**Looking for Permify Managed Service?** See our [pricing plans](https://permify.co/pricing/). - - - -## Roadmap - -You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! - -## Community & Support - -We would love to hear from you :heart: - -You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. - -For feature requests, bugs, or any improvements you can always open an issue. - -### Want to Contribute? Here are the ways to contribute to Permify - -* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. -* **Improve documentation:** Alongside our codebase, documentation one of the most significant part in our open-source journey. We're trying to give the best DX possible to explain ourselfs and Permify. And you can help on that with importing resources or adding new ones. -* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. - -You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). - -## Communication Channels - -If you like Permify, please consider giving us a :star: - -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Need any help on Authorization ? - -Our team is happy to help you anything about authorization. Moreover, if you'd like to learn more about using Permify in your app or have any questions, [schedule a call with one of our founders](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/playground.md b/docs/versioned_docs/version-0.3.x/playground.md deleted file mode 100644 index 00947b27..00000000 --- a/docs/versioned_docs/version-0.3.x/playground.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization in a browser. Our playground consists 4 sections; Authorization Model, Visualizer, Authorization Data and Enforcement. Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Authorization Model - -You can create your authorization model in this section with using Permify authorization language, Permify Schema. You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured with Permify Schema. Also, you can check our docs to learn more about how to model authorization in Permify. - -To demonstrate how playground works, let's choose the "empty" option from our dropdown to create a simple authorization model as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193245391-6ff7cd21-69e3-4b8e-9fa8-d28c9045fe16.png) - -We have 2 permissions these are editing repository and deleting repository. Repository has parent child relation with organizations. Lastly organizations can have organizational wide roles such as admin and member. After completing your authorization model you can just save it with hitting the save button and start testing it. - -## Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. Here is a visualization for example model that we created above. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193245587-ff794d53-c142-44fb-959b-5c4546dd73c1.png) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as relation tuples and these tuples stored in a database that you preferred. The basic relation tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. Lastly, the user is basically the user or user set in our system. Let's say we want make user 1 admin in organization 1 then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the "new" button and a pop up will open. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246047-a6c425bd-b417-4054-b1a0-9352e8f30ded.png) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246036-691cb4ab-a589-4856-887e-7f412a2bb32d.png) - -And this created tuple shown in the Authorization Data section as follows. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246251-ffbb5c8d-944b-4b87-ae50-82a7c2d575e2.png) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 as follows: - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246717-cce0dc69-f10b-4e3a-8a85-ed846373a154.png) - -Created relational tuple after this will be: "repository:1#parent@organization:1#..." We used โ€œ...โ€ when subject type is different from user entity. #โ€ฆ represents a relation that does not affect the semantics of the tuple. - -## Enforcement ( Access Checks) -Finally as we have a sample data lets perform an access check from the right below. Let's check whether user:1 can edit the repository:1. Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission action as: - -`โ€action edit = owner or parent.admin` - -which parent.admin indicates admin in the organization that repository belongs to. So let's type **"can user:1 edit repository:1"** and hit the check button to get result. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246742-4df97b34-5e94-4132-9c7c-8d184ccc32f4.png) - - -Let's try to get unauthorized result. Type "can user:1 delete repository:1" on the question input. Since only owners can delete the repository this access check will result as unauthorized. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/193246754-86332f18-a483-479b-a0cf-62703c38a2f4.png) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. Check out our docs for different modeling use cases, creating and storing relational tuples and more. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/reference/_category_.json b/docs/versioned_docs/version-0.3.x/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/versioned_docs/version-0.3.x/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.3.x/reference/configuration.md b/docs/versioned_docs/version-0.3.x/reference/configuration.md deleted file mode 100644 index 205e59f1..00000000 --- a/docs/versioned_docs/version-0.3.x/reference/configuration.md +++ /dev/null @@ -1,312 +0,0 @@ -# Configuration File - -Permify offers various options for configuring your Permify API Server. - -Here is the example configuration YAML file with glossary below. You can also find this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. - -***Example config.yaml file*** - -```yaml -server: - http: - enabled: true - port: 3476 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - grpc: - port: 3478 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - -logger: - level: 'info' - -profiler: - enabled: true - port: 6060 - -authn: - method: preshared - enabled: false - keys: [] - -tracer: - exporter: 'zipkin' - endpoint: 'http://localhost:9411/api/v2/spans' - enabled: true - -meter: - exporter: 'otlp' - endpoint: 'localhost:4318' - enabled: true - -service: - circuit_breaker: false - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB - permission: - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - relationship: - -database: - engine: 'postgres' - uri: 'postgres://user:password@host:5432/db_name' - auto_migrate: false - max_open_connections: 20 - max_idle_connections: 1 - max_connection_lifetime: 300s - max_connection_idle_time: 60s - garbage_collection: - enable: true - interval: 3m - timeout: 3m - window: 720h - number_of_threads: 1 -``` - -## Options - -
server | Server Configurations -

- -#### Definition -Server options to run Permify. (`grpc` and `http` available for now.) - -#### Structure -``` -โ”œโ”€โ”€ server - โ”œโ”€โ”€ (`grpc` or `http`) - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ port - โ”‚ โ””โ”€โ”€ tls - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ cert - โ”‚ โ””โ”€โ”€ key -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | [ server_type ] | - | server option type can either be `grpc` or `http`. -| [ ] | enabled (for server type) | true | switch option for server. | -| [x] | port | - | port that server run on. -| [x] | tls | - | transport layer security options. | -| [ ] | enabled (for tls) | false | switch option for tls | -| [ ] | cert | - | tls certificate path. | -| [ ] | key | - | tls key pat | - -

-
- -
logger | Logging Options -

- -#### Definition -Real time logs of authorization. Permify uses [zerolog] as a logger. - -[zerolog]: https://github.com/rs/zerolog - -#### Structure -``` -โ”œโ”€โ”€ logger - โ”œโ”€โ”€ level -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | level | info | logger levels: `error`, `warn`, `info` , `debug` - -

-
- -
authn | Server Authentication -

- -#### Definition - -You can choose to authenticate users to interact with Permify API. - -There are 2 authentication method you can choose: - -* [Pre Shared Keys](#pre-shared-keys) -* [OpenID Connect](#openid-connect) - -#### Pre Shared Keys - -On this method, you must provide a pre shared keys in order to identify yourself. - -#### Structure -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ keys -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. -| [ ] | enabled | true | switch option authentication config | -| [x] | keys | - | Private key/keys for server authentication. Permify does not provide this key, so it must be generated by the users. - -#### OpenID Connect - -Permify supports OpenID Connect (OIDC). OIDC provides an identity layer on top of OAuth 2.0 to address the shortcomings of using OAuth 2.0 for establishing identity. - -With this authentication method, you be able to integrate your existing Identity Provider (IDP) to validate JSON Web Tokens (JWTs) using JSON Web Keys (JWKs). By doing so, only trusted tokens from the IDP will be accepted for authentication. - -#### Structure -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ client-id -| โ”œโ”€โ”€ issuer -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. -| [ ] | enabled | false | switch option authentication config | -| [x] | client_id | - | This is the client ID of the application you're developing. It is a unique identifier that is assigned to your application by the OpenID Connect provider, and it should be included in the JWTs that are issued by the provider. -| [x] | issuer | - | This is the URL of the provider that is responsible for authenticating users. You will use this URL to discover information about the provider in step 1 of the authentication process. | - - -

-
- - -
tracer | Tracing Configurations -

- -#### Definition -Permify integrated with [jaeger] , [signoz] and [zipkin] tacing tools to analyze performance and behavior of your authorization when using Permify. -#### Structure -``` -โ”œโ”€โ”€ tracer -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | exporter | - | Tracer exporter, the options are `jaeger`, `signoz` and `zipkin` -| [x] | endpoint | - | export uri for tracing data. | -| [ ] | enabled | false | switch option for tracing. - -

-
- -
meter | Meter Configurations -

- -#### Definition -Configuration for observing metrics; check count, cache check count and session information; Permify version, hostname, os, arch. - -#### Structure -``` -โ”œโ”€โ”€ meter -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. -| [x] | endpoint | - | export uri for metric observation | -| [ ] | enabled | true | switch option for meter tracing. - -

-
- -
database | Database (WriteDB) Configurations -

- -#### Definition -Configurations for the database that points out where your want to store your authorization data (relation tuples, audits, decision logs, authorization model) - -#### Structure -``` -โ”œโ”€โ”€ database -| โ”œโ”€โ”€ engine -| โ”œโ”€โ”€ uri -| โ”œโ”€โ”€ auto_migrate -| โ”œโ”€โ”€ max_open_connections -| โ”œโ”€โ”€ max_idle_connections -| โ”œโ”€โ”€ max_connection_lifetime -| โ”œโ”€โ”€ max_connection_idle_time -| โ”œโ”€โ”€garbage_collection -| โ”œโ”€โ”€enable: true -| โ”œโ”€โ”€interval: 3m -| โ”œโ”€โ”€timeout: 3m -| โ”œโ”€โ”€window: 720h -| โ”œโ”€โ”€number_of_threads: 1 -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------------|---------|---------| -| [x] | engine | memory | Data source. Permify supports **PostgreSQL**(`'postgres'`) for now. Contact with us for your preferred database. -| [x] | uri | - | Uri of your data source. | -| [ ] | auto_migrate | true | When its configured as false migrating flow won't work -| [ ] | max_open_connections | 20 | Configuration parameter determines the maximum number of concurrent connections to the database that are allowed. -| [ ] | max_idle_connections | 1 | Determines the maximum number of idle connections that can be held in the connection pool. -| [ ] | max_connection_lifetime | 300s | Determines the maximum lifetime of a connection in seconds. -| [ ] | max_connection_idle_time | 60s | Determines the maximum time in seconds that a connection can remain idle before it is closed. -| [ ] | enable (for garbage collection) | false | Switch option for garbage collection. -| [ ] | interval | 3m | Determines the run period of a Garbage Collection operation. -| [ ] | timeout | 3m | Sets the duration of the Garbage Collection timeout. -| [ ] | window | 720h | Determines how much backward cleaning the Garbage Collection process will perform. -| [ ] | number_of_threads | 1 | Limits how many threads Garbage Collection processes concurrently with. - -

-
- -
profiler | Performance Profiler Configurations -

- -#### Definition -pprof is a performance profiler for Go programs. It allows developers to analyze and understand the performance characteristics of their code by generating detailed profiles of program execution -#### Structure -``` -โ”œโ”€โ”€ meter -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|---------| -| [ ] | enabled | true | switch option for profiler. -| [x] | port | - | port that profiler runs on *(default: 6060)* | - -

-
- -[jaeger]: https://www.jaegertracing.io/ -[zipkin]: https://zipkin.io/ -[signoz]: https://signoz.io/ diff --git a/docs/versioned_docs/version-0.3.x/reference/glossary.md b/docs/versioned_docs/version-0.3.x/reference/glossary.md deleted file mode 100644 index ccefde53..00000000 --- a/docs/versioned_docs/version-0.3.x/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Write Database - WriteDB - -Permify stores your relational tuples (authorization data) in **WriteDB**. You can configure it **WriteDB** when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/reference/snap-tokens.md b/docs/versioned_docs/version-0.3.x/reference/snap-tokens.md deleted file mode 100644 index 47303667..00000000 --- a/docs/versioned_docs/version-0.3.x/reference/snap-tokens.md +++ /dev/null @@ -1,50 +0,0 @@ - -# Snap Tokens & Zookies - -A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. - -## Why you should use Snap Tokens ? - -Basically, you should use snap tokens both for consistency and performance. The main goal of Permify is to provide an authorization system that ensures excellent performance that can handle millions of requests from different environments while ensuring data consistency. - -Performance standards can be achievable with caching. In Permify, the cache mechanism eliminates re-computing of access control checks that once occurred, unless any relationships of resources don't change. - -Still, all caches suffer from the risk of becoming stale. If some schema update happens, or relations change then all of the caches should be updated according to it to prevent false positive or false negative results. - -Permify avoids this problem with an approach of snapshot reads. Simply, it ensures that access control is evaluated at a consistent point in time to prevent inconsistency. - -To achieve this, we developed tokens called Snap Tokens that consist of a timestamp that is compared in access checks to ensure that the snapshot of the access control is at least as fresh as the resource timestamp - basically its stored snap token. - -## How to use Snap Tokens - -Snap Tokens used in endpoints to represent the snapshot and get fresh results of the API's. It mainly used in [Write API] and [Check API]. - -The general workflow for using snap token is getting the snap token from the reponse of Write API request - basically when writing a relational tuple - then mapped it with the resource. One way of doing that is storing snap token in the additional column in your relational database. - -Then this snap token can be used in endpoints. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -```json -{ - "schema_version": "ce8siqtmmud16etrelag", - "snap_token": "gp/twGSvLBc=", - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - }, -} -``` - -[Write API]: ../../api-overview/relationship/write-relationships -[Check API]: ../../api-overview/permission/check-api - -#### All endpoints that used snap token - -- [Write API](../../api-overview/relationship/write-relationships) -- [Check API](../../api-overview/permission/check-api) -- [Expand API](../../api-overview/permission/expand-api) -- [Schema Lookup](../../api-overview/permission/schema-lookup) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/reference/tracing.md b/docs/versioned_docs/version-0.3.x/reference/tracing.md deleted file mode 100644 index 9f6a3193..00000000 --- a/docs/versioned_docs/version-0.3.x/reference/tracing.md +++ /dev/null @@ -1,52 +0,0 @@ - -# Tracing Tools - -Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: - -- [Jaeger](https://www.jaegertracing.io/) -- [Signoz](https://signoz.io/) -- [Zipkin](https://zipkin.io/) - -## Usage - -### Set Up - -Adding one of these tracing tools to your authorization system is quite simple, you just need to define it in the Permify configuration file as **tracer**. - -```yaml -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -``` -- ***exporter***: enter the tool name that you want to use. `jaeger` , `signoz` and `zipkin`. -- ***endpoint***: export url for tracing data. -- ***disabled***: switch option for tracing. - -**Example YAML configuration file** - -```yaml -app: - name: โ€˜permifyโ€™ -http: - port: 3476 -logger: - log_level: โ€˜debugโ€™ - rollbar_env: โ€˜permifyโ€™ -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -database: - write: - connection: 'postgres' - database: 'morf-health-demo' - uri: 'postgres://postgres:SphU4Uf3QXNntT@permify.us-east-1.rds.amazonaws.com:5432' - pool_max: 2 -``` - -After running Permify in your server, you should run Zipkin as well. If you're using docker here is the docker pull request for Zipkin: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` diff --git a/docs/versioned_docs/version-0.3.x/use-cases.md b/docs/versioned_docs/version-0.3.x/use-cases.md deleted file mode 100644 index 6fae082c..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: use-cases -title: Common Use Cases -slug: /use-cases ---- - -# Common Use Cases - -Common modeling patterns and uses cases we have seen so far from the users from small startups with simple RBAC to multi-regional enterprises that run tens of Permify instances with deeply nested relationships. - -:::success Missing a specific use case? -No problem, let's discuss it together! just open an [issue](https://github.com/Permify/permify/issues) about it or join our conversation at [discord](https://discord.gg/n6KfzYxhPp)! -::: - -```mdx-code-block -import {CaseList} from '@site/src/components/Case'; -import list from './use-cases/_list.json'; - - -``` \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/use-cases/_category_.json b/docs/versioned_docs/version-0.3.x/use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/use-cases/_list.json b/docs/versioned_docs/version-0.3.x/use-cases/_list.json deleted file mode 100644 index 81aacd1a..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/_list.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "id": 1, - "title": "Role Based Access Control (RBAC)", - "description": "Want to implement role to your application ? Define an entity and manage your roles throught your applications.", - "link": "./simple-rbac" - }, - { - "id": 2, - "title": "Nested Hierarchies", - "description": "Define a hierarchy of roles and permissions to inherit permissions from higher level roles.", - "link": "./nested-hierarchies" - }, - { - "id": 3, - "title": "Organizational", - "description": "Group your users by organization with giving them access organizational-wide resources.", - "link": "./organizational" - }, - { - "id": 4, - "title": "User Groups & Teams permissions", - "description": "Organize permissions based on groupings of users or resources.", - "link": "./user-groups" - }, - { - "id": 5, - "title": "Sharing & Collaboration", - "description": "For sharing resources with other users, you can use the invite action.", - "link": "./sharing" - }, - { - "id": 6, - "title": "Ownership", - "description": "Ownership is a special case of sharing where the owner of a resource has full access to it.", - "link": "./ownership" - } -] \ No newline at end of file diff --git a/docs/versioned_docs/version-0.3.x/use-cases/nested-hierarchies.md b/docs/versioned_docs/version-0.3.x/use-cases/nested-hierarchies.md deleted file mode 100644 index 787a5acc..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/nested-hierarchies.md +++ /dev/null @@ -1,73 +0,0 @@ - -# Nested Hierarchies - -This use case shows solving deeply nested hierarchies with [Permify Schema]. We have a unique **action** usage for nested hierarchies, where parent and child entities can share permissions between them. Let's follow the below team project authorization model to examine this case. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organization user types - relation admin @user -} - -entity team { - - //refers to organization that team belongs to - relation org @organization - - // Only the organization administrator can edit - action edit = org.admin -} - -entity project { - - //refers to team that project belongs to - relation team @team - - // This action responsible for nested permission inheritance - // team.edit refers edit action on the team entity which we defined above - // Semantics of this is: Only the organization administrator, who has the - // team, to which this project belongs can edit. - action edit = team.edit -} -``` - -## Sample Relational Tuples - -organization:1#admin@user:1 - -team:1#org@organization:1#... - -project:1#team@team:1#... - -Lets assume we created above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** result since the `user:1` is organizational admin and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md - -Let's break down this case, - -```perm -entity project { - - relation team @team - - action edit = team.edit -} -``` - -Above `team.edit` points out the **edit** action in the **team** (that project belongs to). - -And edit action on the team entity: `action edit = org.admin` states that only **organization (which that team belongs to) admins** can edit. So our project inherits that action and conducts a result accordingly. - -If we roll back to our enforcement: `Can user:1 edit project:1?` gives **Allow** result, because user:1 is admin in an organization that the projects' parent team belongs to. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/use-cases/organizational.md b/docs/versioned_docs/version-0.3.x/use-cases/organizational.md deleted file mode 100644 index 836945f6..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/organizational.md +++ /dev/null @@ -1,149 +0,0 @@ - - -# Organization Specific Resources - -Group your users by organization with giving them access organizational-wide resources. In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with our authorization language DSL, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 3 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -### Relations - -To define relation, **relations** needed to be created as entity attributes. - -#### organization entity - -In our schema we defined 2 relation in organization entity, respectively; ``admin`` and ``member`` - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents the default user that belongs to that organization. - -#### repository entity - -Repository entities have 2 relations, these are ``parent`` and ``owner``. Both of these relations represents actual database relations with other entities rather than a role-based approach likewise to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -``parent`` relation represents the parent organization with a repository. And ``owner`` represents the specific user, the repository's owner. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### repository actions - -In our schema, we examined one of the main functionalities can the user make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who also need to have an administrative role or be an owner of the parent organization, can ``read``. -- Repository owners or administrative roles in an organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since ``parent` represents the parent organization of repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -## Example Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.3.x/use-cases/ownership.md b/docs/versioned_docs/version-0.3.x/use-cases/ownership.md deleted file mode 100644 index f7dc5b94..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/ownership.md +++ /dev/null @@ -1,42 +0,0 @@ - -# Ownership - -Granting privileges to the owner of the resource is a common pattern that many applications follow. Generally we want creators of the resource - document, post, comment etc - have superior power on that resource. Check out the below model see how ownership can be modeled with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity comment { - - // represents comment's owner - relation owner @user - - // permissions - action edit = owner - action delete = owner -} - -``` - -## Sample Relational Tuples - -comment:2#owner@user:1 - -comment:3#owner@user:51 - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/use-cases/sharing.md b/docs/versioned_docs/version-0.3.x/use-cases/sharing.md deleted file mode 100644 index 4a97b41b..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/sharing.md +++ /dev/null @@ -1,40 +0,0 @@ - -# Sharing & Collaboration - -Inviting a team member to a document, project or repository should be hassle free to model. In Permify you can achieve this with simply defining a invite action. Check out the below model block see how sharing can be modeled with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - relation manager @user - -} - -entity project { - - // represents project's parent organization - relation org @organization - - // represents owner of this project - relation owner @user - - // invite permission - action invite = org.admin or owner - -} - -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/use-cases/simple-rbac.md b/docs/versioned_docs/version-0.3.x/use-cases/simple-rbac.md deleted file mode 100644 index 82767daa..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/simple-rbac.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Role Based Access Control - -Want to implement role and permissions to your application ? Permify fully covers you at that point. Below example shows how to model simple role based access control for organizational roles and permissions with our authorization language, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - - //organization files access permissions - action view_files = admin or manager or (member and not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 2 entities, - -- `user`, represents users (maybe corresponds as employees). This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, representing the organization the user (employees) belongs. It has several roles and permissions related to the specific resources such as organization files and vendor files. - -### Relations - -#### organization entity - -We can use **relations** to define roles. In this example, we have 4 organizational wide roles, respectively; admin, manager, member, and agent. - -```perm -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - -} -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### organization actions - -In our schema, we define several actions for controlling access permissions on organization files and organization vendor's files. - -```perm -entity organization { - - //organization files access permissions - action view_files = admin or manager or (member and not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -let's take a look at some of the actions: - -- ``action edit_files = admin or manager`` -indicates that only the admin or manager has permission to edit files in the organization. - -- ``action view_files = admin or manager or (member and not agent)`` -indicates that the admin, manager, or members (without having the agent role) can view organization files. - - - -## Example Relational Tuples for this case - -organization:2#admin@user:daniel - -organization:5#member@user:ashley - -organization:17#manager@user:mert - -organization:21#agent@user:ege - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.3.x/use-cases/user-groups.md b/docs/versioned_docs/version-0.3.x/use-cases/user-groups.md deleted file mode 100644 index 597ab55b..00000000 --- a/docs/versioned_docs/version-0.3.x/use-cases/user-groups.md +++ /dev/null @@ -1,201 +0,0 @@ - -# User Groups & Team Permissions - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // reference for organization that team belong - relation org @organization - - // organization admins or owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be admin and either owner or member of this team - action invite = org.admin and (owner or member) - - // only owners can remove users - action remove_user = owner - -} - -entity project { - - // references for team and organization that project belongs - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 4 entity, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that contain teams. - -- `team`, represents teams, which belongs to a organization. - -- `project`, represents projects that belongs teams. - -### Relations - -#### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -#### team entity - -The eeam entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -#### project entity - -Project entity has ``team`` and ``org`` relations. Both these relations represents parent relationship with other entites, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can perform editing and deleting team spesific resources. - -- Moreover, for inviting a colleague to a team you must have ***admin role*** and either be a ***owner*** or ***member*** on that team. - -- To remove users in team you must be a ***owner*** of that team. - -And these rules reflects Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -#### project actions - -And there are the project actions below. It consists of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Example Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organiation:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 also members in organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/api-overview.md b/docs/versioned_docs/version-0.4.x/api-overview.md deleted file mode 100644 index 64e4a6d1..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Overview - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests and options. -- [RelationshipService]: Authorization data operations such as creating, deleting and reading relational tuples. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ./permission -[RelationshipService]: ./relationship -[SchemaService]: ./schema -[TenancyService]: ./tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](./api-overview/schema/write-schema.md) -- Write relational tuples with [Write Relationships](./api-overview/relationship/write-relationships.md) -- Read relation tuples and filter them with [Read API](./api-overview/relationship/read-api.md) -- Check access with [Check API](./api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](./api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](./api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](./api-overview/relationship/delete-relationships.md) -- Expand schema actions with [Expand API](./api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](./api-overview/watch/watch-changes.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our [documentation](https://docs.permify.co/docs/reference/configuration). We used [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm for rate limiting. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/api-overview/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/permission/_category_.json deleted file mode 100644 index f91d5b46..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Permission Service", - "position": 3, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/check-api.md b/docs/versioned_docs/version-0.4.x/api-overview/permission/check-api.md deleted file mode 100644 index ab24405c..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/check-api.md +++ /dev/null @@ -1,192 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control - -In Permify, you can perform two different types access checks, - -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **subject based** authorization checks, in form of `Which resources can user U edit ?` - -In this section we'll look at the resource based check request of Permify. You can find subject based access checks in [Entity (Data) Filtering] section. - -[Entity (Data) Filtering]: ../lookup-entity - -## Request - -**Path:** POST /v1/permissions/check - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. , see more details on [Contextual Tuples](../../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "", - SchemaVersion: "", - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "can": "RESULT_ALLOWED", - "remaining_depth": 0 -} -``` - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. - -## How Access Decisions Evaluated? - -Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. - -In high level, access of an subject related with the relationships created between the subject and the resource. You can define this relationships in Permify Schema then create and store them as relational tuples, which is basically your authorization data. - -Permify Engine to compute access decision in 2 steps, -1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. -2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. - -Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. - -[relational tuples]: ../../getting-started/sync-data.md -[Permify Schema]: ../../getting-started/modeling.md - -When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity document { - - // represents documents parent organization - relation parent @organization - - // represents owner of this document - relation owner @user - - // permissions - action edit = parent.admin or owner - action delete = owner -} -``` - -Which has a directed graph as follows: - -![relational-tuples](https://github.com/Permify/permify/assets/39353278/cec9936c-f907-42c0-a419-032ebb45454e) - -As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: - -**Q1:** Get the owners of the `document:12`. - -**Q2:** Get admins of the organization where `document:12` belongs to. - -Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. - -Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. - -## Latency & Performance - -With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. - -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../reference/cache.md) - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/expand-api.md b/docs/versioned_docs/version-0.4.x/api-overview/permission/expand-api.md deleted file mode 100644 index d742de2e..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/expand-api.md +++ /dev/null @@ -1,314 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API - -Retrieve all subjects (users and user sets) that have a relationship with given entity and permission - -Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. - -:::caution When To Use ? -Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. - -It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. -::: - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{ - TenantId: "t1", - Metadata: &v1.PermissionExpandRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -}' -``` - - - -## Example Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** POST /v1/tenants/{tenant_id}/permissions/expand - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. | -| [x] | permission | string | - | The permission the user wants to perform on the resource | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../reference/contextual-tuples) | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-entity.md b/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-entity.md deleted file mode 100644 index 77c9f1c6..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-entity.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Entity (Data) Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Entity Filtering - -Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So, we provide 2 separate endpoints for data filtering check request, - -- [/v1/permissions/lookup-entity](#lookup-entity) -- [/v1/permissions/lookup-entity-stream](#lookup-entity-streaming) - -## Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. - -**POST** /v1/permissions/lookup-entity - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - EntityType: "document", - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - } -}) -``` - - - - -```javascript -client.permission.lookupEntity({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity_type: "document", - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response.entity_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type":"user", - "id":"1" - } -}' -``` - - - -## How Lookup Operations Evaluated - -We explicitly designed reverse lookup to be more performant with changing its evaluation pattern. We do not query all the documents in bulk to get response, instead of this Permify first finds the necessary relations with given subject and the permission/action in the API call. Then query these relations with the subject id this way we reduce lots of additional queries. - -To give an example, - -```jsx -entity user {} - -entity organization { - relation admin @user -} - -entity container { - relation parent @organization - relation container_admin @user - action admin = parent.admin or container_admin -} - -entity document { - relation container @container - relation viewer @user - relation owner @user - action view = viewer or owner or container.admin -} -``` - -Lets say we called (reverse) lookup API to find the documents that user:1 can view. Permify first finds the relations that linked with view action, these are - -- `document#viewer` -- `document#owner` -- `organization#admin` -- `container#``container_admin` - -Then queries each of them with `user:1.` - -### Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -**POST** /v1/permissions/lookup-entity-stream - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../reference/contextual-tuples) | - - - - -```go -str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest { - Metadata: &v1.PermissionLookupEntityRequestMetadata { - SnapToken: "", - SchemaVersion: "" - Depth: 50, - }, - EntityType: "document", - Permission: "view", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) - -// handle stream response -for { - res, err: = str.Recv() - - if err == io.EOF { - break - } - - // res.EntityId -} -``` - - - - -```javascript -const permify = require("@permify/permify-node"); -const {PermissionLookupEntityStreamResponse} = require("@permify/permify-node/dist/src/grpc/generated/base/v1/service"); - -function main() { - const client = new permify.grpc.newClient({ - endpoint: "localhost:3478", - }) - - let res = client.permission.lookupEntityStream({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entityType: "document", - permission: "view", - subject: { - type: "user", - id: "1" - } - }) - - handle(res) -} - -async function handle(res: AsyncIterable) { - for await (const response of res) { - // response.entityId - } -} -``` - - - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-subject.md b/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-subject.md deleted file mode 100644 index 27ab6128..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-subject.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Subject Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Filtering - -Lookup Subject endpoint lets you ask questions in form of **โ€œWhich subjects can do action Y on entity:X?โ€**. As a response of this youโ€™ll get a subject results in a format of string array. - -So, we provide 1 endpoint for subject filtering request, - -- [/v1/permissions/lookup-subject](#lookup-subject) - -## Lookup Subject - -In this endpoint you'll get directly the IDs' of the subjects that are authorized in an array. - -**POST** /v1/permissions/lookup-subject - -| Required | Argument | Type | Default | Description | -|----------|---------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1 | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject_reference | object | - | the subject or subject reference who wants to take the action. It contains type and relation of the subject. | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest { - TenantId: "t1", - Metadata: &v1.PermissionLookupSubjectRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Permission: "edit", - SubjectReference: &v1.RelationReference{ - Type: "user", - Relation: "", - } -}) -``` - - - - -```javascript -client.permission.lookupSubject({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - Entity: { - Type: "document", - Id: "1", - }, - permission: "edit", - subject_reference: { - type: "user", - relation: "" - } -}).then((response) => { - console.log(response.subject_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "" - }, - "entity": { - type: "document", - id: "1' - }, - "permission": "edit", - "subject_reference": { - "type": "user", - "relation": "" - } -}' -``` - - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/api-overview/permission/subject-permission.md b/docs/versioned_docs/version-0.4.x/api-overview/permission/subject-permission.md deleted file mode 100644 index c2ff2eaf..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/permission/subject-permission.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Subject Permission List ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Permission List - -The Subject Permission List endpoint allows you to inquire in the form of **โ€œWhich permissions user:x can perform on entity:y?โ€**. In response, you'll receive a list of permissions specific to the user for the given entity, returned in the format of a map. - -So, we provide 1 endpoint for subject permission list, - -- [/v1/permissions/subject-permission](#subject-permission) - -In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as "sample-permission" -> "ALLOWED". This represents the permissions and their associated states in a key-value pair format. - -## Request - -**Path:** POST /v1/permissions/subject-permission - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | only_permission | bool | false | By default, the endpoint returns both permissions and relations associated with the user and entity. However, when the "only_permission" parameter is set to true, it returns only the permissions. | | -| [ ] | contextual_tuples | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. , see more details on [Contextual Tuples](../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest { - TenantId: "t1", - Metadata: &v1.PermissionSubjectPermissionRequestMetadata { - SnapToken: "", - SchemaVersion: "", - OnlyPermission: false, - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) -``` - - - - -```javascript -client.permission.subjectPermission({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - onlyPermission: true, - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response); -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "only_permission": true, - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "results": [ - { - "key": "delete", - "value": "RESULT_ALLOWED" - }, - { - "key": "edit", - "value": "RESULT_ALLOWED" - } - ] -} -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/api-overview/relationship/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/relationship/_category_.json deleted file mode 100644 index bca5fd5e..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/relationship/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Relationship Service", - "position": 2, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/relationship/delete-relationships.md b/docs/versioned_docs/version-0.4.x/api-overview/relationship/delete-relationships.md deleted file mode 100644 index d63b7e68..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/relationship/delete-relationships.md +++ /dev/null @@ -1,103 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Relational Tuples - -You can delete any stored relation tuples with following API - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/relationships/delete - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Relationship.Delete(context.Background(), & v1.RelationshipDeleteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipDeleteRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.delete({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "filter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/relationship/read-api.md b/docs/versioned_docs/version-0.4.x/api-overview/relationship/read-api.md deleted file mode 100644 index eb70b30c..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/relationship/read-api.md +++ /dev/null @@ -1,103 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -## Request - -**Path:** POST /v1/tenants/{tenant_id/relationships/read - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Relationship.Read(context.Background(), & v1.RelationshipReadRequest { - TenantId: "t1", - Metadata: &v1.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.relationship.read({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/api-overview/relationship/write-relationships.md b/docs/versioned_docs/version-0.4.x/api-overview/relationship/write-relationships.md deleted file mode 100644 index 0646ff4e..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/relationship/write-relationships.md +++ /dev/null @@ -1,196 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Relationships - -In Permify, relations between your entities, objects and users stored as [relational tuples] in [writeDB]. Since relations and authorization data's are live instances these relational tuples can be created with an simple API call in runtime. - -When using Permify, the application client should update writeDB about the changes happening in entities or resources that are related to the authorization structure. If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [writeDB] up-to-date. Besides, each relational tuple should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as relation tuples and store in [writeDB]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -[relational tuples]: ../../../getting-started/sync-data -[writeDB]: ../../../getting-started/sync-data#where-relational-tuples-used - -## Request - -So if user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` must be created by using **/v1/relationships/write** endpoint. - -**Path:** POST /v1/tenants/{tenant_id}/relationships/write - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "admin", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -## Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../../reference/snap-tokens) and how its avoiding stale cache. - -## Suggested Workflow - -The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? - -### Two Phase Commit Approach -In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. - -To give more concurrent example of this action, let's take a look at below createDocument function - -```go -func CreateDocuments(db *gorm.DB) error { - - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - } - }() - - if err := tx.Error; err != nil { - return err - } - - if err := tx.Create(docs).Error; err != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteRelationships(...) - return err - } - - // if transaction successful, write relation tuple to Permify - permify.WriteRelationships(...) - - return tx.Commit().Error -} -``` -The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. - -### Relationships that not stored in application database - -Although ownership generally stored in application databases, there are some relations that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. - -For example, you can model a simple project management authorization in Permify as follows, - -```perm -entity user {} - -entity team { - - relation owner @user - relation member @user -} - -entity project { - - relation team @team - relation owner @user - - action view = team.member or team.owner or project.owner - action edit = project.owner or team.owner - action delete = project.owner or team.owner - -} -``` - -This **team member** relation won't need to be stored in the application database. Storing it only in Permify - WriteDB - is enough. In that situation, `WriteRelationships` can be performed in any logical place in your stack. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/schema/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/schema/_category_.json deleted file mode 100644 index 8fd1e959..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/schema/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Schema Service", - "position": 1, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/schema/write-schema.md b/docs/versioned_docs/version-0.4.x/api-overview/schema/write-schema.md deleted file mode 100644 index 47a0e1ff..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/schema/write-schema.md +++ /dev/null @@ -1,93 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema - -Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. - -We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: - -Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -## Request - -**POST** /v1/tenants/{tenant_id}/schemas/write - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - TenantId: "t1", - Schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - `, -}) -``` - - - - -```javascript -client.schema.write({ - tenantId: "t1", - schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - ` -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "schema": "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/write"** - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - - -## Suggested Workflow For Schema Changes - -It's expected that your initial schema will eventually change as your product or system evolves - -As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. - -A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database (your writeDB) unless you delete them with the [delete API](../relationship/delete-relationships.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. - -We recommend applying the following pattern to safely handle schema changes: - -- Set up a central git repository that includes the schema. -- Teams or individuals who need to update the schema should add new permissions or relations to this repository. -- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. -- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. - -## Need any help ? - -Depending on the frequency and the type of the changes that you made on the schemas, this method may not be optimal for you - In such cases, we are open to exploring alternative solutions. Please feel free to [schedule a call with one of our engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/tenancy/_category_.json deleted file mode 100644 index 9771416d..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Tenancy Service", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/create-tenant.md b/docs/versioned_docs/version-0.4.x/api-overview/tenancy/create-tenant.md deleted file mode 100644 index 412ddaec..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/create-tenant.md +++ /dev/null @@ -1,55 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Tenant - -Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. - -:::caution -We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. -::: - -## Request - -**POST /v1/tenants/create** - - - - -```go -rr, err: = client.Tenancy.Create(context.Background(), & v1.TenantCreateRequest { - Id: "" - Name: "" -}) -``` - - - - - -```javascript -client.tenancy.create({ - id: "", - name: "" -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'http://localhost:3476/v1/tenants/create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "id": "", - "name": "" -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/delete-tenant.md b/docs/versioned_docs/version-0.4.x/api-overview/tenancy/delete-tenant.md deleted file mode 100644 index d8bd4a43..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/tenancy/delete-tenant.md +++ /dev/null @@ -1,44 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Tenant - -You can delete a tenant with following API. - -## Request - -**DELETE /v1/tenants/{id}** - - - - -```go -rr, err: = client.Tenancy.Delete(context.Background(), & v1.TenantDeleteRequest { - Id: "" -}) -``` - - - - - -```javascript -client.tenancy.delete({ - id: "", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/watch/_category_.json b/docs/versioned_docs/version-0.4.x/api-overview/watch/_category_.json deleted file mode 100644 index f65c41fd..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/watch/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Watch Service", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/api-overview/watch/watch-changes.md b/docs/versioned_docs/version-0.4.x/api-overview/watch/watch-changes.md deleted file mode 100644 index 921fac97..00000000 --- a/docs/versioned_docs/version-0.4.x/api-overview/watch/watch-changes.md +++ /dev/null @@ -1,140 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Watch - -The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples. - -The Watch API exclusively supports gRPC and works with PostgreSQL, given the track_commit_timestamp option is enabled. Please note, it doesn't support in-memory databases or HTTP communication. - -# Requirements - -- PostgreSQL database set up with track_commit_timestamp option enabled - -## Enabling track_commit_timestamp on PostgreSQL - -To ensure data consistency and synchronization between your application and Permify, enable track_commit_timestamp on -your PostgreSQL server. This can be done by executing the following options in your PostgreSQL: - -### Option 1: SQL Command - -1. Open your PostgreSQL command line interface. -2. Execute the following command: - - ```sql - ALTER SYSTEM SET track_commit_timestamp = ON; - ``` - -3. Reload the configuration with the following command: - - ```sql - SELECT pg_reload_conf(); - ``` - -### Option 2: Editing postgresql.conf - -1. Find and open the postgresql.conf file in a text editor. Its location depends on your PostgreSQL installation. Common - locations are: - - Debian-based systems: /etc/postgresql/[version]/main/postgresql.conf - - Red Hat-based systems: /var/lib/pgsql/data/postgresql.conf - -2. Add or modify the following line in the postgresql.conf file: - ``` - track_commit_timestamp = on - ``` - -3. Save and close the postgresql.conf file. -4. Reload the PostgreSQL configuration for the changes to take effect. This can be done via the PostgreSQL console: - ```sql - SELECT pg_reload_conf(); - ``` - - Or if you have command line access, use: - - ```bash - sudo service postgresql reload - ``` - -Please ensure you have the necessary permissions to execute these commands or modify the postgresql.conf file. Also, remember that changes in the postgresql.conf file will persist across restarts, while the SQL method may need to be reapplied depending on your PostgreSQL version and setup. - -:::info -Important Configuration Requirement: To use the Watch API, it must be enabled in your configuration file. Add or modify the following lines: - -```yaml -service: - watch: - enabled: true -``` - -::: - -## Request - -**Path:** POST /v1/watch/watch - -| Required | Argument | Type | Default | Description | -|----------|------------|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | snap_token | string | - | specifies the starting point for broadcasting changes. If a snap_token is provided, all changes following that specific snapshot will be broadcasted. If a snap_token is not provided, the Watch API will broadcast all changes that occur after the Watch API is initiated., see more details on [Snap Tokens](../../reference/snap-tokens). | - - -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```go) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```javascript) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -## Response - -```json -{ - "changes": { - "tuple_changes": [ - { - "operation": "OPERATION_CREATE", - "tuple": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "56", - "relation": "" - } - } - } - ], - "snap_token": "MgMAAAAAAAA=" - } -} -``` - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or -have any questions about this -example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/comparision.md b/docs/versioned_docs/version-0.4.x/comparision.md deleted file mode 100644 index a73e70f9..00000000 --- a/docs/versioned_docs/version-0.4.x/comparision.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: comparison -title: Comparison Between Other Zanzibar implementations ---- - -:::caution Note -This comparison table shows the differentiation between authorization solutions based or inspired by Google Zanzibar paper. If you use any of these solutions and feel the information could be improved, feel free to reach out. -::: - -## General Aspects - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|---------------------------------|------------|------------|-----------|-----------| -| **Zanzibar Paper Faithfulness** | Medium | High | High | High | -| **Scalability** | Medium | Medium | High | High | -| **Consistency & Cache** | No Zookies | No Zookies | Supported | Supported | -| **Dev UX** | Average | Average | High | High | - -## Feature Set - -- โœ…  Supported, and ready to use with no added configuration or code -- ๐ŸŸก  Limited support and requires extra user-code to implement. -- โ›”  Not officially supported or documented. - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|--------------------------|----------|---------|---------|---------| -| **Check API** | โœ… | โœ… | โœ… | โœ… | -| **Write API** | โœ… | โœ… | โœ… | โœ… | -| **Read API** | โœ… | โœ… | โœ… | โœ… | -| **Expand API** | โœ… | โœ… | โœ… | โœ… | -| **Watch API** | โœ… | โœ… | โœ… | โœ… | -| **RBAC** | โœ… | โœ… | โœ… | โœ… | -| **ReBAC** | โœ… | โœ… | โœ… | โœ… | -| **ABAC** | โ›” | ๐ŸŸก | โœ… | โ›” | -| **Data Filtering** | โ›” | โœ… | โœ… | โœ… | -| **Multi Tenancy** | โ›” | โœ… | โ›” | โœ… | -| **Testing & Validation** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Logging & Tracing** | ๐ŸŸก | โœ… | โœ… | โœ… | diff --git a/docs/versioned_docs/version-0.4.x/getting-started/_category_.json b/docs/versioned_docs/version-0.4.x/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.4.x/getting-started/enforcement.md b/docs/versioned_docs/version-0.4.x/getting-started/enforcement.md deleted file mode 100644 index 9a810df3..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/enforcement.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Interacting With The API - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests, permission checks and their configurations. -- [RelationshipService]: Authorization data operations such as creating, deleting and reading relational tuples. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ../../api-overview/permission -[RelationshipService]: ../../api-overview/relationship -[SchemaService]: ../../api-overview/schema -[TenancyService]: ../../api-overview/tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](../api-overview/schema/write-schema.md) -- Write relational tuples with [Write Relationships](../api-overview/relationship/write-relationships.md) -- Read relation tuples and filter them with [Read API](../api-overview/relationship/read-api.md) -- Check access with [Check API](../api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](../api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](../api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](../api-overview/relationship/delete-relationships.md) -- Expand schema actions with [Expand API](../api-overview/permission/expand-api.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration.md) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Performance & Latency - -Permify designed to answer these authorization questions efficiently and with minimal complexity while providing low latency with: -- Using its parallel graph engine. -- Storing the relationships between resources beforehand in Permify data store: [writeDB], rather than providing these relationships at โ€œcheckโ€ time. -- Implementing permission caching to not recompute repeated permission checks, and in memory cache to store authorization schema. -- Using [Snap Tokens](../../reference/snap-tokens) to achieve consistency and high performance in cache. - -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisms](../reference/cache.md) - -[writeDB]: ../getting-started/sync-data.md - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our documentation. - -- doc: https://docs.permify.co/docs/reference/configuration -- algorithm: https://en.wikipedia.org/wiki/Token_bucket - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/getting-started/examples/_category_.json b/docs/versioned_docs/version-0.4.x/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f801..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/examples/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Example Permission Structures", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/getting-started/examples/notion.md b/docs/versioned_docs/version-0.4.x/getting-started/examples/notion.md deleted file mode 100644 index 9095d464..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/examples/notion.md +++ /dev/null @@ -1,548 +0,0 @@ -# Notion - -This is a schema definition of the authorization model for Notion, a popular productivity and organization tool. - -### Schema | [Open in playground](https://play.permify.co/?s=BsCvLmd4g81sB20XJZI5p) - -```perm -entity user {} - -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin -} - -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write -} - -entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write -} - -entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter -} - -entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author -} - -entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write -} - -entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write -} -``` - -## Brief Examination of the Model - -The model defines several entities, including users, workspaces, pages, databases, blocks, and integrations. It also includes several default roles, such as Admin, Bot, Guest, and Member. Here's a breakdown of the entities: - -### Entities & Relations - -- **`user`**: Represents a user in the system. - -- **`workspace`**: Represents a workspace in which users can collaborate. Each workspace has an owner, members, guests, and bots associated with it. The owner and admin users have permission to manage the workspace. Permissions are defined for creating pages, inviting members, viewing the workspace, and managing the workspace. The read and write permissions can be inherited by child entities. - -- **`page`**: Represents a page within a workspace. Each page is associated with a workspace and has a writer and readers. The read and write permissions are defined based on the writer and readers of the page and can be inherited from the workspace. - -- **`database`**: Represents a database within a workspace. Each database is associated with a workspace and has an editor and viewers. The read and write permissions are defined based on the editor and viewers of the database and can be inherited from the workspace. Permissions are also defined for creating and deleting databases. - -- **`block`**: Represents a block within a page or database. Each block is associated with a page or database and has an editor and commenters. The read and write permissions are defined based on the editor and commenters of the block and can be inherited from the database. Commenters are users who have permission to comment on the block. - -- **`comment`**: Represents a comment on a block. Each comment is associated with a block and has an author. The read and write permissions are defined based on the author of the comment and can be inherited from the block. - -- **`template`**: Represents a template within a workspace. Each template is associated with a workspace and has a creator and viewers. The read and write permissions are defined based on the creator and viewers of the template and can be inherited from the workspace. Permissions are also defined for creating and deleting templates. - -- **`integration`**: Represents an integration within a workspace. Each integration is associated with a workspace and has an owner. Permissions are defined for reading and writing to the integration. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. Let's examine the **read** permission of the page entity. - -#### Page Read Permission - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - - .. - .. - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - .. -} - -entity page { - - // The workspace associated with the page - relation workspace @workspace - - .. - .. - - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - .. - .. - - // Define permissions for page actions - permission read = reader or workspace.read - - .. - .. -} -``` - -This permission specifies who can read the contents of the page at Notion. - -The `reader` relation specifies the users who are members of the workspace associated with the page (`workspace#member`) or guests of the workspace (`workspace#guest`). - -Read permission of the workspace inherited as `workspace.read` in the page entity. THis permission specifies that any user who has been granted read access to the workspace object (i.e., the workspace that the page belongs to) can also read the page. - -In summary, any user who is a member or guest of the workspace and has been granted read access to the page through the reader relation, as well as any user who has been granted read access to the workspace itself, can read the contents of the page. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different workspaces: -workspace:engineering_team#owner@user:alice -workspace:engineering_team#member@user:bob -workspace:engineering_team#guest@user:charlie -workspace:engineering_team#admin@user:alice -workspace:sales_team#owner@user:david -workspace:sales_team#member@user:eve -workspace:sales_team#guest@user:frank -workspace:sales_team#admin@user:david - -// Connect pages, databases, and templates to workspaces: -page:project_plan#workspace@workspace:engineering_team -page:product_spec#workspace@workspace:engineering_team -database:task_list#workspace@workspace:engineering_team -template:weekly_report#workspace@workspace:sales_team -database:customer_list#workspace@workspace:sales_team -template:marketing_campaign#workspace@workspace:sales_team - -// Set permissions for pages, databases, and templates: -page:project_plan#writer@user:frank -page:project_plan#reader@user:bob - -database:task_list#editor@user:alice -database:task_list#viewer@user:bob - -template:weekly_report#creator@user:alice -template:weekly_report#viewer@user:bob - -page:product_spec#writer@user:david -page:product_spec#reader@user:eve - -database:customer_list#editor@user:david -database:customer_list#viewer@user:eve - -template:marketing_campaign#creator@user:david -template:marketing_campaign#viewer@user:eve - -// Set relationships for blocks and comments: -block:task_list_1#database@database:task_list -block:task_list_1#editor@user:alice -block:task_list_1#commenter@user:bob -block:task_list_2#database@database:task_list -block:task_list_2#editor@user:alice -block:task_list_2#commenter@user:bob - -comment:task_list_1_comment_1#block@block:task_list_1 -comment:task_list_1_comment_1#author@user:bob -comment:task_list_1_comment_2#block@block:task_list_1 -comment:task_list_1_comment_2#author@user:charlie -comment:task_list_2_comment_1#block@block:task_list_2 -comment:task_list_2_comment_1#author@user:bob -comment:task_list_2_comment_2#block@block:task_list_2 -comment:task_list_2_comment_2#author@user:charlie -``` - -## Test & Validation - -Since we have our schema and the sample relation tuples, let's check some permissions and test our authorization logic. - -
can user:alice write database:task_list ? -

- -```perm - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - - .. - .. - - // Define permissions for database actions - .. - .. - - permission write = editor or workspace.write - - .. - .. - } -``` - -According to what we have defined for the **'write'** permission, users who are either; - -- The editor in task list database (`database:task_list`) -- Have a write permission in the engineering team workspace, which is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`) - -can edit the task list database (`database:task_list`) - -Based on the relation tuples we created, `user:alice` doesn't have the **editor** relationship with the `database:task_list`. - -Since `user:alice` is the owner and admin in the engineering team workspace (`workspace:engineering_team#admin@user:alice`) it has a write permission defined in the workspace entity, as you can see below: - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - .. - .. - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - // Define permissions that can be inherited by child entities - .. - permission write = owner or admin -} -``` - -And as we mentioned the engineering team workspace is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`). Therefore, the `user:alice write database:task_list` check request should yield a **'true'** response. - -

-
- -
can user:charlie write page:product_spec ? -

- -```perm -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - - .. - .. - - permission write = writer or workspace.write -} -``` - -`user:charlie` is guest in the workspace (`workspace:engineering_team#guest@user:charlie`) and the engineering team workspace is the only workspace that `page:product_spec` belongs to. - -As we defined, guests doesn't have write permission in a workspace. - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - permission write = owner or admin -} -``` - -So that, `user:charlie` doesn't have a write relationship in the workspace. And ultimately, the `user:charlie write page:product_spec` check request should yield a **'false'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin - } - - entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write - } - - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write - } - - entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter - } - - entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author - } - - entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write - } - - entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write - } - -relationships: - - workspace:engineering_team#owner@user:alice - - workspace:engineering_team#member@user:bob - - workspace:engineering_team#guest@user:charlie - - workspace:engineering_team#admin@user:alice - - workspace:sales_team#owner@user:david - - workspace:sales_team#member@user:eve - - workspace:sales_team#guest@user:frank - - workspace:sales_team#admin@user:david - - page:project_plan#workspace@workspace:engineering_team - - page:product_spec#workspace@workspace:engineering_team - - database:task_list#workspace@workspace:engineering_team - - template:weekly_report#workspace@workspace:sales_team - - database:customer_list#workspace@workspace:sales_team - - template:marketing_campaign#workspace@workspace:sales_team - - page:project_plan#writer@user:frank - - page:project_plan#reader@user:bob - - database:task_list#editor@user:alice - - database:task_list#viewer@user:bob - - template:weekly_report#creator@user:alice - - template:weekly_report#viewer@user:bob - - page:product_spec#writer@user:david - - page:product_spec#reader@user:eve - - database:customer_list#editor@user:david - - database:customer_list#viewer@user:eve - - template:marketing_campaign#creator@user:david - - template:marketing_campaign#viewer@user:eve - - block:task_list_1#database@database:task_list - - block:task_list_1#editor@user:alice - - block:task_list_1#commenter@user:bob - - block:task_list_2#database@database:task_list - - block:task_list_2#editor@user:alice - - block:task_list_2#commenter@user:bob - - comment:task_list_1_comment_1#block@block:task_list_1 - - comment:task_list_1_comment_1#author@user:bob - - comment:task_list_1_comment_2#block@block:task_list_1 - - comment:task_list_1_comment_2#author@user:charlie - - comment:task_list_2_comment_1#block@block:task_list_2 - - comment:task_list_2_comment_1#author@user:bob - - comment:task_list_2_comment_2#block@block:task_list_2 - - comment:task_list_2_comment_2#author@user:charlie - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "database:task_list" - subject: "user:alice" - assertions: - write: true - - entity: "page:product_spec" - subject: "user:charlie" - assertions: - write: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233154924-c31a76f4-86f5-4ed3-a1ec-750b642927e6.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/getting-started/modeling.md b/docs/versioned_docs/version-0.4.x/getting-started/modeling.md deleted file mode 100644 index ba07ed5f..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/modeling.md +++ /dev/null @@ -1,442 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Modeling Authorization - -Permify has its own language that you can model your authorization logic with it. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager, member and also dynamic attributes such as boolean variables, IP range, time period, etc. - -![modeling-authorization](https://raw.githubusercontent.com/Permify/permify/master/assets/permify-dsl.gif) - -## Permify Schema - -You can define your entities, relations between them and access control decisions with using Permify Schema. It includes set-algebraic operators such as intersection and union for specifying potentially complex access control policies in terms of those user-object relations. - -Hereโ€™s a simple breakdown of our schema. - -![permify-schema](https://user-images.githubusercontent.com/34595361/183866396-9d2850fc-043f-4254-aa4c-ee2c4172afb8.png) - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is **_".perm"_**. - -## Developing a Schema - -This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. - -We'll follow a simplified version of github access control system. To see completed model you can jump directly to [Github Example](#github-example). - -:::info -You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. - -[vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm - -::: - -### Defining Entities - -The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. - -Think of entities as tables in your relationship database. We are strongly advice to name entities same as your database table name that its corresponds. In that way you can easily model and reason your authorization as well as eliminating the error possibility. - -You can create entities using `entity` keyword. Since we're following example of simplified github access control, lets create some of our entities as follows. - -```perm -entity user {} - -entity organization {} - -entity team {} - -entity repository {} -``` - -Entities has 2 different attributes. These are; - -- **relations** -- **actions (or permissions)** - -### Defining Relations - -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. Keyword **_relation_** need to used to create a entity relation with name and type attributes. - -**Relation Attributes:** - -- **name:** relation name. -- **type:** relation type, basically the entity itโ€™s related to (e.g. user, organization, document, etc.) - -An example relation takes form of, - -```perm -relation [name] @[type] -``` - -Lets turn back to our example and define our relations inside our entities: - -#### User Entity - -โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. - -```perm -entity user {} -``` - -#### Organization Entity - -โ†’ For the sake of simplicity let's define only 2 user types in an organization, these are administrators and direct members of the organization. - -```perm -entity organization { - - relation admin @user - relation member @user - -} -``` - -#### Team Entity - -โ†’ Let's say teams can belong organizations and can have a member inside of it as follows, - -```perm -entity team { - - relation parent @organization - relation member @user - -} -``` - -The parent relation is indicating the organization the team belongs to. This way we can achieve **parent-child relationship** inside this entity. - -#### Repository Entity - -โ†’ Organizations and users can have multiple repositories, so each repository is related with an organization and with users. We can define repository relations as as follows. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - -} -``` - -The owner relation indicates the creator of the repository, that way we can achieve **ownership** in Permify. - -**Defining Multiple Relation Types** - -As you can see we have new syntax above, - -```perm - relation maintainer @user @team#member -``` - -When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. - -:::info -You can use **#** to reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. - -Actual purpose of feature locking is to giving ability to specify the sets of users that can be assigned. - -For example: - -```perm - relation viewer @user -``` - -When you define it like this, you can only add users directly as tuples (you can find out what relation tuples is in next section): - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 - -However, if you define it as: - -```perm - relation viewer @user @organization#member -``` - -You will then be able to specify not only individual users but also members of an organization: - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 -- organization:1#viewer@organization:O1#member - -You can think of these definitions as a precaution taken against creating undesired user set relationships. -::: - -Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. - -### Defining Actions and Permissions - -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. So, the basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. - -#### Intersection and Exclusion - -The Permify Schema supports **`and`**, **`or`** and **`not`** operators to achieve permission **intersection** and **exclusion**. The keywords **_action_** or **_permission_** can be used with those operators to form rules for your authorization logic. - -Lets get back to our github example and create some permissions on repository entity, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - .. - .. - - action push = owner - -} -``` - -โ†’ `action push = owner or maintainer` indicates only the repository owner or maintainers can push to -repository. - -:::info -The same `push` can also be defined using the **permission** keyword, as follows: - -```perm -permission push = owner -``` - -Using `action` and `permission` will yield the same result for defining permissions in your authorization logic. - -The reason we have two keywords for defining permissions is that while most permissions are based on actions (such as view, read, edit, etc.), there are still cases where we need to define permissions based on roles or user types, such as admin or member. - -Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. - -See **Real World Examples Section** for examining the contextualize difference between using permission and action keyword. You can start with observing [Google Docs Simplified](./examples/google-docs.md) example. -::: - -For this tutorial we'll continue with `action` keyword, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - .. - .. - - action read = org.admin and (owner or maintainer or org.member) - -} -``` - -โ†’ Let's examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. - -:::info Permission Union - -Permify allows you to set permissions that are effectively the union of multiple permission sets. - -You can define permissions as relations to union all rules that permissions have. Here is an simple demonstration how to achieve permission union in our DSL, you can use actions (or permissions) when defining another action (or permission) like relations, - -```perm - action edit = member or manager - action delete = edit or org.admin -``` - -The `delete` action inherits the rules from the `edit` action. By doing that, we'll be able to state that only organization administrators and any relation capable of performing the edit action (member or manager) can also perform the delete action. - -Permission union is super beneficial in scenarios where a user needs to have varied access across different departments or roles. -::: - -### Completed Schema - -Here is full implementation of simple Github access control example with using Permify Schema. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity team { - - relation parent @organization - relation member @user - - action edit = member or parent.admin - -} - -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - action push = owner or maintainer - action read = (owner or maintainer or parent.member) and parent.admin - action delete = parent.admin or owner - -} -``` - - -### Defining Attribute Based Permissions - -:::success Beta -Please keep in mind that this feature is still in the **beta stage**, and we're actively seeking user feedback to improve it. As a Beta feature, Permify ABAC support may have some limitations, and its functionality and interface could change in future updates. -::: - - -To support Attribute Based Access Control (ABAC) in Permify, we've added two main components into our DSL: **attributes** and **rules**. - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```perm -attribute ip_range string[] -``` - -There are different types of attributes you can use; - -#### 1. Boolean - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```perm -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: - -#### 2. **String** - -String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: - -:::info Defining Rules - -In above we defined a function called with **rule** keyword. - -Rules are structures that allow you to write specific conditions for the model. They accept parameters and are based on conditions. - -Another example, a rule could be used to check if a given IP address falls within a specified IP range: - -```perm -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` -::: - -#### 3. Integer - -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```perm -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: - -#### 4. **Double** - -Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```perm -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: - -See more details on [Attribute Based Access Control](../../use-cases/abac) section to learn our approach on ABAC as well as how it operates in Permify. - -## Permission Capabilities - -1. **Permission Union:** - -Permify allows you to set permissions that are effectively the union of multiple permission sets. For example, if a user belongs to multiple roles, each with their own permissions, the userโ€™s effective permissions will be the union of all permissions of the roles they belong to. This is beneficial in scenarios where a user needs to have varied access across different departments or roles. - -2. **Permission Indirection: (ReBAC)** - -Permify supports indirect permission granting through relationships. For instance, you can define that a user has certain permissions because of their relation to other entities. - -An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -We utilize ReBAC and Google Zanzibar to create natural linkage between business units, functions, and entities of an organization. - -## Real World Examples - -This example shows almost all aspects of the Permify Schema. - -You can check out more schema examples from the [Real World Examples](../examples) section with their detailed examination. diff --git a/docs/versioned_docs/version-0.4.x/getting-started/sync-data.md b/docs/versioned_docs/version-0.4.x/getting-started/sync-data.md deleted file mode 100644 index 98a1bca6..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/sync-data.md +++ /dev/null @@ -1,461 +0,0 @@ ---- -sidebar_position: 2 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Managing Authorization Data - -Permify unifies your authorization data in a database you prefer. We named that database as Write Database, shortly **WriteDB**. - -Permify API provides various functionalities - checking access, reasoning permissions, etc - to maintain separate access control mechanisms for individual applications. And **WriteDB** stands as a source of truth for these authorization functionalities. - -## Access Control as Relations - Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -In Permify, the simplest form of relational tuple structured as: `entity # relation @ user`. Here are some relational tuples with semantics, - -![relational-tuples](https://user-images.githubusercontent.com/34595361/183959294-149fcbb9-7f10-4c1e-8d66-20a839893909.png) - -## Where Relational Tuples Used ? - -In Permify, these relational tuples represents your authorization data. - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure the database when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -Stored relational tuples are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user's action is authorized. - -As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, or even a role such as an admin or manager. -[WriteDB]: #write-database - -## Creating Relational Tuples - -Relational tuples can be created with an simple API call in runtime, since relations and authorization data's are live instances. Each relational tuple should be created according to its authorization model, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -Let's follow a simple document management system example with the following Permify Schema to see how to create relation tuples. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - -} - -entity document { - - relation owner @user - relation parent @organization - relation maintainer @user @organization#member - - action view = owner or parent.member or maintainer or parent.admin - action edit = owner or maintainer or parent.admin - action delete = owner or parent.admin -} -``` - -According to the schema above; when a user creates a document in an organization, more specifically let's say, when user:1 create a document:2 we need to create the following relational tuple, - -- `document:2#owner@user:1` - -[WriteDB]: #write-database - -### Write Relationships API - -You can create relational tuples by using `Write Relationships API`. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "2", - }, - Relation: "owner", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "2" - }, - relation: "owner", - subject: { - type: "user", - id: "1" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "2s" - }, - "relation": "owner", - "subject":{ - "type": "user", - "id": "1", - "relation": "" - } - } - ] -}' -``` - - - -### Snap Tokens - -In Write Relationships API response you'll get a snap token of the operation. - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../reference/snap-tokens.md) - -## More Relationships - -Let's create more relationships according to the schema we defined above. - -### Organization Admin - -**relational tuple:** organization:1#admin@user:3 - -**Semantics:** User 3 is administrator in organization 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "user", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -### Parent Organization - -**Relational Tuple:** document:1#parent@organization:1#โ€ฆ - -**Semantics:** Organization 1 is parent of document 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "parent", - Subject: & v1.Subject { - Type: "organization", - Id: "1", - Relation: "..." - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "parent", - subject: { - type: "organization", - id: "1", - relation: "..." - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "parent", - "subject":{ - "type": "organization", - "id": "1", - "relation": "..." - } - } - ] -}' -``` - - - -:::info -Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. - -Simply, the usage of ... is straightforward: if you're use user entity as an subject, you should not be using the `...` If you're using another subject rather than user entity then you need to use the `...` -::: - -### Organization Members Are Maintainers in specific Doc - -**Created relational tuple:** document:1#maintainer@organization:2#member - -**Definition:** Members of organization 2 are maintainers in document 1. - - - - -```go -rr, err: = client.Relationship.Write(context.Background(), & v1.RelationshipWriteRequest { - TenantId: "t1", - Metadata: &v1.RelationshipWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "maintainer", - Subject: & v1.Subject { - Type: "organization", - Id: "2", - Relation: "member" - }, - } - }, -}) -``` - - - - - -```javascript -client.relationship.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "maintainer", - subject: { - type: "organization", - id: "2", - relation: "member" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/relationships/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "maintainer", - "subject":{ - "type": "organization", - "id": "2", - "relation": "member" - } - } - ] -}' -``` - - - -#### Test this Example on [Playground](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_) - -## Audit Logs For Permission Changes - -Permify does support audit logs for permission changes. Leveraging the [MVCC (Multi-Version Concurrency Control)](http://mbukowicz.github.io/databases/2020/05/01/snapshot-isolation-in-postgresql.html) pattern, we maintain a history of all permission data changes. This essentially provides an audit trail, allowing users to track alterations and when they occurred. - -In cloud version, our system supports change history auditing. It automatically generates and securely stores logs for all significant actions. These logs detail who made the change, what was changed, and when the change occurred. Furthermore, your system allows for easy searching and analysis of these logs, supporting automated alerting for suspicious activities. This comprehensive approach ensures thorough and effective auditing of all changes - -## Permission Baselining (Reviewing) - -We have a strong foundation for permission baselining and review, thanks to MVCC. - -**Historical Review:** You can review the history of permissions changes as each version is stored. This enables retrospective audits and analysis. - -**Current State Review:** You can review the current state of permissions by examining the latest versions of each permission setting. - -**Cleanup:** Your system incorporates a garbage collector for managing old relationships, which helps keep your permissions structure clean and optimized. - -## Next - -Let's now head over to the **Access Control Check** section and learn how to perform access control in Permify to ensure that only authorized users have the right level of access to our resources. diff --git a/docs/versioned_docs/version-0.4.x/getting-started/testing.md b/docs/versioned_docs/version-0.4.x/getting-started/testing.md deleted file mode 100644 index 950b1b44..00000000 --- a/docs/versioned_docs/version-0.4.x/getting-started/testing.md +++ /dev/null @@ -1,243 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Testing & Validation - -Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. - -Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. - -We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Adding Validate Action To Your Workflow - -After adding [permify-validate-action] to your Github Action workflow, you need to define the schema validation yaml file as, - -- **With local file:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "test.yaml" -``` - -- **With external url:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" -``` - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Schema Validation File - -Below you can examine an example schema validation yaml file. It consists 3 parts; -- `schema` which is the authorization model you want to test, -- `relationships` sample data to test your model, -- `scenarios` to test access check queries within created scenarios. - -Here is an example Schema Validation file, - -```yaml -schema: >- - entity user {} - - entity organization { - - relation admin @user - relation member @user - - action create_repository = (admin or member) - action delete = admin - } - - entity repository { - - relation owner @user @organization#member - relation parent @organization - - action push = owner - action read = (owner and (parent.admin and parent.member)) - action delete = (parent.member and (parent.admin or owner)) - action edit = parent.member not owner - } - -relationships: - - "organization:1#admin@user:1" - - "organization:1#member@user:1" - - "repository:1#owner@user:1" - - "repository:2#owner@user:2" - - "repository:2#owner@user:3" - - "repository:1#parent@organization:1#..." - - "organization:1#member@user:43" - - "repository:1#owner@user:43" - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - assertions: - push : true - owner : true - - entity: "repository:2" - subject: "user:1" - assertions: - push : false - - entity: "repository:3" - subject: "user:1" - context: - - "repository:3#owner@user:1" - assertions: - push : true - - entity: "repository:1" - subject: "user:43" - assertions: - edit : false - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - - "repository:3#owner@user:1" - - "repository:4#owner@user:1" - - "repository:5#owner@user:1" - assertions: - push : ["1", "3", "4", "5"] - edit : [] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - - "organization:1#member@user:58" - assertions: - push : ["1", "43"] - edit : ["58"] -``` - -Assuming that you're well-familiar with the `schema` and `relationships` sections of the above YAML file. If not, please see the previous sections to learn how to create an authorization model (schema) and generate data (relationships) according to it. - -We'll continue by examining how to create scenarios. - -## Creating Test Scenarios - -You can create multiple access checks at once to test whether your authorization logic behaves as expected or not. - -Besides simple access checks you can also test subject filtering queries and data (entity) filtering with it. - -Let's deconstruct the `scenarios`, - -### Scenarios - -```js -scenarios: - - name: // name of the scenario - description: // description of the scenario - checks: // simple access check case/cases - entity_filters: // entity (data) filtering query/queries - subject_filters: // subject filtering query/queries -``` - -### Access Check - -You can create `check` inside `scenarios` to test multiple access check cases, - -```js -checks: - - entity: "repository:3" // resource/entity that you want to check access for - subject: "user:1" // subject that performs the access check - context: // additional data provided during an access check to be evaluated - - "repository:3#owner@user:1" - assertions: // expected result/results for specific action/s or an permission/s. - push : true -``` - -Semantics for above check is: whether `user:1` can push to `repository:3`, additional to stored tuples take account that user:1 is owner of repository:3 (`repository:3#owner@user:1`). Expected result for that check it **true** - `push : true` - -:::info Contextual Tuples -We use `context` (Contextual Tuples) with simple relational tuples for simplicity in this example. However, it is primarily used for dynamic access checks, such as those involving time, date, or IP address, etc. - -To learn more about how `context` works, see the [Contextual Tuples](../reference/contextual-tuples.md) section. -::: - -### Entity Filtering - -You can create `entity_filters` within `scenarios` to test your data filtering queries. - -```js -entity_filters: - - entity_type: "repository" // entity that you want to filter - subject: "user:1" // subject that you want to perform data filtering - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "3", "4", "5"] // IDs of the resources that we expected to return - edit : [] -``` - -The major difference between `check` lies in the assertions part. Since we're performing data filtering with bulk data, instead of a true-false result, we enter the IDs of the resources that we expect to be returned - -### Subject Filtering - -You can create `subject_filters` within `scenarios` to test your subject filtering queries, a.k.a which users can perform action Y or have permission X on entity:Z? - -```js -- subject_reference: "user" - entity: "repository:1" - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "43"] // IDs of the users that we expected to return - edit : ["58"] -``` - -:::info API Endpoints -You can find the related API endpoints for `check`, `entity_filters`, and `subject_filters` in the Permission service in the [Using The API](../api-overview.md) section. -::: - -## Coverage Analysis - -By using the command `permify coverage {path of your schema validation file}`, you can measure the coverage for your schema. - -The coverage is calculated by analyzing the relationships and assertions in your created model, identifying any missing elements. - -The output of the example provided above is as follows. - -![schema-coverage](https://user-images.githubusercontent.com/39353278/236303688-15cc2673-05e6-42d3-9ad4-0c538f546fb0.png) - -## Testing in Local - -You can also test your new authorization model in your local (Permify clone) without using [permify-validate-action] at all. - -For that open up a new file and add a schema yaml file inside. Then build your project with, run `make serve` command and run `./permify validate {path of your schema validation file}`. - -If we use the above example schema validation file, after running `./permify validate {path of your schema validation file}` it gives a result on the terminal as: - -![schema-validation](https://user-images.githubusercontent.com/39353278/236303542-930de83f-ebdd-4b0a-a09e-5c069744cc5c.png) - -[permify-validate-action]: https://github.com/Permify/permify-validate-action - -## Unit Tests For Schema Changes - -We recommend leveraging Permify's in-memory databases for a simplified and isolated testing environment. These in-memory databases can be easily created and disposed of for each individual unit test, ensuring that your tests do not interfere with each other and each one starts with a clean slate. - -For managing permission/relation changes, we suggest storing schema in an abstracted place such as a git repo and centrally checking and approving every change before deploying it via the CI pipeline that utilizes theย **Write Schema API**. - -We recommend adding ourย [schema validator](https://github.com/Permify/permify-validate-action)ย to the pipeline to ensure that any changes are automatically validated. - -You can find more details about our suggested workflow to handle schema changes in [Write Schema](hhttps://docs.permify.co/docs/api-overview/schema/write-schema#suggested-workflow-for-schema-changes) section. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - - diff --git a/docs/versioned_docs/version-0.4.x/installation.md b/docs/versioned_docs/version-0.4.x/installation.md deleted file mode 100644 index c855058f..00000000 --- a/docs/versioned_docs/version-0.4.x/installation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: installation -title: Setup Permify -slug: /installation ---- - -# Setup Permify - -Here is some options that you can use to set up and deploy Permify in your servers. - -```mdx-code-block -import {CardList} from '../../src/components/Card'; - - -``` - -If options your deployment preference is not listed below please let us know Also if you have any questions join our [Discord community](https://discord.gg/n6KfzYxhPp) or send us an email at support@permify.co. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/installation/_category_.json b/docs/versioned_docs/version-0.4.x/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.4.x/installation/azure.md b/docs/versioned_docs/version-0.4.x/installation/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/installation/brew.md b/docs/versioned_docs/version-0.4.x/installation/brew.md deleted file mode 100644 index c56a06d8..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/brew.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "Install with Brew" ---- - -# Brew With Configurations - -This section shows how to intall and run Permify Service with using brew. - -### Install Permify - -Open terminal and run following line, - -```shell -brew install permify/tap/permify -``` - -### Run Permify Service - -To run the Permify Service, `permify serve` command should be run with configurations. - -By default, the service is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather then an actual database. You can override these with running the command with configuration flags. - -### Configure With Using Flags - -See all configuration flags with running, - -```shell -permify serve --help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flags' argument with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Configure With Using Config File - -You can also configure Permify Service with using a configuration file. - -```shell - permify serve -c=config.yaml -``` - -or - -```shell - permify serve --config=config.yaml -``` - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/installation/container.md b/docs/versioned_docs/version-0.4.x/installation/container.md deleted file mode 100644 index c91a1ac4..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/container.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: "Docker Container" ---- - -# Deploy using Docker - -This section shows how to run Permify Service from a docker container. You can run Permify service from a container with following steps. - -## Run following line on Terminal - -```shell -docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve -``` - -This will start an API server with the configuration options that pointed out on the **{YOUR-CONFIG-PATH}**. - -### Configure With a YAML file - -This config path - `{YOUR-CONFIG-PATH}` - addresses the [config yaml file](../reference/configuration.md), where you can configure running options of the Permify Server as well as define the ***database*** to store your authorization related data. - -As an example if you had a "config.yaml" file at your Desktop, the path `{YOUR-CONFIG-PATH}:/config` would look like: -`Users/your_user_name/Desktop:/config`. - -:::info Talk to an Permify Engineer -By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. -::: - -### Configure With Using Flags - -Alternatively, you can set configuration options with the respected flags when running the command. See all configuration flags with running, - -```shell -docker run -p 8080:8080 ghcr.io/permify/permify serve -help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flags' argument with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Test your connection. - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/installation/google.md b/docs/versioned_docs/version-0.4.x/installation/google.md deleted file mode 100644 index deb30dc9..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/google.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -title: Deploy on Google Compute Engine ---- - -This guide outlines the process of deploying Permify, on Google Compute Engine. The steps include setting up Google Cloud SDK and kubectl, managing containers using Google Kubernetes Engine (GKE), deploying Permify, and implementing Permify in a distributed configuration with Serf. By following these steps, you can efficiently deploy Permify on Google's scalable and secure infrastructure. - -## Google Cloud SDK Install - -1. At the command line, run the following command: - - ```bash - curl https://sdk.cloud.google.com | bash - ``` - -2. When prompted, choose a location on your file system (usually your Home directory) to create theย `google-cloud-sdk`ย subdirectory under. -3. If you want to send anonymous usage statistics to help improve gcloud CLI, answerย `Y`ย when prompted. -4. To add gcloud CLI command-line tools to yourย `PATH`ย and enable command completion, answerย `Y`ย when prompted -5. Restart your shell: - - ```bash - exec -l $SHELL - ``` - -6. To initialize the Google Cloud CLI environment, run `gcloud init` - -## Install kubectl - -1. Install theย `kubectl`ย component: - - ```bash - gcloud components install kubectl - ``` - -2. Verify thatย `kubectl`ย is installed: - - ```bash - kubectl version - ``` - -3. Install Authn Plug-in - - ```bash - gcloud components install gke-gcloud-auth-plugin - ``` - - Check theย `gke-gcloud-auth-plugin`ย binary version: - - ```bash - gke-gcloud-auth-plugin --version - ``` - - -## Create Containers with GKE - -1. Login & Initialize Google Cloud CLI - - ```bash - gcloud init - ``` - -2. Follow configuration instructions -3. Create Container Cluster - - ```bash - gcloud container clusters create [CLUSTER_NAME] - ``` - -4. Authenticate the cluster - - ```bash - gcloud container clusters get-credentials [CLUSTER_NAME] - ``` - - -## Deploy Permify - -1. Apply deployment config - - ```bash - kubectl apply -f deployment.yaml - ``` - - - **Deployment.yaml** - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - app: permify - name: permify - spec: - replicas: 3 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always - status: {} - ``` - -2. Apply service manfiest - - ```bash - kubectl apply -f service.yaml - ``` - - - **Service Manifest** - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer - status: - loadBalancer: {} - ``` - - -## Deploying Permify in a Distributed Configuration - -If you aim to deploy Permify in a distributed configuration, you will need to create a Serf deployment. The Serf deployment can be dockerized to our Container Registry under the name permify/serf:v1.0, which is provided by Hashicorp. - -Please note: It is crucial to ensure that both Serf and Permify deployments reside within the same namespace for proper operation. - -1. Serf Service Create: - - Serf Deployment&Service yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: serf-deployment - spec: - replicas: 1 - selector: - matchLabels: - app: serf - template: - metadata: - labels: - app: serf - spec: - containers: - - name: serf - image: permify/serf:v1.0 - args: - - "-node=main-serf" - ports: - - containerPort: 7946 - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 200m - memory: 256Mi - --- - apiVersion: v1 - kind: Service - metadata: - name: serf - spec: - selector: - app: serf - ports: - - protocol: TCP - port: 7946 - targetPort: 7946 - name: serf - type: ClusterIP - ``` - -2. Apply Deployment Manifest - - Deployment.yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: permify-deployment - spec: - replicas: 3 - selector: - matchLabels: - app: permify - template: - metadata: - labels: - app: permify - spec: - containers: - - image: permify/permify:tagname - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - - "--distributed-enabled=true" - - "--distributed-node=serf:7946" - - "--distributed-node-name=main-serf" - - "--distributed-protocol=serf" - resources: - requests: - memory: "128Mi" - cpu: "200m" - limits: - memory: "128Mi" - cpu: "400m" - ports: - - containerPort: 3476 - name: permify-port - - containerPort: 7946 - name: permify-dist - - containerPort: 6060 - name: permify-pprof - ``` - -3. Apply Service Manifest - - Service.yaml - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: permify-port - port: 3476 - targetPort: 3476 - - name: permify-dist - port: 7946 - targetPort: 7946 - selector: - app: permify - type: LoadBalancer - ``` - - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/installation/kubernetes.md b/docs/versioned_docs/version-0.4.x/installation/kubernetes.md deleted file mode 100644 index f2a1e21b..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/kubernetes.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Kubernetes Cluster ---- - -# ย Deploy on Kubernetes Cluster - -In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. - -Hereโ€™s what weโ€™re going to do step-by-step; - -1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) -3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) - -There are a couple of small prerequisites for this tutorial. - -### Pre-requisites - -- An AWS account. -- The AWS Command Line Interface (CLI) is installed and configured on your local machine. โ€” [Click here](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/home) to go to IAM -- The AWS IAM Authenticator for Kubernetes is installed and configured on your local machine. - -## Configure AWS CLI with your IAM account. - -The first step is to configure our AWS IAM account into our local terminal so that we can run commands. Most of you probably have a configured AWS account if you ever set up anything into AWS programmatically, so you can skip this. If you donโ€™t follow these steps. - -### Create an AWS IAM Programmatic Access Account - -First, letโ€™s create IAM credentials for ourselves. Search IAM from the AWS console. You need to write down the account ID if you want to log in AWS console with this account as well. Letโ€™s go over users and start creating our credentials. - -![kubernetes-1](https://user-images.githubusercontent.com/34595361/211697636-6e106115-bd68-4909-aea0-5a7b6f8d5e18.png) - -At Users screen click to โ€œAdd usersโ€ โ€” and youโ€™ll end up in your first screen creating user credentials. Here you can define the name of the user. Also there 2 options that you can choose simultaneously. - -But you must choose โ€œAccess key - Programmatic accessโ€ option. Itโ€™ll allow us to configure our AWS CLI on our local machine. - -You can also choose โ€œPassword - AWS Management Console accessโ€ if you want to log in to this account through the console. But youโ€™ll need the Account ID that I mentioned in the IAM console screen. - -In the next screen, youโ€™ll be asked to create or copy the user-set permissions. For this tutorial, youโ€™ll only need to access EKS resources and features. So lets create group by clicking the โ€œCreate groupโ€ โ€” and then at pop-up screen search for EKS. - -![kubernetes-2](https://user-images.githubusercontent.com/34595361/211697647-f39d73e7-b6e2-40ae-8c3b-ad68032d6b21.png) - -Iโ€™ll choose all EKS permissions but if you have certain policies internally, just stick with them. Youโ€™ll only need following permission to; - -- `AmazonEKSClusterPolicy` -- `AmazonEKSServicePolicy` -- `AmazonEKSVPCResourceController` -- `AmazonEKSWorkerNodePolicy` - -Then simply you can review and create the user. - -![kubernetes-4](https://user-images.githubusercontent.com/34595361/211697655-1b75d4f9-a2ee-4b7e-9e1e-0be0b5aaad7d.png) - -Once you created the credentials youโ€™ll prompt the โ€œAccess key IDโ€ and โ€œSecret access keyโ€, you should save this down somewhere. Weโ€™re going the use these to configure our local machine with AWS CLI. - -### **Configure AWS CLI with your IAM account** - -Letโ€™s open our local terminal - -```jsx -aws configure -``` - -Next youโ€™ll ask for the following credentials; - -- `AWS Access Key ID` -- `AWS Secret Access Key` -- `Default region name` -- `Default output format` (leave it empty) - -## Creating an AWS EKS Cluster - -For the first step, we need to install [eksctl](https://eksctl.io/) โ€” which is like kubectl but for AWS EKS. It helps us to set up and deploy our cluster and nodes within a fraction of the time. - -Letโ€™s download eksctl using brew. - - -```jsx -brew tap weaveworks/tap -``` - -While installing the eksctl, weโ€™ll end up getting kubectl and other dependencies. - -```jsx -brew install weaveworks/tap/eksctl -``` - -Now, weโ€™re ready to create our EKS cluster. You can define certain things while deploying standard the cluster beside the name and version like; the region you want to deploy, the EC2 instance type of each node, and the number of nodes you want to run. - -```bash -eksctl create cluster \ ---name \ ---version 1.24 \ ---region ย \ ---nodegroup-name permify \ ---node-type t2.small \ ---nodes 2 -``` - -## Deploying & Running Permify in Nodes - -The next stop is applying our manifests which will help us to deploy and configure our container/Permify. - -Letโ€™s create our deployment manifest first. - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: permify - name: permify -spec: - replicas: 2 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://postgres:nOcodeSTIAnLAba@permify-test.ceuo5kqsxyea.us-east-1.rds.amazonaws.com:5432/demo" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always -status: {} -``` - -Now letโ€™s apply our deployment manifest - -```jsx -kubectl apply -f deployment.yaml -``` - -The next step is to create a service manifest, this will allow us to configure our container app. - -```jsx -apiVersion: v1 -kind: Service -metadata: - name: permify -spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer -status: - loadBalancer: {} -``` - -Letโ€™s apply service.yaml to our nodes. - -```jsx -kubectl apply -f service.yaml -``` - -Last but not least, we can check our pods & nodes. And we can start using the container with load balancer \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/installation/overview.md b/docs/versioned_docs/version-0.4.x/installation/overview.md deleted file mode 100644 index 4dc5560d..00000000 --- a/docs/versioned_docs/version-0.4.x/installation/overview.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Guide - -This guide shows you how to set up Permify in your servers and use it across your applications. - -:::info Minimum Requirements -PostgreSQL: Version 13.8 or higher -::: - -Please ensure your system meets these requirements before proceeding with the following steps: - -1. [Set Up & Run Permify Service](#set-up-permify-service) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Manage and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) -4. [Perform Access Check](#perform-access-check) - -:::info Talk to an Permify Engineer -Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -## Set Up Permify Service - -You can run Permify Service with various options but in that tutorial we'll run it via docker container. - -### Run From Docker Container - -Production usage of Permify needs some configurations such as defining running options, selecting datastore to store authorization data and more. - -However, for the sake of this tutorial we'll not do any configurations and quickly start Permify on your local with running the docker command below: - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -:::info -You can examine [Deploy using Docker] section to get more about the configuration options and learn the full integration to run Permify Service from docker container. - -[Deploy using Docker]: ../container -::: - -### Test your connection - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core endpoints. - -[Using the API]: ../../api-overview - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -## Model your Authorization with Permify Schema - -After installation completed and Permify server is running, next step is modeling authorization with Permify authorization language - [Permify Schema]- and configure it to Permify API. - -You can define your entities, relations between them and access control decisions of each actions with using [Permify Schema]. - -### Creating your authorization model - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: - -Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action view_files = admin or member - action edit_files = admin - -} -``` - -We have 2 entities these are **"user"** and **"organization"**. Entities represents your main tables. We strongly advise naming entities the same as your original database entities. - -Lets roll back our example, - -- The `user` entity represents users. This entity is empty because it's only responsible for referencing users. - -- The `organization` entity has its own relations (`admin` and `member`) which related with user entity. This entity also has 2 actions, respectively: - - Organization member and admin can view files. - - Only admins can edit files. - -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. - -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../use-cases/simple-rbac -::: - -### Configuring Schema via API - -After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../api-overview/schema/write-schema.md) for configuration of your authorization model on Permify authorization service. - -:::caution Before Continue on Writing Schema -You'll see **tenant_id** parameter almost all Permify APIs including Write Schema. With version 0.3.x Permify became a tenancy based authorization infrastructure, and supports multi-tenancy by default so its a mandatory parameter when doing any operations. - -We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. -::: - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - -**POST /v1/tenants/{tenant_id}/schemas/write** - -![permify-schema](https://user-images.githubusercontent.com/34595361/214457054-19b141ac-6bfa-4db4-aeab-f7b7149c3351.png) - -## Store Authorization Data as Relational Tuples - -After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. - -### Create Relational Tuples - -You can create relational tuples as authorization rules at this writeDB by using [Write Relationships API](../api-overview/relationship/write-relationships.md) - -For our guide let's grant one of the team members (Ashley) an admin role. - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - -**POST /v1/tenants/{tenant_id}relationships/write** - -```json -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" //Organization identifier - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1", //Ashley's identifier - "relation": "" - } - } - ] -} -``` - -![write-relationships](https://user-images.githubusercontent.com/34595361/214458203-8264e141-642d-48b0-9242-416bbf6f8795.png) - -**Created relational tuple:** organization:1#admin@user:1 - -**Semantics:** User 1 (Ashley) has admin role on organization 1. - -:::tip -In ideal production usage Permify stores your authorization data in a database you prefer. We called that database as WriteDB, and you can configure it with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) or CLI flag options. - -But in this tutorial Permify Service running default configurations on local, so authorization data will be stored in memory. You can find more detailed explanation how Permify stores authorization data in [Managing Authorization Data] section. - -[Managing Authorization Data]: ../../getting-started/sync-data -::: - -## Perform Access Check - -Finally we're ready to control authorization. Access decision results computed according to relational tuples and the stored model, [Permify Schema] action conditions. - -Lets get back to our example and perform an example access check via [Check API]. We want to check whether an specific user has an access to view files in a organization. - -[Check API]: ../../api-overview/permission/check-api -[Permify Schema]: ../../getting-started/modeling - -#### Example HTTP Request: - -***Can the user 45 view files on organization 1 ?*** - -**POST /v1/tenants/{tenant_id}/permissions/check** - -| Required | Argument | Type | Default | Description | -|----------|----------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. | -| [x] | entity | object | - | name and id of the entity. Example: organization:1. | -| [x] | action | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action | -| [ ] | schema_version | string | - | get results according to given schema version | -| [ ] | depth | integer | 8 | - | - -### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_files", - "subject": { - "type": "user", - "id": "45", - "relation": "" - }, -} -``` - -### Response - -```json -{ - "can": "RESULT_ALLOW", - "metadata": { - "check_count": 0 - } -} -``` - -See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify - -[Access Control Check]: ../api-overview/permission/check-api.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you struggle with installation or have any questions, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.4.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/versioned_docs/version-0.4.x/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.4.x/permify-overview/authorization-service.md b/docs/versioned_docs/version-0.4.x/permify-overview/authorization-service.md deleted file mode 100644 index e8df72d3..00000000 --- a/docs/versioned_docs/version-0.4.x/permify-overview/authorization-service.md +++ /dev/null @@ -1,42 +0,0 @@ - -# What is Authorization Service? - -Authorization is an important part of software development. There are many different ways to implement authorization, but it's important for all apps to have some form of it in order to protect the user from malicious actors and unauthorized access attempts. - -An authorization service is a module that allows you to manage access to your application and ease the development and maintenance of your authorization system. It works in run time and respond to all authorization questions from any of your apps. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify] is a fully open source authorization service that offers a variety of binding and crafting options to secure your applications. - -[Permify]: https://github.com/Permify/permify - -## Why should I use Authorization Service instead of doing from scratch? - -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. You can get started quickly with a simple API that you can easily integrate into your application to move and iterate faster. - -### Do Not Reinvent The Wheel -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. Building a scalable and robust authorization system is hard and needs a quite engineering time. Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services. - -[Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell/ - -### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. - -### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. - -### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. - -## Cases that can benefit from An Authorization Service: - -- If you already have an identity/auth solution and want to plug in fine-grained authorization on top of that. -- If you want to create a unified access control mechanism to use across your individual applications. -- If you want to make future-proof authorization system and don't want to spend engineering effort for it. -- If youโ€™re managing authorization for growing micro-service infrastructure. -- If your authorization logic is cluttering your code base. -- If your data model is getting too complicated to handle your authorization within the service. -- If your authorization is growing too complex to handle within code or API gateway. - diff --git a/docs/versioned_docs/version-0.4.x/permify-overview/infrastructure.md b/docs/versioned_docs/version-0.4.x/permify-overview/infrastructure.md deleted file mode 100644 index 9cc9733c..00000000 --- a/docs/versioned_docs/version-0.4.x/permify-overview/infrastructure.md +++ /dev/null @@ -1,50 +0,0 @@ - -# Where does Permify fit into your environment? - -Permify is a simply GRPC service that responsible for managing and authorization in your environment. This section shows where and how does Permify fit into your environment with examining Permify's architecture, deployment patterns and the usage with the authentication and identity providers. - -## Architecture - -Permify stores access control relations on a database you choose and performs authorization checks based on the stored relations. We called that database Write Database - **WriteDB** - and it behaves as a centralized data source for your authorization system. - -You can model your authorization with Permify's DSL - Permify Schema and your applications can talk to Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data, or make changes to data stored in WriteDb. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify with Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/versioned_docs/version-0.4.x/permify-overview/intro.md b/docs/versioned_docs/version-0.4.x/permify-overview/intro.md deleted file mode 100644 index 90312362..00000000 --- a/docs/versioned_docs/version-0.4.x/permify-overview/intro.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -sidebar_position: 1 ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is a **relationship based authorization service** for creating and maintaining fine-grained authorizations while ensuring least privilege across your organization. - -With Permify, you can easily structure your authorization model, store authorization data in your preferred database, and interact with the Permify API to handle all authorization queries from your applications or services. - -Permify inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -## A true ReBAC solution to ensure least privilege - -Permify has designed and structured as a true ReBAC solution, so besides roles and traditional permissions Permify also supports indirect permission granting through relationships. - -For instance, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -Our goal is to create a robust, flexible, and easily auditable authorization system that establishes a natural linkage between permissions across the business units, functions, and entities of an organization. - -## Key Features - -๐Ÿ›ก๏ธ **Production ready** authorization API that serve as **gRPC** and **REST** - -๐Ÿ”ฎ Domain Specific Authorization Language - Permify Schema - to **easily model** your authorization - -๐Ÿ” Database Configuration to store your permissions **in house** with **high availability** - -โœ… Perform access control checks and get answers **down to 10ms** with **parallel graph engine** - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) - -โš™๏ธ Create custom permissions for your **tenants**, and manage them in single place with **Multi Tenancy** - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin] - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Features Beyond Zanzibar - -Weโ€™re trying to make [Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf) available to everyone to use and benefit in their applications and services. So that we utilize Zanzibar features and add new features on top of it to achieve robust permission systems. Here are some additional features that we have, - -- **Multi-Tenancy Support** - It enables users to create a custom authorization model for different applications, all managed within a single Permify instance. - -- **Testing Framework - Permify Validate** - Thisย enhances the testability of authorization logic. It includes features like scenario-based validation actions, policy coverage analysis, and IDL parser Integration to achieve end-to-end validation for the desired authorization schema. - -- **Data Filtering** - In Zanzibar typical access check has the form of **"Does user U has relation R to object O?โ€** and yields true or false response. Additional to that, we have data filtering endpoints that let you ask questions in the form ofย **โ€œWhich resources can user:X do action Y?โ€** or **โ€œWhich user(s) can edit doc:Yโ€**. As a response to this, youโ€™ll get a entity results in the format of a string array or as a streaming response depending on the endpoint you're using. - -## Getting Started - -In Permify, authorization divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify [Store Authorization Data] as relations. -- Perform an [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../../getting-started/modeling -[Store Authorization Data]: ../../getting-started/sync-data -[Access Checks]: ../../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want trying out and examine it instantly, - - - -## Community & Support - -We would love to hear from you :heart: - -You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. - -For feature requests, bugs, or any improvements you can always open an issue. - -### Want to Contribute? Here are the ways to contribute to Permify - -* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. -* **Improve documentation:** Alongside our codebase, documentation one of the most significant part in our open-source journey. We're trying to give the best DX possible to explain ourselfs and Permify. And you can help on that with importing resources or adding new ones. -* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. - -You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). - -## Communication Channels - -If you like Permify, please consider giving us a :star: - -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Roadmap - -You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! - -## Need any help on Authorization ? - -Our team is happy to help you anything about authorization. Moreover, if you'd like to learn more about using Permify in your app or have any questions, [schedule a call with one of our founders](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/playground.md b/docs/versioned_docs/version-0.4.x/playground.md deleted file mode 100644 index 73f6ca4d..00000000 --- a/docs/versioned_docs/version-0.4.x/playground.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization in a browser. Our playground consists 4 sections, - -- Schema (Authorization Model) -- Authorization Data -- Visualizer -- Enforcement - -Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Schema (Authorization Model) - -You can create your authorization model in this section with using Permify authorization language, Permify Schema. - -You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured. Also, you can check our docs to learn more about how to model authorization in Permify. - -To demonstrate how the playground works, let's create a simple authorization model as follows. This model should be selected as the default when you open the playground. - -![authorization-model](https://github.com/Permify/permify/assets/34595361/9da0957c-a6ee-4dd7-81ff-693a98b3d4d1) - -We have 2 permissions, `edit` for editing repository and `delete` for deleting repository. - -Repository has parent child relation with organizations. The `parent` relation in the repository entity represents that parent child association, while ownership of the repository is represented with the `owner` relation. - -Organizations can have organizational wide roles such as admin and member, which defined as `admin` and `member` relation in organization entity. - -:::info Automatic Saving for Schema Changes -Schema changes are captured automatically, and other sections update accordingly. Some delays may occur at times; please feel free to reach out if these delays hinder your testing process. -::: - -## Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. - -So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. Here is a visualization for example model that we created above. - -![relational-tuples](https://github.com/Permify/permify/assets/39353278/a80a39b3-5139-4f13-9395-bdf1f9296c49) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as relation tuples and these tuples stored in a database that you preferred. - -The basic relation tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. - -The user is basically the user or user set in our system. Let's say we want make the **user 1** `admin` in **organization 1** then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the **Add Relationship** button. - -![create-tuple-empty](https://github.com/Permify/permify/assets/34595361/33b85fe7-25e2-400d-8055-94d305023d8c) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![create-tuple-user](https://github.com/Permify/permify/assets/34595361/016d6f9e-955a-4c39-ab55-21a9fd6dffd9) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 - `โ€repository:1#parent@organization:1#...` as follows: - -![create-tuple-parent](https://github.com/Permify/permify/assets/34595361/42daf251-818a-4bd2-8790-1c8656cd497f) - -Created tuples shown in the **Data** section as follows. - -![authorization-data](https://github.com/Permify/permify/assets/34595361/ccc25da1-5212-425d-b604-6a31a8f9555f) - -## Enforcement (Access Checks) - -Finally as we have a sample data let's perform an access check! - -In Playground you should create an scenario in order to perform access checks. This scenario based testing process gives ability to perform complex access scenarios in a single place. - -Hit the **New Scenario** button in the right side and a pop up will open. You can enter name and description of the scenario in here. - -![new-scenario-popup](https://github.com/Permify/permify/assets/34595361/c9c50da5-e3d8-4bc2-9599-985092006358) - - - -Let's check **whether user:1 can edit the repository:1** as follows: - -![scenario-check](https://github.com/Permify/permify/assets/34595361/0168f013-45a0-49fe-8164-3f5f5311f15c) - -In the above YAML structure, - -#### entity -Represents the resource for which we want to check access - `repository:1` - -#### subject -Represents the subject that performs the action or grants access - `user:1`. - -#### context -Refers to additional data provided during an access check to be evaluated for the access decision. It's primarily used for dynamic access checks, such as those involving time, date, or IP address, etc. - -In our case, we leave it empty as null.For our case we leave it empty as null. You can check the details from the [Contextual Tuples](./reference/contextual-tuples.md) section. - -#### assertions - -Assertions stands for defining the expected result for specific action or an permission. In our case we're evaluating access for edit action. - -Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission as: - -`โ€permission edit = parent.admin or owner` - -which `โ€parent.admin`โ€ indicates admin in the organization that repository belongs to. So user:1 should be able to edit resource:1, therefore expected outcome for that access request is true - `edit: true` - -:::info Create More Advanced Scenarios -For simplicity, we've created a basic scenario. However, you can create more advanced scenarios using our validation YAML structure. - -To learn how to use this syntax for complex scenarios, refer to the [Creating Test Scenarios](../getting-started/testing#creating-test-scenarios) section in [Testing & Validation](../getting-started/testing) page. -::: - -Let's click the Run button to execute our scenario. The scenario name should turn green once the scenario result is confirmed as correct. - -![scenario-check-true](https://github.com/Permify/permify/assets/34595361/208e1761-f202-449d-a9e0-498ab0d4ce6d) - -Let's change the expected outcome as false (`edit: false`) and hit the **Run** button again we'll see an error message. - -![scenario-check-false](https://github.com/Permify/permify-validate-action/assets/34595361/28a206ca-f7cb-42a8-a8c4-a18376ebf8f3) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/reference/_category_.json b/docs/versioned_docs/version-0.4.x/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.4.x/reference/cache.md b/docs/versioned_docs/version-0.4.x/reference/cache.md deleted file mode 100644 index f281a1a1..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/cache.md +++ /dev/null @@ -1,87 +0,0 @@ -# Cache Mechanisms - -This section showcases the cache mechanisms that Permify uses. - -## Schema Cache - -Schemas are stored in an in-memory cache based on their versions. If a version is specified in the request metadata, it will be searched for in the in-memory cache. If not found, it will query the database for the version and store it in the cache. If no version information is given in the metadata, versions will be assumed to be alphanumeric and sorted in that order, and Permify will request the head version and check if it exists in the memory cache. - -The size of this can be determined through the Permify configuration. Here is an example configuration: -service: - -```yaml -โ€ฆ - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB -โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -## Relationships Cache - -Permify applies the MVCC (Multi Version Concurrency Control) pattern for Postgres, creating a separate database snapshot for each write and delete operation. This both enhances performance and provides a consistent cache. - -An example of a cache key is: -check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request} - -Permify hashes each request and searches for the same key. If it cannot find it, it runs the check engine and writes to the cache, thus creating a consistently working hash. - -The size of this can also be determined via the Permify configuration. Hereโ€™s an example: -service: - -```yaml - โ€ฆ - permission: - bulk_limit: 100 - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -Note: Another advantage of the MVCC pattern is the ability to historically store data. However, it has a downside of accumulation of too many relationships. For this, we have developed a garbage collector that will delete old relationships at a time period you specify. - -## Distributed Cache - -Permify does provide a distributed cache across availability zones (within an AWS region) via **Consistent Hashing**. Permify uses Consistent Hashing across its distributed instances for more efficient use of their individual caches. - -This would allow for high availability and resilience in the face of individual nodes or even entire availability zone failure, as well as improved performance due to data locality benefits. - -Consistent Hashing is a distributed hashing scheme that operates independently of the number of objects in a distributed hash table. This method hashes according to the nodesโ€™ peers, estimating which node a key would be on and thereby ensuring the most suitable request goes to the most suitable node, effectively creating a natural load balancer. - -### How Consistent Hashing Operates in Permify - -With a single instance, when an API request is made, request and corresponding response stored in its corresponding local cache. - -If we have more than one Permify instance consistent hashing activates on API calls, hashes the request, and outputs a unique key representing the node/instance that will store the request's data. Suppose it stored in the instance 2, subsequent API calls with the same hash will retrieve the response from the instance 2, regardless of which instance that API called from. - -Using this consistent hashing approach, we can effectively utilize individual cache capacities. Adding more instances automatically increases the total cache capacity in Permify. - -You can learn more about consistent hashing from the following blog post: [Introducing Consistent Hashing](https://itnext.io/introducing-consistent-hashing-9a289769052e) - -:::info -Note, however, that while the consistent hashing approach will distribute keys evenly across the cache nodes, it's up to the application logic to ensure the cache is used effectively (i.e., that it reads from and writes to the cache appropriately). -::: - -Here is an example configuration: - -```yaml -distributed: - enabled: false - nodes: ["permify-1:3480", "permify-2:3480", "permify-3:3480"] -``` - -Additional to that weโ€™re using a [circuit breaker](https://blog.bitsrc.io/circuit-breaker-pattern-in-microservices-26bf6e5b21ff) pattern to detect and handle failures when the underlying database is unavailable. It prevents unnecessary calls when the database is down and handles the process on the rebooting phase. - -## Need any help ? - -Our team is happy help you to structure right architecture for your permission system. Feel free to [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - diff --git a/docs/versioned_docs/version-0.4.x/reference/configuration.md b/docs/versioned_docs/version-0.4.x/reference/configuration.md deleted file mode 100644 index bda87c61..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/configuration.md +++ /dev/null @@ -1,465 +0,0 @@ -# Configuration File - -Permify offers various options for configuring your Permify API Server. - -Here is the example configuration YAML file with glossary below. You can also find -this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. - -***Example config.yaml file*** - -```yaml -server: - rate_limit: 100 - http: - enabled: true - port: 3476 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - grpc: - port: 3478 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - -logger: - level: 'info' - -profiler: - enabled: true - port: 6060 - -authn: - method: preshared - enabled: false - keys: [ ] - -tracer: - exporter: 'zipkin' - endpoint: 'http://localhost:9411/api/v2/spans' - enabled: true - -meter: - exporter: 'otlp' - endpoint: 'localhost:4318' - enabled: true - -service: - circuit_breaker: false - watch: - enabled: false - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB - permission: - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - relationship: - -database: - engine: 'postgres' - uri: 'postgres://user:password@host:5432/db_name' - auto_migrate: false - max_open_connections: 20 - max_idle_connections: 1 - max_connection_lifetime: 300s - max_connection_idle_time: 60s - garbage_collection: - enable: true - interval: 3m - timeout: 3m - window: 720h - number_of_threads: 1 -``` - -## Options - -
server | Server Configurations -

- -#### Definition - -Server options to run Permify. (`grpc` and `http` available for now.) - -#### Structure - -``` -โ”œโ”€โ”€ server - โ”œโ”€โ”€ rate_limit - โ”œโ”€โ”€ (`grpc` or `http`) - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ port - โ”‚ โ””โ”€โ”€ tls - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ cert - โ”‚ โ””โ”€โ”€ key -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------|---------|---------------------------------------------------------------------| -| [ ] | rate_limit | 100 | the maximum number of requests the server should handle per second. | -| [x] | [ server_type ] | - | server option type can either be `grpc` or `http`. | -| [ ] | enabled (for server type) | true | switch option for server. | -| [x] | port | - | port that server run on. | -| [x] | tls | - | transport layer security options. | -| [ ] | enabled (for tls) | false | switch option for tls | -| [ ] | cert | - | tls certificate path. | -| [ ] | key | - | tls key pat | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|-----------------------------------|--------------| -| rate_limit | PERMIFY_RATE_LIMIT | int | -| grpc-port | PERMIFY_GRPC_PORT | string | -| grpc-tls-enabled | PERMIFY_GRPC_TLS_ENABLED | boolean | -| grpc-tls-key-path | PERMIFY_GRPC_TLS_KEY_PATH | string | -| grpc-tls-cert-path | PERMIFY_GRPC_TLS_CERT_PATH | string | -| http-enabled | PERMIFY_HTTP_ENABLED | boolean | -| http-port | PERMIFY_HTTP_PORT | string | -| http-tls-key-path | PERMIFY_HTTP_TLS_KEY_PATH | string | -| http-tls-cert-path | PERMIFY_HTTP_TLS_CERT_PATH | string | -| http-cors-allowed-origins | PERMIFY_HTTP_CORS_ALLOWED_ORIGINS | string array | -| http-cors-allowed-headers | PERMIFY_HTTP_CORS_ALLOWED_HEADERS | string array | - -

-
- -
logger | Logging Options -

- -#### Definition - -Real time logs of authorization. Permify uses [zerolog] as a logger. - -[zerolog]: https://github.com/rs/zerolog - -#### Structure - -``` -โ”œโ”€โ”€ logger - โ”œโ”€โ”€ level -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------| -| [x] | level | info | logger levels: `error`, `warn`, `info` , `debug` | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|---------------------------------|--------| -| log-level | PERMIFY_LOG_LEVEL | string | - -

-
- -
authn | Server Authentication -

- -#### Definition - -You can choose to authenticate users to interact with Permify API. - -There are 2 authentication method you can choose: - -* [Pre Shared Keys](#pre-shared-keys) -* [OpenID Connect](#openid-connect) - -#### Pre Shared Keys - -On this method, you must provide a pre shared keys in order to identify yourself. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ keys -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | true | switch option authentication config | -| [x] | keys | - | Private key/keys for server authentication. Permify does not provide this key, so it must be generated by the users. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-preshared-keys | PERMIFY_AUTHN_PRESHARED_KEYS | string array | - - -#### OpenID Connect - -Permify supports OpenID Connect (OIDC). OIDC provides an identity layer on top of OAuth 2.0 to address the shortcomings -of using OAuth 2.0 for establishing identity. - -With this authentication method, you be able to integrate your existing Identity Provider (IDP) to validate JSON Web -Tokens (JWTs) using JSON Web Keys (JWKs). By doing so, only trusted tokens from the IDP will be accepted for -authentication. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ client-id -| โ”œโ”€โ”€ issuer -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | false | switch option authentication config | -| [x] | client_id | - | This is the client ID of the application you're developing. It is a unique identifier that is assigned to your application by the OpenID Connect provider, and it should be included in the JWTs that are issued by the provider. | -| [x] | issuer | - | This is the URL of the provider that is responsible for authenticating users. You will use this URL to discover information about the provider in step 1 of the authentication process. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-oidc-issuer | PERMIFY_AUTHN_OIDC_ISSUER | string | -| authn-oidc-client-id | PERMIFY_AUTHN_OIDC_CLIENT_ID | string | - -

-
- - -
tracer | Tracing Configurations -

- -#### Definition - -Permify integrated with [jaeger], [otlp], [signoz], and [zipkin] tacing tools to analyze performance and behavior of your -authorization when using Permify. - -#### Structure - -``` -โ”œโ”€โ”€ tracer -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------| -| [x] | exporter | - | Tracer exporter, the options are `jaeger`, `otlp`, `signoz`, and `zipkin`. | -| [x] | endpoint | - | export uri for tracing data. | -| [ ] | enabled | false | switch option for tracing. | -| [ ] | insecure | false | Whether to use HTTP instead of HTTPs for exporting the traces. | - -#### ENV - -| Argument | ENV | Type | -|----------------------|-------------------------------|--------------| -| tracer-enabled | PERMIFY_TRACER_ENABLED | boolean | -| tracer-exporter | PERMIFY_TRACER_EXPORTER | string | -| tracer-endpoint | PERMIFY_TRACER_ENDPOINT | string | -| tracer-insecure | PERMIFY_TRACER_INSECURE | boolean | - -

-
- -
meter | Meter Configurations -

- -#### Definition - -Configuration for observing metrics; check count, cache check count and session information; Permify version, hostname, -os, arch. - -#### Structure - -``` -โ”œโ”€โ”€ meter -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | -| [x] | endpoint | - | export uri for metric observation | -| [ ] | enabled | true | switch option for meter tracing. | - -#### ENV - -| Argument | ENV | Type | -|--------------------|-------------------------|--------------| -| meter-enabled | PERMIFY_METER_ENABLED | boolean | -| meter-exporter | PERMIFY_METER_EXPORTER | string | -| meter-endpoint | PERMIFY_METER_ENDPOINT | string | - -

-
- -
database | Database (WriteDB) Configurations -

- -#### Definition - -Configurations for the database that points out where your want to store your authorization data (relation tuples, -audits, decision logs, authorization model) - -#### Structure - -``` -โ”œโ”€โ”€ database -| โ”œโ”€โ”€ engine -| โ”œโ”€โ”€ uri -| โ”œโ”€โ”€ auto_migrate -| โ”œโ”€โ”€ max_open_connections -| โ”œโ”€โ”€ max_idle_connections -| โ”œโ”€โ”€ max_connection_lifetime -| โ”œโ”€โ”€ max_connection_idle_time -| โ”œโ”€โ”€garbage_collection -| โ”œโ”€โ”€enable: true -| โ”œโ”€โ”€interval: 3m -| โ”œโ”€โ”€timeout: 3m -| โ”œโ”€โ”€window: 720h -| โ”œโ”€โ”€number_of_threads: 1 -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------------|---------|-------------------------------------------------------------------------------------------------------------------| -| [x] | engine | memory | Data source. Permify supports **PostgreSQL**(`'postgres'`) for now. Contact with us for your preferred database. | -| [x] | uri | - | Uri of your data source. | -| [ ] | auto_migrate | true | When its configured as false migrating flow won't work. | -| [ ] | max_open_connections | 20 | Configuration parameter determines the maximum number of concurrent connections to the database that are allowed. | -| [ ] | max_idle_connections | 1 | Determines the maximum number of idle connections that can be held in the connection pool. | -| [ ] | max_connection_lifetime | 300s | Determines the maximum lifetime of a connection in seconds. | -| [ ] | max_connection_idle_time | 60s | Determines the maximum time in seconds that a connection can remain idle before it is closed. | -| [ ] | enable (for garbage collection) | false | Switch option for garbage collection. | -| [ ] | interval | 3m | Determines the run period of a Garbage Collection operation. | -| [ ] | timeout | 3m | Sets the duration of the Garbage Collection timeout. | -| [ ] | window | 720h | Determines how much backward cleaning the Garbage Collection process will perform. | -| [ ] | number_of_threads | 1 | Limits how many threads Garbage Collection processes concurrently with. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------------------------------|--------------------------------------------------------|----------| -| database-engine | PERMIFY_DATABASE_ENGINE | string | -| database-uri | PERMIFY_DATABASE_URI | string | -| database-auto-migrate | PERMIFY_DATABASE_AUTO_MIGRATE | boolean | -| database-max-open-connections | PERMIFY_DATABASE_MAX_OPEN_CONNECTIONS | int | -| database-max-idle-connections | PERMIFY_DATABASE_MAX_IDLE_CONNECTIONS | int | -| database-max-connection-lifetime | PERMIFY_DATABASE_MAX_CONNECTION_LIFETIME | duration | -| database-max-connection-idle-time | PERMIFY_DATABASE_MAX_CONNECTION_IDLE_TIME | duration | -| database-garbage-collection-enabled | PERMIFY_DATABASE_GARBAGE_ENABLED | boolean | -| database-garbage-collection-interval | PERMIFY_DATABASE_GARBAGE_COLLECTION_INTERVAL | duration | -| database-garbage-collection-timeout | PERMIFY_DATABASE_GARBAGE_COLLECTION_TIMEOUT | duration | -| database-garbage-collection-window | PERMIFY_DATABASE_GARBAGE_COLLECTION_WINDOW | duration | -| database-garbage-collection-number-of-threads | PERMIFY_DATABASE_GARBAGE_COLLECTION_NUMBER_OF_THREADS | int | - -

-
- -
profiler | Performance Profiler Configurations -

- -#### Definition - -pprof is a performance profiler for Go programs. It allows developers to analyze and understand the performance -characteristics of their code by generating detailed profiles of program execution - -#### Structure - -``` -โ”œโ”€โ”€ profiler -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|-----------------------------------------------| -| [ ] | enabled | true | switch option for profiler. | -| [x] | port | - | port that profiler runs on *(default: 6060)*. | - -#### ENV - -| Argument | ENV | Type | -|------------------|----------------------------|--------------| -| profiler-enabled | PERMIFY_PROFILER_ENABLED | boolean | -| profiler-port | PERMIFY_PROFILER_PORT | string | - -

-
- -
Distributed | Consistent hashing Configurations -

- -#### Definition - -A consistent hashing ring ensures data distribution that minimizes reorganization when nodes are added or removed, improving scalability and performance in distributed systems." - -#### Structure - -``` -โ”œโ”€โ”€ distributed -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ node -| โ”œโ”€โ”€ node-name -| โ”œโ”€โ”€ protocol -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-----------|---------|--------------------------------------------------------------------------| -| [x] | enabled | false | switch option for distributed. | -| [x] | node | - | endpoint definition for distributed | -| [x] | node-name | - | node name definition for protocol agent (for example serf node name) | -| [x] | protocol | - | a field where you specify which gossip protocol to use, for example serf | - - -#### ENV - -| Argument | ENV | Type | -|------------------------|-------------------------------|---------| -| distributed-enabled | PERMIFY_DISTRIBUTED_ENABLED | boolean | -| distributed-node | PERMIFY_DISTRIBUTED_NODE | string | -| distributed-node-name | PERMIFY_DISTRIBUTED_NODE_NAME | string | -| distributed-protocol | PERMIFY_DISTRIBUTED_PROTOCOL | string | - -

-
- -[jaeger]: https://www.jaegertracing.io/ - -[otlp]: https://opentelemetry.io/ - -[zipkin]: https://zipkin.io/ - -[signoz]: https://signoz.io/ diff --git a/docs/versioned_docs/version-0.4.x/reference/contextual-tuples.md b/docs/versioned_docs/version-0.4.x/reference/contextual-tuples.md deleted file mode 100644 index d92c536d..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/contextual-tuples.md +++ /dev/null @@ -1,246 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Contextual Tuples (Dynamic Permissions) - -## What is it ? - -Contextual tuples are relations that can be dynamically added to permission request operations. When you send these relations along with your requests, they get processed alongside existing relations in the database and will return a result. - -You can utilize Contextual Tuples in authorization checks that depend on certain dynamic or contextual data (such as location, time, IP address, etc) that have not been written as traditional Permify relation tuples. - -## Use Case - -Let's give an example to better understand the usage of Contextual Tuples aka dynamic permissions in access checks. - -Consider you're modeling an permission system for an internal application that belongs to an multi regional organization. - -### Authorization Model - -In that application an employee that belongs to HR department can view details of another employee if: - -1. If he/she is an Manager in HR department -2. Connected via the branch's internal network or through the branch's VPN - -As you notice we can model the rule **1.** easily with our existing schema language, which gives ability to define arbitrary relations between users and objects such as manager of HR entity, as follows, - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - -} -``` - -But to create the `view_employee` permission in the organization entity, we need to consider not only whether the employee is a manager but also check the IP address. - -At this point, traditional relation tuples of Permify are insufficient since network address is an dynamic variable that cannot be added as static relations. - -So, to incorporate the IP address into our authorization model we will use Contextual Tuples and send dynamic relations values when sending the access check request. - -Let's extend our authorization model with adding contextual entities and relations to create the `view_employee` action. - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - - relation ip_address_range @ip_address_range - - action view_employee = hr_manager and ip_address_range.user - -} - -entity ip_address_range { - relation user @user -} -``` - -A quick breakdown we define **type** for contextual variable `ip_address_range` and related them with user. Afterwards call that dynamic entities inside our organization entity and form the `view_employee` permission as follows: - -```perm -action view_employee = hr_manager and ip_address_range.user -``` - -### Dynamic Access Check - -Since we cannot create relation statically for `ip_address_range` we need to send ip value on runtime, specifically when performing access control check. - -So let's say user Ashley trying to view employee X. And lets assume that, - -* She has a **manager** relation in HR department with the tuple `organization:1#hr_manager@user:1` -* She connected to VPN which connected to network 192.158.1.38 - which is Branch's internal network. - - - - -```go -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: &v1.Entity { - Type: "organization", - Id: "1", - }, - Permission: "view_employee", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - ContextualTuples: []*v1.Tuple{ - { - Entity: &v1.Entity { - Type: "organization", - Id: "1", - }, - relation: "ip_address_range", - Subject: &v1.Subject { - Type: "ip_address_range", - Id: "192.158.1.38", - }, - }, - { - Entity: &v1.Entity { - Type: "ip_address_range", - Id: "192.158.1.38", - }, - relation: "user", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - }, - } - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "organization", - id: "1" - }, - permission: "view_employee", - subject: { - type: "user", - id: "1" - }, - contextualTuples: [ - { - entity: { - type: "organization", - id: "1" - }, - relation: "ip_address_range", - subject: { - type: "ip_address_range", - id: "192.158.1.38", - } - }, - { - entity: { - type: "ip_address_range", - id: "192.158.1.38" - }, - relation: "user", - subject: { - type: "user", - id: "1", - } - } - ] -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_employee", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, - "contextual_tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "ip_address_range", - "subject": { - "type": "ip_address_range", - "id": "192.158.1.38" - } - }, - { - "entity": { - "type": "ip_address_range", - "id": "192.158.1.38" - }, - "relation": "user", - "subject": { - "type": "user", - "id": "1" - } - } - ] -}' -``` - - - - -A quick note, - -When you use contextual tuples, the cache system will not be operational. This is because the cache system is written along with snapshots and if contextual tuples are written, using the cache would lead to incorrect results. - -Hence, to prevent this, the use of the cache is blocked at the time of the request. See more on the section [Permify Cache Mechanisims](./cache.md) - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/reference/glossary.md b/docs/versioned_docs/version-0.4.x/reference/glossary.md deleted file mode 100644 index ccefde53..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Write Database - WriteDB - -Permify stores your relational tuples (authorization data) in **WriteDB**. You can configure it **WriteDB** when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/reference/snap-tokens.md b/docs/versioned_docs/version-0.4.x/reference/snap-tokens.md deleted file mode 100644 index 919974f5..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/snap-tokens.md +++ /dev/null @@ -1,54 +0,0 @@ - -# Snap Tokens & Zookies - -A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. - -## Why you should use Snap Tokens ? - -Basically, you should use snap tokens both for consistency and performance. The main goal of Permify is to provide an authorization system that ensures excellent performance that can handle millions of requests from different environments while ensuring data consistency. - -Performance standards can be achievable with caching. In Permify, the cache mechanism eliminates re-computing of access control checks that once occurred, unless any relationships of resources don't change. - -Still, all caches suffer from the risk of becoming stale. If some schema update happens, or relations change then all of the caches should be updated according to it to prevent false positive or false negative results. - -Permify avoids this problem with an approach of snapshot reads. Simply, it ensures that access control is evaluated at a consistent point in time to prevent inconsistency. - -To achieve this, we developed tokens called Snap Tokens that consist of a timestamp that is compared in access checks to ensure that the snapshot of the access control is at least as fresh as the resource timestamp - basically its stored snap token. - -## How to use Snap Tokens - -Snap Tokens used in endpoints to represent the snapshot and get fresh results of the API's. It mainly used in [Write API] and [Check API]. - -The general workflow for using snap token is getting the snap token from the response of Write API request - basically when writing a relational tuple - then mapped it with the resource. One way of doing that is storing snap token in the additional column in your relational database. - -Then this snap token can be used in endpoints. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -```json -{ - "schema_version": "ce8siqtmmud16etrelag", - "snap_token": "gp/twGSvLBc=", - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - }, -} -``` - -[Write API]: ../../api-overview/relationship/write-relationships -[Check API]: ../../api-overview/permission/check-api - -#### All endpoints that used snap token - -- [Write API](../../api-overview/relationship/write-relationships) -- [Check API](../../api-overview/permission/check-api) -- [Expand API](../../api-overview/permission/expand-api) - - -## More on Cache Mechanism - -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisms](./cache.md) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/reference/tracing.md b/docs/versioned_docs/version-0.4.x/reference/tracing.md deleted file mode 100644 index 12810526..00000000 --- a/docs/versioned_docs/version-0.4.x/reference/tracing.md +++ /dev/null @@ -1,55 +0,0 @@ - -# Tracing Tools - -Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: - -- [Jaeger](https://www.jaegertracing.io/) -- [OpenTelemetry](https://opentelemetry.io/) -- [Signoz](https://signoz.io/) -- [Zipkin](https://zipkin.io/) - -## Usage - -### Set Up - -Adding one of these tracing tools to your authorization system is quite simple, you just need to define it in the Permify configuration file as **tracer**. - -```yaml -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -``` - -- ***exporter***: enter the tool name that you want to use. `jaeger` , `otlp`, `signoz`, and `zipkin`. -- ***endpoint***: export url for tracing data. -- ***disabled***: switch option for tracing. -- ***insecure***: configures the exporter to connect to the collcetor using HTTP instead of HTTPS. This configuration is relevant only for `signoz` and `otlp`. - -**Example YAML configuration file** - -```yaml -app: - name: โ€˜permifyโ€™ -http: - port: 3476 -logger: - log_level: โ€˜debugโ€™ - rollbar_env: โ€˜permifyโ€™ -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -database: - write: - connection: 'postgres' - database: 'morf-health-demo' - uri: 'postgres://postgres:SphU4Uf3QXNntT@permify.us-east-1.rds.amazonaws.com:5432' - pool_max: 2 -``` - -After running Permify in your server, you should run Zipkin as well. If you're using docker here is the docker pull request for Zipkin: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` diff --git a/docs/versioned_docs/version-0.4.x/use-cases.md b/docs/versioned_docs/version-0.4.x/use-cases.md deleted file mode 100644 index 6fae082c..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: use-cases -title: Common Use Cases -slug: /use-cases ---- - -# Common Use Cases - -Common modeling patterns and uses cases we have seen so far from the users from small startups with simple RBAC to multi-regional enterprises that run tens of Permify instances with deeply nested relationships. - -:::success Missing a specific use case? -No problem, let's discuss it together! just open an [issue](https://github.com/Permify/permify/issues) about it or join our conversation at [discord](https://discord.gg/n6KfzYxhPp)! -::: - -```mdx-code-block -import {CaseList} from '@site/src/components/Case'; -import list from './use-cases/_list.json'; - - -``` \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/use-cases/_category_.json b/docs/versioned_docs/version-0.4.x/use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/use-cases/_list.json b/docs/versioned_docs/version-0.4.x/use-cases/_list.json deleted file mode 100644 index 7b6e7fb1..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/_list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 1, - "title": "Role Based Access Control (RBAC)", - "description": "Want to implement role to your application ? Define an entity and manage your roles throught your applications.", - "link": "./simple-rbac" - }, - { - "id": 2, - "title": "Attribute Based Access Control (ABAC)", - "description": "Grant access what based on specific characteristics or attributes.", - "link": "./abac" - }, - { - "id": 3, - "title": "Relationship Based Access Control (ReBAC)", - "description": "Define permissions based on the relationships between resources and subjects in your system", - "link": "./rebac" - }, - { - "id": 4, - "title": "Custom Roles", - "description": "Assign specific permissions to users based on the custom roles that they are assigned within the system.", - "link": "./custom-roles" - }, - { - "id": 5, - "title": "Multi Tenancy", - "description": "Create custom authorization schema and relation tuples for the different tenants and manage them in a single place.", - "link": "./multi-tenancy" - } -] \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/use-cases/abac.md b/docs/versioned_docs/version-0.4.x/use-cases/abac.md deleted file mode 100644 index e3945ec0..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/abac.md +++ /dev/null @@ -1,590 +0,0 @@ -# Attribute Based Access Control (Beta) - -This page explains design approach of Permify ABAC support as well as demonstrates how to create and use attribute based permissions in Permify. - -:::info -You can find Permify's support for ABAC in our [beta release](https://github.com/Permify/permify/pkgs/container/permify-beta) and explore the active [API documentation](https://permify.github.io/permify-swagger/) for the ***beta*** version. - -We are eager to hear your thoughts and looking forward to your feedback! -::: - -# What is Attribute Based Access Control (ABAC)? - -Attribute-Based Access Control (ABAC) is like a security guard that decides who gets to access what based on specific characteristics or "attributes". - -These attributes can be associated with users, resources, or the environment, and their values can influence the outcome of an access request. - -Letโ€™s make an analogy, itโ€™s the best of way to understanding complex ideas. - -Think about an amusement park, and there are 3 different rides. In order to access each ride, you need to obtain different qualities. For the; - -1. first ride you need to be over 6ft tall. -2. second ride you need to be under 200lbs. -3. third ride you need to be between 12 - 18 years old. - -Similar to this ABAC check certain qualities that you defined on users, resources, or the environment. - -# Why Would Need ABAC? - -Itโ€™s obvious but simple answer is โ€œuse casesโ€โ€ฆ Sometimes, using ReBAC and RBAC isn't the best fit for the job. It's like using winter tires on a hot desert road, or summer tires in a snowstorm - they're just not the right tools for the conditions. - -1. **Geographically Restricted:** Think of ABAC like a bouncer at a club who only lets in people from certain towns. For example, a movie streaming service might only show certain movies in certain countries because of rules about who can watch what and where. -2. **Time-Based:** ABAC can also act like a parent setting rules about when you can use the computer. For example, a system might only let you do certain things during office hours. -3. **Compliance with Privacy Regulations:** ABAC can help follow rules about privacy. For example, a hospital system might need to limit who can see a patient's data based on the patient's permission, why they want to see it, and who the person is. -4. **Limit Range:** ABAC can help you create a rules defining a number limit or range. For instance, a banking system might have limits for wiring or withdrawing money. -5. **Device Information:** ABAC can control access based on attributes of the device, such as the device type, operating system version, or whether the device has the latest security patches. - -As you can see ABAC has more contextual approach. You can define access rights regarding context around subject and object in an application. - -# Introducing New Key Elements - -To support ABAC in Permify, we've added two main components into our DSL: attributes and rules. - -## Attribute - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```sql -attribute ip_range string[] -``` - -There are different types of attributes you can use; - -### Boolean - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```go -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - - - -### String - -String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```sql -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - - - -### Integer - -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```jsx -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - - - -### Double - -Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```sql -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - - - -## Rule - -Rules are structures that allow you to write specific conditions for the model. They accept parameters and are based on conditions. For example, a rule could be used to check if a given IP address falls within a specified IP range: - -```sql -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` - -## Evaluation - -**Model** - -```sql -entity user {} - -entity organization { - - relation admin @user - - attribute ip_range string[] - - permission view = check_ip_range(request.ip_address, ip_range) or admin -} - -rule check_ip_range(ip_address string, ip_range string[]) { - ip in ip_range -} -``` - -In this case, the part written as 'context' refers to the context within the request. Any type of data can be added from within the request and can be called within model. - -For instance, - -```sql -... -"context": { - "ip_address": "187.182.51.206", - "day_of_week": "monday" -} -... -``` - -**Relationships** - -- organization:1#admin@user:1 - -**Attributes** - -- organization:1$ip_range|string[]:[โ€˜187.182.51.206โ€™, โ€˜250.89.38.115โ€™] - -**Check request** - -```sql -{ - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view", - "subject" : { - "type": "user", - "id": "1" - }, - "context": { - "ip_address": "187.182.51.206" - } -} -``` - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$check_ip_range(context.ip_address,ip_range) โ†’ true -โ†’ organization:1#admin@user:1 โ†’ true - -**Cache Mechanism** -The cache mechanism works by hashing the snapshot of the database, schema version, and sub-queries as keys and adding their results, so it will operate in the same way in calls as in relationships. For example, - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_organization:1#admin@user:1 โ†’ true -- check_{snapshot}_{schema_version}_{context}_organization:1$check_ip_range(ip_range) โ†’ true - -## Some Use Cases - -### Example of Public/Private Repository - -In this example, **`is_public`** is defined as a boolean attribute. If an attribute is boolean, it can be directly written without the need for a rule. This is only applicable for boolean types. - -```sql -entity user {} - -entity post { - - relation owner @user - - attribute is_public boolean - - permission view = is_public or owner - permission edit = owner -} -``` - -In this context, if the **`is_public`** attribute of the repository is set to true, everyone can view it. If it's not public (i.e., **`is_public`** is false), only the owner, in this case **`user:1`**, can view it. - -The permissions in this model are defined as such: - -**`permission view = is_public or owner`** - -This means that the 'view' permission is granted if either the repository is public (**`is_public`** is true) or if the current user is the owner of the repository. - -**relationships:** - -- post:1#owner@user:1 - -**attributes:** - -- post:1$is_public|boolean:true - -**Check Evolution Sub Queries Post View** -โ†’ post:1#is_public โ†’ true -โ†’ post:1#admin@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_post:1$is_public โ†’ true -- check_{snapshot}_{schema_version}_{context}_post:1#admin@user:1 โ†’ true - -### Example of Weekday - -In this example, to be able to view the repository, it must not be a weekend, and the user must be a member of the organization. - -```sql -entity user {} - -entity organization { - - relation member @user - - permission view = is_weekday(request.day_of_week) and member -} - -entity repository { - - relation organization @organization - - permission view = organization.view -} - -rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' -} -``` - -The permissions in this model state that to 'view' the repository, the user must fulfill two conditions: the current day (according to the context data **`day_of_week`**) must not be a weekend (determined by the **`is_weekday`** rule), and the user must be a member of the organization that owns the repository. - -**Relationships:** - -- organization:1#member@user:1 - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$is_weekday(context.day_of_week) โ†’ true -โ†’ organization:1#member@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_organization:1$is_weekday(context.day_of_week) โ†’ true -- check_{snapshot}_{schema_version}_{context}_post:1#member@user:1 โ†’ true - -### Example of Banking System - -This model represents a banking system with two entities: **`user`** and **`account`**. - -1. **`user`**: Represents a customer of the bank. -2. **`account`**: Represents a bank account that has an **`owner`** (which is a **`user`**), and a **`balance`** (amount of money in the account). - -```sql -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -**The check_balance rule:** This rule verifies if the withdrawal amount is less than or equal to the account's balance and doesn't exceed 5000 (the maximum amount allowed for a withdrawal). It accepts two parameters, the withdrawal amount (amount) and the account's current balance (balance). -**The owner check:** This condition checks if the person requesting the withdrawal is the owner of the account. - -Both of these conditions need to be true for the **`withdraw`** permission to be granted. In other words, a user can withdraw money from an account only if they are the owner of that account, and the amount they want to withdraw is within the account balance and doesn't exceed 5000. - -**Relationships** - -- account:1#owner@user:1 - -**Attributes** - -- account:1$balance|double:4000 - -**Check Evolution Sub Queries For Account Withdraw** -โ†’ account:1$check_balance(context.amount,balance) โ†’ true -โ†’ account:1#owner@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_account:1$check_balance(context.amount,balance) โ†’ true -- check_{snapshot}_{schema_version}_{context}_account:1#owner@user:1 โ†’ true - -### Hierarchical Usage - -In this model: - -1. **`employee`**: Represents an individual worker. It has no specific attributes or relations in this case. -2. **`organization`**: Represents an entire organization, which has a **`founding_year`** attribute. The **`view`** permission is granted if the **`check_founding_year`** rule (which checks if the organization was founded after 2000) returns true. -3. **`department`**: Represents a department within the organization. It has a **`budget`** attribute and a relation to its parent **`organization`**. The **`view`** permission is granted if the department's budget is more than 10,000 (checked by the **`check_budget`** rule) and if the **`organization.view`** permission is true. - -Note: In this model, permissions can refer to higher-level permissions (like **`organization.view`**). However, you cannot use the attribute of a relation in this way. For example, you cannot directly reference **`organization.founding_year`** in a permission expression. Permissions can depend on permissions in a related entity, but not directly on the related entity's attributes. - -```sql -entity employee {} - -entity organization { - attribute founding_year integer - - permission view = check_founding_year(founding_year) -} - -entity department { - relation organization @organization - attribute budget double - - permission view = check_budget(budget) and organization.view -} - -rule check_founding_year(founding_year integer) { - founding_year > 2000 -} - -rule check_budget(budget double) { - budget > 10000 -} -``` - -**Relationships** - -- department:1#organization@organization:1 -- department:1#organization@organization:2 - -**Attributes** - -- department:1$budget|double:20000 -- organization:1$organization|integer:2021 - -**Check Evolution Sub Queries For Department View** -โ†’ department:1$check_budget(budget) โ†’ true -โ†’ department:1#organization@user:1 โ†’ true - โ†’ organization:2$check_founding_year(founding_year) โ†’ false - โ†’ organization:1$check_founding_year(founding_year) โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_department:1$check_budget(budget) โ†’ true -- check_{snapshot}_{schema_version}_{context}_organization:2$check_founding_year(founding_year) โ†’ false -- check_{snapshot}_{schema_version}_{context}_organization:1$check_founding_year(founding_year) โ†’ true - -## How To Use Demo - -**Install Permify nightly release** - -```yaml -docker pull **ghcr.io/permify/permify-beta:latest** -``` - -**New Validation Yaml Structure** - -```yaml -schema: >- - {string schem} - -relationships: - - entity_name:entity_id#relation@subject_type:subject_id - -attributes: - - entity_name:entity_id#attribute@attribute_type:attribute_value - -scenarios: - - name: "name" - description: "description" - checks: - - entity: "entity_name:entity_id" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result - entity_filters: - - entity_type: "entity_name" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array - subject_filters: - - subject_reference: "subject_name" - entity: "entity_name:entity_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array -``` - -**Note:** The 'data' field within the 'context' can be assigned a desired value as a key-value pair. Later, this value can be retrieved within the model using 'request.key'. - -**Example in validation file:** - -```yaml -context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" -``` - -This YAML snippet specifies a validation context with no tuples or attributes, and a data field indicating the day of the week is Saturday. - -**Example in model** - -```yaml -permission delete = is_weekday(request.day_of_week) -``` - -In the model, a **`delete`** permission rule is set. It calls the function **`is_weekday`** with the value of **`day_of_week`** from the context. If **`is_weekday("saturday")`** is true, the delete permission is granted. - -**Create Validation File** - -```yaml -schema: >- - entity user {} - - entity organization { - - relation member @user - - attribute credit integer - - permission view = check_credit(credit) and member - } - - entity repository { - - relation organization @organization - - attribute is_public boolean - - permission view = is_public - permission edit = organization.view - permission delete = is_weekday(request.day_of_week) - } - - rule check_credit(credit integer) { - credit > 5000 - } - - rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' - } - -relationships: - - organization:1#member@user:1 - - repository:1#organization@organization:1 - -attributes: - - organization:1$credit|integer:6000 - - repository:1$is_public|boolean:true - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - context: - assertions: - view: true - - entity: "repository:1" - subject: "user:1" - context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" - assertions: - view: true - delete: false - - entity: "organization:1" - subject: "user:1" - context: - assertions: - view: true - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - assertions: - view : ["1"] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - assertions: - view : ["1"] - edit : ["1"] -``` - -**Run validation command** - -```yaml -docker run -v {your_config_folder}:/config **ghcr.io/permify/permify-beta:latest validate /config/validation.yaml** -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/use-cases/custom-roles.md b/docs/versioned_docs/version-0.4.x/use-cases/custom-roles.md deleted file mode 100644 index 1df64794..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/custom-roles.md +++ /dev/null @@ -1,74 +0,0 @@ - -# Custom Roles - -This document highlights a solution for custom roles with [Permify Schema]. In this tutorial, we will create custom **admin** and **member** roles in a project. Then set the permissions of these roles according to their capabilities on the dashboard and tasks. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity role { - relation assignee @user -} - -entity dashboard { - relation view @role#assignee - relation edit @role#assignee -} - -entity task { - relation view @role#assignee - relation edit @role#assignee -} -``` - -This schema encompasses several crucial elements to structure a custom role-based access control system. The role entity serves as a particularly important component, as it enables the creation of multiple custom roles. These roles may vary according to the needs of the application and could include roles like **admin**, **editor**, or **member**, among others. - -Once these custom roles have been established, they can be assigned to other entities in the system. Specifically, in this schema, these roles are attached to the dashboard and task entities. Each of these entities, dashboard and task, has pre-defined permissions associated with them. These permissions, defined within the schema or model, could represent various operations such as **view**, **edit**, and so forth. - -With this setup, it's possible to map these pre-defined permissions of the dashboard and task entities to the custom roles that have been created. This implies that specific permissions, for instance, **view** and **edit** for a dashboard or a task, could be assigned to a particular custom role. - -Based on this model, the example relationships are as follows. With these relationships, custom roles such as **admin** and **member** have been created. - -## Relationships - -dashboard:project-progress#view@role:admin#assignee - -dashboard:project-progress#view@role:member#assignee - -dashboard:project-progress#edit@role:admin#assignee - -task:website-design-review#view@role:admin#assignee - -task:website-design-review#view@role:member#assignee - -task:website-design-review#edit@role:admin#assignee - -Together with these relationships and the model, a view has been created for the **project-progress** dashboard and the **website-design-review** task as shown in the table below. - -| permission | admin | member | -|--------------------|-------|---------| -| **dashboard:view** | โœ… | โœ… | -| **dashboard:edit** | โœ… | โ›” | -| **task:view** | โœ… | โœ… | -| **task:edit** | โœ… | โ›” | - - -Subsequently, you can make authorization decisions by assigning these custom roles to the users that you have created. - -role:member#assignee@user:1 - -When we write these relationship, the final situation will be as follows. - -`Can user:1 view dashboard:project-progress?` gives **Allow** result since the `user:1` is assignee of `role:member` and `role:member` has `dashboard:project-progress#view` permission. - -`Can user:1 view task:website-design-review?` gives **Denied** result since the `user:1` is not assignee of `role:admin`. - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/use-cases/multi-tenancy.md b/docs/versioned_docs/version-0.4.x/use-cases/multi-tenancy.md deleted file mode 100644 index 00a455b4..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/multi-tenancy.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: "Multi Tenancy" ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -With version 0.3.x Permify moved to a tenancy-based infrastructure, which affects almost all of the API operations. - -## Multi Tenancy on Permify - -Multi-tenancy in Permify refers to an authorization architecture, where a single Permify authorization service serves multiple applications/organizations (tenants). - -This allows the ability to customize the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create custom authorization schema and relation tuples accordingly for the different tenants and manage them in a single place. - -For the users that don't have/need multi-tenancy in their authorization structure, we created a pre-inserted tenant (id: **t1**) that comes default when you serve a Permify service. - -Several things changed when we moved to tenant based infrastructure, these are: - -* [API endpoints now have Tenant ID field](#api-endpoints-now-have-tenant-id-field) -* [Added Tenancy Service](#added-tenancy-service) -* [WriteDB tables and tenant id column](#writedb-tables-and-tenant-id-column) - -### API endpoints now have Tenant ID field - -All API endpoints now have a `โ€tenant_id` mandatory field. Let's examine a check request below, - -#### Check API - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../api-overview.md). - -### Added Tenancy Service - -To manage tenants we have added a Tenancy service; you can create, delete and list tenants accordingly. See the [Tenancy Service](../../api-overview/tenancy) on Using The API section. - -### WriteDB tenancy table and tenant id column - -#### Tenant Table - -Tenants table have added the Write DB to store tenant's details. The new WriteDB folder structure changed as follows: -``` -tables -โ”œโ”€โ”€ migrations -โ”œโ”€โ”€ relation_tuples -โ”œโ”€โ”€ schema_definitions -โ”œโ”€โ”€ tenants -โ”œโ”€โ”€ transactions -``` - -#### Tenant ID Column - -Relation tuples and schema definition tables now have a tenant_id column, which stores the id of the tenant that data belongs. - -Let's take a look at a snapshot of the demo table on an example WriteDB. - -Example Relation Tuples data table: -![tenant-id-tuples](https://user-images.githubusercontent.com/34595361/214724165-a3775756-0649-4869-b994-d837fadd271d.png) - -Example Schema Definitions data table -![tenant-id-schema](https://user-images.githubusercontent.com/34595361/214724727-01eadad3-720c-4c10-a88d-6ee293ecf4a8.png) - -## Need any help ? - -Our team is happy to help! If you struggle with migration or need help on using the multi-tenancy, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. diff --git a/docs/versioned_docs/version-0.4.x/use-cases/nested-hierarchies.md b/docs/versioned_docs/version-0.4.x/use-cases/nested-hierarchies.md deleted file mode 100644 index 787a5acc..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/nested-hierarchies.md +++ /dev/null @@ -1,73 +0,0 @@ - -# Nested Hierarchies - -This use case shows solving deeply nested hierarchies with [Permify Schema]. We have a unique **action** usage for nested hierarchies, where parent and child entities can share permissions between them. Let's follow the below team project authorization model to examine this case. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organization user types - relation admin @user -} - -entity team { - - //refers to organization that team belongs to - relation org @organization - - // Only the organization administrator can edit - action edit = org.admin -} - -entity project { - - //refers to team that project belongs to - relation team @team - - // This action responsible for nested permission inheritance - // team.edit refers edit action on the team entity which we defined above - // Semantics of this is: Only the organization administrator, who has the - // team, to which this project belongs can edit. - action edit = team.edit -} -``` - -## Sample Relational Tuples - -organization:1#admin@user:1 - -team:1#org@organization:1#... - -project:1#team@team:1#... - -Lets assume we created above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** result since the `user:1` is organizational admin and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md - -Let's break down this case, - -```perm -entity project { - - relation team @team - - action edit = team.edit -} -``` - -Above `team.edit` points out the **edit** action in the **team** (that project belongs to). - -And edit action on the team entity: `action edit = org.admin` states that only **organization (which that team belongs to) admins** can edit. So our project inherits that action and conducts a result accordingly. - -If we roll back to our enforcement: `Can user:1 edit project:1?` gives **Allow** result, because user:1 is admin in an organization that the projects' parent team belongs to. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/use-cases/organizational.md b/docs/versioned_docs/version-0.4.x/use-cases/organizational.md deleted file mode 100644 index f5438bf0..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/organizational.md +++ /dev/null @@ -1,149 +0,0 @@ - - -# Organization Specific Resources - -Group your users by organization with giving them access organizational-wide resources. In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with our authorization language DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 3 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -### Relations - -To define relation, **relations** needed to be created as entity attributes. - -#### organization entity - -In our schema we defined 2 relation in organization entity, respectively; ``admin`` and ``member`` - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents the default user that belongs to that organization. - -#### repository entity - -Repository entities have 2 relations, these are ``parent`` and ``owner``. Both of these relations represents actual database relations with other entities rather than a role-based approach likewise to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -``parent`` relation represents the parent organization with a repository. And ``owner`` represents the specific user, the repository's owner. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### repository actions - -In our schema, we examined one of the main functionalities can the user make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who also need to have an administrative role or be an owner of the parent organization, can ``read``. -- Repository owners or administrative roles in an organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since ``parent` represents the parent organization of repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -## Example Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.4.x/use-cases/ownership.md b/docs/versioned_docs/version-0.4.x/use-cases/ownership.md deleted file mode 100644 index f7dc5b94..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/ownership.md +++ /dev/null @@ -1,42 +0,0 @@ - -# Ownership - -Granting privileges to the owner of the resource is a common pattern that many applications follow. Generally we want creators of the resource - document, post, comment etc - have superior power on that resource. Check out the below model see how ownership can be modeled with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity comment { - - // represents comment's owner - relation owner @user - - // permissions - action edit = owner - action delete = owner -} - -``` - -## Sample Relational Tuples - -comment:2#owner@user:1 - -comment:3#owner@user:51 - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/use-cases/rebac.md b/docs/versioned_docs/version-0.4.x/use-cases/rebac.md deleted file mode 100644 index 36ad8d54..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/rebac.md +++ /dev/null @@ -1,426 +0,0 @@ - -# Relationship Based Access Control - -Permify has designed and structured as a true [Relationship Based Access Control(ReBAC)](https://permify.co/post/relationship-based-access-control-rebac/) solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -Here are some common use cases where you can benefit from using ReBAC models in your Permify Schema. - -- [Protecting Organizational-Wide Resources](#protecting-organizational-wide-resources) -- [Deeply Nested Hierarchies](#deeply-nested-hierarchies) -- [User Groups & Team Permissions](#user-groups--team-permissions) - -## Protecting Organizational-Wide Resources - -This example demonstrate grouping the users by organization with giving them access organizational-wide resources. - -In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with our authorization language DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists 3 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -#### Relations - -To define relation, **relations** needed to be created as entity attributes. - -##### organization entity - -In our schema we defined 2 relation in organization entity, respectively; ``admin`` and ``member`` - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents the default user that belongs to that organization. - -##### repository entity - -Repository entities have 2 relations, these are ``parent`` and ``owner``. Both of these relations represents actual database relations with other entities rather than a role-based approach likewise to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -``parent`` relation represents the parent organization with a repository. And ``owner`` represents the specific user, the repository's owner. - -#### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -##### repository actions - -In our schema, we examined one of the main functionalities can the user make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who also need to have an administrative role or be an owner of the parent organization, can ``read``. -- Repository owners or administrative roles in an organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since ``parent` represents the parent organization of repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -### Sample Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -For instance, you can define that a user has certain permissions because of their relation to other entities. - -An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -## Deeply Nested Hierarchies - -This use case shows solving deeply nested hierarchies with [Permify Schema]. - -We have a unique **action** usage for nested hierarchies, where parent and child entities can share permissions between them. Let's follow the below team project authorization model to examine this case. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organization user types - relation admin @user -} - -entity team { - - //refers to organization that team belongs to - relation org @organization - - // Only the organization administrator can edit - action edit = org.admin -} - -entity project { - - //refers to team that project belongs to - relation team @team - - // This action responsible for nested permission inheritance - // team.edit refers edit action on the team entity which we defined above - // Semantics of this is: Only the organization administrator, who has the - // team, to which this project belongs can edit. - action edit = team.edit -} -``` - -### Sample Relational Tuples - -organization:1#admin@user:1 - -team:1#org@organization:1#... - -project:1#team@team:1#... - -Lets assume we created above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** result since the `user:1` is organizational admin and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md - -Let's break down this case, - -```perm -entity project { - - relation team @team - - action edit = team.edit -} -``` - -Above `team.edit` points out the **edit** action in the **team** (that project belongs to). - -And edit action on the team entity: `action edit = org.admin` states that only **organization (which that team belongs to) admins** can edit. So our project inherits that action and conducts a result accordingly. - -If we roll back to our enforcement: `Can user:1 edit project:1?` gives **Allow** result, because user:1 is admin in an organization that the projects' parent team belongs to. - -## User Groups & Team Permissions - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // reference for organization that team belong - relation org @organization - - // organization admins or owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be admin and either owner or member of this team - action invite = org.admin and (owner or member) - - // only owners can remove users - action remove_user = owner - -} - -entity project { - - // references for team and organization that project belongs - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists 4 entity, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that contain teams. - -- `team`, represents teams, which belongs to a organization. - -- `project`, represents projects that belongs teams. - -#### Relations - -##### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -##### team entity - -The eeam entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -##### project entity - -Project entity has ``team`` and ``org`` relations. Both these relations represents parent relationship with other entites, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -#### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or*** and ***not*** operators to define actions. - -##### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can perform editing and deleting team spesific resources. - -- Moreover, for inviting a colleague to a team you must have ***admin role*** and either be a ***owner*** or ***member*** on that team. - -- To remove users in team you must be a ***owner*** of that team. - -And these rules reflects Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -##### project actions - -And there are the project actions below. It consists of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Sample Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organiation:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 also members in organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -## Need any help on Authorization ? - -Our team is happy to help you anything about authorization. If you'd like to learn more about using Permify in your app or have any questions, [schedule a call with one of our founders](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.4.x/use-cases/sharing.md b/docs/versioned_docs/version-0.4.x/use-cases/sharing.md deleted file mode 100644 index 4a97b41b..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/sharing.md +++ /dev/null @@ -1,40 +0,0 @@ - -# Sharing & Collaboration - -Inviting a team member to a document, project or repository should be hassle free to model. In Permify you can achieve this with simply defining a invite action. Check out the below model block see how sharing can be modeled with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - relation manager @user - -} - -entity project { - - // represents project's parent organization - relation org @organization - - // represents owner of this project - relation owner @user - - // invite permission - action invite = org.admin or owner - -} - -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/use-cases/simple-rbac.md b/docs/versioned_docs/version-0.4.x/use-cases/simple-rbac.md deleted file mode 100644 index f5cf485b..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/simple-rbac.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Role Based Access Control - -Want to implement role and permissions to your application ? Permify fully covers you at that point. Below example shows how to model simple role based access control for organizational roles and permissions with our authorization language, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 2 entities, - -- `user`, represents users (maybe corresponds as employees). This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, representing the organization the user (employees) belongs. It has several roles and permissions related to the specific resources such as organization files and vendor files. - -### Relations - -#### organization entity - -We can use **relations** to define roles. In this example, we have 4 organizational wide roles, respectively; admin, manager, member, and agent. - -```perm -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - -} -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or*** and ***not*** operators to define actions. - -#### organization actions - -In our schema, we define several actions for controlling access permissions on organization files and organization vendor's files. - -```perm -entity organization { - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -let's take a look at some of the actions: - -- ``action edit_files = admin or manager`` -indicates that only the admin or manager has permission to edit files in the organization. - -- ``action view_files = admin or manager or (member not agent)`` -indicates that the admin, manager, or members (without having the agent role) can view organization files. - - - -## Example Relational Tuples for this case - -organization:2#admin@user:daniel - -organization:5#member@user:ashley - -organization:17#manager@user:mert - -organization:21#agent@user:ege - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.4.x/use-cases/user-groups.md b/docs/versioned_docs/version-0.4.x/use-cases/user-groups.md deleted file mode 100644 index aa48ce15..00000000 --- a/docs/versioned_docs/version-0.4.x/use-cases/user-groups.md +++ /dev/null @@ -1,201 +0,0 @@ - -# User Groups & Team Permissions - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // reference for organization that team belong - relation org @organization - - // organization admins or owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be admin and either owner or member of this team - action invite = org.admin and (owner or member) - - // only owners can remove users - action remove_user = owner - -} - -entity project { - - // references for team and organization that project belongs - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists 4 entity, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that contain teams. - -- `team`, represents teams, which belongs to a organization. - -- `project`, represents projects that belongs teams. - -### Relations - -#### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -#### team entity - -The eeam entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -#### project entity - -Project entity has ``team`` and ``org`` relations. Both these relations represents parent relationship with other entites, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -### Actions - -Actions describe what relations, or relationโ€™s relation can do, think of actions as entities' permissions. Actions defines who can perform a specific action in which circumstances. - -Permify Schema supports ***and***, ***or*** and ***not*** operators to define actions. - -#### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can perform editing and deleting team spesific resources. - -- Moreover, for inviting a colleague to a team you must have ***admin role*** and either be a ***owner*** or ***member*** on that team. - -- To remove users in team you must be a ***owner*** of that team. - -And these rules reflects Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -#### project actions - -And there are the project actions below. It consists of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -## Example Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organiation:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 also members in organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -For more details about how relational tuples created and stored your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.5.x/api-overview.md b/docs/versioned_docs/version-0.5.x/api-overview.md deleted file mode 100644 index 08346b46..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Overview - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests and options. -- [DataService]: Authorization data operations such as creating, deleting and reading relational tuples. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ./permission -[DataService]: ./data -[SchemaService]: ./schema -[TenancyService]: ./tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](./api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](./api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](./api-overview/data/read-relationships.md) -- Check access with [Check API](./api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](./api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](./api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](./api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](./api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](./api-overview/watch/watch-changes.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our [documentation](https://docs.permify.co/docs/reference/configuration). We used [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm for rate limiting. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/api-overview/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/data/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/data/_category_.json deleted file mode 100644 index d245a3c3..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/data/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Data Service", - "position": 2, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/data/delete-data.md b/docs/versioned_docs/version-0.5.x/api-overview/data/delete-data.md deleted file mode 100644 index fe8ba7d4..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/data/delete-data.md +++ /dev/null @@ -1,108 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Data - -You can delete any stored relation tuples or attributes with following API - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/data/delete - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.delete) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | tuples_filter | object |filter to delete relational tuples. Contains **entity**, **relation** and **subject**. -| [x] | attribute_filter | object | filter to delete attributes. Contains **entity** and **attributes**. -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | relation of the given entity | -| [x] | attribute | string array | attributes to be deleted | -| [ ] | subject | object | the user or user set. It contains type and id of the subject. || - - - - -```go -rr, err: = client.Data.Delete(context.Background(), & v1.DataDeleteRequest { - TenantId: "t1", - Metadata: &v1.DataDeleteRequestMetadata { - SnapToken: "" - }, - TupleFilter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.delete({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - tupleFilter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tupleFilter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - }, -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/data/read-attributes.md b/docs/versioned_docs/version-0.5.x/api-overview/data/read-attributes.md deleted file mode 100644 index 248d34d9..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/data/read-attributes.md +++ /dev/null @@ -1,94 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Attributes - -Read API allows for directly querying the stored graph data to display and filter stored attributes. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/data/attributes/read - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.attributes.read) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | attributes | string array | attributes of the given entity | - - - - - -```go -rr, err: = client.Data.ReadAttributes(context.Background(), & v1.Data.AttributeReadRequest { - TenantId: "t1", - Metadata: &v1.Data.AttributeReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.AttributeFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Attributes: []string {"private"}, -}) -``` - - - - - -```javascript -client.data.readAttributes.read({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/api-overview/data/read-relationships.md b/docs/versioned_docs/version-0.5.x/api-overview/data/read-relationships.md deleted file mode 100644 index 384f766f..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/data/read-relationships.md +++ /dev/null @@ -1,105 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -## Request - -**Path:** POST /v1/tenants/{tenant_id/data/relationships/read - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.relationships.read) - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Data.ReadRelationships(context.Background(), & v1.Data.RelationshipReadRequest { - TenantId: "t1", - Metadata: &v1.Data.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.readRelationships({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/api-overview/data/write-data.md b/docs/versioned_docs/version-0.5.x/api-overview/data/write-data.md deleted file mode 100644 index 73199ac7..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/data/write-data.md +++ /dev/null @@ -1,366 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Authorization Data - -In Permify, relations between your entities, objects and users stored as [relational tuples] in a [preferred database]. Since relations and authorization data's are live instances these relational tuples can be created with an simple API call in runtime. - -When using Permify, the application client should update preferred database about the changes happening in entities or resources that are related to the authorization structure. If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [preferred database] up-to-date. Besides, each relational tuple should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as relation tuples and store in [preferred database]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -[relational tuples]: ../../../getting-started/sync-data -[preferred database]: ../../../getting-started/sync-data#where-relational-tuples-used - -## Write Request - -:::info -You can use the **/v1/tenants/{tenant_id}/data/write** endpoint for both creating **relation tuples** and for creating **attribute data**. -::: - -**Path:** POST /v1/tenants/{tenant_id}/data/write - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.write) - -#### Glossary for parameters & payload objects: - -| Required | Argument | Type | Default | Description | -| -------- | -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this parameter. | -| [ ] | schema_version | string | 8 | Version of the schema. | -| [x] | tuples | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **relation**, and **subject** arguments.| -| [x] | attributes | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **attribute**, and **value** arguments. | -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€ | -| [x] | subject | string | - | User or user set who wants to take the action. | -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc. | -| [x] | attribute | string | - | Custom attribute name. | -| [x] | value | object | - | Represents value and type of the attribute data. | - - -### Creating Relational Tuple - -Let's create an example relation tuple. If user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` should be created as follows: - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "admin", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.data - .write({ - tenantId: "t1", - metadata: { - schemaVersion: "", - }, - tuples: [ - { - entity: { - type: "organization", - id: "1", - }, - relation: "admin", - subject: { - type: "user", - id: "3", - }, - }, - ], - }) - .then((response) => { - // handle response - }); -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - - -### Creating Attribute Data - -You can use `attributes` argument to create attribute/attributes, similarly the `tuples`. - -Let's conitnue with an example: Assume user:1 has been granted an admin role in organization:1 and organization:1 is a private (boolean) organization: - -:::warning **value** field -**value** field is mandatory on attribute data creation. - -Here are the available attribute value types: - -- **type.googleapis.com/base.v1.StringValue** -- **type.googleapis.com/base.v1.BooleanValue** -- **type.googleapis.com/base.v1.IntegerValue** -- **type.googleapis.com/base.v1.DoubleValue** -- **type.googleapis.com/base.v1.StringArrayValue** -- **type.googleapis.com/base.v1.BooleanArrayValue** -- **type.googleapis.com/base.v1.IntegerArrayValue** -- **type.googleapis.com/base.v1.DoubleArrayValue** -::: - - - - -```go -// Convert the wrapped attribute value into Any proto message -value, err := anypb.New(&v1.BooleanValue{ - Data: true, -}) -if err != nil { - // Handle error -} - -cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{ - TenantId: "t1",, - Metadata: &v1.DataWriteRequestMetadata{ - SchemaVersion: "", - }, - Tuples: []*v1.Attribute{ - { - Entity: &v1.Entity{ - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: &v1.Subject{ - Type: "user", - Id: "1", - Relation: "", - }, - }, - }, - Attributes: []*v1.Attribute{ - { - Entity: &v1.Entity{ - Type: "account", - Id: "1", - }, - Attribute: "public", - Value: value, - }, - }, -}) -``` - - - - - -```javascript -const booleanValue = BooleanValue.fromJSON({ data: true }); - -const value = Any.fromJSON({ - typeUrl: 'type.googleapis.com/base.v1.BooleanValue', - value: BooleanValue.encode(booleanValue).finish() -}); - -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "1" - } - }], - attributes: [{ - entity: { - type: "document", - id: "1" - }, - attribute: "public", - value: value, - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1" - } - } - ], - "attributes": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "attribute": "private", - "value": { - "@type": "type.googleapis.com/base.v1.BooleanValue", - "data": true - } - } - ] -} -}' -``` - - - - -## Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../../reference/snap-tokens) and how its avoiding stale cache. - -## Suggested Workflow - -The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? - -### Two Phase Commit Approach - -In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. - -To give more concurrent example of this action, let's take a look at below createDocument function - -```go -func CreateDocuments(db *gorm.DB) error { - - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteData(...) - } - }() - - if err := tx.Error; err != nil { - return err - } - - if err := tx.Create(docs).Error; err != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteData(...) - return err - } - - // if transaction successful, write relation tuple to Permify - permify.WriteData(...) - - return tx.Commit().Error -} -``` - -The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. - -### Data that not stored in application database - -Although ownership generally stored in application databases, there are some data that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. - -For example, you can model a simple project management authorization in Permify as follows, - -```perm -entity user {} - -entity team { - - relation owner @user - relation member @user -} - -entity project { - - relation team @team - relation owner @user - - action view = team.member or team.owner or project.owner - action edit = project.owner or team.owner - action delete = project.owner or team.owner - -} -``` - -This **team member** relation won't need to be stored in the application database. Storing it only in Permify - preferred database - is enough. In that situation, `WriteData` can be performed in any logical place in your stack. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/permission/_category_.json deleted file mode 100644 index f91d5b46..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Permission Service", - "position": 3, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/check-api.md b/docs/versioned_docs/version-0.5.x/api-overview/permission/check-api.md deleted file mode 100644 index e85b48af..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/check-api.md +++ /dev/null @@ -1,194 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Check Access Control - -In Permify, you can perform two different types access checks, - -- **resource based** authorization checks, in form of `Can user U perform action Y in resource Z ?` -- **subject based** authorization checks, in form of `Which resources can user U edit ?` - -In this section we'll look at the resource based check request of Permify. You can find subject based access checks in [Entity (Data) Filtering] section. - -[Entity (Data) Filtering]: ../lookup-entity - -## Request - -**Path:** POST /v1/permissions/check - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.check) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. , see more details on [Contextual Tuples](../../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "", - SchemaVersion: "", - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "can": "RESULT_ALLOWED", - "remaining_depth": 0 -} -``` - -Answering access checks is accomplished within Permify using a basic graph walking mechanism. - -## How Access Decisions Evaluated? - -Access decisions are evaluated by stored [relational tuples] and your authorization model, [Permify Schema]. - -In high level, access of an subject related with the relationships or attributes created between the subject and the resource. You can define this data in Permify Schema then create and store them as relational tuples and attributes, which is basically forms your authorization data. - -Permify Engine to compute access decision in 2 steps, -1. Looking up authorization model for finding the given action's ( **edit**, **push**, **delete** etc.) relations. -2. Walk over a graph of each relation to find whether given subject ( user or user set ) is related with the action. - -Let's turn back to above authorization question ( ***"Can the user 3 edit document 12 ?"*** ) to better understand how decision evaluation works. - -[relational tuples]: ../../getting-started/sync-data.md -[Permify Schema]: ../../getting-started/modeling.md - -When Permify Engine receives this question it directly looks up to authorization model to find document `โ€edit` action. Let's say we have a model as follows - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity document { - - // represents documents parent organization - relation parent @organization - - // represents owner of this document - relation owner @user - - // permissions - action edit = parent.admin or owner - action delete = owner -} -``` - -Which has a directed graph as follows: - -![relational-tuples](https://github.com/Permify/permify/assets/39353278/cec9936c-f907-42c0-a419-032ebb45454e) - -As we can see above: only users with an admin role in an organization, which `document:12` belongs, and owners of the `document:12` can edit. Permify runs two concurrent queries for **parent.admin** and **owner**: - -**Q1:** Get the owners of the `document:12`. - -**Q2:** Get admins of the organization where `document:12` belongs to. - -Since edit action consist **or** between owner and parent.admin, if Permify Engine found user:3 in results of one of these queries then it terminates the other ongoing queries and returns authorized true to the client. - -Rather than **or**, if we had an **and** relation then Permify Engine waits the results of these queries to returning a decision. - -## Latency & Performance - -With the right architecture we expect **7-12 ms** latency. Depending on your load, cache usage and architecture you can get up to **30ms**. - -Permify implements several cache mechanisms in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](../../reference/cache.md) - -## Need any help ? - -:::info -Bulk permission check or with other name data filtering is a common use case we have seen so far. If you have a similar use case we would love to hear from you. Join our [discord](https://discord.gg/n6KfzYxhPp) to discuss or [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/expand-api.md b/docs/versioned_docs/version-0.5.x/api-overview/permission/expand-api.md deleted file mode 100644 index ea91bb1f..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/expand-api.md +++ /dev/null @@ -1,316 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API - -Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission - -Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. - -:::caution When To Use ? -Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. - -It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. -::: - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.expand) - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{ - TenantId: "t1", - Metadata: &v1.PermissionExpandRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -}' -``` - - - -## Example Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** POST /v1/tenants/{tenant_id}/permissions/expand - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. | -| [x] | permission | string | - | The permission the user wants to perform on the resource | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../reference/contextual-tuples) | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-entity.md b/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-entity.md deleted file mode 100644 index a14679db..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-entity.md +++ /dev/null @@ -1,222 +0,0 @@ ---- -title: Entity (Data) Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Entity Filtering - -Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So, we provide 2 separate endpoints for data filtering check request, - -- [Entity Filtering](#entity-filtering) - - [Lookup Entity](#lookup-entity) - - [How Lookup Operations Evaluated](#how-lookup-operations-evaluated) - - [Lookup Entity (Streaming)](#lookup-entity-streaming) - -## Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. - -**POST** /v1/permissions/lookup-entity - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntity) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - EntityType: "document", - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - } -}) -``` - - - - -```javascript -client.permission.lookupEntity({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity_type: "document", - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response.entity_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type":"user", - "id":"1" - } -}' -``` - - - -## How Lookup Operations Evaluated - -We explicitly designed reverse lookup to be more performant with changing its evaluation pattern. We do not query all the documents in bulk to get response, instead of this Permify first finds the necessary relations with given subject and the permission/action in the API call. Then query these relations with the subject id this way we reduce lots of additional queries. - -To give an example, - -```jsx -entity user {} - -entity organization { - relation admin @user -} - -entity container { - relation parent @organization - relation container_admin @user - action admin = parent.admin or container_admin -} - -entity document { - relation container @container - relation viewer @user - relation owner @user - action view = viewer or owner or container.admin -} -``` - -Lets say we called (reverse) lookup API to find the documents that user:1 can view. Permify first finds the relations that linked with view action, these are - -- `document#viewer` -- `document#owner` -- `organization#admin` -- `container#``container_admin` - -Then queries each of them with `user:1.` - -### Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -**POST** /v1/permissions/lookup-entity-stream - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntityStream) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../../reference/contextual-tuples) | - - - - -```go -str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest { - Metadata: &v1.PermissionLookupEntityRequestMetadata { - SnapToken: "", - SchemaVersion: "" - Depth: 50, - }, - EntityType: "document", - Permission: "view", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) - -// handle stream response -for { - res, err: = str.Recv() - - if err == io.EOF { - break - } - - // res.EntityId -} -``` - - - - -```javascript -const permify = require("@permify/permify-node"); -const {PermissionLookupEntityStreamResponse} = require("@permify/permify-node/dist/src/grpc/generated/base/v1/service"); - -function main() { - const client = new permify.grpc.newClient({ - endpoint: "localhost:3478", - }) - - let res = client.permission.lookupEntityStream({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entityType: "document", - permission: "view", - subject: { - type: "user", - id: "1" - } - }) - - handle(res) -} - -async function handle(res: AsyncIterable) { - for await (const response of res) { - // response.entityId - } -} -``` - - - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-subject.md b/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-subject.md deleted file mode 100644 index baaacd6b..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-subject.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Subject Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Filtering - -Lookup Subject endpoint lets you ask questions in form of **โ€œWhich subjects can do action Y on entity:X?โ€**. As a response of this youโ€™ll get a subject results in a format of string array. - -So, we provide 1 endpoint for subject filtering request, - -- [/v1/permissions/lookup-subject](#lookup-subject) - -## Lookup Subject - -In this endpoint you'll get directly the IDs' of the subjects that are authorized in an array. - -**POST** /v1/permissions/lookup-subject - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupSubject) - -| Required | Argument | Type | Default | Description | -|----------|---------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1 | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject_reference | object | - | the subject or subject reference who wants to take the action. It contains type and relation of the subject. | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. See more details on [Contextual Tuples](../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest { - TenantId: "t1", - Metadata: &v1.PermissionLookupSubjectRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Permission: "edit", - SubjectReference: &v1.RelationReference{ - Type: "user", - Relation: "", - } -}) -``` - - - - -```javascript -client.permission.lookupSubject({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - Entity: { - Type: "document", - Id: "1", - }, - permission: "edit", - subject_reference: { - type: "user", - relation: "" - } -}).then((response) => { - console.log(response.subject_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "" - }, - "entity": { - type: "document", - id: "1' - }, - "permission": "edit", - "subject_reference": { - "type": "user", - "relation": "" - } -}' -``` - - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/api-overview/permission/subject-permission.md b/docs/versioned_docs/version-0.5.x/api-overview/permission/subject-permission.md deleted file mode 100644 index 9048b33c..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/permission/subject-permission.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: Subject Permission List ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Permission List - -The Subject Permission List endpoint allows you to inquire in the form of **โ€œWhich permissions user:x can perform on entity:y?โ€**. In response, you'll receive a list of permissions specific to the user for the given entity, returned in the format of a map. - -So, we provide 1 endpoint for subject permission list, - -- [/v1/permissions/subject-permission](#subject-permission) - -In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as "sample-permission" -> "ALLOWED". This represents the permissions and their associated states in a key-value pair format. - -## Request - -**Path:** POST /v1/permissions/subject-permission - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.subjectPermission) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | only_permission | bool | false | By default, the endpoint returns both permissions and relations associated with the user and entity. However, when the "only_permission" parameter is set to true, it returns only the permissions. | | -| [ ] | context | object | - | Contextual tuples are relations that can be dynamically added to permission request operations. , see more details on [Contextual Tuples](../../reference/contextual-tuples) | - - - - -```go -cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest { - TenantId: "t1", - Metadata: &v1.PermissionSubjectPermissionRequestMetadata { - SnapToken: "", - SchemaVersion: "", - OnlyPermission: false, - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) -``` - - - - -```javascript -client.permission.subjectPermission({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - onlyPermission: true, - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response); -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "only_permission": true, - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "results": [ - { - "key": "delete", - "value": "RESULT_ALLOWED" - }, - { - "key": "edit", - "value": "RESULT_ALLOWED" - } - ] -} -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.5.x/api-overview/schema/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/schema/_category_.json deleted file mode 100644 index 8fd1e959..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/schema/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Schema Service", - "position": 1, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/schema/write-schema.md b/docs/versioned_docs/version-0.5.x/api-overview/schema/write-schema.md deleted file mode 100644 index bb4f9459..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/schema/write-schema.md +++ /dev/null @@ -1,95 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema - -Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. - -We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: - -Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -## Request - -**POST** /v1/tenants/{tenant_id}/schemas/write - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Schema/schemas.write) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - TenantId: "t1", - Schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - `, -}) -``` - - - - -```javascript -client.schema.write({ - tenantId: "t1", - schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - ` -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "schema": "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/write"** - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - - -## Suggested Workflow For Schema Changes - -It's expected that your initial schema will eventually change as your product or system evolves - -As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. - -A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database unless you delete them with the [delete API](../data/delete-data.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. - -We recommend applying the following pattern to safely handle schema changes: - -- Set up a central git repository that includes the schema. -- Teams or individuals who need to update the schema should add new permissions or relations to this repository. -- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. -- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. - -## Need any help ? - -Depending on the frequency and the type of the changes that you made on the schemas, this method may not be optimal for you - In such cases, we are open to exploring alternative solutions. Please feel free to [schedule a call with one of our engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/tenancy/_category_.json deleted file mode 100644 index 9771416d..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Tenancy Service", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/create-tenant.md b/docs/versioned_docs/version-0.5.x/api-overview/tenancy/create-tenant.md deleted file mode 100644 index 69a7a6ff..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/create-tenant.md +++ /dev/null @@ -1,57 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Tenant - -Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. - -:::caution -We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. -::: - -## Request - -**POST /v1/tenants/create** - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.create) - - - - -```go -rr, err: = client.Tenancy.Create(context.Background(), & v1.TenantCreateRequest { - Id: "" - Name: "" -}) -``` - - - - - -```javascript -client.tenancy.create({ - id: "", - name: "" -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'http://localhost:3476/v1/tenants/create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "id": "", - "name": "" -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/delete-tenant.md b/docs/versioned_docs/version-0.5.x/api-overview/tenancy/delete-tenant.md deleted file mode 100644 index a74a11e8..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/tenancy/delete-tenant.md +++ /dev/null @@ -1,46 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Tenant - -You can delete a tenant with following API. - -## Request - -**DELETE /v1/tenants/{id}** - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.delete) - - - - -```go -rr, err: = client.Tenancy.Delete(context.Background(), & v1.TenantDeleteRequest { - Id: "" -}) -``` - - - - - -```javascript -client.tenancy.delete({ - id: "", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/watch/_category_.json b/docs/versioned_docs/version-0.5.x/api-overview/watch/_category_.json deleted file mode 100644 index f65c41fd..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/watch/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Watch Service", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/api-overview/watch/watch-changes.md b/docs/versioned_docs/version-0.5.x/api-overview/watch/watch-changes.md deleted file mode 100644 index ff48c097..00000000 --- a/docs/versioned_docs/version-0.5.x/api-overview/watch/watch-changes.md +++ /dev/null @@ -1,142 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Watch - -The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples. - -The Watch API exclusively supports gRPC and works with PostgreSQL, given the track_commit_timestamp option is enabled. Please note, it doesn't support in-memory databases or HTTP communication. - -# Requirements - -- PostgreSQL database set up with track_commit_timestamp option enabled - -## Enabling track_commit_timestamp on PostgreSQL - -To ensure data consistency and synchronization between your application and Permify, enable track_commit_timestamp on -your PostgreSQL server. This can be done by executing the following options in your PostgreSQL: - -### Option 1: SQL Command - -1. Open your PostgreSQL command line interface. -2. Execute the following command: - - ```sql - ALTER SYSTEM SET track_commit_timestamp = ON; - ``` - -3. Reload the configuration with the following command: - - ```sql - SELECT pg_reload_conf(); - ``` - -### Option 2: Editing postgresql.conf - -1. Find and open the postgresql.conf file in a text editor. Its location depends on your PostgreSQL installation. Common - locations are: - - Debian-based systems: /etc/postgresql/[version]/main/postgresql.conf - - Red Hat-based systems: /var/lib/pgsql/data/postgresql.conf - -2. Add or modify the following line in the postgresql.conf file: - ``` - track_commit_timestamp = on - ``` - -3. Save and close the postgresql.conf file. -4. Reload the PostgreSQL configuration for the changes to take effect. This can be done via the PostgreSQL console: - ```sql - SELECT pg_reload_conf(); - ``` - - Or if you have command line access, use: - - ```bash - sudo service postgresql reload - ``` - -Please ensure you have the necessary permissions to execute these commands or modify the postgresql.conf file. Also, remember that changes in the postgresql.conf file will persist across restarts, while the SQL method may need to be reapplied depending on your PostgreSQL version and setup. - -:::info -Important Configuration Requirement: To use the Watch API, it must be enabled in your configuration file. Add or modify the following lines: - -```yaml -service: - watch: - enabled: true -``` - -::: - -## Request - -**Path:** POST /v1/watch/watch - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Watch/watch.watch) - -| Required | Argument | Type | Default | Description | -|----------|------------|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | snap_token | string | - | specifies the starting point for broadcasting changes. If a snap_token is provided, all changes following that specific snapshot will be broadcasted. If a snap_token is not provided, the Watch API will broadcast all changes that occur after the Watch API is initiated., see more details on [Snap Tokens](../../reference/snap-tokens). | - - -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```go) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```javascript) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -## Response - -```json -{ - "changes": { - "tuple_changes": [ - { - "operation": "OPERATION_CREATE", - "tuple": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "56", - "relation": "" - } - } - } - ], - "snap_token": "MgMAAAAAAAA=" - } -} -``` - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or -have any questions about this -example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.5.x/comparision.md b/docs/versioned_docs/version-0.5.x/comparision.md deleted file mode 100644 index 75fb39c4..00000000 --- a/docs/versioned_docs/version-0.5.x/comparision.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: comparison -title: Comparison Between Other Zanzibar implementations ---- - -:::caution Note -This comparison table shows the differentiation between authorization solutions based or inspired by Google Zanzibar paper. If you use any of these solutions and feel the information could be improved, feel free to reach out. -::: - -## General Aspects - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|---------------------------------|------------|------------|-----------|-----------| -| **Zanzibar Paper Faithfulness** | Medium | High | High | High | -| **Scalability** | Medium | Medium | High | High | -| **Consistency & Cache** | No Zookies | No Zookies | Supported | Supported | -| **Dev UX** | Average | Average | High | High | - -## Feature Set - -- โœ…  Supported, and ready to use with no added configuration or code -- ๐ŸŸก  Limited support and requires extra user-code to implement. -- โ›”  Not officially supported or documented. - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|--------------------------|----------|---------|---------|---------| -| **Check API** | โœ… | โœ… | โœ… | โœ… | -| **Write API** | โœ… | โœ… | โœ… | โœ… | -| **Read API** | โœ… | โœ… | โœ… | โœ… | -| **Expand API** | โœ… | โœ… | โœ… | โœ… | -| **Watch API** | โœ… | โœ… | โœ… | โœ… | -| **RBAC** | โœ… | โœ… | โœ… | โœ… | -| **ReBAC** | โœ… | โœ… | โœ… | โœ… | -| **ABAC** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Data Filtering** | โ›” | โœ… | โœ… | โœ… | -| **Multi Tenancy** | โ›” | โœ… | โ›” | โœ… | -| **Testing & Validation** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Logging & Tracing** | ๐ŸŸก | โœ… | โœ… | โœ… | diff --git a/docs/versioned_docs/version-0.5.x/getting-started/_category_.json b/docs/versioned_docs/version-0.5.x/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.5.x/getting-started/enforcement.md b/docs/versioned_docs/version-0.5.x/getting-started/enforcement.md deleted file mode 100644 index df4c80f5..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/enforcement.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Interacting With The API - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests and options. -- [DataService]: Authorization data operations such as creating, deleting and reading relational tuples. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permify/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ../../api-overview/permission -[DataService]: ../../api-overview/data -[SchemaService]: ../../api-overview/schema -[TenancyService]: ../../api-overview/tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](../api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](../api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](../api-overview/data/read-relationships.md) -- Check access with [Check API](../api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](../api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](../api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](../api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](../api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](../api-overview/watch/watch-changes.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our [documentation](https://docs.permify.co/docs/reference/configuration). We used [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm for rate limiting. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/_category_.json b/docs/versioned_docs/version-0.5.x/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f801..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Example Permission Structures", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/facebook-groups.md b/docs/versioned_docs/version-0.5.x/getting-started/examples/facebook-groups.md deleted file mode 100644 index 1b918630..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/facebook-groups.md +++ /dev/null @@ -1,546 +0,0 @@ -# Facebook Groups - -This example demonstrate the authorization structure for Facebook groups, which enables users to perform various actions based on their roles and permissions within the group. - -### Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) - -```perm -// Represents a user -entity user {} - -// Represents a Facebook group -entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator -} - -// Represents a post in a Facebook group -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner -} - -// Represents a comment like on a post in a Facebook group -entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member -} - -// Definition of poll entity -entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin -} - -// Definition of file entity -entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin -} - -// Definition of event entity -entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member -} -``` - -## Brief Examination of the Model - -The model defines several entities and relations, as well as actions and permissions that can be taken by users within the group. Let's examine them shortly; - -### Entities & Relations - -* **`user`** entity represents a user in the Facebook. - -* **`group`** entity represents the Facebook group, and it has several relations including member, admin, and moderator to represent the members, admins, and moderators of the group. Additionally, there are relations to represent the posts and comments in the group. - -* **`post`** entity represents a post in the Facebook group, and it has relations to represent the owner of the post and the group that the post belongs to. - -* **`comment`** entity represents a comment on a post in the Facebook group, and it has relations to represent the owner of the comment, the post that the comment belongs to, and the comment itself. - -* **`like`** entity represents a like on a post in the Facebook group, and it has relations to represent the owner of the like and the post that the like belongs to. - -* **`poll`** entity represents a poll in the Facebook group, and it has relations to represent the owner of the poll and the group that the poll belongs to. - -* **`file`** entity represents a file in the Facebook group, and it has relations to represent the owner of the file and the group that the file belongs to. - -* **`event`** entity represents an event in the Facebook group, and it has relations to represent the owner of the event and the group that the event belongs to. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. - -For example, the `create_group` action can only be performed by a `member`, as follows: - -#### Creating a group permission - -```perm -entity group { - - // Relation to represent the members of the group - relation member @user - - .. - - // Create group permission - action create_group = member - - .. - .. -} -``` - -Another example would be given from the `edit_post` action in the post entity, which specifies the permissions required to edit a post in a Facebook group. - -#### Editing a post permission - -```perm -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - .. - - action edit_post = owner or group.admin - - .. - .. -} -``` - -An **owner** of a post can always edit their own post. In addition, members who are defined as **admin** of the group - which the post belongs to - can also edit the post. - -Since most entities are deeply nested together, we also have multiple hierarchical permissions. - -#### Nested Hierarchies - -For example, we can define a permission "view_comment" if only user is owner of that comment or user is a member of the group which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -//group relationships -group:1#member@user:1 -group:1#admin@user:2 -group:2#moderator@user:3 -group:2#member@user:4 -group:1#member@user:5 - -//post relationships -post:1#owner@user:1 -post:1#group@group:1 -post:2#owner@user:4 -post:2#group@group:1 - -//comment relationships -comment:1#owner@user:2 -comment:1#post@post:1 -comment:2#owner@user:5 -comment:2#post@post:2 - -//like relationships -like:1#owner@user:3 -like:1#post@post:1 -like:2#owner@user:4 -like:2#post@post:2 - -//poll relationships -poll:1#owner@user:2 -poll:1#group@group:1 -poll:2#owner@user:5 -poll:2#group@group:1 - -//like relationships -file:1#owner@user:1 -file:1#group@group:1 - -//event relationships -event:1#owner@user:3 -event:1#group@group:1 -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:4 RSVP_to_event event:1 ? -

- -```perm - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - - .. - .. - - action RSVP_to_event = owner or group.member - } -``` - -According to what we have defined for the **'RSVP_to_event'** action, users who are either the owner of `event:1` or a member of the group that belongs to `event:1` can grant access to RSVP to the event. - -According to the relation tuples we created, `user:4` is not the **owner** of the event. Furthermore, when we check whether `user:4` is a **member** of the only group (`group:1`) that `event:1` is part of (`event:1#group@group:1`), we see that there is no **member** relation for `user:4` in that group. - -Therefore, the `user:4 RSVP_to_event event:1` check request should yield a **'false'** response. - -

-
- -
can user:5 view_comment comment:1 ? -

- -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -According to the relation tuples we created, `user:5` is not the **owner** of the comment. But member of the `group:1` and thats grant `user:5` (`group:1#member@user:5`) access to perform view the comment:1. In particularly, `comment:1` is part of the `post:1` (`comment:1#post@post:1`) and `post:1` is part of the group:1 (`post:1#group@group:1`). And from the action definition on above model group:1 members can view the `comment:1`. - -Therefore, the `user:5 view_comment comment:1` check request should yield a **'true'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator - } - - entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member - } - - entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner - } - - entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member - } - - entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin - } - - entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin - } - - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member - } - -relationships: - - group:1#member@user:1 - - group:1#admin@user:2 - - group:2#moderator@user:3 - - group:2#member@user:4 - - group:1#member@user:5 - - post:1#owner@user:1 - - post:1#group@group:1 - - post:2#owner@user:4 - - post:2#group@group:1 - - comment:1#owner@user:2 - - comment:1#post@post:1 - - comment:2#owner@user:5 - - comment:2#post@post:2 - - like:1#owner@user:3 - - like:1#post@post:1 - - like:2#owner@user:4 - - like:2#post@post:2 - - poll:1#owner@user:2 - - poll:1#group@group:1 - - poll:2#owner@user:5 - - poll:2#group@group:1 - - file:1#owner@user:1 - - file:1#group@group:1 - - event:1#owner@user:3 - - event:1#group@group:1 - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "event:1" - subject: "user:4" - assertions: - RSVP_to_event : false - - entity: "comment:1" - subject: "user:5" - assertions: - view_comment : true -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233152003-1fbaf2af-d208-4290-af1f-359870b0de49.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/google-docs.md b/docs/versioned_docs/version-0.5.x/getting-started/examples/google-docs.md deleted file mode 100644 index 3f14e1f7..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/google-docs.md +++ /dev/null @@ -1,344 +0,0 @@ -# Google Docs Simplified - -This example models a simplified version of Google Docs style permission system where users can be granted direct access to a document, or access via organizations and nested groups. - -### Schema | [Open in playground](https://play.permify.co/?s=iuRic3nR1HeZJcFyRNKPo) - -```perm -entity user {} - -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} - -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} - -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -## Breakdown of the Model - -### User - -```perm -entity user {} -``` - -Represents a user who can be granted permission to access a documents directly, or through their membership in a group or organization. - -### Document - -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -Represents a document that users can be granted permission to access. The document entity has two relationships: - -#### Relations - -**org:** Represents organization that document belongs to. - -**manager:** A relationship between users who are authorized to manage the document. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group member and manager relations. - -**viewer:** A relationship between users who are authorized to view the document. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager relations. - -The document entity has two actions defined: - -#### Actions - -**manage:**: An action that can be performed by users who are authorized to manage the document, as determined by the manager relationship. - -**view:** An action that can be performed by users who are authorized to view the document, as determined by the viewer and manager relationships. - -### Group - -```perm -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} -``` - -Represents a group of users who can be granted permission to access a document. The group entity has two relationships: - -#### Relations - -**manager:** A relationship between users who are authorized to manage the group. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are members of the group. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager. - -The group entity has one action defined: - -### Organization - -```perm -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} -``` - -Represents an organization that can contain groups, users, and documents. The organization entity has several relationships: - -#### Relations - -**group:** A relationship between the organization and its groups. This relationship is defined by the `@group` annotation on the end corresponding to the group entity. - -**document:** A relationship between the organization and its document. This relationship is defined by the `@document` annotation on the end corresponding to the group entity. - -**administrator:** A relationship between users who are authorized to manage the organization. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are directly members of the organization. This relationship is defined by the `@user` annotation on the end corresponding to the user entity. - -The organization entity has two permissions defined: - -#### Permissions - -**admin:** An permission that can be performed by users who are authorized to manage the organization, as determined by the administrator relationship. - -**member:** An permission that can be performed by users who are directly members of the organization, or who have administrator relationship, or who are members of groups that are part of the organization, - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different groups -group:tech#manager@user:ashley -group:tech#direct_member@user:david -group:marketing#manager@user:john -group:marketing#direct_member@user:jenny -group:hr#manager@user:josh -group:hr#direct_member@user:joe - -// Assign groups to other groups -group:tech#direct_member@group:marketing#direct_member -group:tech#direct_member@group:hr#direct_member - -// Connect groups to organization -organization:acme#group@group:tech -organization:acme#group@group:marketing -organization:acme#group@group:hr - -// Add some documents under the organization -organization:acme#document@document:product_database -organization:acme#document@document:marketing_materials -organization:acme#document@document:hr_documents - -// Assign a user and members of a group as administrators for the organization -organization:acme#administrator@group:tech#manager -organization:acme#administrator@user:jenny - -// Set the permissions on some documents -document:product_database#manager@group:tech#manager -document:product_database#viewer@group:tech#direct_member -document:marketing_materials#viewer@group:marketing#direct_member -document:hr_documents#manager@group:hr#manager -document:hr_documents#viewer@group:hr#direct_member -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:ashley edit document:product_database ? -

- -```perm - entity document { - relation org @organization - - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } -``` - -According what we have defined for the edit action managers and admins, of the organization that document belongs, can edit product database. In this context, Permify engine will check does subject `user:ashley` has any direct or indirect manager relation within `document:product_database`. Consecutively it will check does `user:ashley` has admin relation in the Acme Org - `organization:acme#document@document:product_database`. - -Ashley doesn't have any administrative relation in Acme Org but she is the manager in group tech (`group:tech#manager@user:ashley`) and we have defined that manager of group tech is manager of product_database with the tuple (`document:product_database#manager@group:tech#manager`). Therefore, the **user:ashley edit document:product_database** check request should yield **true** response. - -

-
- -
can user:joe view document:hr_documents ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check whether subject `user:joe` has any direct or indirect manager or viewer relation within `document:hr_documents`. Also consecutively it will check does `user:joe` has admin relation in the Acme Org - `organization:acme#document@document:hr_documents`. - -Joe doesn't have administrative role/relation in Acme Org. - -Also he doesn't have have manager relationship in that document or within any entity. - -But he is member in the hr group (`group:hr#member@user:joe`) and we defined hr members have viewer relationship in hr documents (`document:hr_documents#viewer@group:hr#member`). So that, this enforcement should yield **true** response. - -

-
- -
can user:david view document:marketing_materials ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check does subject `user:david` has any direct or indirect manager or viewer relation within `document:marketing_materials`. Also consecutively it will check does `user:david` has admin relation in the Acme Org - `organization:acme#document@document:marketing_materials`. - -Similar Joe and Ashley, David also doesn't have administrative role/relation in Acme Org. - -Also David doesn't have member or manager relationship related with marketing group - `document:marketing_materials`. So that, this enforcement should yield **false** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member - } - - entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager - } - - entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } - -relationships: - - group:tech#manager@user:ashley - - group:tech#direct_member@user:david - - group:marketing#manager@user:john - - group:marketing#direct_member@user:jenny - - group:hr#manager@user:josh - - group:hr#direct_member@user:joe - - - group:tech#direct_member@group:marketing#direct_member - - group:tech#direct_member@group:hr#direct_member - - - organization:acme#group@group:tech - - organization:acme#group@group:marketing - - organization:acme#group@group:hr - - organization:acme#document@document:product_database - - organization:acme#document@document:marketing_materials - - organization:acme#document@document:hr_documents - - organization:acme#administrator@group:tech#manager - - organization:acme#administrator@user:jenny - - - document:product_database#manager@group:tech#manager - - document:product_database#viewer@group:tech#direct_member - - document:marketing_materials#viewer@group:marketing#direct_member - - document:hr_documents#manager@group:hr#manager - - document:hr_documents#viewer@group:hr#direct_member - - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "document:product_database" - subject: "user:ashley" - assertions: - edit: true - - entity: "document:hr_documents" - subject: "user:joe" - assertions: - view: true - - entity: "document:marketing_materials" - subject: "user:david" - assertions: - view: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://github.com/Permify/permify/assets/39353278/85b96987-5932-4805-ac81-89820daad7e9) - -## Need any help ? - -This is the end of modeling Google Docs style permission system. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/mercury.md b/docs/versioned_docs/version-0.5.x/getting-started/examples/mercury.md deleted file mode 100644 index c755aafc..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/mercury.md +++ /dev/null @@ -1,74 +0,0 @@ -# Mercury - -Explore **Mercury's Authorization Schema** in this example, delving into the intricate interplay among **users**, **organizations**, and **accounts**. Uncover the defined user roles, approval workflows, and limits, providing a snapshot of the dynamic relationships within the Mercury ecosystem. - -## Schema | [Open in playground](https://play.permify.co/?s=mercury&tab=schema) - -```perm -entity user {} - -entity organization { - relation admin @user - relation member @user - - attribute admin_approval_limit integer - attribute member_approval_limit integer - attribute approval_num integer - - action approve = admin - action create_account = admin - - permission approval = (member and check_member_approval(approval_num, member_approval_limit)) or (admin and check_admin_approval(approval_num, admin_approval_limit)) -} - -entity account { - relation checkings @account - relation savings @account - - relation owner @organization - - attribute withdraw_limit double - attribute balance double - - action withdraw = check_balance(balance, request.amount) and (check_limit(withdraw_limit, request.amount) or owner.approval) -} - -rule check_balance(balance double, amount double) { - balance >= amount -} - -rule check_limit(withdraw_limit double, amount double) { - withdraw_limit >= amount -} - -rule check_admin_approval(approval_num integer, admin_approval_limit integer) { - approval_num >= admin_approval_limit -} - -rule check_member_approval(approval_num integer, member_approval_limit integer) { - approval_num >= member_approval_limit -} -``` - -## Brief Examination of the Model - -Mercury's authorization model consists of three primary entities: **user**, **organization**, and **account**. -These entities are interconnected through defined relations and governed by specific rules and actions. - -### Entities & Relations - -**user**: Represents individual users within the system. - -**organization**: Represents organizational entities and establishes relations with users (`admin` and `member`). Additionally, this entity holds attributes like `admin_approval_limit`, `member_approval_limit`, and `approval_num`. - -**account**: Represents user accounts with relations to different account types (`checkings` and `savings`). It also has a relation to the owning `organization` and attributes such as `withdraw_limit` and `balance`. - -### Permissions - -The authorization schema defines two crucial permissions: - -**approval**: Determines the conditions under which a user (either `member` or `admin`) can approve actions based on approval limits. - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. diff --git a/docs/versioned_docs/version-0.5.x/getting-started/examples/notion.md b/docs/versioned_docs/version-0.5.x/getting-started/examples/notion.md deleted file mode 100644 index 9095d464..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/examples/notion.md +++ /dev/null @@ -1,548 +0,0 @@ -# Notion - -This is a schema definition of the authorization model for Notion, a popular productivity and organization tool. - -### Schema | [Open in playground](https://play.permify.co/?s=BsCvLmd4g81sB20XJZI5p) - -```perm -entity user {} - -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin -} - -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write -} - -entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write -} - -entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter -} - -entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author -} - -entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write -} - -entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write -} -``` - -## Brief Examination of the Model - -The model defines several entities, including users, workspaces, pages, databases, blocks, and integrations. It also includes several default roles, such as Admin, Bot, Guest, and Member. Here's a breakdown of the entities: - -### Entities & Relations - -- **`user`**: Represents a user in the system. - -- **`workspace`**: Represents a workspace in which users can collaborate. Each workspace has an owner, members, guests, and bots associated with it. The owner and admin users have permission to manage the workspace. Permissions are defined for creating pages, inviting members, viewing the workspace, and managing the workspace. The read and write permissions can be inherited by child entities. - -- **`page`**: Represents a page within a workspace. Each page is associated with a workspace and has a writer and readers. The read and write permissions are defined based on the writer and readers of the page and can be inherited from the workspace. - -- **`database`**: Represents a database within a workspace. Each database is associated with a workspace and has an editor and viewers. The read and write permissions are defined based on the editor and viewers of the database and can be inherited from the workspace. Permissions are also defined for creating and deleting databases. - -- **`block`**: Represents a block within a page or database. Each block is associated with a page or database and has an editor and commenters. The read and write permissions are defined based on the editor and commenters of the block and can be inherited from the database. Commenters are users who have permission to comment on the block. - -- **`comment`**: Represents a comment on a block. Each comment is associated with a block and has an author. The read and write permissions are defined based on the author of the comment and can be inherited from the block. - -- **`template`**: Represents a template within a workspace. Each template is associated with a workspace and has a creator and viewers. The read and write permissions are defined based on the creator and viewers of the template and can be inherited from the workspace. Permissions are also defined for creating and deleting templates. - -- **`integration`**: Represents an integration within a workspace. Each integration is associated with a workspace and has an owner. Permissions are defined for reading and writing to the integration. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. Let's examine the **read** permission of the page entity. - -#### Page Read Permission - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - - .. - .. - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - .. -} - -entity page { - - // The workspace associated with the page - relation workspace @workspace - - .. - .. - - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - .. - .. - - // Define permissions for page actions - permission read = reader or workspace.read - - .. - .. -} -``` - -This permission specifies who can read the contents of the page at Notion. - -The `reader` relation specifies the users who are members of the workspace associated with the page (`workspace#member`) or guests of the workspace (`workspace#guest`). - -Read permission of the workspace inherited as `workspace.read` in the page entity. THis permission specifies that any user who has been granted read access to the workspace object (i.e., the workspace that the page belongs to) can also read the page. - -In summary, any user who is a member or guest of the workspace and has been granted read access to the page through the reader relation, as well as any user who has been granted read access to the workspace itself, can read the contents of the page. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different workspaces: -workspace:engineering_team#owner@user:alice -workspace:engineering_team#member@user:bob -workspace:engineering_team#guest@user:charlie -workspace:engineering_team#admin@user:alice -workspace:sales_team#owner@user:david -workspace:sales_team#member@user:eve -workspace:sales_team#guest@user:frank -workspace:sales_team#admin@user:david - -// Connect pages, databases, and templates to workspaces: -page:project_plan#workspace@workspace:engineering_team -page:product_spec#workspace@workspace:engineering_team -database:task_list#workspace@workspace:engineering_team -template:weekly_report#workspace@workspace:sales_team -database:customer_list#workspace@workspace:sales_team -template:marketing_campaign#workspace@workspace:sales_team - -// Set permissions for pages, databases, and templates: -page:project_plan#writer@user:frank -page:project_plan#reader@user:bob - -database:task_list#editor@user:alice -database:task_list#viewer@user:bob - -template:weekly_report#creator@user:alice -template:weekly_report#viewer@user:bob - -page:product_spec#writer@user:david -page:product_spec#reader@user:eve - -database:customer_list#editor@user:david -database:customer_list#viewer@user:eve - -template:marketing_campaign#creator@user:david -template:marketing_campaign#viewer@user:eve - -// Set relationships for blocks and comments: -block:task_list_1#database@database:task_list -block:task_list_1#editor@user:alice -block:task_list_1#commenter@user:bob -block:task_list_2#database@database:task_list -block:task_list_2#editor@user:alice -block:task_list_2#commenter@user:bob - -comment:task_list_1_comment_1#block@block:task_list_1 -comment:task_list_1_comment_1#author@user:bob -comment:task_list_1_comment_2#block@block:task_list_1 -comment:task_list_1_comment_2#author@user:charlie -comment:task_list_2_comment_1#block@block:task_list_2 -comment:task_list_2_comment_1#author@user:bob -comment:task_list_2_comment_2#block@block:task_list_2 -comment:task_list_2_comment_2#author@user:charlie -``` - -## Test & Validation - -Since we have our schema and the sample relation tuples, let's check some permissions and test our authorization logic. - -
can user:alice write database:task_list ? -

- -```perm - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - - .. - .. - - // Define permissions for database actions - .. - .. - - permission write = editor or workspace.write - - .. - .. - } -``` - -According to what we have defined for the **'write'** permission, users who are either; - -- The editor in task list database (`database:task_list`) -- Have a write permission in the engineering team workspace, which is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`) - -can edit the task list database (`database:task_list`) - -Based on the relation tuples we created, `user:alice` doesn't have the **editor** relationship with the `database:task_list`. - -Since `user:alice` is the owner and admin in the engineering team workspace (`workspace:engineering_team#admin@user:alice`) it has a write permission defined in the workspace entity, as you can see below: - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - .. - .. - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - // Define permissions that can be inherited by child entities - .. - permission write = owner or admin -} -``` - -And as we mentioned the engineering team workspace is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`). Therefore, the `user:alice write database:task_list` check request should yield a **'true'** response. - -

-
- -
can user:charlie write page:product_spec ? -

- -```perm -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - - .. - .. - - permission write = writer or workspace.write -} -``` - -`user:charlie` is guest in the workspace (`workspace:engineering_team#guest@user:charlie`) and the engineering team workspace is the only workspace that `page:product_spec` belongs to. - -As we defined, guests doesn't have write permission in a workspace. - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - permission write = owner or admin -} -``` - -So that, `user:charlie` doesn't have a write relationship in the workspace. And ultimately, the `user:charlie write page:product_spec` check request should yield a **'false'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin - } - - entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write - } - - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write - } - - entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter - } - - entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author - } - - entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write - } - - entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write - } - -relationships: - - workspace:engineering_team#owner@user:alice - - workspace:engineering_team#member@user:bob - - workspace:engineering_team#guest@user:charlie - - workspace:engineering_team#admin@user:alice - - workspace:sales_team#owner@user:david - - workspace:sales_team#member@user:eve - - workspace:sales_team#guest@user:frank - - workspace:sales_team#admin@user:david - - page:project_plan#workspace@workspace:engineering_team - - page:product_spec#workspace@workspace:engineering_team - - database:task_list#workspace@workspace:engineering_team - - template:weekly_report#workspace@workspace:sales_team - - database:customer_list#workspace@workspace:sales_team - - template:marketing_campaign#workspace@workspace:sales_team - - page:project_plan#writer@user:frank - - page:project_plan#reader@user:bob - - database:task_list#editor@user:alice - - database:task_list#viewer@user:bob - - template:weekly_report#creator@user:alice - - template:weekly_report#viewer@user:bob - - page:product_spec#writer@user:david - - page:product_spec#reader@user:eve - - database:customer_list#editor@user:david - - database:customer_list#viewer@user:eve - - template:marketing_campaign#creator@user:david - - template:marketing_campaign#viewer@user:eve - - block:task_list_1#database@database:task_list - - block:task_list_1#editor@user:alice - - block:task_list_1#commenter@user:bob - - block:task_list_2#database@database:task_list - - block:task_list_2#editor@user:alice - - block:task_list_2#commenter@user:bob - - comment:task_list_1_comment_1#block@block:task_list_1 - - comment:task_list_1_comment_1#author@user:bob - - comment:task_list_1_comment_2#block@block:task_list_1 - - comment:task_list_1_comment_2#author@user:charlie - - comment:task_list_2_comment_1#block@block:task_list_2 - - comment:task_list_2_comment_1#author@user:bob - - comment:task_list_2_comment_2#block@block:task_list_2 - - comment:task_list_2_comment_2#author@user:charlie - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "database:task_list" - subject: "user:alice" - assertions: - write: true - - entity: "page:product_spec" - subject: "user:charlie" - assertions: - write: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233154924-c31a76f4-86f5-4ed3-a1ec-750b642927e6.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/getting-started/modeling.md b/docs/versioned_docs/version-0.5.x/getting-started/modeling.md deleted file mode 100644 index 4a0a8316..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/modeling.md +++ /dev/null @@ -1,555 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Modeling Authorization - -Permify was designed and structured as a true ReBAC solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -With Permify, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. - -This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager, member and also dynamic attributes such as boolean variables, IP range, time period, etc. - -![modeling-authorization](https://raw.githubusercontent.com/Permify/permify/master/assets/permify-dsl.gif) - -You can define your entities, relations between them and access control decisions with using Permify Schema. It includes set-algebraic operators such as intersection and union for specifying potentially complex access control policies in terms of those user-object relations. - -Hereโ€™s a simple breakdown of our schema. - -![permify-schema](https://user-images.githubusercontent.com/34595361/183866396-9d2850fc-043f-4254-aa4c-ee2c4172afb8.png) - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is **_".perm"_**. - -## Developing a Schema - -This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. - -We'll follow a simplified version of the GitHub access control system, where teams and organizations have control over the viewing, editing, or deleting access rights of repositories. - -Before start I want to share the full implementation of simple Github access control example with using Permify Schema. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity team { - - relation parent @organization - relation member @user - - action edit = member or parent.admin - -} - -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - action push = owner or maintainer - action read = org.admin and (owner or maintainer or org.member) - action delete = parent.admin or owner - -} -``` - -:::info -You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. - -[vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm - -::: - -## Defining Entities - -The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. - -Think of entities as tables in your database. We are strongly advice to name entities same as your database table name that its corresponds. In that way you can easily model and reason your authorization as well as eliminating the error possibility. - -You can create entities using `entity` keyword. Let's create some entities according to our example GitHub authorization logic." - -```perm -entity user {} - -entity organization {} - -entity team {} - -entity repository {} -``` - -Entities has 2 different attributes. These are; - -- **relations** -- **actions or permissions** - -## Defining Relations - -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. - -Keyword **_relation_** need to used to create a entity relation with name and type attributes. - -**Relation Attributes:** - -- **name:** relation name. -- **type:** relation type, basically the entity itโ€™s related to (e.g. user, organization, document, etc.) - -An example relation takes form of, - -```perm -relation [name] @[type] -``` - -Lets turn back to our example and define our relations inside our entities: - -#### User Entity - -โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. - -```perm -entity user {} -``` - -### Roles and User Types - -You can define user types and roles within the entity. If you specifically want to define a global role, such as `admin`, we advise defining it at the entity with the most global hierarchy, such as an organization. Then, spread it to the rest of the entities to include it within permissions. - -For the sake of simplicity, let's define only 2 user types in an organization, these are administrators and direct members of the organization. - -```perm -entity organization { - - relation admin @user - relation member @user - -} -``` - -### Parent-Child Relationship - -โ†’ Let's say teams can belong organizations and can have a member inside of it as follows, - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -entity team { - - relation parent @organization - relation member @user - -} -``` - -The parent relation is indicating the organization the team belongs to. This way we can achieve **parent-child relationship** within these entities. - -### Ownership - -In Github workflow, organizations and users can have multiple repositories, so each repository is related with an organization and with users. We can define repository relations as as follows. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - -} -``` - -The owner relation indicates the creator of the repository, that way we can achieve **ownership** in Permify. - -### Multiple Relation Types - -As you can see we have new syntax above, - -```perm - relation maintainer @user @team#member -``` - -When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. - -:::info -You can use **#** to reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. - -Actual purpose of feature locking is to giving ability to specify the sets of users that can be assigned. - -For example: - -```perm - relation viewer @user -``` - -When you define it like this, you can only add users directly as tuples (you can find out what relation tuples is in next section): - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 - -However, if you define it as: - -```perm - relation viewer @user @organization#member -``` - -You will then be able to specify not only individual users but also members of an organization: - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 -- organization:1#viewer@organization:O1#member - -You can think of these definitions as a precaution taken against creating undesired user set relationships. -::: - -Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. - -## Defining Actions and Permissions - -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. - -The basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. - -### Intersection and Exclusion - -The Permify Schema supports **`and`**, **`or`** and **`not`** operators to achieve permission **intersection** and **exclusion**. The keywords **_action_** or **_permission_** can be used with those operators to form rules for your authorization logic. - -#### Intersection - -Lets get back to our github example and create a read action on repository entity to represent usage of **`and`** &, **`or`** operators, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - .. - .. - - action read = org.admin and (owner or maintainer or org.member) - -} -``` - -โ†’ If we examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. - -:::info Permission Keyword -The same `read` can also be defined using the **permission** keyword, as follows: - -```perm - permission read = org.admin and (owner or maintainer or org.member) -``` - -Using `action` and `permission` will yield the same result for defining permissions in your authorization logic. See why we have 2 keywords for defining an permission from the [Nested Hierarchies](#nested-hierarchies) section. -::: - -#### Exclusion - -After this point, we'll move beyond the GitHub example and explore more advanced abilities of Permify DSL. - -Before delving into details, let's examine the **`not`** operator and conclude [Intersection and Exclusion](#intersection-and-exclusion) section. - -Here is the **post** entity from our sample [Instagram Authorization Structure](./examples/google-docs.md)example, - -```perm -entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - .. - .. - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted -} -``` - -As you can see from the comment and like actions, a user tagged with the `restricted` attribute โ€” details of defining attributes can be found in the [Attribute Based Permissions (ABAC)](#attribute-based-permissions-abac) section โ€” won't be able to like or comment on the specific post. - -This is a simple example to demonstrate how you can exclude users, resources, or any subject from permissions using the **`not`** operator. - -### Permission Union - -Permify allows you to set permissions that are effectively the union of multiple permission sets. - -You can define permissions as relations to union all rules that permissions have. Here is an simple demonstration how to achieve permission union in our DSL, you can use actions (or permissions) when defining another action (or permission) like relations, - -```perm - action edit = member or manager - action delete = edit or org.admin -``` - -The `delete` action inherits the rules from the `edit` action. By doing that, we'll be able to state that only organization administrators and any relation capable of performing the edit action (member or manager) can also perform the delete action. - -Permission union is super beneficial in scenarios where a user needs to have varied access across different departments or roles. - -### Nested Hierarchies - -The reason we have two keywords for defining permissions (`action` and `permission`) is that while most permissions are based on actions (such as view, read, edit, etc.), there are still cases where we need to define permissions based on roles or user types, such as admin or member. - -Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. - -Here is a simple example to demonstrate inherited permissions. - -Let's examine a small snippet from our [Facebook Groups](./examples/google-docs.md) real world example. Let's create a permission called 'view' in the comment entity (which represents the comments of the post in Facebook Groups) - -Users can only view a comment if: - -- The user is the owner of that comment -**or** -- The user is a member of the group to which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -### Recursive ReBAC - -With Permify DSL, you can define recursive relationship-based permissions within the same entity. - -As an example, consider a system where there are multiple organizations within a company, some of which may have a parent-child relationship between them. - -As expected, organization members are also granted permission to view their organization details. You can model that as follows: - -```perm -entity user {} - -entity organization { - relation parent @organization - relation member @user @organization#member - - action view = member or parent.member -} -``` - -Let's extend the scenario by adding a rule allowing parent organization members to view details of child organizations. Specifically, a member of **Organization Alpha** could view the details of **Organization Beta** if **Organization Beta** belongs to **Organization Alpha**. - -![modeling-authorization](https://user-images.githubusercontent.com/58391988/279456032-485a0aef-b83b-4257-af48-0fcbe6fa2e64.png) - -First authorization schema that we provide won't solve this issue because `parent.member` accommodate single upward traversal in a hierarchy. - -Instead of `parent.member` we can call the parent view permission on the same entity - `parent.view` to achieve multiple levels of upward traversal, as follows: - -```perm -entity user {} - -entity organization { - relation parent @organization - relation member @user @organization#member - - action view = member or parent.view -} -``` - -This way, we achieve a recursive relationship between parent-child organizations. - -:::note -*Credits to [Lรฉo](https://github.com/LeoFVO) for the illustration and for [highlighting](https://github.com/Permify/permify/issues/790) this use case.* -::: - -## Attribute Based Permissions (ABAC) - -:::success Beta -Please keep in mind that this feature is still in the **beta stage**, and we're actively seeking user feedback to improve it. As a Beta feature, Permify ABAC support may have some limitations, and its functionality and interface could change in future updates. -::: - - -To support Attribute Based Access Control (ABAC) in Permify, we've added two main components into our DSL: **attributes** and **rules**. - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```perm -attribute ip_range string[] -``` - -There are different types of attributes you can use; - -### Boolean - True/False Conditions - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```perm -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: - -### Text & Object Based Conditions - -String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: - -:::info Defining Rules - -In above we defined a function called with **rule** keyword. - -Rules are structures that allow you to write specific conditions for the model. They accept parameters and are based on conditions. - -Another example, a rule could be used to check if a given IP address falls within a specified IP range: - -```perm -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` -::: - -### Numerical Conditions - -#### Integers - -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```perm -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: - -#### Double - Precise numerical information - -Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```perm -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: - -See more details on [Attribute Based Access Control](#attribute-based-permissions-abac) section to learn our approach on ABAC as well as how it operates in Permify. - -## More Advanced Examples - -You can check out more advanced and completed schema examples from the [Real World Examples](https://docs.permify.co/docs/getting-started/examples/) section with their detailed examination. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/getting-started/sync-data.md b/docs/versioned_docs/version-0.5.x/getting-started/sync-data.md deleted file mode 100644 index b8726a02..00000000 --- a/docs/versioned_docs/version-0.5.x/getting-started/sync-data.md +++ /dev/null @@ -1,456 +0,0 @@ ---- -sidebar_position: 2 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Managing Authorization Data - -Permify unifies your authorization data in a database of your preference, which serves as the single source of truth for all authorization queries and requests via the Permify API. - -## Access Control as Relations - Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -In Permify, the simplest form of relational tuple structured as: `entity # relation @ user`. Here are some relational tuples with semantics, - -![relational-tuples](https://user-images.githubusercontent.com/34595361/183959294-149fcbb9-7f10-4c1e-8d66-20a839893909.png) - -## Where Relational Tuples Used ? - -In Permify, these relational tuples represents your authorization data. - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure the database when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -Stored relational tuples are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user's action is authorized. - -As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, or even a role such as an admin or manager. - -## Creating Relational Tuples - -Relational tuples can be created with an simple API call in runtime, since relations and authorization data's are live instances. Each relational tuple should be created according to its authorization model, [Permify Schema]. - -[Permify Schema]: ../modeling - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -Let's follow a simple document management system example with the following Permify Schema to see how to create relation tuples. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - -} - -entity document { - - relation owner @user - relation parent @organization - relation maintainer @user @organization#member - - action view = owner or parent.member or maintainer or parent.admin - action edit = owner or maintainer or parent.admin - action delete = owner or parent.admin -} -``` - -According to the schema above; when a user creates a document in an organization, more specifically let's say, when user:1 create a document:2 we need to create the following relational tuple, - -- `document:2#owner@user:1` - -### Write Data API - -You can create relational tuples by using `Write Data API`. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "2", - }, - Relation: "owner", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "2" - }, - relation: "owner", - subject: { - type: "user", - id: "1" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "2s" - }, - "relation": "owner", - "subject":{ - "type": "user", - "id": "1", - "relation": "" - } - } - ] -}' -``` - - - -### Snap Tokens - -In Write Data API response you'll get a snap token of the operation. - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../reference/snap-tokens.md) - -## More Examples - -Let's create more example data according to the schema we defined above. - -### Organization Admin - -**relational tuple:** organization:1#admin@user:3 - -**Semantics:** User 3 is administrator in organization 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "user", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -### Parent Organization - -**Relational Tuple:** document:1#parent@organization:1#โ€ฆ - -**Semantics:** Organization 1 is parent of document 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "parent", - Subject: & v1.Subject { - Type: "organization", - Id: "1", - Relation: "..." - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "parent", - subject: { - type: "organization", - id: "1", - relation: "..." - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "parent", - "subject":{ - "type": "organization", - "id": "1", - "relation": "..." - } - } - ] -}' -``` - - - -:::info -Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. - -Simply, the usage of ... is straightforward: if you're use user entity as an subject, you should not be using the `...` If you're using another subject rather than user entity then you need to use the `...` -::: - -### Organization Members Are Maintainers in specific Doc - -**Created relational tuple:** document:1#maintainer@organization:2#member - -**Definition:** Members of organization 2 are maintainers in document 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "maintainer", - Subject: & v1.Subject { - Type: "organization", - Id: "2", - Relation: "member" - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "maintainer", - subject: { - type: "organization", - id: "2", - relation: "member" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "maintainer", - "subject":{ - "type": "organization", - "id": "2", - "relation": "member" - } - } - ] -}' -``` - - - -#### Test this Example on [Playground](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_) - -## Audit Logs For Permission Changes - -Permify does support audit logs for permission changes. Leveraging the [MVCC (Multi-Version Concurrency Control)](http://mbukowicz.github.io/databases/2020/05/01/snapshot-isolation-in-postgresql.html) pattern, we maintain a history of all permission data changes. This essentially provides an audit trail, allowing users to track alterations and when they occurred. - -In cloud version, our system supports change history auditing. It automatically generates and securely stores logs for all significant actions. These logs detail who made the change, what was changed, and when the change occurred. Furthermore, your system allows for easy searching and analysis of these logs, supporting automated alerting for suspicious activities. This comprehensive approach ensures thorough and effective auditing of all changes - -## Permission Baselining (Reviewing) - -We have a strong foundation for permission baselining and review, thanks to MVCC. - -**Historical Review:** You can review the history of permissions changes as each version is stored. This enables retrospective audits and analysis. - -**Current State Review:** You can review the current state of permissions by examining the latest versions of each permission setting. - -**Cleanup:** Your system incorporates a garbage collector for managing old data, which helps keep your permissions structure clean and optimized. - -## Next - -Let's now head over to the **Access Control Check** section and learn how to perform access control in Permify to ensure that only authorized users have the right level of access to our resources. diff --git a/docs/versioned_docs/version-0.5.x/installation.md b/docs/versioned_docs/version-0.5.x/installation.md deleted file mode 100644 index c855058f..00000000 --- a/docs/versioned_docs/version-0.5.x/installation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: installation -title: Setup Permify -slug: /installation ---- - -# Setup Permify - -Here is some options that you can use to set up and deploy Permify in your servers. - -```mdx-code-block -import {CardList} from '../../src/components/Card'; - - -``` - -If options your deployment preference is not listed below please let us know Also if you have any questions join our [Discord community](https://discord.gg/n6KfzYxhPp) or send us an email at support@permify.co. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/installation/_category_.json b/docs/versioned_docs/version-0.5.x/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.5.x/installation/aws.md b/docs/versioned_docs/version-0.5.x/installation/aws.md deleted file mode 100644 index c1c204d9..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/aws.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: AWS ECS, ECR & EC2 ---- - -# ย Deploy on AWS ECS, ECR & EC2 - -AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. - -There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). - -ECS is a container management service. You can run your containers as task definitions, and Itโ€™s one of the easiest ways to deploy containers. - -If youโ€™d like to watch this tutorial rather than reading. Hereโ€™s the video version. - - - -There is no prerequisite in this tutorial. You can simply deploy permify by following this step-by-step guide. However, if you want to integrate more advanced AWS security & networking features, weโ€™ll follow up with a new tutorial guideline. - -At the end of this tutorial youโ€™ll be able to; - -1. [Create a security group](#create-an-ec2-security-group) -2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) -3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) -4. [Running our task definition](#4-running-our-task-definition) - -## 1. Create an EC2 Security Group - -So first thing first, letโ€™s go over into security groups and create our security group. Weโ€™ll need this security group while creating our cluster. - -![security-group-1](https://user-images.githubusercontent.com/34595361/208877994-e9461acc-4ffd-4591-b43e-db254366d25d.png) - -Search for โ€œSecurity Groupsโ€ in the search bar. And go to the EC2 security groups feature. - -![security-group-2](https://user-images.githubusercontent.com/34595361/208877493-ab11228c-1aa0-4bc5-b41d-4527737028e9.png) - -Then start creating a new security group. - -![security-group-3](https://user-images.githubusercontent.com/34595361/208877500-2c299883-6107-4b70-aa96-0f28eb00cf3d.png) - -You have to name your security group, and give a description. Also, you need to choose the same VPC that youโ€™ll going to use in EC2. So, I choose the default one. And Iโ€™m going to use same one while creating the ECS cluster. - -The next step is to configure our inbound rules. Hereโ€™s the configuration; - -```json -//for mapping HTTP request port. -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for mapping RPC request port. -type = "Custom TCP", protocol = "TCP", port_range = "3478",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for using SSH for connecting from your local computer. -type = "Custom TCP", protocol = "TCP", port_range = "22",source = "Anywhere", 0.0.0.0/0 -``` - -We have configured the HTTP and RPC ports for Permify. Also, we added port โ€œ22โ€ for SSH connection. So, we can connect to EC2 through our local terminal. - -Now, weโ€™re good to go. You can create the security group. And itโ€™s ready to use in our ECS. - -## 2. Creating an ECS cluster - -![create-ecs-cluster-1](https://user-images.githubusercontent.com/34595361/208878666-98c5d3ce-b079-444d-bc66-53f13038a08a.png) - -The next step is to create an ECS cluster. From your AWS console search for Elastic Container Service or ECS for short. - -![create-ecs-cluster-2](https://user-images.githubusercontent.com/34595361/208878675-2f266cfc-defb-4c7f-9186-b4de39f1743b.png) - -Then go over the clusters. As you can see there are 2 types of clusters. One is for ECS and another for EKS. We need to use ECS, EKS stands for Elastic Kubernetes Service. Today weโ€™re not going to cover Kubernetes. - -Click **โ€œCreate Clusterโ€** - -![create-ecs-cluster-3](https://user-images.githubusercontent.com/34595361/208878685-3edac67b-5b3d-4f0d-b2f7-70a5ec2e4870.png) - -Letโ€™s create our first Cluster. Simply you have 3 options; Serverless(Network Only), Linux, and Windows. Weโ€™re going to cover EC2 Linux + Networking option. - -![create-ecs-cluster-4](https://user-images.githubusercontent.com/34595361/208878681-d98a77db-16b1-42af-a697-3036cc604c85.png) - -The next step is to configure our Cluster, starting with your Cluster name. Since weโ€™re deploying Permify, Iโ€™ll call it โ€œpermifyโ€. - -Then choose your instance type. You can take a look at different instances and pricing from [here](https://aws.amazon.com/ec2/pricing/on-demand/). Iโ€™m going with the t4 large. For cost purposes, you can choose t2.micro if youโ€™re just trying out. Itโ€™s free tier eligible. - -Also, if you want to connect this EC2 instance from your local computer. You need to use SSH. Thus choose a key pair. If you have no such intention, leave it โ€œnoneโ€. - -![create-ecs-cluster-5](https://user-images.githubusercontent.com/34595361/208878989-801839f5-8fce-4410-99e0-0a2dcccb47fa.png) - -Now, we need to configure networking. First, choose your VPC, we use the default VPC as we did in the security groups. And choose any subnet on that VPC. - -You want to enable auto-assigned IP to make your app reachable from the internet. - -Choose the security group we have created previously. - -And voila, you can create your cluster. Now, we need to run our container in this cluster. To do that, letโ€™s go over task definitions. And create our container definition. - -## 3. Creating and running task definitions - -Go over to ECS, and click the task definitions. - -![create-run-task-1](https://user-images.githubusercontent.com/34595361/208879726-fe5aac07-16a8-4f8c-9cc9-1c95ca191a42.png) - -And create a new task definition. - -![create-run-task-2](https://user-images.githubusercontent.com/34595361/208879733-e9aa6fa4-9f66-44e4-8c70-dfa0e33c1b73.png) - -Again, youโ€™re going to ask to choose between; FARGATE, EC2, and EXTERNAL (On-premise). Weโ€™ll continue with EC2. - -Leave everything in default under the โ€œConfigure task and container definitionsโ€ section. - -![create-run-task-3](https://user-images.githubusercontent.com/34595361/208879735-789ec411-5829-47be-9634-c09c7b0c0320.png) - -Under the IAM role section you can choose โ€œecsTaskExecutionRoleโ€ if you want to use Cloud Watch later. - -You can leave task size in default since itโ€™s optional for EC2. - -The critical part over here is to add our container. Click on the โ€œAdd Containerโ€ button. - -![create-run-task-4](https://user-images.githubusercontent.com/34595361/208879740-4515e884-1efd-46fd-8e8c-cfa86634b673.png) - -Then we need to add our container details. First, give a name. And then the most important part is our image URI. Permify is registered on the Github Registry so our image is; - -```yaml -ghcr.io/permify/permify:latest -``` - -Then we need to define memory limit for the container, I went with 1024. You can define as much as your instance allows. - -Next step is to mapping our ports. As we mentioned in security groups, Permify by default listens; - -- `3476 for HTTP port` -- `3478 for RPC port` - -![create-run-task-5](https://user-images.githubusercontent.com/34595361/208879746-5991a04c-73d5-4e35-97b0-67aa9ebf61fc.png) - -Then we need to define command under the environment section. So, in order to start permify we first need to add โ€œserveโ€ command. - -For using properly we need a few other. Hereโ€™s the commands we need. - -```yaml -serve, --database-engine=postgres, --database-uri=postgres://:@:/, --database-pool-max=20 -``` - -- `serve` โ‡’ for starting the Permify. -- `--database-engine=postgres` โ‡’ for defining the db we use. -- `--database-uri=postgres://:password@:/` โ‡’ for connecting your database with URI. -- `--database-pool-max=20` โ‡’ the depth for running in graph. - -Weโ€™re nice and clear, add the container and then just create your task definition. Weโ€™ll use this definition to run in our cluster. - -So, letโ€™s go over and run our task definition. - -## 4. Running our task definition - -![run-task-definition-1](https://user-images.githubusercontent.com/34595361/208880326-c5ecb48c-e210-47f8-bd92-d1f789be24ff.png) - -Letโ€™s go to ECS and enter into our cluster. And go over into the tasks to run our task. - -![run-task-definition-2](https://user-images.githubusercontent.com/34595361/208880332-97a5732d-bc7d-401e-bae9-216d4273c5bf.png) - -Click to โ€œRun new Taskโ€ - -![run-task-definition-3](https://user-images.githubusercontent.com/34595361/208880335-b3ce229f-33ff-4f03-90e7-6d6a306928ae.png) - -Choose EC2 as a launch type. Then pick the task definition we just created. And leave everything else in the default. You can run your task now. - -We have just deployed our container into EC2 instance with ECS. Letโ€™s test it. - -Now you can go over into EC2, and click on the running instances. Find the instance named `ECS Instance - EC2ContainerService-` in the running instances. - -![run-task-definition-4](https://user-images.githubusercontent.com/34595361/208880339-a508354c-99ee-4219-8ace-1c7fdbbe90ed.png) - -Copy the Public IPv4 DNS from the right corner, and paste it into your browser. But you need to add `:3476` to access our http endpoint. So it should be like this; - -`:3476` - -and if you add healthz at the end like this; - -`:3476/healthz` - -you should get Serving status :) - -![run-task-definition-5](https://user-images.githubusercontent.com/34595361/208880346-d19a6877-3013-4347-86c9-9f865b8a3e3c.png) - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/installation/azure.md b/docs/versioned_docs/version-0.5.x/installation/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/installation/brew.md b/docs/versioned_docs/version-0.5.x/installation/brew.md deleted file mode 100644 index 0212df8b..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/brew.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "Install with Brew" ---- - -# Brew With Configurations - -This section shows how to install and run Permify Service using brew. - -### Install Permify - -Open terminal and run the following line, - -```shell -brew install permify/tap/permify -``` - -### Run Permify Service - -To run the Permify Service, `permify serve` command should be run. - -By default, the service is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather then an actual database. You can override these by running the command with configuration flags. - -### Configure By Using Flags - -See all the configuration flags by running, - -```shell -permify serve --help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Configure With Using Config File - -You can also configure Permify Service by using a configuration file. - -```shell - permify serve -c=config.yaml -``` - -or - -```shell - permify serve --config=config.yaml -``` - -### Test your connection. - -You can test your connection by making an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with a Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/installation/container.md b/docs/versioned_docs/version-0.5.x/installation/container.md deleted file mode 100644 index aa0b7686..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/container.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: "Docker Container" ---- - -# Run using Docker - -This section shows how to run Permify using our docker container. You can run Permify using Docker with following command. - -## Run in a terminal - -```shell -docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve -``` - -This will start a Permify server with the configuration that is in **{YOUR-CONFIG-PATH}**. - -### Configure with a YAML file - -This config path - `{YOUR-CONFIG-PATH}` - should contain the [config yaml file](../reference/configuration.md), where you can configure the Permify Server as well as define the ***database*** to store your authorization related data in. - -:::info Talk to an Permify Engineer -By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. -::: - -### Configure Using Flags - -Alternatively, you can set configuration options using flags when running the command. See all the configuration flags by running, - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Test your connection. - -You can test your connection by making an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with a Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/installation/google.md b/docs/versioned_docs/version-0.5.x/installation/google.md deleted file mode 100644 index deb30dc9..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/google.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -title: Deploy on Google Compute Engine ---- - -This guide outlines the process of deploying Permify, on Google Compute Engine. The steps include setting up Google Cloud SDK and kubectl, managing containers using Google Kubernetes Engine (GKE), deploying Permify, and implementing Permify in a distributed configuration with Serf. By following these steps, you can efficiently deploy Permify on Google's scalable and secure infrastructure. - -## Google Cloud SDK Install - -1. At the command line, run the following command: - - ```bash - curl https://sdk.cloud.google.com | bash - ``` - -2. When prompted, choose a location on your file system (usually your Home directory) to create theย `google-cloud-sdk`ย subdirectory under. -3. If you want to send anonymous usage statistics to help improve gcloud CLI, answerย `Y`ย when prompted. -4. To add gcloud CLI command-line tools to yourย `PATH`ย and enable command completion, answerย `Y`ย when prompted -5. Restart your shell: - - ```bash - exec -l $SHELL - ``` - -6. To initialize the Google Cloud CLI environment, run `gcloud init` - -## Install kubectl - -1. Install theย `kubectl`ย component: - - ```bash - gcloud components install kubectl - ``` - -2. Verify thatย `kubectl`ย is installed: - - ```bash - kubectl version - ``` - -3. Install Authn Plug-in - - ```bash - gcloud components install gke-gcloud-auth-plugin - ``` - - Check theย `gke-gcloud-auth-plugin`ย binary version: - - ```bash - gke-gcloud-auth-plugin --version - ``` - - -## Create Containers with GKE - -1. Login & Initialize Google Cloud CLI - - ```bash - gcloud init - ``` - -2. Follow configuration instructions -3. Create Container Cluster - - ```bash - gcloud container clusters create [CLUSTER_NAME] - ``` - -4. Authenticate the cluster - - ```bash - gcloud container clusters get-credentials [CLUSTER_NAME] - ``` - - -## Deploy Permify - -1. Apply deployment config - - ```bash - kubectl apply -f deployment.yaml - ``` - - - **Deployment.yaml** - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - app: permify - name: permify - spec: - replicas: 3 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always - status: {} - ``` - -2. Apply service manfiest - - ```bash - kubectl apply -f service.yaml - ``` - - - **Service Manifest** - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer - status: - loadBalancer: {} - ``` - - -## Deploying Permify in a Distributed Configuration - -If you aim to deploy Permify in a distributed configuration, you will need to create a Serf deployment. The Serf deployment can be dockerized to our Container Registry under the name permify/serf:v1.0, which is provided by Hashicorp. - -Please note: It is crucial to ensure that both Serf and Permify deployments reside within the same namespace for proper operation. - -1. Serf Service Create: - - Serf Deployment&Service yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: serf-deployment - spec: - replicas: 1 - selector: - matchLabels: - app: serf - template: - metadata: - labels: - app: serf - spec: - containers: - - name: serf - image: permify/serf:v1.0 - args: - - "-node=main-serf" - ports: - - containerPort: 7946 - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 200m - memory: 256Mi - --- - apiVersion: v1 - kind: Service - metadata: - name: serf - spec: - selector: - app: serf - ports: - - protocol: TCP - port: 7946 - targetPort: 7946 - name: serf - type: ClusterIP - ``` - -2. Apply Deployment Manifest - - Deployment.yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: permify-deployment - spec: - replicas: 3 - selector: - matchLabels: - app: permify - template: - metadata: - labels: - app: permify - spec: - containers: - - image: permify/permify:tagname - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - - "--distributed-enabled=true" - - "--distributed-node=serf:7946" - - "--distributed-node-name=main-serf" - - "--distributed-protocol=serf" - resources: - requests: - memory: "128Mi" - cpu: "200m" - limits: - memory: "128Mi" - cpu: "400m" - ports: - - containerPort: 3476 - name: permify-port - - containerPort: 7946 - name: permify-dist - - containerPort: 6060 - name: permify-pprof - ``` - -3. Apply Service Manifest - - Service.yaml - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: permify-port - port: 3476 - targetPort: 3476 - - name: permify-dist - port: 7946 - targetPort: 7946 - selector: - app: permify - type: LoadBalancer - ``` - - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/installation/overview.md b/docs/versioned_docs/version-0.5.x/installation/overview.md deleted file mode 100644 index 76fb8a56..00000000 --- a/docs/versioned_docs/version-0.5.x/installation/overview.md +++ /dev/null @@ -1,259 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Guide - -This guide shows you how to set up Permify in your servers and use it across your applications. - -:::info Minimum Requirements -PostgreSQL: Version 13.8 or higher -::: - -Please ensure your system meets these requirements before proceeding with the following steps: - -1. [Set Up & Run Permify Service](#set-up-permify-service) -2. [Model your Authorization with Permify's DSL, Permify Schema](#model-your-authorization-with-permify-schema) -3. [Manage and Store Authorization Data as Relational Tuples](#store-authorization-data-as-relational-tuples) -4. [Perform Access Check](#perform-access-check) - -:::info Talk to an Permify Engineer -Want to walk through this guide 1x1 rather than docs ? [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). -::: - -## Set Up Permify Service - -You can run Permify Service with various options but in that tutorial we'll run it via docker container. - -### Run From Docker Container - -Production usage of Permify needs some configurations such as defining running options, selecting datastore to store authorization data and more. - -However, for the sake of this tutorial we'll not do any configurations and quickly start Permify on your local with running the docker command below: - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -``` - -This will start Permify with the default configuration options: -* Port 3476 is used to serve the REST API. -* Port 3478 is used to serve the GRPC Service. -* Authorization data stored in memory. - -:::info -You can examine [Deploy using Docker] section to get more about the configuration options and learn the full integration to run Permify Service from docker container. - -[Deploy using Docker]: ../container -::: - -### Test your connection - -You can test your connection with creating an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core endpoints. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -## Model your Authorization with Permify Schema - -After installation completed and Permify server is running, next step is modeling authorization with Permify authorization language - [Permify Schema]- and configure it to Permify API. - -You can define your entities, relations between them and access control decisions of each actions with using [Permify Schema]. - -### Creating your authorization model - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) - -::: - -Let's create our authorization model. We'll be using following a simple user-organization authorization case for this guide. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action view_files = admin or member - action edit_files = admin - -} -``` - -We have 2 entities these are **"user"** and **"organization"**. Entities represents your main tables. We strongly advise naming entities the same as your original database entities. - -Lets roll back our example, - -- The `user` entity represents users. This entity is empty because it's only responsible for referencing users. - -- The `organization` entity has its own relations (`admin` and `member`) which related with user entity. This entity also has 2 actions, respectively: - - Organization member and admin can view files. - - Only admins can edit files. - -:::info -For implementation sake we'll not dive more deep about modeling but you can find more information about modeling on [Modeling Authorization with Permify] section. Also can check out [example use cases] to better understand some basic use cases modeled with Permify Schema. - -[Modeling Authorization with Permify]: ../../getting-started/modeling -[example use cases]: ../../use-cases/simple-rbac -::: - -### Configuring Schema via API - -After modeling completed, you need to send Permify Schema - authorization model - to [Write Schema API](../api-overview/schema/write-schema.md) for configuration of your authorization model on Permify authorization service. - -:::caution Before Continue on Writing Schema -You'll see **tenant_id** parameter almost all Permify APIs including Write Schema. With version 0.3.x Permify became a tenancy based authorization infrastructure, and supports multi-tenancy by default so its a mandatory parameter when doing any operations. - -We provide a pre-inserted tenant - **t1** - for ones that don't need/want to use multi-tenancy. So, we will be passing **t1** to all tenant id parameters throughout this guidance. -::: - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - -**POST /v1/tenants/{tenant_id}/schemas/write** - -![permify-schema](https://user-images.githubusercontent.com/34595361/214457054-19b141ac-6bfa-4db4-aeab-f7b7149c3351.png) - -## Store Authorization Data as Relational Tuples - -After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. - -### Create Relational Tuples - -You can create relational tuples as authorization rules by using [Write Data API](../api-overview/data/write-data.md) - -For our guide let's grant one of the team members (Ashley) an admin role. - -#### Example HTTP Request on Postman: - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. -| [x] | tuples | array | - | Can contain multiple relation tuple object| -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€| -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc.| -| [x] | subject | string | - | User or user set who wants to take the action. | -| [ ] | schema_version | string | 8 | Version of the schema | - -**POST /v1/tenants/{tenant_id}/data/write** - -```json -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" //Organization identifier - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "1", //Ashley's identifier - "relation": "" - } - } - ] -} -``` - -![write-data](https://user-images.githubusercontent.com/34595361/214458203-8264e141-642d-48b0-9242-416bbf6f8795.png) - -**Created relational tuple:** organization:1#admin@user:1 - -**Semantics:** User 1 (Ashley) has admin role on organization 1. - -:::tip -In ideal production usage Permify stores your authorization data in a database you prefer. You can configure the database with using [configuration yaml file](https://github.com/Permify/permify/blob/master/example.config.yaml) or CLI flag options. - -But in this tutorial Permify Service running default configurations on local, so authorization data will be stored in memory. You can find more detailed explanation how Permify stores authorization data in [Managing Authorization Data] section. - -[Managing Authorization Data]: ../../getting-started/sync-data -::: - -## Perform Access Check - -Finally we're ready to control authorization. Access decision results computed according to relational tuples and the stored model, [Permify Schema] action conditions. - -Lets get back to our example and perform an example access check via [Check API]. We want to check whether an specific user has an access to view files in a organization. - -[Check API]: ../../api-overview/permission/check-api -[Permify Schema]: ../../getting-started/modeling - -#### Example HTTP Request: - -***Can the user 45 view files on organization 1 ?*** - -**POST /v1/tenants/{tenant_id}/permissions/check** - -| Required | Argument | Type | Default | Description | -|----------|----------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this field. | -| [x] | entity | object | - | name and id of the entity. Example: organization:1. | -| [x] | action | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action | -| [ ] | schema_version | string | - | get results according to given schema version | -| [ ] | depth | integer | 8 | - | - -### Request - -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_files", - "subject": { - "type": "user", - "id": "45", - "relation": "" - }, -} -``` - -### Response - -```json -{ - "can": "RESULT_ALLOW", - "metadata": { - "check_count": 0 - } -} -``` - -See [Access Control Check] section for learn how access checks works and access decisions evaluated in Permify - -[Access Control Check]: ../api-overview/permission/check-api.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you struggle with installation or have any questions, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.5.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/versioned_docs/version-0.5.x/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.5.x/permify-overview/authorization-service.md b/docs/versioned_docs/version-0.5.x/permify-overview/authorization-service.md deleted file mode 100644 index 55a6906d..00000000 --- a/docs/versioned_docs/version-0.5.x/permify-overview/authorization-service.md +++ /dev/null @@ -1,40 +0,0 @@ - -# Authorization As A Service - -An authorization service is a module that allows you to manage access to your application and ease the development and maintenance of your authorization system. It works in run time and respond to all authorization questions from any of your apps. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify] is a fully open source **authorization service** that offers a variety of binding and crafting options to secure your applications. It's designed to be deployed as a authorization service rather than a library compiled into an application. - -[Permify]: https://github.com/Permify/permify - -## Benefits of using an Authorization Service - -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. You can get started quickly with a simple API that you can easily integrate into your application to move and iterate faster. - -### Scale As You Wish -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. Building a scalable and robust authorization system is hard and needs a quite engineering time. Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services. - -[Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell/ - -### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. - -### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. - -### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. - -## Cases that can benefit from An Authorization Service: - -- If you already have an identity/auth solution and want to plug in fine-grained authorization on top of that. -- If you want to create a unified access control mechanism to use across your individual applications. -- If you want to make future-proof authorization system and don't want to spend engineering effort for it. -- If youโ€™re managing authorization for growing micro-service infrastructure. -- If your authorization logic is cluttering your code base. -- If your data model is getting too complicated to handle your authorization within the service. -- If your authorization is growing too complex to handle within code or API gateway. - diff --git a/docs/versioned_docs/version-0.5.x/permify-overview/infrastructure.md b/docs/versioned_docs/version-0.5.x/permify-overview/infrastructure.md deleted file mode 100644 index 0f29223d..00000000 --- a/docs/versioned_docs/version-0.5.x/permify-overview/infrastructure.md +++ /dev/null @@ -1,79 +0,0 @@ - -# Architecture & Deployment - -Permify is a infrastructure for ease the process of creating and managing scalable authorization systems in your environment. - -This section shows where and how does Permify fit into your environment with examining Permify's high level design, internal architecture, deployment patterns and the usage with the authentication and identity providers. - -## High Level Design - -You can model your authorization logic with **Permify's domain specific language** and your applications can interpolate with Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data and more! - -Permify stores access control relations in a **database of your choice**, and each API request evaluates and takes into account access decisions based on the stored relations. - -So this preferred database behaves as a **centralized data source** for your authorization system. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify vs Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Architecture - -Permify supports both HTTP and GRPC. HTTP requests are converted to GRPC and then transferred to Permify servers. - -There are 4 servers in a Permify Instance: Permission, Relationship, Schema, and Watch. - -- **Permission Server:** The permission server forwards the request to the invoker. The invoker checks for any missing parts of the query, letโ€™s say if no snapshot is provided, it finds the head snapshot. It then hashes the request (with snapshot and schema version) and forwards it to the most convenient Permify instance. If the hash matches its own, it directs it to the local cache. If the cache does not contain the request, it proceeds to the engine. The engine breaks down the query into sub-queries and returns it to the invoker. This process continues until a final decision is made. -- **Relationship Server:** After validating the request, it passes it to the database access layer. -- **Schema Server:** After validating the request, it passes it to the database access layer. -- **Watch Server:** It broadcasts changes in relationships based on their snapshots. - -![architecture](https://github.com/Permify/permify/assets/34595361/b943bc0d-5faf-4a06-abb9-fbd70eb42ea0) - -Database abstractions for the reader and writer can use a database like Aurora Postgres. - -When deploying, separate hosts can be used in the Permify config for the reader and writer. This way, different Permify instances can read from different read replicas. - -**Note:** we are using serf (https://github.com/hashicorp/serf) agent for node discovery on hashring. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. - -Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. - -See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. - -As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/versioned_docs/version-0.5.x/permify-overview/intro.md b/docs/versioned_docs/version-0.5.x/permify-overview/intro.md deleted file mode 100644 index 69c135f2..00000000 --- a/docs/versioned_docs/version-0.5.x/permify-overview/intro.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_position: 1 ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is an **open source authorization service** for creating fine-grained and scalable authorization systems. - -With Permify, you can easily structure your authorization model, store authorization data in your preferred database, and interact with the Permify API to handle all authorization queries from your applications or services. - -Permify is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://permify.co/post/google-zanzibar-in-a-nutshell/). - -### Motivation - -Our goal is to make **Google's Zanzibar** available to everyone and help them to build robust, flexible, and easily auditable authorization system that establishes a [natural linkage between permissions](https://permify.co/post/relationship-based-access-control-rebac/) across the business units, functions, and entities of an organization. - -## Key Features - -๐Ÿ›ก๏ธ **Production ready** authorization API that serve as **gRPC** and **REST**. - -๐Ÿ”ฎ Domain Specific Authorization Language to **easily model** your authorization. Supporting RBAC, ReBAC, ABAC and more. - -๐Ÿ” Database Configuration to store your permissions with **high availability** and **low latency**. - -โœ… Perform access control checks and get answers **down to 10ms** with our various cache mechanisms that we operate. - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -โš™๏ธ Create custom permissions for your **tenants**, and manage them in a single place with **Multi Tenancy**. - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin]. - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Getting Started - -In Permify, authorization is divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify will [Store Authorization Data] as relations. -- Perform [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../../getting-started/modeling -[Store Authorization Data]: ../../getting-started/sync-data -[Access Checks]: ../../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want to try it out and examine it instantly, - - - -## Community & Support - -We would love to hear from you :heart: - -You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. - -For feature requests, bugs, or any improvements you can always open an [issue](https://github.com/permify/permify/issues). - -### Want to Contribute? Here are the ways to contribute to Permify - -* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. -* **Improve documentation:** Alongside our codebase, documentation is an important part of our open-source journey. We're trying to give the best DX possible to explain ourselves and Permify. And you can help with that by importing resources or adding new ones. -* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. - -You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). - -## Communication Channels - -If you like Permify, please consider giving us a :star: on [github](https://github.com/permify/permify) - -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Roadmap - -You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! - -## Need any help on Authorization ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify or how it might fit into your authorization workflow, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.5.x/playground.md b/docs/versioned_docs/version-0.5.x/playground.md deleted file mode 100644 index 384c3485..00000000 --- a/docs/versioned_docs/version-0.5.x/playground.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization schema in a browser. - -Our playground consists 3 main sections, - -- [Schema (Authorization Model)](#schema-authorization-model) -- [Authorization Data](#authorization-data) -- [Enforcement](#enforcement-access-check-scenarios) - -Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Schema (Authorization Model) - -You can create your authorization model in this section with using our domain specific language. - -You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured. Also, you can check our docs to [learn more about how to model authorization](./getting-started/modeling.md) in Permify. - -To demonstrate how the playground works, let's create a simple authorization model as follows. This model should be selected as the default when you open the playground. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents owner of this repository - relation owner @user - - // permissions - permission edit = parent.admin or owner - permission delete = owner -} -``` - -We have 2 permissions, `edit` for access of editing repository and `delete` for access of deleting repository. - -Repositories has parent child relation with organizations. The `parent` relation in the repository entity represents that parent child association, while ownership of the repository is represented with the `owner` relation. - -Organizations can have organizational wide roles such as admin and member, which defined as `admin` and `member` relation in organization entity. - -:::info Automatic Saving for Schema Changes -Schema changes are captured automatically, and other sections update accordingly. Some delays may occur at times; please feel free to reach out if these delays hinder your testing process. -::: - -### Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. - -So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. - -![relational-tuples](https://github.com/Permify/permify/assets/34595361/f8b77c18-dd46-461c-9408-392b642cc900) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as tuples and these tuples stored in a database that you preferred. - -The basic tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. - -The user is basically the user or user set in our system. Let's say we want make the **user 1** `admin` in **organization 1** then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the **Add Relationship** button. - -![create-tuple-empty](https://github.com/Permify/permify/assets/34595361/33b85fe7-25e2-400d-8055-94d305023d8c) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![create-tuple-user](https://github.com/Permify/permify/assets/34595361/016d6f9e-955a-4c39-ab55-21a9fd6dffd9) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 - `โ€repository:1#parent@organization:1#...` as follows: - -![create-tuple-parent](https://github.com/Permify/permify/assets/34595361/42daf251-818a-4bd2-8790-1c8656cd497f) - -Created tuples shown in the **Data** section as follows. - -![authorization-data](https://github.com/Permify/permify/assets/34595361/ccc25da1-5212-425d-b604-6a31a8f9555f) - -## Enforcement (Access Check Scenarios) - -Finally as we have a sample data let's perform an access check! - -The YAML in the Enforcement section represents a test scenario for conducting access checks. This scenario-based testing process provides the ability to execute complex access scenarios in a single place. - -Let's name our scenario **"admin_access_test"** and create tests to check: - -- Whether user:1 (admin) can edit repository:1? -- Whether user:1 (admin) can delete repository:1? - -Below is the YAML scenario covering these two tests: - -![scenario-check](https://github.com/Permify/permify/assets/34595361/934add02-6b6a-45ed-9b5b-6a2539778fcf) - -In the above YAML structure, - -#### entity - -Represents the resource for which we want to check access - `repository:1` - -#### subject - -Represents the subject that performs the action or grants access - `user:1`. - -#### assertions - -Assertions stands for defining the expected result for specific action or an permission. In our case we're evaluating access for edit action. - -Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission as: - -`โ€permission edit = parent.admin or owner` - -:::note -which `โ€parent.admin`โ€ indicates admin in the organization that repository belongs to. -::: - -So user:1 should be able to edit resource:1, therefore expected outcome for that access request is true. -- `edit: true` - -On the other hand, user:1 should't be able to delete resource:1, because only owners can. Therefore expected outcome for that is false. -- `delete: false` - -:::info Create More Advanced Scenarios -For simplicity, we've created a basic scenario. However, you can create more advanced scenarios using our validation YAML structure. - -To learn how to use this syntax for complex scenarios, refer to the [Creating Test Scenarios](../getting-started/testing#creating-test-scenarios) section in [Testing & Validation](./getting-started/testing.md) page. -::: - -Let's click the Run button to execute our scenario. - -![scenario-check-true](https://github.com/Permify/permify/assets/34595361/a90c042f-e0f8-46a0-9800-383620226acd) - -Let's change the expected outcome as false (`edit: false`) and hit the **Run** button again we'll see an error message. - -![scenario-check-false](https://github.com/Permify/permify/assets/34595361/9f9768bf-c534-4b1d-9447-e55cab2dafca) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/reference/_category_.json b/docs/versioned_docs/version-0.5.x/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.5.x/reference/configuration.md b/docs/versioned_docs/version-0.5.x/reference/configuration.md deleted file mode 100644 index df94202b..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/configuration.md +++ /dev/null @@ -1,491 +0,0 @@ -# Configuration File - -Permify offers various options for configuring your Permify API Server. - -Here is the example configuration YAML file with glossary below. You can also find -this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. - -***Example config.yaml file*** - -```yaml -# The server section specifies the HTTP and gRPC server settings, -# including whether or not TLS is enabled and the certificate and -# key file locations. -server: - rate_limit: 100 - http: - enabled: true - port: 3476 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - grpc: - port: 3478 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - -# The logger section sets the logging level for the service. -logger: - level: info - -# The profiler section enables or disables the pprof profiler and -# sets the port number for the profiler endpoint. -profiler: - enabled: true - port: 6060 - -# The authn section specifies the authentication method for the service. -authn: - enabled: true - method: preshared - preshared: - keys: [] - -# The tracer section enables or disables distributed tracing and sets the -# exporter and endpoint for the tracing data. -tracer: - exporter: zipkin - endpoint: http://localhost:9411/api/v2/spans - enabled: true - -# The meter section enables or disables metrics collection and sets the -# exporter and endpoint for the collected metrics. -meter: - exporter: otlp - endpoint: localhost:4318 - enabled: true - -# The service section sets various service-level settings, including whether -# or not to use a circuit breaker, and cache sizes for schema, permission, -# and relationship data. -service: - circuit_breaker: false - watch: - enabled: false - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB - permission: - bulk_limit: 100 - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - relationship: - -# The database section specifies the database engine and connection settings, -# including the URI for the database, whether or not to auto-migrate the database, -# and connection pool settings. -database: - engine: postgres - uri: postgres://user:password@host:5432/db_name - auto_migrate: false - max_open_connections: 20 - max_idle_connections: 1 - max_connection_lifetime: 300s - max_connection_idle_time: 60s - garbage_collection: - enabled: true - interval: 200h - window: 200h - timeout: 5m - -# distributed configuration settings -distributed: - # Indicates whether the distributed mode is enabled or not - enabled: true - - # The address of the distributed service. - # Using a Kubernetes DNS name suggests this service runs in a Kubernetes cluster - # under the 'default' namespace and is named 'permify' - address: "kubernetes:///permify.default" - - # The port on which the service is exposed - port: "5000" - -``` - -## Options - -
server | Server Configurations -

- -#### Definition - -Server options to run Permify. (`grpc` and `http` available for now.) - -#### Structure - -``` -โ”œโ”€โ”€ server - โ”œโ”€โ”€ rate_limit - โ”œโ”€โ”€ (`grpc` or `http`) - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ port - โ”‚ โ””โ”€โ”€ tls - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ cert - โ”‚ โ””โ”€โ”€ key -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------|---------|---------------------------------------------------------------------| -| [ ] | rate_limit | 100 | the maximum number of requests the server should handle per second. | -| [x] | [ server_type ] | - | server option type can either be `grpc` or `http`. | -| [ ] | enabled (for server type) | true | switch option for server. | -| [x] | port | - | port that server run on. | -| [x] | tls | - | transport layer security options. | -| [ ] | enabled (for tls) | false | switch option for tls | -| [ ] | cert | - | tls certificate path. | -| [ ] | key | - | tls key pat | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|-----------------------------------|--------------| -| rate_limit | PERMIFY_RATE_LIMIT | int | -| grpc-port | PERMIFY_GRPC_PORT | string | -| grpc-tls-enabled | PERMIFY_GRPC_TLS_ENABLED | boolean | -| grpc-tls-key-path | PERMIFY_GRPC_TLS_KEY_PATH | string | -| grpc-tls-cert-path | PERMIFY_GRPC_TLS_CERT_PATH | string | -| http-enabled | PERMIFY_HTTP_ENABLED | boolean | -| http-port | PERMIFY_HTTP_PORT | string | -| http-tls-key-path | PERMIFY_HTTP_TLS_KEY_PATH | string | -| http-tls-cert-path | PERMIFY_HTTP_TLS_CERT_PATH | string | -| http-cors-allowed-origins | PERMIFY_HTTP_CORS_ALLOWED_ORIGINS | string array | -| http-cors-allowed-headers | PERMIFY_HTTP_CORS_ALLOWED_HEADERS | string array | - -

-
- -
logger | Logging Options -

- -#### Definition - -Real time logs of authorization. Permify uses [zerolog] as a logger. - -[zerolog]: https://github.com/rs/zerolog - -#### Structure - -``` -โ”œโ”€โ”€ logger - โ”œโ”€โ”€ level -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------| -| [x] | level | info | logger levels: `error`, `warn`, `info` , `debug` | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|---------------------------------|--------| -| log-level | PERMIFY_LOG_LEVEL | string | - -

-
- -
authn | Server Authentication -

- -#### Definition - -You can choose to authenticate users to interact with Permify API. - -There are 2 authentication method you can choose: - -* [Pre Shared Keys](#pre-shared-keys) -* [OpenID Connect](#openid-connect) - -#### Pre Shared Keys - -On this method, you must provide a pre shared keys in order to identify yourself. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ keys -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | true | switch option authentication config | -| [x] | keys | - | Private key/keys for server authentication. Permify does not provide this key, so it must be generated by the users. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-preshared-keys | PERMIFY_AUTHN_PRESHARED_KEYS | string array | - - -#### OpenID Connect - -Permify supports OpenID Connect (OIDC). OIDC provides an identity layer on top of OAuth 2.0 to address the shortcomings -of using OAuth 2.0 for establishing identity. - -With this authentication method, you be able to integrate your existing Identity Provider (IDP) to validate JSON Web -Tokens (JWTs) using JSON Web Keys (JWKs). By doing so, only trusted tokens from the IDP will be accepted for -authentication. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ client-id -| โ”œโ”€โ”€ issuer -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | false | switch option authentication config | -| [x] | client_id | - | This is the client ID of the application you're developing. It is a unique identifier that is assigned to your application by the OpenID Connect provider, and it should be included in the JWTs that are issued by the provider. | -| [x] | issuer | - | This is the URL of the provider that is responsible for authenticating users. You will use this URL to discover information about the provider in step 1 of the authentication process. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-oidc-issuer | PERMIFY_AUTHN_OIDC_ISSUER | string | -| authn-oidc-client-id | PERMIFY_AUTHN_OIDC_CLIENT_ID | string | - -

-
- - -
tracer | Tracing Configurations -

- -#### Definition - -Permify integrated with [jaeger], [otlp], [signoz], and [zipkin] tacing tools to analyze performance and behavior of your -authorization when using Permify. - -#### Structure - -``` -โ”œโ”€โ”€ tracer -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------| -| [x] | exporter | - | Tracer exporter, the options are `jaeger`, `otlp`, `signoz`, and `zipkin`. | -| [x] | endpoint | - | export uri for tracing data. | -| [ ] | enabled | false | switch option for tracing. | -| [ ] | insecure | false | Whether to use HTTP instead of HTTPs for exporting the traces. | - -#### ENV - -| Argument | ENV | Type | -|----------------------|-------------------------------|--------------| -| tracer-enabled | PERMIFY_TRACER_ENABLED | boolean | -| tracer-exporter | PERMIFY_TRACER_EXPORTER | string | -| tracer-endpoint | PERMIFY_TRACER_ENDPOINT | string | -| tracer-insecure | PERMIFY_TRACER_INSECURE | boolean | - -

-
- -
meter | Meter Configurations -

- -#### Definition - -Configuration for observing metrics; check count, cache check count and session information; Permify version, hostname, -os, arch. - -#### Structure - -``` -โ”œโ”€โ”€ meter -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | -| [x] | endpoint | - | export uri for metric observation | -| [ ] | enabled | true | switch option for meter tracing. | - -#### ENV - -| Argument | ENV | Type | -|--------------------|-------------------------|--------------| -| meter-enabled | PERMIFY_METER_ENABLED | boolean | -| meter-exporter | PERMIFY_METER_EXPORTER | string | -| meter-endpoint | PERMIFY_METER_ENDPOINT | string | - -

-
- -
database | Database Configurations -

- -#### Definition - -Configurations for the database that points out where your want to store your authorization data (relation tuples, -audits, decision logs, authorization model) - -#### Structure - -``` -โ”œโ”€โ”€ database -| โ”œโ”€โ”€ engine -| โ”œโ”€โ”€ uri -| โ”œโ”€โ”€ auto_migrate -| โ”œโ”€โ”€ max_open_connections -| โ”œโ”€โ”€ max_idle_connections -| โ”œโ”€โ”€ max_connection_lifetime -| โ”œโ”€โ”€ max_connection_idle_time -| โ”œโ”€โ”€garbage_collection -| โ”œโ”€โ”€enable: true -| โ”œโ”€โ”€interval: 3m -| โ”œโ”€โ”€timeout: 3m -| โ”œโ”€โ”€window: 720h -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------------|---------|-------------------------------------------------------------------------------------------------------------------| -| [x] | engine | memory | Data source. Permify supports **PostgreSQL**(`'postgres'`) for now. Contact with us for your preferred database. | -| [x] | uri | - | Uri of your data source. | -| [ ] | auto_migrate | true | When its configured as false migrating flow won't work. | -| [ ] | max_open_connections | 20 | Configuration parameter determines the maximum number of concurrent connections to the database that are allowed. | -| [ ] | max_idle_connections | 1 | Determines the maximum number of idle connections that can be held in the connection pool. | -| [ ] | max_connection_lifetime | 300s | Determines the maximum lifetime of a connection in seconds. | -| [ ] | max_connection_idle_time | 60s | Determines the maximum time in seconds that a connection can remain idle before it is closed. | -| [ ] | enable (for garbage collection) | false | Switch option for garbage collection. | -| [ ] | interval | 3m | Determines the run period of a Garbage Collection operation. | -| [ ] | timeout | 3m | Sets the duration of the Garbage Collection timeout. | -| [ ] | window | 720h | Determines how much backward cleaning the Garbage Collection process will perform. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------------------------------|--------------------------------------------------------|----------| -| database-engine | PERMIFY_DATABASE_ENGINE | string | -| database-uri | PERMIFY_DATABASE_URI | string | -| database-auto-migrate | PERMIFY_DATABASE_AUTO_MIGRATE | boolean | -| database-max-open-connections | PERMIFY_DATABASE_MAX_OPEN_CONNECTIONS | int | -| database-max-idle-connections | PERMIFY_DATABASE_MAX_IDLE_CONNECTIONS | int | -| database-max-connection-lifetime | PERMIFY_DATABASE_MAX_CONNECTION_LIFETIME | duration | -| database-max-connection-idle-time | PERMIFY_DATABASE_MAX_CONNECTION_IDLE_TIME | duration | -| database-garbage-collection-enabled | PERMIFY_DATABASE_GARBAGE_ENABLED | boolean | -| database-garbage-collection-interval | PERMIFY_DATABASE_GARBAGE_COLLECTION_INTERVAL | duration | -| database-garbage-collection-timeout | PERMIFY_DATABASE_GARBAGE_COLLECTION_TIMEOUT | duration | -| database-garbage-collection-window | PERMIFY_DATABASE_GARBAGE_COLLECTION_WINDOW | duration | - -

-
- -
profiler | Performance Profiler Configurations -

- -#### Definition - -pprof is a performance profiler for Go programs. It allows developers to analyze and understand the performance -characteristics of their code by generating detailed profiles of program execution - -#### Structure - -``` -โ”œโ”€โ”€ profiler -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|-----------------------------------------------| -| [ ] | enabled | true | switch option for profiler. | -| [x] | port | - | port that profiler runs on *(default: 6060)*. | - -#### ENV - -| Argument | ENV | Type | -|------------------|----------------------------|--------------| -| profiler-enabled | PERMIFY_PROFILER_ENABLED | boolean | -| profiler-port | PERMIFY_PROFILER_PORT | string | - -

-
- -
Distributed | Consistent hashing Configurations -

- -#### Definition - -A consistent hashing ring ensures data distribution that minimizes reorganization when nodes are added or removed, improving scalability and performance in distributed systems." - -#### Structure - -``` -โ”œโ”€โ”€ distributed -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ address -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-------------|---------|--------------------------------------| -| [x] | enabled | false | switch option for distributed. | -| [] | address | - | address of the distributed service | -| [] | port | 5000 | port on which the service is exposed | - - -#### ENV - -| Argument | ENV | Type | -|----------------------|-----------------------------|---------| -| distributed-enabled | PERMIFY_DISTRIBUTED_ENABLED | boolean | -| distributed-address | PERMIFY_DISTRIBUTED_ADDRESS | string | -| distributed-port | PERMIFY_DISTRIBUTED_PORT | string | - -

-
- -[jaeger]: https://www.jaegertracing.io/ - -[otlp]: https://opentelemetry.io/ - -[zipkin]: https://zipkin.io/ - -[signoz]: https://signoz.io/ diff --git a/docs/versioned_docs/version-0.5.x/reference/contextual-tuples.md b/docs/versioned_docs/version-0.5.x/reference/contextual-tuples.md deleted file mode 100644 index ff3a806b..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/contextual-tuples.md +++ /dev/null @@ -1,252 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Context (Dynamic Permissions) - -## What is it ? - -Contextual tuples are relations that can be dynamically added to permission request operations. When you send these relations along with your requests, they get processed alongside existing relations in the database and will return a result. - -You can utilize Contextual Tuples in authorization checks that depend on certain dynamic or contextual data (such as location, time, IP address, etc) that have not been written as traditional Permify relation tuples. - -## Use Case - -Let's give an example to better understand the usage of Contextual Tuples aka dynamic permissions in access checks. - -Consider you're modeling an permission system for an internal application that belongs to an multi regional organization. - -### Authorization Model - -In that application an employee that belongs to HR department can view details of another employee if: - -1. If he/she is an Manager in HR department -2. Connected via the branch's internal network or through the branch's VPN - -As you notice we can model the rule **1.** easily with our existing schema language, which gives ability to define arbitrary relations between users and objects such as manager of HR entity, as follows, - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - -} -``` - -But to create the `view_employee` permission in the organization entity, we need to consider not only whether the employee is a manager but also check the IP address. - -At this point, traditional relation tuples of Permify are insufficient since network address is an dynamic variable that cannot be added as static relations. - -So, to incorporate the IP address into our authorization model we will use Contextual Tuples and send dynamic relations values when sending the access check request. - -Let's extend our authorization model with adding contextual entities and relations to create the `view_employee` action. - -```perm -entity user {} - -entity organization { - - relation employee @user - relation hr_manager @user @organization#employee - - relation ip_address_range @ip_address_range - - action view_employee = hr_manager and ip_address_range.user - -} - -entity ip_address_range { - relation user @user -} -``` - -A quick breakdown we define **type** for contextual variable `ip_address_range` and related them with user. Afterwards call that dynamic entities inside our organization entity and form the `view_employee` permission as follows: - -```perm -action view_employee = hr_manager and ip_address_range.user -``` - -### Dynamic Access Check - -Since we cannot create relation statically for `ip_address_range` we need to send ip value on runtime, specifically when performing access control check. - -So let's say user Ashley trying to view employee X. And lets assume that, - -* She has a **manager** relation in HR department with the tuple `organization:1#hr_manager@user:1` -* She connected to VPN which connected to network 192.158.1.38 - which is Branch's internal network. - - - - -```go -cr, err: = client.Permission.Check(context.Background(), &v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: &v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: &v1.Entity { - Type: "organization", - Id: "1", - }, - Permission: "view_employee", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - Context: *v1.Context { - Tuples: []*v1.Tuple{ - { - Entity: &v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "ip_address_range", - Subject: &v1.Subject { - Type: "ip_address_range", - Id: "192.158.1.38", - }, - }, - { - Entity: &v1.Entity { - Type: "ip_address_range", - Id: "192.158.1.38", - }, - Relation: "user", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, - }, - }, - } - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "organization", - id: "1" - }, - permission: "view_employee", - subject: { - type: "user", - id: "1" - }, - context: { - tuples: [ - { - entity: { - type: "organization", - id: "1" - }, - relation: "ip_address_range", - subject: { - type: "ip_address_range", - id: "192.158.1.38", - } - }, - { - entity: { - type: "ip_address_range", - id: "192.158.1.38" - }, - relation: "user", - subject: { - type: "user", - id: "1", - } - } - ] - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view_employee", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, - "context": { - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "ip_address_range", - "subject": { - "type": "ip_address_range", - "id": "192.158.1.38" - } - }, - { - "entity": { - "type": "ip_address_range", - "id": "192.158.1.38" - }, - "relation": "user", - "subject": { - "type": "user", - "id": "1" - } - } - ] - } -}' -``` - - - - -A quick note, - -When you use contextual tuples, the cache system will not be operational. This is because the cache system is written along with snapshots and if contextual tuples are written, using the cache would lead to incorrect results. - -Hence, to prevent this, the use of the cache is blocked at the time of the request. See more on the section [Permify Cache Mechanisims](./cache.md) - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.5.x/reference/glossary.md b/docs/versioned_docs/version-0.5.x/reference/glossary.md deleted file mode 100644 index ab02007e..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Permission Database - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure it when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/reference/snap-tokens.md b/docs/versioned_docs/version-0.5.x/reference/snap-tokens.md deleted file mode 100644 index 8f1a2178..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/snap-tokens.md +++ /dev/null @@ -1,53 +0,0 @@ - -# Snap Tokens & Zookies - -A Snap Token is a token that consists of an encoded timestamp, which is used to ensure fresh results in access control checks. - -## Why you should use Snap Tokens ? - -Basically, you should use snap tokens both for consistency and performance. The main goal of Permify is to provide an authorization system that ensures excellent performance that can handle millions of requests from different environments while ensuring data consistency. - -Performance standards can be achievable with caching. In Permify, the cache mechanism eliminates re-computing of access control checks that once occurred, unless any relationships of resources don't change. - -Still, all caches suffer from the risk of becoming stale. If some schema update happens, or relations change then all of the caches should be updated according to it to prevent false positive or false negative results. - -Permify avoids this problem with an approach of snapshot reads. Simply, it ensures that access control is evaluated at a consistent point in time to prevent inconsistency. - -To achieve this, we developed tokens called Snap Tokens that consist of a timestamp that is compared in access checks to ensure that the snapshot of the access control is at least as fresh as the resource timestamp - basically its stored snap token. - -## How to use Snap Tokens - -Snap Tokens used in endpoints to represent the snapshot and get fresh results of the API's. It mainly used in [Write API] and [Check API]. - -The general workflow for using snap token is getting the snap token from the reponse of Write API request - basically when writing a relational tuple - then mapped it with the resource. One way of doing that is storing snap token in the additional column in your relational database. - -Then this snap token can be used in endpoints. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -```json -{ - "schema_version": "ce8siqtmmud16etrelag", - "snap_token": "gp/twGSvLBc=", - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - }, -} -``` - -[Write API]: ../../api-overview/data/write-data/ -[Check API]: ../../api-overview/permission/check-api - -#### All endpoints that used snap token - -- [Write API](../../api-overview/data/write-data/) -- [Expand API](../../api-overview/permission/expand-api) - - -## More on Cache Mechanism - -Permify implements several cache mecnanisims in order to achieve low latency in scaled distributed systems. See more on the section [Cache Mechanisims](./cache.md) \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/reference/tracing.md b/docs/versioned_docs/version-0.5.x/reference/tracing.md deleted file mode 100644 index 12810526..00000000 --- a/docs/versioned_docs/version-0.5.x/reference/tracing.md +++ /dev/null @@ -1,55 +0,0 @@ - -# Tracing Tools - -Permify has integrations with some of popular tracing tools to analyze performance and behavior of your authorization. These are: - -- [Jaeger](https://www.jaegertracing.io/) -- [OpenTelemetry](https://opentelemetry.io/) -- [Signoz](https://signoz.io/) -- [Zipkin](https://zipkin.io/) - -## Usage - -### Set Up - -Adding one of these tracing tools to your authorization system is quite simple, you just need to define it in the Permify configuration file as **tracer**. - -```yaml -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -``` - -- ***exporter***: enter the tool name that you want to use. `jaeger` , `otlp`, `signoz`, and `zipkin`. -- ***endpoint***: export url for tracing data. -- ***disabled***: switch option for tracing. -- ***insecure***: configures the exporter to connect to the collcetor using HTTP instead of HTTPS. This configuration is relevant only for `signoz` and `otlp`. - -**Example YAML configuration file** - -```yaml -app: - name: โ€˜permifyโ€™ -http: - port: 3476 -logger: - log_level: โ€˜debugโ€™ - rollbar_env: โ€˜permifyโ€™ -tracer: - exporter: 'zipkin' - endpoint: 'http://172.17.0.4:9411/api/v2/spans' - disabled: false -database: - write: - connection: 'postgres' - database: 'morf-health-demo' - uri: 'postgres://postgres:SphU4Uf3QXNntT@permify.us-east-1.rds.amazonaws.com:5432' - pool_max: 2 -``` - -After running Permify in your server, you should run Zipkin as well. If you're using docker here is the docker pull request for Zipkin: - -``` -docker run -d -p 9411:9411 openzipkin/zipkin -``` diff --git a/docs/versioned_docs/version-0.5.x/use-cases.md b/docs/versioned_docs/version-0.5.x/use-cases.md deleted file mode 100644 index 6fae082c..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: use-cases -title: Common Use Cases -slug: /use-cases ---- - -# Common Use Cases - -Common modeling patterns and uses cases we have seen so far from the users from small startups with simple RBAC to multi-regional enterprises that run tens of Permify instances with deeply nested relationships. - -:::success Missing a specific use case? -No problem, let's discuss it together! just open an [issue](https://github.com/Permify/permify/issues) about it or join our conversation at [discord](https://discord.gg/n6KfzYxhPp)! -::: - -```mdx-code-block -import {CaseList} from '@site/src/components/Case'; -import list from './use-cases/_list.json'; - - -``` \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/use-cases/_category_.json b/docs/versioned_docs/version-0.5.x/use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/use-cases/_list.json b/docs/versioned_docs/version-0.5.x/use-cases/_list.json deleted file mode 100644 index 7b6e7fb1..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/_list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 1, - "title": "Role Based Access Control (RBAC)", - "description": "Want to implement role to your application ? Define an entity and manage your roles throught your applications.", - "link": "./simple-rbac" - }, - { - "id": 2, - "title": "Attribute Based Access Control (ABAC)", - "description": "Grant access what based on specific characteristics or attributes.", - "link": "./abac" - }, - { - "id": 3, - "title": "Relationship Based Access Control (ReBAC)", - "description": "Define permissions based on the relationships between resources and subjects in your system", - "link": "./rebac" - }, - { - "id": 4, - "title": "Custom Roles", - "description": "Assign specific permissions to users based on the custom roles that they are assigned within the system.", - "link": "./custom-roles" - }, - { - "id": 5, - "title": "Multi Tenancy", - "description": "Create custom authorization schema and relation tuples for the different tenants and manage them in a single place.", - "link": "./multi-tenancy" - } -] \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/use-cases/abac.md b/docs/versioned_docs/version-0.5.x/use-cases/abac.md deleted file mode 100644 index a4acee33..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/abac.md +++ /dev/null @@ -1,590 +0,0 @@ -# Attribute Based Access Control (Beta) - -This page explains the design approach of Permify's ABAC support as well as demonstrates how to create and use attribute based permissions in Permify. - -:::info -You can find Permify's support for ABAC in our [beta release](https://github.com/Permify/permify/pkgs/container/permify-beta) and explore the active [API documentation](https://permify.github.io/permify-swagger/) for the ***beta*** version. - -We are eager to hear your thoughts and looking forward to your feedback! -::: - -# What is Attribute Based Access Control (ABAC)? - -Attribute-Based Access Control (ABAC) is like a security guard that decides who gets to access what based on specific characteristics or "attributes". - -These attributes can be associated with users, resources, or the environment, and their values can influence the outcome of an access request. - -Letโ€™s make an analogy, itโ€™s the best way to understand complex ideas. - -Think about an amusement park, and there are 3 different rides. In order to access each ride, you need to have different qualities. For example: - -1. ride one you need to be over 6ft tall. -2. ride two you need to be under 200lbs. -3. ride three you need to be between 12 - 18 years old. - -Similar to this ABAC checks certain qualities that you have defined on users, resources, or the environment. - -# Why Would Need ABAC? - -Itโ€™s obvious but the simple answer is โ€œuse casesโ€โ€ฆ Sometimes, using ReBAC and RBAC isn't the best fit for the job. It's like using winter tires on a hot desert road, or summer tires in a snowstorm - they're just not the right tools for the conditions. - -1. **Geographically Restricted:** Think of ABAC like a bouncer at a club who only lets in people from certain towns. For example, a movie streaming service might only show certain movies in certain countries because of rules about who can watch what and where. -2. **Time-Based:** ABAC can also act like a parent setting rules about when you can use the computer. For example, a system might only let you do certain things during office hours. -3. **Compliance with Privacy Regulations:** ABAC can help follow rules about privacy. For example, a hospital system might need to limit who can see a patient's data based on the patient's permission, why they want to see it, and who the person is. -4. **Limit Range:** ABAC can help you create a rules defining a number limit or range. For instance, a banking system might have limits for wiring or withdrawing money. -5. **Device Information:** ABAC can control access based on attributes of the device, such as the device type, operating system version, or whether the device has the latest security patches. - -As you can see ABAC has a more contextual approach. You can define access rights regarding context around subjects and objects in an application. - -# Introducing New Key Elements - -To support ABAC in Permify, we've added two main components into our DSL: attributes and rules. - -## Attribute - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```sql -attribute ip_range string[] -``` - -There are different types of attributes you can use; - -### Boolean - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```go -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - - - -### String - -String can be used as an attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```sql -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - - - -### Integer - -Integer can be used as an attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```jsx -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - - - -### Double - -Double can be used as an attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```sql -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - - - -## Rule - -Rules are structures that allow you to write specific conditions for the model. They accept parameters and are based on conditions. For example, a rule could be used to check if a given IP address falls within a specified IP range: - -```sql -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` - -## Evaluation - -**Model** - -```sql -entity user {} - -entity organization { - - relation admin @user - - attribute ip_range string[] - - permission view = check_ip_range(request.ip_address, ip_range) or admin -} - -rule check_ip_range(ip_address string, ip_range string[]) { - ip in ip_range -} -``` - -In this case, the part written as 'context' refers to the context within the request. Any type of data can be added from within the request and can be called within the model. - -For instance, - -```sql -... -"context": { - "ip_address": "187.182.51.206", - "day_of_week": "monday" -} -... -``` - -**Relationships** - -- organization:1#admin@user:1 - -**Attributes** - -- organization:1$ip_range|string[]:[โ€˜187.182.51.206โ€™, โ€˜250.89.38.115โ€™] - -**Check request** - -```sql -{ - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view", - "subject" : { - "type": "user", - "id": "1" - }, - "context": { - "ip_address": "187.182.51.206" - } -} -``` - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$check_ip_range(context.ip_address,ip_range) โ†’ true -โ†’ organization:1#admin@user:1 โ†’ true - -**Cache Mechanism** -The cache mechanism works by hashing the snapshot of the database, schema version, and sub-queries as keys and adding their results, so it will operate in the same way in calls as in relationships. For example, - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_organization:1#admin@user:1 โ†’ true -- check_{snapshot}_{schema_version}_{context}_organization:1$check_ip_range(ip_range) โ†’ true - -## Some Use Cases - -### Example of Public/Private Repository - -In this example, **`is_public`** is defined as a boolean attribute. If an attribute is a boolean, it can be directly used without the need for a rule. This is only applicable for boolean types. - -```sql -entity user {} - -entity post { - - relation owner @user - - attribute is_public boolean - - permission view = is_public or owner - permission edit = owner -} -``` - -In this context, if the **`is_public`** attribute of the repository is set to true, everyone can view it. If it's not public (i.e., **`is_public`** is false), only the owner, in this case **`user:1`**, can view it. - -The permissions in this model are defined as such: - -**`permission view = is_public or owner`** - -This means that the 'view' permission is granted if either the repository is public (**`is_public`** is true) or if the current user is the owner of the repository. - -**relationships:** - -- post:1#owner@user:1 - -**attributes:** - -- post:1$is_public|boolean:true - -**Check Evolution Sub Queries Post View** -โ†’ post:1#is_public โ†’ true -โ†’ post:1#admin@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_post:1$is_public โ†’ true -- check_{snapshot}_{schema_version}_{context}_post:1#admin@user:1 โ†’ true - -### Example of Weekday - -In this example, to be able to view the repository it must not be a weekend, and the user must be a member of the organization. - -```sql -entity user {} - -entity organization { - - relation member @user - - permission view = is_weekday(request.day_of_week) and member -} - -entity repository { - - relation organization @organization - - permission view = organization.view -} - -rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' -} -``` - -The permissions in this model state that to 'view' the repository, the user must fulfill two conditions: the current day (according to the context data **`day_of_week`**) must not be a weekend (determined by the **`is_weekday`** rule), and the user must be a member of the organization that owns the repository. - -**Relationships:** - -- organization:1#member@user:1 - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$is_weekday(context.day_of_week) โ†’ true -โ†’ organization:1#member@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_organization:1$is_weekday(context.day_of_week) โ†’ true -- check_{snapshot}_{schema_version}_{context}_post:1#member@user:1 โ†’ true - -### Example of Banking System - -This model represents a banking system with two entities: **`user`** and **`account`**. - -1. **`user`**: Represents a customer of the bank. -2. **`account`**: Represents a bank account that has an **`owner`** (which is a **`user`**), and a **`balance`** (amount of money in the account). - -```sql -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -**The check_balance rule:** This rule verifies if the withdrawal amount is less than or equal to the account's balance and doesn't exceed 5000 (the maximum amount allowed for a withdrawal). It accepts two parameters, the withdrawal amount (amount) and the account's current balance (balance). -**The owner check:** This condition checks if the person requesting the withdrawal is the owner of the account. - -Both of these conditions need to be true for the **`withdraw`** permission to be granted. In other words, a user can withdraw money from an account only if they are the owner of that account, and the amount they want to withdraw is within the account balance and doesn't exceed 5000. - -**Relationships** - -- account:1#owner@user:1 - -**Attributes** - -- account:1$balance|double:4000 - -**Check Evolution Sub Queries For Account Withdraw** -โ†’ account:1$check_balance(context.amount,balance) โ†’ true -โ†’ account:1#owner@user:1 โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_account:1$check_balance(context.amount,balance) โ†’ true -- check_{snapshot}_{schema_version}_{context}_account:1#owner@user:1 โ†’ true - -### Hierarchical Usage - -In this model: - -1. **`employee`**: Represents an individual worker. It has no specific attributes or relations in this case. -2. **`organization`**: Represents an entire organization, which has a **`founding_year`** attribute. The **`view`** permission is granted if the **`check_founding_year`** rule (which checks if the organization was founded after 2000) returns true. -3. **`department`**: Represents a department within the organization. It has a **`budget`** attribute and a relation to its parent **`organization`**. The **`view`** permission is granted if the department's budget is more than 10,000 (checked by the **`check_budget`** rule) and if the **`organization.view`** permission is true. - -Note: In this model, permissions can refer to higher-level permissions (like **`organization.view`**). However, you cannot use the attribute of a relation in this way. For example, you cannot directly reference **`organization.founding_year`** in a permission expression. Permissions can depend on permissions in a related entity, but not directly on the related entity's attributes. - -```sql -entity employee {} - -entity organization { - attribute founding_year integer - - permission view = check_founding_year(founding_year) -} - -entity department { - relation organization @organization - attribute budget double - - permission view = check_budget(budget) and organization.view -} - -rule check_founding_year(founding_year integer) { - founding_year > 2000 -} - -rule check_budget(budget double) { - budget > 10000 -} -``` - -**Relationships** - -- department:1#organization@organization:1 -- department:1#organization@organization:2 - -**Attributes** - -- department:1$budget|double:20000 -- organization:1$organization|integer:2021 - -**Check Evolution Sub Queries For Department View** -โ†’ department:1$check_budget(budget) โ†’ true -โ†’ department:1#organization@user:1 โ†’ true - โ†’ organization:2$check_founding_year(founding_year) โ†’ false - โ†’ organization:1$check_founding_year(founding_year) โ†’ true - -**Request keys before hash** - -- check_{snapshot}_{schema_version}_{context}_department:1$check_budget(budget) โ†’ true -- check_{snapshot}_{schema_version}_{context}_organization:2$check_founding_year(founding_year) โ†’ false -- check_{snapshot}_{schema_version}_{context}_organization:1$check_founding_year(founding_year) โ†’ true - -## How To Use Demo - -**Install Permify nightly release** - -```yaml -docker pull **ghcr.io/permify/permify-beta:latest** -``` - -**New Validation Yaml Structure** - -```yaml -schema: >- - {string schema} - -relationships: - - entity_name:entity_id#relation@subject_type:subject_id - -attributes: - - entity_name:entity_id#attribute@attribute_type:attribute_value - -scenarios: - - name: "name" - description: "description" - checks: - - entity: "entity_name:entity_id" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result - entity_filters: - - entity_type: "entity_name" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array - subject_filters: - - subject_reference: "subject_name" - entity: "entity_name:entity_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array -``` - -**Note:** The 'data' field within the 'context' can be assigned a desired value as a key-value pair. Later, this value can be retrieved within the model using 'request.key'. - -**Example in validation file:** - -```yaml -context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" -``` - -This YAML snippet specifies a validation context with no tuples or attributes, and a data field indicating the day of the week is Saturday. - -**Example in model** - -```yaml -permission delete = is_weekday(request.day_of_week) -``` - -In the model, a **`delete`** permission rule is set. It calls the function **`is_weekday`** with the value of **`day_of_week`** from the context. If **`is_weekday("saturday")`** is true, the delete permission is granted. - -**Create Validation File** - -```yaml -schema: >- - entity user {} - - entity organization { - - relation member @user - - attribute credit integer - - permission view = check_credit(credit) and member - } - - entity repository { - - relation organization @organization - - attribute is_public boolean - - permission view = is_public - permission edit = organization.view - permission delete = is_weekday(request.day_of_week) - } - - rule check_credit(credit integer) { - credit > 5000 - } - - rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' - } - -relationships: - - organization:1#member@user:1 - - repository:1#organization@organization:1 - -attributes: - - organization:1$credit|integer:6000 - - repository:1$is_public|boolean:true - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - context: - assertions: - view: true - - entity: "repository:1" - subject: "user:1" - context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" - assertions: - view: true - delete: false - - entity: "organization:1" - subject: "user:1" - context: - assertions: - view: true - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - assertions: - view : ["1"] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - assertions: - view : ["1"] - edit : ["1"] -``` - -**Run validation command** - -```yaml -docker run -v {your_config_folder}:/config **ghcr.io/permify/permify-beta:latest validate /config/validation.yaml** -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/use-cases/custom-roles.md b/docs/versioned_docs/version-0.5.x/use-cases/custom-roles.md deleted file mode 100644 index 4ddf1658..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/custom-roles.md +++ /dev/null @@ -1,74 +0,0 @@ - -# Custom Roles - -This document highlights a solution for custom roles with the [Permify Schema]. In this tutorial, we will create custom **admin** and **member** roles in a project. Then set the permissions of these roles according to their capabilities on the dashboard and tasks. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity role { - relation assignee @user -} - -entity dashboard { - relation view @role#assignee - relation edit @role#assignee -} - -entity task { - relation view @role#assignee - relation edit @role#assignee -} -``` - -This schema encompasses several crucial elements to structure a custom role-based access control system. The role entity serves as a particularly important component, as it enables the creation of multiple custom roles. These roles may vary according to the needs of the application and could include roles like **admin**, **editor**, or **member**, among others. - -Once these custom roles have been established, they can be assigned to other entities in the system. Specifically, in this schema, these roles are attached to the dashboard and task entities. Each of these entities, dashboard and task, has pre-defined permissions associated with them. These permissions, defined within the schema or model, could represent various operations such as **view**, **edit**, and so forth. - -With this setup, it's possible to map these pre-defined permissions of the dashboard and task entities to the custom roles that have been created. This implies that specific permissions, for instance, **view** and **edit** for a dashboard or a task, could be assigned to a particular custom role. - -Based on this model, the example relationships are as follows. With these relationships, custom roles such as **admin** and **member** have been created. - -## Relationships - -dashboard:project-progress#view@role:admin#assignee - -dashboard:project-progress#view@role:member#assignee - -dashboard:project-progress#edit@role:admin#assignee - -task:website-design-review#view@role:admin#assignee - -task:website-design-review#view@role:member#assignee - -task:website-design-review#edit@role:admin#assignee - -Together with these relationships and the model, a view has been created for the **project-progress** dashboard and the **website-design-review** task as shown in the table below. - -| permission | admin | member | -|--------------------|-------|---------| -| **dashboard:view** | โœ… | โœ… | -| **dashboard:edit** | โœ… | โ›” | -| **task:view** | โœ… | โœ… | -| **task:edit** | โœ… | โ›” | - - -Subsequently, you can make authorization decisions by assigning these custom roles to the users that you have created. - -role:member#assignee@user:1 - -When we write these relationship, the final situation will be as follows. - -`Can user:1 view dashboard:project-progress?` gives **Allow** result since the `user:1` is assignee of `role:member` and `role:member` has `dashboard:project-progress#view` permission. - -`Can user:1 view task:website-design-review?` gives **Denied** result since the `user:1` is not assignee of `role:admin`. - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. - diff --git a/docs/versioned_docs/version-0.5.x/use-cases/rebac.md b/docs/versioned_docs/version-0.5.x/use-cases/rebac.md deleted file mode 100644 index 561b500e..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/rebac.md +++ /dev/null @@ -1,424 +0,0 @@ - -# Relationship Based Access Control - -Permify was designed and structured as a true [Relationship Based Access Control(ReBAC)](https://permify.co/post/relationship-based-access-control-rebac/) solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -Here are some common use cases where you can benefit from using ReBAC models in your Permify Schema. - -- [Protecting Organizational-Wide Resources](#protecting-organizational-wide-resources) -- [Deeply Nested Hierarchies](#deeply-nested-hierarchies) -- [User Groups & Team Permissions](#user-groups--team-permissions) - -## Protecting Organizational-Wide Resources - -This example demonstrates grouping the users by organization and giving them access to organizational-wide resources. - -In this use case we'll follow a simplified version of Github's access control that shows how to model basic repository push, read and delete permissions with our authorization language DSL, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user - -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents user of this repository - relation owner @user - - // permissions - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists of 3 entities, - -- `user`, represents users. This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents organization that user and repositories belongs. - -- `repository`, represents a repository in a github. - -#### Relations - -To define a relation, **relations** need to be created as entity attributes. - -##### organization entity - -In our schema we defined 2 relations in the organization entity: ``admin`` and ``member``. - -```perm - -entity organization { - - relation admin @user - relation member @user - -} - -``` - -``admin`` indicates that the user got an administrative role in that organization and with the same logic ``member`` represents a default user that belongs to that organization. - -##### repository entity - -Repository entities have 2 relations: ``parent`` and ``owner``. Both of these relations represent actual database relations with other entities rather than a role-based approach similar to the **organization** entity above. - -```perm -entity repository { - - relation parent @organization - relation owner @user - -} -``` - -The ``parent`` relation represents the parent organization of a repository. And ``owner`` represents the specific user, the repository's owner. - -#### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -##### repository actions - -In our schema, we examined one of the main functionalities user can make on any GitHub repository. These are pushing to the repo, reading & viewing the repo, and deleting that repo. - -We can say only, - -- Repository owners can ``push`` to that repo. -- Repository owners, who have an admin or member role of the parent organization, can ``read``. -- Repository owners or admins of the parent organization can ``delete`` the repository. - -``` -entity repository { - - action push = owner - action read = owner and (parent.admin or parent.member) - action delete = parent.admin or owner - -} -``` - -Since `parent` represents the parent organization of a repository. It can reach repositories parent organization relations with comma. So, - -- ``parent.admin`` -indicates admin role on organization - -- ``parent.member`` -indicates member of that organization. - -### Sample Relational Tuples - -organization:2#admin@user:daniel - -organization:54#member@user:ege - -organization:12#member@user:jack - -repository:34#parent@organization:54 - -repository:68#owner@user:12 - -repository:12#owner@user:46 - - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -For instance, you can define that a user has certain permissions because of their relation to other entities. - -An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -## Deeply Nested Hierarchies - -This use case shows solving deeply nested hierarchies with the [Permify Schema]. - -We have a unique **action** usage for nested hierarchies, where parent and child entities can share permissions between them. Let's follow the below team project authorization model to examine this case. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - // organization user types - relation admin @user -} - -entity team { - - //refers to the organization that a team belongs to - relation org @organization - - // Only the organization administrator can edit - action edit = org.admin -} - -entity project { - - //refers to the team that a project belongs to - relation team @team - - // This action is responsible for nested permission inheritance - // team.edit refers to the edit action on the team entity which we defined above - // This means that the organization admin, who can edit the team - // can also edit the project related to the team. - action edit = team.edit -} -``` - -### Sample Relational Tuples - -organization:1#admin@user:1 - -team:1#org@organization:1#... - -project:1#team@team:1#... - -Lets assume we created the above [relational tuples]. If we try to enforce `Can user:1 edit project:1?` we will get **Allow** since the `user:1` is an admin of the `organization:1` and `project:1` belongs to `team:1`, which belongs to `organization:1`. - -[relational tuples]: ../getting-started/sync-data.md - -Let's break down this case, - -```perm -entity project { - - relation team @team - - action edit = team.edit -} -``` - -In the above `team.edit` points to the **edit** action in the **team** (that the project belongs to). That edit action on the team entity (`action edit = org.admin`) states that only admins of the **organization (which that team belongs to)** can edit. So our project inherits that action and conducts a result accordingly. - -If we go back to our question: `Can user:1 edit project:1?` this will give an **Allow** result, because user:1 is an admin in an organization that the projects' parent team belongs to. - -## User Groups & Team Permissions - -This use case shows how to organize permissions based on groupings of users or resources. In this use case we'll follow a simple project management app with our authorization language, [Permify Schema]. - -[Permify Schema]: ../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //organizational roles - relation admin @user - relation member @user - -} - -entity team { - - // represents owner or creator of the team - relation owner @user - - // represents direct member of the team - relation member @user - - // represents the organization that the team belongs to - relation org @organization - - // organization admins or team owners can edit, delete the team details - action edit = org.admin or owner - action delete = org.admin or owner - - // to invite someone you need to be an organization admin and either an owner or member of this team - action invite = org.admin and (owner or member) - - // only team owners can remove users - action remove_user = owner - -} - -entity project { - - // represents team and organization that a project belongs to - relation team @team - relation org @organization - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Schema Deconstruction - -#### Entities - -This schema consists of 4 entities, - -- `user`, represents users. This entity is empty because its only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents an organization that contain teams. - -- `team`, represents teams, which belong to an organization. - -- `project`, represents projects that belong to teams. - -#### Relations - -##### organization entity - -We can use **relations** to define roles. - -The organization entity has 2 relations ``admin`` and ``member`` users. Think of these as organizational-wide roles. - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -``` - -Roles (relations) can be scoped with different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates that each organization has its own roles. - -##### team entity - -The team entity has its own relations respectively, ``owner``, ``member`` and ``org`` - -```perm -entity team { - - relation owner @user - relation member @user - relation org @organization - -} -``` - -##### project entity - -The project entity has ``team`` and ``org`` relations. Both these relations represent parent relationships with other entities, parent team and parent organization. - -```perm -entity project { - - relation team @team - relation org @organization - -} -``` - -#### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or*** and ***not*** operators to define actions. - -##### team actions - -- Only organization ***admin (admin role)*** and ***team owner*** can edit and delete team specific resources. - -- Moreover, to invite a colleague to a team you must have an organizational ***admin role*** and either be a ***owner*** or ***member*** of that team. - -- To remove users in team you must be an ***owner*** of that team. - -And these rules are defined in Permify Schema as: - -```perm -entity team { - - action edit = org.admin or owner - action delete = org.admin or owner - - action invite = org.admin and (owner or member) - action remove_user = owner - -} -``` - -##### project actions - -And here are the project actions. The actions consist of checking access for basic operations such as viewing, editing, or deleting project resources. - -```perm -entity project { - - action view = org.admin or team.member - action edit = org.admin or team.member - action delete = team.member - -} -``` - -### Sample Relational Tuples - -team:2#member@user:daniel - -team:54#owner@user:daniel - -organization:12#admin@user:jack - -organization:51#member@user:jack - -organization:41#member@team:42#member - -project:35#team@team:34#.... - - -. -. -. -. -. - - -organization:41#member@team:42#member - -**--> represents members of team 42 are also members of organization 41** - -project:35#team@team:34#.... - -**--> represents project 54 is in team 34** - -## Need any help on Authorization ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.5.x/use-cases/simple-rbac.md b/docs/versioned_docs/version-0.5.x/use-cases/simple-rbac.md deleted file mode 100644 index 85d70b6b..00000000 --- a/docs/versioned_docs/version-0.5.x/use-cases/simple-rbac.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Role Based Access Control - -Want to implement roles and permissions in your application? Permify fully covers you at that point. The example below shows how to model simple role based access controls for organizational roles and permissions with our authorization language, [Permify Schema]. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -## Schema Deconstruction - -### Entities - -This schema consists of 2 entities, - -- `user`, represents users (maybe corresponds to employees). This entity is empty because it's only responsible for referencing users. - -```perm - entity user {} -``` - -- `organization`, represents the organization the user (employees) belongs. It has several roles and permissions related to the specific resources such as organization files and vendor files. - -### Relations - -#### organization entity - -We can use **relations** to define roles. In this example, we have 4 organization wide roles: admin, manager, member, and agent. - -```perm -entity organization { - - //roles - relation admin @user - relation member @user - relation manager @user - relation agent @user - -} -``` - -Roles (relations) can be scoped to different kinds of entities. But for simplicity, we follow a multi-tenancy approach, which demonstrates each organization has its own roles. - -### Actions - -Actions describe what relations, or relation's relation, can do. You can think of actions as entities' permissions. Actions define who can perform a specific action and in which circumstances. - -Permify Schema supports ***and***, ***or***, ***and not*** and ***or not*** operators to define actions. - -#### organization actions - -In our schema, we define several actions for controlling access permissions on organization files and organization vendor's files. - -```perm -entity organization { - - //organization files access permissions - action view_files = admin or manager or (member not agent) - action edit_files = admin or manager - action delete_file = admin - - //vendor files access permissions - action view_vendor_files = admin or manager or agent - action edit_vendor_files = admin or agent - action delete_vendor_file = agent - -} -``` - -let's take a look at some of the actions: - -- ``action edit_files = admin or manager`` -indicates that only the admin or manager has permission to edit files in the organization. - -- ``action view_files = admin or manager or (member not agent)`` -indicates that the admin, manager, or members (without having the agent role) can view organization files. - - - -## Example Relational Tuples for this case - -organization:2#admin@user:daniel - -organization:5#member@user:ashley - -organization:17#manager@user:mert - -organization:21#agent@user:ege - -. -. -. - -For more details about how relational tuples are created and stored in your preferred database, see [Relational Tuples]. - -[Relational Tuples]: ../getting-started/sync-data.md - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. - diff --git a/docs/versioned_docs/version-0.6.x/api-overview.md b/docs/versioned_docs/version-0.6.x/api-overview.md deleted file mode 100644 index 931d562b..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -id: api-overview -title: API Overview -sidebar_label: Using the API -slug: /api-overview ---- - -# Overview - -Permify API provides various functionalities around authorization such as performing access checks, reading and writing relation tuples, expanding your permissions (schema actions), and more. - -We structured Permify API in 4 core parts: - -- [PermissionService]: Consists access control requests and options. -- [DataService]: Authorization data operations such as creating, deleting and reading relational tuples and attributes. -- [BundleService]: Facilitates the creation and execution of bundles that perform predefined operations, establishing relationships and attributes based on provided arguments. -- [SchemaService]: Modeling and Permify Schema related functionalities including configuration and auditing. -- [TenancyService]: Consists tenant operations such as creating, deleting and listing. - -Permify exposes its APIs via both [gRPC](https://buf.build/permifyco/permify/docs/main:base.v1) - with [go] and [nodeJS] client options - and [REST](https://restfulapi.net/). - -[PermissionService]: ./permission -[DataService]: ./data -[BundleService]: ./bundle -[SchemaService]: ./schema -[TenancyService]: ./tenancy -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -:::info Integration with a Service Mesh -Our software does not include built-in support for service meshes (eg. Istio). - -However, since it communicates using standard protocols like gRPC and HTTP, it is compatible with Istio and similar service meshes. Users will need to configure their service mesh setup manually to manage traffic for our software within their deployment environment. -::: - -## Core Paths - -- Configure your authorization model with [Schema Write](./api-overview/schema/write-schema.md) -- Write relational tuples with [Write Data](./api-overview/data/write-data.md) -- Read relation tuples and filter them with [Read Relationships](./api-overview/data/read-relationships.md) -- Check access with [Check API](./api-overview/permission/check-api.md) -- Check entities permissions with [Lookup Entity](./api-overview/permission/lookup-entity.md) -- Check subject permissions with [Lookup Subject](./api-overview/permission/lookup-subject.md) -- Delete relation tuples with [Delete Tuple](./api-overview/data/delete-data.md) -- Expand schema actions with [Expand API](./api-overview/permission/expand-api.md) -- Watch changes in the relation tuples in real-time with [Watch API](./api-overview/watch/watch-changes.md) - -## Authentication - -You can secure APIs with our authentication methods; **Open ID Connect** or **Pre Shared Keys**. They can be configurable with flags or using configuration yaml file. See more details how to enable authentication from [Configuration Options](../reference/configuration) - -To access the endpoints after enabling authentication, it's necessary to provide a Bearer Token for identification. If your using golang or nodeJs client library, an authentication token can be provided via interceptors. You can find details in the clients' documentation. - -## Availability of the Service - -For our dedicated instance service we do have **99.9%** level of availability and to assure this level of availability, we employ several strategies: - -1. **Redundancy:** We deploy our system across multiple Availability Zones in a region, ensuring that it remains operational even if one zone experiences issues. -2. **Load Balancing:** Load balancers are used to distribute traffic across multiple instances of the service, ensuring that no single instance becomes a bottleneck. -3. **Auto-Scaling:** Our system is capable of scaling automatically based on the incoming load, ensuring that we have sufficient capacity to handle any increase in traffic. -4. **Data Replication:** Our PostgreSQL database replicates data to ensure its availability even in the event of a single-node failure. -5. **Backup and Recovery:** Regular backups are maintained, and our system supports a robust recovery strategy in case of significant failures. -6. **Monitoring & Alerts:** Using tools like Amazon CloudWatch, we monitor the health and performance of our system and can quickly respond to any detected issues. - -## Service Credits for Availability Failures - -In case of availability failures, Permify's Service Level Agreement (SLA) provides for Service Credits which are applied as a discount on your future bills: - -- If uptime is less than 99.95% but above or equal to 99.0%, you get a 10% Service Credit. -- If uptime is less than 99.0%, you get a 25% Service Credit. -- If uptime is less than 95.0%, you get a 100% Service Credit. - -These credits are your sole remedy for any availability failures under our SLA. - -## Request Rate Limits - -Default rate limit is set to 100 requests per second. However, users can adjust this based on their specific needs following our [documentation](https://docs.permify.co/docs/reference/configuration). We used [Token bucket](https://en.wikipedia.org/wiki/Token_bucket) algorithm for rate limiting. - -## Error Handling - -Permify API uses a set of defined error codes to indicate various types of failures or issues. -Understanding these error codes and their implications is vital for effective error handling and troubleshooting within the Permify API. -Each error code is designed to provide clear insights into what went wrong and how to resolve it, ensuring a smoother integration and operation of the API in your applications -Refer to the [Error Codes](https://github.com/Permify/permify/blob/master/proto/base/v1/errors.proto) documentation for detailed descriptions and resolution steps for each error code. - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/_category_.json deleted file mode 100644 index 5e515400..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Using the API", - "position": 5, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/bundle/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/bundle/_category_.json deleted file mode 100644 index d8d37140..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/bundle/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Bundle Service", - "position": 4, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/api-overview/bundle/delete-bundle.md b/docs/versioned_docs/version-0.6.x/api-overview/bundle/delete-bundle.md deleted file mode 100644 index d175eda8..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/bundle/delete-bundle.md +++ /dev/null @@ -1,58 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Bundle [Beta] - -The "Delete Bundle" API is designed for removing specific data bundles within a multi-tenant application environment. This API facilitates the deletion of a bundle, identified by its unique name, from a designated tenant's environment. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/delete - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.delete) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | - - - - -```go -rr, err: = client.Bundle.Delete(context.Background(), &v1.BundleDeleteRequest{ - TenantId: "t1", - Name: "organization_created", -}) -``` - - - - - -```javascript -client.bundle.delete({ - tenantId: "t1", - name: "organization_created", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/bundle/read-bundle.md b/docs/versioned_docs/version-0.6.x/api-overview/bundle/read-bundle.md deleted file mode 100644 index f96a54ce..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/bundle/read-bundle.md +++ /dev/null @@ -1,58 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Bundle [Beta] - -The "Read Bundle" API is a crucial tool for retrieving details of specific data bundles in a multi-tenant application setup. It is designed to access information about a bundle, uniquely identified by its name, within the specified tenant's environment. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/read - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.read) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | - - - - -```go -rr, err: = client.Bundle.Read(context.Background(), &v1.BundleReadRequest{ - TenantId: "t1", - Name: "organization_created", -}) -``` - - - - - -```javascript -client.bundle.read({ - tenantId: "t1", - name: "organization_created", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/bundle/write-bundle.md b/docs/versioned_docs/version-0.6.x/api-overview/bundle/write-bundle.md deleted file mode 100644 index 9ee9d653..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/bundle/write-bundle.md +++ /dev/null @@ -1,117 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Bundle [Beta] - -The "Write Bundle" API is designed for handling data in a multi-tenant application environment. Its primary function is to write and delete data according to predefined structures. This API allows users to define or update data bundles, each distinguished by a unique name. - -## Request - -**Path:** POST /v1/tenants/{tenant_id}/bundle/write - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Bundle/bundle.write) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | -| [x] | operations | object | Represent actions that can be performed on data, such as adding or deleting relationships or attributes when certain events occur. | -| [x] | arguments | string[] | Parameters that will be used in the operations | - - - - -```go -rr, err := client.Bundle.Write(context.Background(), &v1.BundleWriteRequest{ - TenantId: "t1", - Bundles: []*v1.DataBundle{ - { - Name: "organization_created", - Arguments: []string{ - "creatorID", - "organizationID", - }, - Operations: []*v1.Operation{ - { - RelationshipsWrite: []string{ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - }, - AttributesWrite: []string{ - "organization:{{.organizationID}}$public|boolean:false", - }, - }, - }, - }, - }, -}) -``` - - - - - -```javascript -client.bundle.write({ - tenantId: "t1", - bundles: [ - { - name: "organization_created", - arguments: [ - "creatorID", - "organizationID", - ], - operations: [ - { - relationships_write: [ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - ], - attributes_write: [ - "organization:{{.organizationID}}$public|boolean:false", - ] - } - ] - } - ] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "bundles": [ - { - "name": "organization_created" - "arguments": [ - "creatorID", - "organizationID" - ], - "operations": [ - { - "relationships_write": [ - "organization:{{.organizationID}}#admin@user:{{.creatorID}}", - "organization:{{.organizationID}}#manager@user:{{.creatorID}}", - ], - "attributes_write": [ - "organization:{{.organizationID}}$public|boolean:false", - ], - }, - ], - }, - ], -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/data/_category_.json deleted file mode 100644 index 1a2612e1..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Data Service", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/delete-data.md b/docs/versioned_docs/version-0.6.x/api-overview/data/delete-data.md deleted file mode 100644 index b8d09e33..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/delete-data.md +++ /dev/null @@ -1,111 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Data - -You can delete any stored relation tuples or attributes with following API - -## Request - -**Path:** -```javascript -POST /v1/tenants/{tenant_id}/data/delete -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.delete) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | tuples_filter | object |filter to delete relational tuples. Contains **entity**, **relation** and **subject**. -| [x] | attribute_filter | object | filter to delete attributes. Contains **entity** and **attributes**. -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | relation of the given entity | -| [x] | attribute | string array | attributes to be deleted | -| [ ] | subject | object | the user or user set. It contains type and id of the subject. || - - - - -```go -rr, err: = client.Data.Delete(context.Background(), & v1.DataDeleteRequest { - TenantId: "t1", - Metadata: &v1.DataDeleteRequestMetadata { - SnapToken: "" - }, - TupleFilter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "admin", - Subject: &v1.SubjectFilter { - Type: "user", - Id: []string {"1"}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.delete({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - tupleFilter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "admin", - subject: { - type: "user", - ids: [ - "1" - ], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "tupleFilter": { - "entity": { - "type": "organization", - "ids": [ - "1" - ] - }, - "relation": "admin", - "subject": { - "type": "user", - "ids": [ - "1" - ], - "relation": "" - } - }, -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/read-attributes.md b/docs/versioned_docs/version-0.6.x/api-overview/data/read-attributes.md deleted file mode 100644 index 86b4af1b..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/read-attributes.md +++ /dev/null @@ -1,95 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Attributes - -Read API allows for directly querying the stored graph data to display and filter stored attributes. - -## Request -```javascript -POST /v1/tenants/{tenant_id}/data/attributes/read -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.attributes.read) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | attributes | string array | attributes of the given entity | - - - - - -```go -rr, err: = client.Data.ReadAttributes(context.Background(), & v1.Data.AttributeReadRequest { - TenantId: "t1", - Metadata: &v1.Data.AttributeReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.AttributeFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Attributes: []string {"private"}, -}) -``` - - - - - -```javascript -client.data.readAttributes({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - attributes: [ - "private" - ], - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/read-relationships.md b/docs/versioned_docs/version-0.6.x/api-overview/data/read-relationships.md deleted file mode 100644 index f59a3d57..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/read-relationships.md +++ /dev/null @@ -1,106 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Read Relational Tuples - -Read API allows for directly querying the stored graph data to display and filter stored relational tuples. - -## Request -```javascript -POST /v1/tenants/{tenant_id}/data/relationships/read -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.relationships.read) - -| Required | Argument | Type | Default | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens) | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1โ€. -| [x] | relation | string | - | relation of the given entity | -| [ ] | subject | object | - | the user or user set. It containes type and id of the subject. || - - - - -```go -rr, err: = client.Data.ReadRelationships(context.Background(), & v1.Data.RelationshipReadRequest { - TenantId: "t1", - Metadata: &v1.Data.RelationshipReadRequestMetadata { - SnapToken: "" - }, - Filter: &v1.TupleFilter { - Entity: &v1.EntityFilter { - Type: "organization", - Ids: []string {"1"} , - }, - Relation: "member", - Subject: &v1.SubjectFilter { - Type: "", - Id: []string {""}, - Relation: "" - }} -}) -``` - - - - - -```javascript -client.data.readRelationships({ - tenantId: "t1", - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - metadata: { - snap_token: "", - }, - filter: { - entity: { - type: "organization", - ids: [ - "1" - ] - }, - relation: "member", - subject: { - type: "", - ids: [], - relation: "" - } - } -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/run-bundle.md b/docs/versioned_docs/version-0.6.x/api-overview/data/run-bundle.md deleted file mode 100644 index a86d53ea..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/run-bundle.md +++ /dev/null @@ -1,75 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Run Bundle [Beta] - -The "Run Bundle" API provides a straightforward way to execute predefined bundles within your application's tenant -environment. By sending a POST request to this endpoint, you can activate specific functionalities or processes -encapsulated in a bundle. - -## Request - -```javascript - POST /v1/tenants/{tenant_id}/data/run-bundle -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/bundle.run) - -| Required | Argument | Type | Description | -|----------|----------|---------|---------|-------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [x] | name | string | unique name identifying the bundle. | -| [ ] | arguments | map | parameters for the bundle in key-value format. | - - - - -```go -rr, err: = client.Data.RunBundle(context.Background(), &v1.BundleRunRequest{ - TenantId: "t1", - Name: "organization_created", - Arguments: map[string]string{ - "creatorID": "564", - "organizationID": "789", - }, -}) -``` - - - - - -```javascript -client.data.runBundle({ - tenantId: "t1", - name: "organization_created", - arguments: { - creatorID: "564", - organizationID: "789", - } -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/run-bundle' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "name": "organization_created", - "arguments": { - "creatorID": "564", - "organizationID": "789", - } -}' -``` - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/data/write-data.md b/docs/versioned_docs/version-0.6.x/api-overview/data/write-data.md deleted file mode 100644 index 67e87027..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/data/write-data.md +++ /dev/null @@ -1,477 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Authorization Data - -In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a [preferred database]. - -Since these attributes and relations are live instances, meaning they can be affected by specific user actions within the application, they can be created/deleted with a simple Permify API call at runtime. - -More specifically, the application client should update preferred database about the changes happening in entities or resources that are related to the authorization structure. - -If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write relational tuples to keep [preferred database] up-to-date. Besides, each attribute or relationship should be created according to its authorization model, Permify Schema. - -Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as tuples and store in [preferred database]. - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -[relational tuples]: ../../../getting-started/sync-data -[preferred database]: ../../../getting-started/sync-data#where-relational-tuples-used - -## Write Request - -:::info -You can use the **/v1/tenants/{tenant_id}/data/write** endpoint for both creating **relation tuples** and for creating **attribute data**. -::: - -**Path:** -```javascript - POST /v1/tenants/{tenant_id}/data/write -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Data/data.write) - -#### Glossary for parameters & payload objects: - -| Required | Argument | Type | Default | Description | -| -------- | -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant in your system) use pre-inserted tenant **t1** for this parameter. | -| [ ] | schema_version | string | 8 | Version of the schema. | -| [x] | tuples | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **relation**, and **subject** arguments.| -| [x] | attributes | array | - | Array of objects that are used to define relationships. Each object contains **entity**, **attribute**, and **value** arguments. | -| [x] | entity | object | - | Type and id of the entity. Example: "organization:1โ€ | -| [x] | subject | string | - | User or user set who wants to take the action. | -| [x] | relation | string | - | Custom relation name. Eg. admin, manager, viewer etc. | -| [x] | attribute | string | - | Custom attribute name. | -| [x] | value | object | - | Represents value and type of the attribute data. | - - -### Creating Relational Tuple - -Let's create an example relation tuple. If user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` should be created as follows: - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "user", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.data - .write({ - tenantId: "t1", - metadata: { - schemaVersion: "", - }, - tuples: [ - { - entity: { - type: "organization", - id: "1", - }, - relation: "admin", - subject: { - type: "user", - id: "3", - }, - }, - ], - }) - .then((response) => { - // handle response - }); -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - - -### Creating Attribute Data - -You can use `attributes` argument to create attribute/attributes with a single API call, similarly creating a `relational tuple`. - -Let's say **document:1** is a **private (boolean)** document, that only specific users have view access - `document:1$is_private|boolean:true`. - -:::info Attribute Data Syntax -As you noticed, the attribute tuple syntax differs from the relationship syntax, structured similarly as: -`entity $ attribute | value` -::: - - - - -```go -// Convert the wrapped attribute value into Any proto message -value, err := anypb.New(&v1.BooleanValue{ - Data: true, -}) -if err != nil { - // Handle error -} - -cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{ - TenantId: "t1",, - Metadata: &v1.DataWriteRequestMetadata{ - SchemaVersion: "", - }, - Attributes: []*v1.Attribute{ - { - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Attribute: "is_private", - Value: value, - }, - }, -}) -``` - - - - - -```javascript -const booleanValue = BooleanValue.fromJSON({ data: true }); - -const value = Any.fromJSON({ - typeUrl: 'type.googleapis.com/base.v1.BooleanValue', - value: BooleanValue.encode(booleanValue).finish() -}); - -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - attributes: [{ - entity: { - type: "document", - id: "1" - }, - attribute: "is_private", - value: value, - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ -{ - "metadata": { - "schema_version": "" - }, - "attributes": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "attribute": "is_private", - "value": { - "@type": "type.googleapis.com/base.v1.BooleanValue", - "data": true - } - } - ] -} -}' -``` - - - - -:::warning Attribute **value** field -**value** field is mandatory on attribute data creation. - -Here are the available attribute value types: - -- **type.googleapis.com/base.v1.StringValue** -- **type.googleapis.com/base.v1.BooleanValue** -- **type.googleapis.com/base.v1.IntegerValue** -- **type.googleapis.com/base.v1.DoubleValue** -- **type.googleapis.com/base.v1.StringArrayValue** -- **type.googleapis.com/base.v1.BooleanArrayValue** -- **type.googleapis.com/base.v1.IntegerArrayValue** -- **type.googleapis.com/base.v1.DoubleArrayValue** -::: - -#### Creating Attributes and Relations In Single Request - -Assume we want to both create relational tuple and attribute within in single request. Specifically we want to create following tuples, - -- `document:1#editor@user:1` -- `document:1$is_private|boolean:true` - - - - - -```go -// Convert the wrapped attribute value into Any proto message -value, err := anypb.New(&v1.BooleanValue{ - Data: true, -}) -if err != nil { - // Handle error -} - -cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{ - TenantId: "t1",, - Metadata: &v1.DataWriteRequestMetadata{ - SchemaVersion: "", - }, - Tuples: []*v1.Attribute{ - { - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Relation: "editor", - Subject: &v1.Subject{ - Type: "user", - Id: "1", - Relation: "", - }, - }, - }, - Attributes: []*v1.Attribute{ - { - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Attribute: "is_private", - Value: value, - }, - }, -}) -``` - - - - - -```javascript -const booleanValue = BooleanValue.fromJSON({ data: true }); - -const value = Any.fromJSON({ - typeUrl: 'type.googleapis.com/base.v1.BooleanValue', - value: BooleanValue.encode(booleanValue).finish() -}); - -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "editor", - subject: { - type: "user", - id: "1" - } - }], - attributes: [{ - entity: { - type: "document", - id: "1" - }, - attribute: "is_private", - value: value, - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ -{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "editor", - "subject": { - "type": "user", - "id": "1" - } - } - ], - "attributes": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "attribute": "is_private", - "value": { - "@type": "type.googleapis.com/base.v1.BooleanValue", - "data": true - } - } - ] -} -}' -``` - - - - -## Response - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -You can store that snap token alongside with the resource in your relational database, then use it used in endpoints to get fresh results from the API's. For example it can be used in access control check with sending via `snap_token` field to ensure getting check result as fresh as previous request. - -See more details on what is [Snap Tokens](../../reference/snap-tokens) and how its avoiding stale cache. - -## Suggested Workflow - -The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ? - -### Two Phase Commit Approach - -In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document. - -To give more concurrent example of this action, let's take a look at below createDocument function - -```go -func CreateDocuments(db *gorm.DB) error { - - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteData(...) - } - }() - - if err := tx.Error; err != nil { - return err - } - - if err := tx.Create(docs).Error; err != nil { - tx.Rollback() - // if transaction fails, then delete malformed relation tuple - permify.DeleteData(...) - return err - } - - // if transaction successful, write relation tuple to Permify - permify.WriteData(...) - - return tx.Commit().Error -} -``` - -The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency. - -### Data That Not Stored In Application Database - -Although ownership generally stored in application databases, there are some data that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc. - -For example, you can model a simple project management authorization in Permify as follows, - -```perm -entity user {} - -entity team { - - relation owner @user - relation member @user -} - -entity project { - - relation team @team - relation owner @user - - action view = team.member or team.owner or project.owner - action edit = project.owner or team.owner - action delete = project.owner or team.owner - -} -``` - -This **team member** relation won't need to be stored in the application database. Storing it only in Permify - preferred database - is enough. In that situation, `WriteData` can be performed in any logical place in your stack. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/permission/_category_.json deleted file mode 100644 index e810c587..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Permission Service", - "position": 2, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/expand-api.md b/docs/versioned_docs/version-0.6.x/api-overview/permission/expand-api.md deleted file mode 100644 index 998955c1..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/expand-api.md +++ /dev/null @@ -1,319 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Expand API - -Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission - -Expand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other โŸจobject#relationโŸฉ pairs. - -:::caution When To Use ? -Expand is designed for reasoning the complete set of users that have access to their objects, which allows our users to build efficient search indices for access-controlled content. - -It is not designed to use as a check access. Expand request has a high latency which can cause a performance issues when its used as access check. -::: - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.expand) - - - - -```go -cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{ - TenantId: "t1", - Metadata: &v1.PermissionExpandRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - }, - Entity: &v1.Entity{ - Type: "repository", - Id: "1", - }, - Permission: "push", -}) -``` - - - - - -```javascript -client.permission.expand({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - }, - entity: { - type: "repository", - id: "1" - }, - permission: "push", -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -}' -``` - - - -## Example Usage - -To give an example usage for Expand API, let's examine following authorization model. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity repository { - - relation parent @organization - relation owner @user - - action push = owner - action read = owner and (parent.admin or parent.member) - -} -``` - -Above schema - modeled with Permify DSL - represents a simplified version of GitHub access control. When we look at the repository entity, we can see two actions and corresponding accesses: - - - Only owners can push to a private repository. - - To read a private repository, the user should be one of the owners of that repository and need to belong to the parent organization of that repository ( user can either be admin or member on that organization). - -According to above authorization model, let's create 3 example relation tuples for testing expand API, - -`organization:1#admin@user:1` --> User 1 is admin in organization 1โ€ - -`repository:1#owner@user:1` --> User 1 is owner of repository 1 - -`repository:1#parent@organization:1#...` --> repository 1 belongs to organization 1 - -We can use expand API to reason the access actions. If we want to reason access structure for actions of repository entity, we can use expand API with ***POST "/v1/permissions/expand"***. - -**Path:** -```javascript -POST /v1/tenants/{tenant_id}/permissions/expand -``` - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md) | -| [x] | entity | string | - | Name and id of the entity. Example: repository:1โ€. | -| [x] | permission | string | - | The permission the user wants to perform on the resource | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - -### Expand Push Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "push" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } -} -``` - -

-
- -### Expand Read Action - -
Request -

- -```json -{ - "metadata": { - "schema_version": "", - "snap_token": "" - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "read" -} -``` - -

-
- -
Response -

- -```json -{ - "tree": { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_INTERSECTION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "owner" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin" - }, - "leaf": { - "subjects": [ - { - "type": "user", - "id": "1", - "relation": "" - } - ] - } - } - ] - } - }, - { - "target": { - "entity": { - "type": "repository", - "id": "1" - }, - "relation": "read" - }, - "expand": { - "operation": "OPERATION_UNION", - "children": [ - { - "target": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "member" - }, - "leaf": { - "subjects": [] - } - } - ] - } - } - ] - } - } - ] - } - } -} -``` -

-
- diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-entity.md b/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-entity.md deleted file mode 100644 index b87d77a4..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-entity.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -title: Entity (Data) Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Entity Filtering - -Lookup Entity endpoint lets you ask questions in form of **โ€œWhich resources can user:X do action Y?โ€**. As a response of this youโ€™ll get a entity results in a format of string array or as a streaming response depending on the endpoint you're using. - -So, we provide 2 separate endpoints for data filtering check request, - -- [Lookup Entity](#lookup-entity) -- [Lookup Entity (Streaming)](#lookup-entity-streaming) - -## Lookup Entity - -In this endpoint you'll get directly the IDs' of the entities that are authorized in an array. - -**Path** -```javascript - POST /v1/permissions/lookup-entity -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntity) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../../reference/snap-tokens) | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest { - TenantId: "t1", - Metadata: & v1.PermissionLookupEntityRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - EntityType: "document", - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - } -}) -``` - - - - -```javascript -client.permission.lookupEntity({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity_type: "document", - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response.entity_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity_type": "document", - "permission": "edit", - "subject": { - "type":"user", - "id":"1" - } -}' -``` - - - -## How Lookup Operations Evaluated - -We explicitly designed reverse lookup to be more performant with changing its evaluation pattern. We do not query all the documents in bulk to get response, instead of this Permify first finds the necessary relations with given subject and the permission/action in the API call. Then query these relations with the subject id this way we reduce lots of additional queries. - -To give an example, - -```jsx -entity user {} - -entity organization { - relation admin @user -} - -entity container { - relation parent @organization - relation container_admin @user - action admin = parent.admin or container_admin -} - -entity document { - relation container @container - relation viewer @user - relation owner @user - action view = viewer or owner or container.admin -} -``` - -Lets say we called (reverse) lookup API to find the documents that user:1 can view. Permify first finds the relations that linked with view action, these are - -- `document#viewer` -- `document#owner` -- `organization#admin` -- `container#``container_admin` - -Then queries each of them with `user:1.` - -## Lookup Entity (Streaming) - -The difference between this endpoint from direct Lookup Entity is response of this entity gives the IDs' as stream. This could be useful if you have large data set that getting all of the authorized data can take long with direct lookup entity endpoint. - -**Path** -```javascript - POST /v1/permissions/lookup-entity-stream -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupEntityStream) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md) | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [x] | entity_type | object | - | type of the entity. Example: repositoryโ€. | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest { - Metadata: &v1.PermissionLookupEntityRequestMetadata { - SnapToken: "", - SchemaVersion: "" - Depth: 50, - }, - EntityType: "document", - Permission: "view", - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) - -// handle stream response -for { - res, err: = str.Recv() - - if err == io.EOF { - break - } - - // res.EntityId -} -``` - - - - -```javascript -const permify = require("@permify/permify-node"); -const {PermissionLookupEntityStreamResponse} = require("@permify/permify-node/dist/src/grpc/generated/base/v1/service"); - -function main() { - const client = new permify.grpc.newClient({ - endpoint: "localhost:3478", - }) - - let res = client.permission.lookupEntityStream({ - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entityType: "document", - permission: "view", - subject: { - type: "user", - id: "1" - } - }) - - handle(res) -} - -async function handle(res: AsyncIterable) { - for await (const response of res) { - // response.entityId - } -} -``` - - - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-subject.md b/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-subject.md deleted file mode 100644 index 0d0e17ff..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-subject.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Subject Filtering ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Filtering - -Lookup Subject endpoint lets you ask questions in form of **โ€œWhich subjects can do action Y on entity:X?โ€**. As a response of this youโ€™ll get a subject results in a format of string array. - -So, we provide 1 endpoint for subject filtering request, - -- [/v1/permissions/lookup-subject](#lookup-subject) - -## Lookup Subject - -In this endpoint you'll get directly the IDs' of the subjects that are authorized in an array. - -**POST** -```javascript -/v1/permissions/lookup-subject -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.lookupSubject) - -| Required | Argument | Type | Default | Description | -|----------|---------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | - | Version of the schema | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1 | -| [x] | permission | string | - | the action the user wants to perform on the resource | -| [x] | subject_reference | object | - | the subject or subject reference who wants to take the action. It contains type and relation of the subject. | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest { - TenantId: "t1", - Metadata: &v1.PermissionLookupSubjectRequestMetadata{ - SnapToken: "", - SchemaVersion: "", - Depth: 20, - }, - Entity: &v1.Entity{ - Type: "document", - Id: "1", - }, - Permission: "edit", - SubjectReference: &v1.RelationReference{ - Type: "user", - Relation: "", - } -}) -``` - - - - -```javascript -client.permission.lookupSubject({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "" - depth: 20, - }, - Entity: { - Type: "document", - Id: "1", - }, - permission: "edit", - subject_reference: { - type: "user", - relation: "" - } -}).then((response) => { - console.log(response.subject_ids) -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "" - "depth": 20, - }, - "entity": { - type: "document", - id: "1' - }, - "permission": "edit", - "subject_reference": { - "type": "user", - "relation": "" - } -}' -``` - - - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/api-overview/permission/subject-permission.md b/docs/versioned_docs/version-0.6.x/api-overview/permission/subject-permission.md deleted file mode 100644 index 8157f3dc..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/permission/subject-permission.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Subject Permission List ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Subject Permission List - -The Subject Permission List endpoint allows you to inquire in the form of **โ€œWhich permissions user:x can perform on entity:y?โ€**. In response, you'll receive a list of permissions specific to the user for the given entity, returned in the format of a map. - -So, we provide 1 endpoint for subject permission list, - -- [/v1/permissions/subject-permission](#subject-permission) - -In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as "sample-permission" -> "ALLOWED". This represents the permissions and their associated states in a key-value pair format. - -## Request - -**Path:** -```javascript -POST /v1/permissions/subject-permission -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Permission/permissions.subjectPermission) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | schema_version | string | 8 | Version of the schema | -| [ ] | snap_token | string | - | the snap token to avoid stale cache, see more details on [Snap Tokens](../../reference/snap-tokens.md). | -| [x] | entity | object | - | contains entity type and id of the entity. Example: repository:1. | -| [x] | subject | object | - | the user or user set who wants to take the action. It contains type and id of the subject. | -| [x] | depth | integer | 8 | Timeout limit when if recursive database queries got in loop | -| [ ] | only_permission | bool | false | By default, the endpoint returns both permissions and relations associated with the user and entity. However, when the "only_permission" parameter is set to true, it returns only the permissions. | | -| [ ] | context | object | - | Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../reference/contextual-tuples.md) | - - - - -```go -cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest { - TenantId: "t1", - Metadata: &v1.PermissionSubjectPermissionRequestMetadata { - SnapToken: "", - SchemaVersion: "", - OnlyPermission: false, - Depth: 20, - }, - Entity: &v1.Entity { - Type: "repository", - Id: "1", - }, - Subject: &v1.Subject { - Type: "user", - Id: "1", - }, -}) -``` - - - - -```javascript -client.permission.subjectPermission({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - onlyPermission: true, - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - subject: { - type: "user", - id: "1" - } -}).then((response) => { - console.log(response); -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "only_permission": true, - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -## Response - -```json -{ - "results": [ - { - "key": "delete", - "value": "RESULT_ALLOWED" - }, - { - "key": "edit", - "value": "RESULT_ALLOWED" - } - ] -} -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.6.x/api-overview/schema/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/schema/_category_.json deleted file mode 100644 index 8fd1e959..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/schema/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Schema Service", - "position": 1, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/schema/write-schema.md b/docs/versioned_docs/version-0.6.x/api-overview/schema/write-schema.md deleted file mode 100644 index 4ae10d83..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/schema/write-schema.md +++ /dev/null @@ -1,97 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Write Schema - -Permify provide it's own authorization language to model common patterns of easily. We called the authorization model Permify Schema and it can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. - -We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is ***".perm"***. - -:::caution Use Playground For Testing -If you're planning to test Permify manually, maybe with an API Design platform such as [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), etc; we're suggesting using our playground to create model. Because Permify Schema needs to be configured (send to API) in Permify API in a **string** format. Therefore, created model should be converted to **string**. - -Although, it could easily be done programmatically, it could be little challenging to do it manually. To help on that, we have a button on the playground to copy created model to the clipboard as a string, so you get your model in string format easily. - -![copy-btn](https://user-images.githubusercontent.com/34595361/198015792-a7f0d727-a1a5-4039-b0be-d097321b8d53.png) -::: - -Permify Schema needed to be send to API endpoint **/v1/schemas/write"** for configuration of your authorization model on Permify API. - -## Request - -```javascript -POST /v1/tenants/{tenant_id}/schemas/write -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Schema/schemas.write) - -| Required | Argument | Type | Default | Description | -|----------|-------------------|--------|---------|-------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. -| [x] | schema | string | - | Permify Schema as string| - - - - -```go -sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest { - TenantId: "t1", - Schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - `, -}) -``` - - - - -```javascript -client.schema.write({ - tenantId: "t1", - schema: ` - "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" - ` -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "schema": "entity user {}\n\n entity organization {\n\n relation admin @user\n relation member @user\n\n action create_repository = (admin or member)\n action delete = admin\n }\n\n entity repository {\n\n relation owner @user\n relation parent @organization\n\n action push = owner\n action read = (owner and (parent.admin and parent.member))\n action delete = (parent.member and (parent.admin or owner))\n }" -}' -``` - - - -## Example Request on Postman -**POST** "/v1/tenants/{tenant_id}/schemas/write"** - -**Example Request on Postman:** - -![permify-schema](https://user-images.githubusercontent.com/34595361/197405641-d8197728-2080-4bc3-95cb-123e274c58ce.png) - - -## Suggested Workflow For Schema Changes - -It's expected that your initial schema will eventually change as your product or system evolves - -As an example when a new feature arise and related permissions created you need to change the schema (rewrite it with adding new permission) then configure it using this Write Schema API. Afterwards, you can use the preferred version of the schema in your API requests with **schema_version**. If you do not prefer to use **schema_version** params in API calls Permify automatically gets the latest schema on API calls. - -A potential caveat of changing or creating schemas too often is the creation of many idle relation tuples. In Permify, created relation tuples are not removed from the stored database unless you delete them with the [delete API](../data/delete-data.md). For this case, we have a [garbage collector](https://github.com/Permify/permify/pull/381) which you can use to clear expired or idle relation tuples. - -We recommend applying the following pattern to safely handle schema changes: - -- Set up a central git repository that includes the schema. -- Teams or individuals who need to update the schema should add new permissions or relations to this repository. -- Centrally check and approve every change before deploying it via CI pipeline that utilizes the **Write Schema API**. We recommend adding our [schema validator](https://github.com/Permify/permify-validate-action) to the pipeline to ensure that any changes are automatically validated. -- After successful deployment, you can use the newly created schema on further API calls by either specifying its schema ID or by not providing any schema ID, which will automatically retrieve the latest schema on API calls. - -## Need any help ? - -Depending on the frequency and the type of the changes that you made on the schemas, this method may not be optimal for you - In such cases, we are open to exploring alternative solutions. Please feel free to [schedule a call with one of our engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/tenancy/_category_.json deleted file mode 100644 index e1ebce3c..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Tenancy Service", - "position": 5, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/create-tenant.md b/docs/versioned_docs/version-0.6.x/api-overview/tenancy/create-tenant.md deleted file mode 100644 index 20a35d7e..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/create-tenant.md +++ /dev/null @@ -1,59 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Tenant - -Permify Multi Tenancy support you can create custom schemas for tenants and manage them in a single place. You can create a tenant with following API. - -:::caution -We have a pre-inserted tenant - **t1** - by default for the ones that don't use multi-tenancy. -::: - -## Request - -```javascript -POST /v1/tenants/create -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.create) - - - - -```go -rr, err: = client.Tenancy.Create(context.Background(), & v1.TenantCreateRequest { - Id: "" - Name: "" -}) -``` - - - - - -```javascript -client.tenancy.create({ - id: "", - name: "" -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'http://localhost:3476/v1/tenants/create' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "id": "", - "name": "" -}' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/delete-tenant.md b/docs/versioned_docs/version-0.6.x/api-overview/tenancy/delete-tenant.md deleted file mode 100644 index aca60ef7..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/tenancy/delete-tenant.md +++ /dev/null @@ -1,47 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Delete Tenant - -You can delete a tenant with following API. - -## Request -```javascript -DELETE /v1/tenants/{id} -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Tenancy/tenants.delete) - - - - -```go -rr, err: = client.Tenancy.Delete(context.Background(), & v1.TenantDeleteRequest { - Id: "" -}) -``` - - - - - -```javascript -client.tenancy.delete({ - id: "", -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1' -``` - - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/api-overview/watch/_category_.json b/docs/versioned_docs/version-0.6.x/api-overview/watch/_category_.json deleted file mode 100644 index bb0c647b..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/watch/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Watch Service", - "position": 6, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/api-overview/watch/watch-changes.md b/docs/versioned_docs/version-0.6.x/api-overview/watch/watch-changes.md deleted file mode 100644 index aabc7416..00000000 --- a/docs/versioned_docs/version-0.6.x/api-overview/watch/watch-changes.md +++ /dev/null @@ -1,145 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Watch - -The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples. - -The Watch API exclusively supports gRPC and works with PostgreSQL, given the track_commit_timestamp option is enabled. Please note, it doesn't support in-memory databases or HTTP communication. - -# Requirements - -- PostgreSQL database set up with track_commit_timestamp option enabled - -## Enabling track_commit_timestamp on PostgreSQL - -To ensure data consistency and synchronization between your application and Permify, enable track_commit_timestamp on -your PostgreSQL server. This can be done by executing the following options in your PostgreSQL: - -### Option 1: SQL Command - -1. Open your PostgreSQL command line interface. -2. Execute the following command: - - ```sql - ALTER SYSTEM SET track_commit_timestamp = ON; - ``` - -3. Reload the configuration with the following command: - - ```sql - SELECT pg_reload_conf(); - ``` - -### Option 2: Editing postgresql.conf - -1. Find and open the postgresql.conf file in a text editor. Its location depends on your PostgreSQL installation. Common - locations are: - - Debian-based systems: /etc/postgresql/[version]/main/postgresql.conf - - Red Hat-based systems: /var/lib/pgsql/data/postgresql.conf - -2. Add or modify the following line in the postgresql.conf file: - ``` - track_commit_timestamp = on - ``` - -3. Save and close the postgresql.conf file. -4. Reload the PostgreSQL configuration for the changes to take effect. This can be done via the PostgreSQL console: - ```sql - SELECT pg_reload_conf(); - ``` - - Or if you have command line access, use: - - ```bash - sudo service postgresql reload - ``` - -Please ensure you have the necessary permissions to execute these commands or modify the postgresql.conf file. Also, remember that changes in the postgresql.conf file will persist across restarts, while the SQL method may need to be reapplied depending on your PostgreSQL version and setup. - -:::info -Important Configuration Requirement: To use the Watch API, it must be enabled in your configuration file. Add or modify the following lines: - -```yaml -service: - watch: - enabled: true -``` - -::: - -## Request - -**Path:** -```javascript -POST /v1/watch/watch -``` - -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/#/Watch/watch.watch) - -| Required | Argument | Type | Default | Description | -|----------|------------|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | tenant_id | string | - | identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant `t1` for this field. | -| [ ] | snap_token | string | - | specifies the starting point for broadcasting changes. If a snap_token is provided, all changes following that specific snapshot will be broadcasted. If a snap_token is not provided, the Watch API will broadcast all changes that occur after the Watch API is initiated., see more details on [Snap Tokens](../../../reference/snap-tokens). | - - -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```go) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -[//]: # () -[//]: # (```javascript) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # () - -[//]: # () - -## Response - -```json -{ - "changes": { - "tuple_changes": [ - { - "operation": "OPERATION_CREATE", - "tuple": { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject": { - "type": "user", - "id": "56", - "relation": "" - } - } - } - ], - "snap_token": "MgMAAAAAAAA=" - } -} -``` - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or -have any questions about this -example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.6.x/comparision.md b/docs/versioned_docs/version-0.6.x/comparision.md deleted file mode 100644 index 75fb39c4..00000000 --- a/docs/versioned_docs/version-0.6.x/comparision.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: comparison -title: Comparison Between Other Zanzibar implementations ---- - -:::caution Note -This comparison table shows the differentiation between authorization solutions based or inspired by Google Zanzibar paper. If you use any of these solutions and feel the information could be improved, feel free to reach out. -::: - -## General Aspects - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|---------------------------------|------------|------------|-----------|-----------| -| **Zanzibar Paper Faithfulness** | Medium | High | High | High | -| **Scalability** | Medium | Medium | High | High | -| **Consistency & Cache** | No Zookies | No Zookies | Supported | Supported | -| **Dev UX** | Average | Average | High | High | - -## Feature Set - -- โœ…  Supported, and ready to use with no added configuration or code -- ๐ŸŸก  Limited support and requires extra user-code to implement. -- โ›”  Not officially supported or documented. - -| | Ory/Keto | OpenFGA | SpiceDB | Permify | -|--------------------------|----------|---------|---------|---------| -| **Check API** | โœ… | โœ… | โœ… | โœ… | -| **Write API** | โœ… | โœ… | โœ… | โœ… | -| **Read API** | โœ… | โœ… | โœ… | โœ… | -| **Expand API** | โœ… | โœ… | โœ… | โœ… | -| **Watch API** | โœ… | โœ… | โœ… | โœ… | -| **RBAC** | โœ… | โœ… | โœ… | โœ… | -| **ReBAC** | โœ… | โœ… | โœ… | โœ… | -| **ABAC** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Data Filtering** | โ›” | โœ… | โœ… | โœ… | -| **Multi Tenancy** | โ›” | โœ… | โ›” | โœ… | -| **Testing & Validation** | โ›” | ๐ŸŸก | โœ… | โœ… | -| **Logging & Tracing** | ๐ŸŸก | โœ… | โœ… | โœ… | diff --git a/docs/versioned_docs/version-0.6.x/getting-started/_category_.json b/docs/versioned_docs/version-0.6.x/getting-started/_category_.json deleted file mode 100644 index 52b54bbb..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Getting Started", - "position": 2, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/_category_.json b/docs/versioned_docs/version-0.6.x/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f801..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Example Permission Structures", - "position": 4, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/facebook-groups.md b/docs/versioned_docs/version-0.6.x/getting-started/examples/facebook-groups.md deleted file mode 100644 index 1b918630..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/facebook-groups.md +++ /dev/null @@ -1,546 +0,0 @@ -# Facebook Groups - -This example demonstrate the authorization structure for Facebook groups, which enables users to perform various actions based on their roles and permissions within the group. - -### Schema | [Open in playground](https://play.permify.co/?s=XNEAs8dr0AINwCuSMcxHI) - -```perm -// Represents a user -entity user {} - -// Represents a Facebook group -entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator -} - -// Represents a post in a Facebook group -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner -} - -// Represents a comment like on a post in a Facebook group -entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member -} - -// Definition of poll entity -entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin -} - -// Definition of file entity -entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin -} - -// Definition of event entity -entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member -} -``` - -## Brief Examination of the Model - -The model defines several entities and relations, as well as actions and permissions that can be taken by users within the group. Let's examine them shortly; - -### Entities & Relations - -* **`user`** entity represents a user in the Facebook. - -* **`group`** entity represents the Facebook group, and it has several relations including member, admin, and moderator to represent the members, admins, and moderators of the group. Additionally, there are relations to represent the posts and comments in the group. - -* **`post`** entity represents a post in the Facebook group, and it has relations to represent the owner of the post and the group that the post belongs to. - -* **`comment`** entity represents a comment on a post in the Facebook group, and it has relations to represent the owner of the comment, the post that the comment belongs to, and the comment itself. - -* **`like`** entity represents a like on a post in the Facebook group, and it has relations to represent the owner of the like and the post that the like belongs to. - -* **`poll`** entity represents a poll in the Facebook group, and it has relations to represent the owner of the poll and the group that the poll belongs to. - -* **`file`** entity represents a file in the Facebook group, and it has relations to represent the owner of the file and the group that the file belongs to. - -* **`event`** entity represents an event in the Facebook group, and it has relations to represent the owner of the event and the group that the event belongs to. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. - -For example, the `create_group` action can only be performed by a `member`, as follows: - -#### Creating a group permission - -```perm -entity group { - - // Relation to represent the members of the group - relation member @user - - .. - - // Create group permission - action create_group = member - - .. - .. -} -``` - -Another example would be given from the `edit_post` action in the post entity, which specifies the permissions required to edit a post in a Facebook group. - -#### Editing a post permission - -```perm -entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - .. - - action edit_post = owner or group.admin - - .. - .. -} -``` - -An **owner** of a post can always edit their own post. In addition, members who are defined as **admin** of the group - which the post belongs to - can also edit the post. - -Since most entities are deeply nested together, we also have multiple hierarchical permissions. - -#### Nested Hierarchies - -For example, we can define a permission "view_comment" if only user is owner of that comment or user is a member of the group which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -//group relationships -group:1#member@user:1 -group:1#admin@user:2 -group:2#moderator@user:3 -group:2#member@user:4 -group:1#member@user:5 - -//post relationships -post:1#owner@user:1 -post:1#group@group:1 -post:2#owner@user:4 -post:2#group@group:1 - -//comment relationships -comment:1#owner@user:2 -comment:1#post@post:1 -comment:2#owner@user:5 -comment:2#post@post:2 - -//like relationships -like:1#owner@user:3 -like:1#post@post:1 -like:2#owner@user:4 -like:2#post@post:2 - -//poll relationships -poll:1#owner@user:2 -poll:1#group@group:1 -poll:2#owner@user:5 -poll:2#group@group:1 - -//like relationships -file:1#owner@user:1 -file:1#group@group:1 - -//event relationships -event:1#owner@user:3 -event:1#group@group:1 -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:4 RSVP_to_event event:1 ? -

- -```perm - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - - .. - .. - - action RSVP_to_event = owner or group.member - } -``` - -According to what we have defined for the **'RSVP_to_event'** action, users who are either the owner of `event:1` or a member of the group that belongs to `event:1` can grant access to RSVP to the event. - -According to the relation tuples we created, `user:4` is not the **owner** of the event. Furthermore, when we check whether `user:4` is a **member** of the only group (`group:1`) that `event:1` is part of (`event:1#group@group:1`), we see that there is no **member** relation for `user:4` in that group. - -Therefore, the `user:4 RSVP_to_event event:1` check request should yield a **'false'** response. - -

-
- -
can user:5 view_comment comment:1 ? -

- -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view_comment = owner or post.group_member - - .. - .. -} -``` - -According to the relation tuples we created, `user:5` is not the **owner** of the comment. But member of the `group:1` and thats grant `user:5` (`group:1#member@user:5`) access to perform view the comment:1. In particularly, `comment:1` is part of the `post:1` (`comment:1#post@post:1`) and `post:1` is part of the group:1 (`post:1#group@group:1`). And from the action definition on above model group:1 members can view the `comment:1`. - -Therefore, the `user:5 view_comment comment:1` check request should yield a **'true'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity group { - - // Relation to represent the members of the group - relation member @user - // Relation to represent the admins of the group - relation admin @user - // Relation to represent the moderators of the group - relation moderator @user - - // Permissions for the group entity - action create = member - action join = member - action leave = member - action invite_to_group = admin - action remove_from_group = admin or moderator - action edit_settings = admin or moderator - action post_to_group = member - action comment_on_post = member - action view_group_insights = admin or moderator - } - - entity post { - - // Relation to represent the owner of the post - relation owner @user - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - action view_post = owner or group.member - action edit_post = owner or group.admin - action delete_post = owner or group.admin - - permission group_member = group.member - } - - entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - - // Permissions for the comment entity - action view_comment = owner or post.group_member - action edit_comment = owner - action delete_comment = owner - } - - entity like { - - // Relation to represent the owner of the like - relation owner @user - - // Relation to represent the post that the like belongs to - relation post @post - - // Permissions for the like entity - action like_post = owner or post.group_member - action unlike_post = owner or post.group_member - } - - entity poll { - - // Relation to represent the owner of the poll - relation owner @user - - // Relation to represent the group that the poll belongs to - relation group @group - - // Permissions for the poll entity - action create_poll = owner or group.admin - action view_poll = owner or group.member - action edit_poll = owner or group.admin - action delete_poll = owner or group.admin - } - - entity file { - - // Relation to represent the owner of the file - relation owner @user - - // Relation to represent the group that the file belongs to - relation group @group - - // Permissions for the file entity - action upload_file = owner or group.member - action view_file = owner or group.member - action delete_file = owner or group.admin - } - - entity event { - - // Relation to represent the owner of the event - relation owner @user - // Relation to represent the group that the event belongs to - relation group @group - - // Permissions for the event entity - action create_event = owner or group.admin - action view_event = owner or group.member - action edit_event = owner or group.admin - action delete_event = owner or group.admin - action RSVP_to_event = owner or group.member - } - -relationships: - - group:1#member@user:1 - - group:1#admin@user:2 - - group:2#moderator@user:3 - - group:2#member@user:4 - - group:1#member@user:5 - - post:1#owner@user:1 - - post:1#group@group:1 - - post:2#owner@user:4 - - post:2#group@group:1 - - comment:1#owner@user:2 - - comment:1#post@post:1 - - comment:2#owner@user:5 - - comment:2#post@post:2 - - like:1#owner@user:3 - - like:1#post@post:1 - - like:2#owner@user:4 - - like:2#post@post:2 - - poll:1#owner@user:2 - - poll:1#group@group:1 - - poll:2#owner@user:5 - - poll:2#group@group:1 - - file:1#owner@user:1 - - file:1#group@group:1 - - event:1#owner@user:3 - - event:1#group@group:1 - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "event:1" - subject: "user:4" - assertions: - RSVP_to_event : false - - entity: "comment:1" - subject: "user:5" - assertions: - view_comment : true -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233152003-1fbaf2af-d208-4290-af1f-359870b0de49.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/google-docs.md b/docs/versioned_docs/version-0.6.x/getting-started/examples/google-docs.md deleted file mode 100644 index 3f14e1f7..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/google-docs.md +++ /dev/null @@ -1,344 +0,0 @@ -# Google Docs Simplified - -This example models a simplified version of Google Docs style permission system where users can be granted direct access to a document, or access via organizations and nested groups. - -### Schema | [Open in playground](https://play.permify.co/?s=iuRic3nR1HeZJcFyRNKPo) - -```perm -entity user {} - -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} - -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} - -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -## Breakdown of the Model - -### User - -```perm -entity user {} -``` - -Represents a user who can be granted permission to access a documents directly, or through their membership in a group or organization. - -### Document - -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -Represents a document that users can be granted permission to access. The document entity has two relationships: - -#### Relations - -**org:** Represents organization that document belongs to. - -**manager:** A relationship between users who are authorized to manage the document. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group member and manager relations. - -**viewer:** A relationship between users who are authorized to view the document. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager relations. - -The document entity has two actions defined: - -#### Actions - -**manage:**: An action that can be performed by users who are authorized to manage the document, as determined by the manager relationship. - -**view:** An action that can be performed by users who are authorized to view the document, as determined by the viewer and manager relationships. - -### Group - -```perm -entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager -} -``` - -Represents a group of users who can be granted permission to access a document. The group entity has two relationships: - -#### Relations - -**manager:** A relationship between users who are authorized to manage the group. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are members of the group. This relationship is defined by the `@user` annotation on one end and the `@group#member` and `@group#manager` annotations on the other end corresponding to the group entity member and manager. - -The group entity has one action defined: - -### Organization - -```perm -entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member -} -``` - -Represents an organization that can contain groups, users, and documents. The organization entity has several relationships: - -#### Relations - -**group:** A relationship between the organization and its groups. This relationship is defined by the `@group` annotation on the end corresponding to the group entity. - -**document:** A relationship between the organization and its document. This relationship is defined by the `@document` annotation on the end corresponding to the group entity. - -**administrator:** A relationship between users who are authorized to manage the organization. This relationship is defined by the `@user` annotation on both ends, and by the `@group#member` and `@group#manager` annotations on the ends corresponding to the group entity member and manager. - -**direct_member:** A relationship between users who are directly members of the organization. This relationship is defined by the `@user` annotation on the end corresponding to the user entity. - -The organization entity has two permissions defined: - -#### Permissions - -**admin:** An permission that can be performed by users who are authorized to manage the organization, as determined by the administrator relationship. - -**member:** An permission that can be performed by users who are directly members of the organization, or who have administrator relationship, or who are members of groups that are part of the organization, - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different groups -group:tech#manager@user:ashley -group:tech#direct_member@user:david -group:marketing#manager@user:john -group:marketing#direct_member@user:jenny -group:hr#manager@user:josh -group:hr#direct_member@user:joe - -// Assign groups to other groups -group:tech#direct_member@group:marketing#direct_member -group:tech#direct_member@group:hr#direct_member - -// Connect groups to organization -organization:acme#group@group:tech -organization:acme#group@group:marketing -organization:acme#group@group:hr - -// Add some documents under the organization -organization:acme#document@document:product_database -organization:acme#document@document:marketing_materials -organization:acme#document@document:hr_documents - -// Assign a user and members of a group as administrators for the organization -organization:acme#administrator@group:tech#manager -organization:acme#administrator@user:jenny - -// Set the permissions on some documents -document:product_database#manager@group:tech#manager -document:product_database#viewer@group:tech#direct_member -document:marketing_materials#viewer@group:marketing#direct_member -document:hr_documents#manager@group:hr#manager -document:hr_documents#viewer@group:hr#direct_member -``` - -## Test & Validation - -Finally, let's check some permissions and test our authorization logic. - -
can user:ashley edit document:product_database ? -

- -```perm - entity document { - relation org @organization - - relation viewer @user @group#member @group#manager - relation manager @user @group#member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } -``` - -According what we have defined for the edit action managers and admins, of the organization that document belongs, can edit product database. In this context, Permify engine will check does subject `user:ashley` has any direct or indirect manager relation within `document:product_database`. Consecutively it will check does `user:ashley` has admin relation in the Acme Org - `organization:acme#document@document:product_database`. - -Ashley doesn't have any administrative relation in Acme Org but she is the manager in group tech (`group:tech#manager@user:ashley`) and we have defined that manager of group tech is manager of product_database with the tuple (`document:product_database#manager@group:tech#manager`). Therefore, the **user:ashley edit document:product_database** check request should yield **true** response. - -

-
- -
can user:joe view document:hr_documents ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check whether subject `user:joe` has any direct or indirect manager or viewer relation within `document:hr_documents`. Also consecutively it will check does `user:joe` has admin relation in the Acme Org - `organization:acme#document@document:hr_documents`. - -Joe doesn't have administrative role/relation in Acme Org. - -Also he doesn't have have manager relationship in that document or within any entity. - -But he is member in the hr group (`group:hr#member@user:joe`) and we defined hr members have viewer relationship in hr documents (`document:hr_documents#viewer@group:hr#member`). So that, this enforcement should yield **true** response. - -

-
- -
can user:david view document:marketing_materials ? -

- -```perm -entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin -} -``` - -According what we have defined for the view action viewers or managers or org.admin's can view hr documents. In this context, Permify engine will check does subject `user:david` has any direct or indirect manager or viewer relation within `document:marketing_materials`. Also consecutively it will check does `user:david` has admin relation in the Acme Org - `organization:acme#document@document:marketing_materials`. - -Similar Joe and Ashley, David also doesn't have administrative role/relation in Acme Org. - -Also David doesn't have member or manager relationship related with marketing group - `document:marketing_materials`. So that, this enforcement should yield **false** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity organization { - relation group @group - relation document @document - relation administrator @user @group#direct_member @group#manager - relation direct_member @user - - permission admin = administrator - permission member = direct_member or administrator or group.member - } - - entity group { - relation manager @user @group#direct_member @group#manager - relation direct_member @user @group#direct_member @group#manager - - permission member = direct_member or manager - } - - entity document { - relation org @organization - - relation viewer @user @group#direct_member @group#manager - relation manager @user @group#direct_member @group#manager - - action edit = manager or org.admin - action view = viewer or manager or org.admin - } - -relationships: - - group:tech#manager@user:ashley - - group:tech#direct_member@user:david - - group:marketing#manager@user:john - - group:marketing#direct_member@user:jenny - - group:hr#manager@user:josh - - group:hr#direct_member@user:joe - - - group:tech#direct_member@group:marketing#direct_member - - group:tech#direct_member@group:hr#direct_member - - - organization:acme#group@group:tech - - organization:acme#group@group:marketing - - organization:acme#group@group:hr - - organization:acme#document@document:product_database - - organization:acme#document@document:marketing_materials - - organization:acme#document@document:hr_documents - - organization:acme#administrator@group:tech#manager - - organization:acme#administrator@user:jenny - - - document:product_database#manager@group:tech#manager - - document:product_database#viewer@group:tech#direct_member - - document:marketing_materials#viewer@group:marketing#direct_member - - document:hr_documents#manager@group:hr#manager - - document:hr_documents#viewer@group:hr#direct_member - - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "document:product_database" - subject: "user:ashley" - assertions: - edit: true - - entity: "document:hr_documents" - subject: "user:joe" - assertions: - view: true - - entity: "document:marketing_materials" - subject: "user:david" - assertions: - view: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://github.com/Permify/permify/assets/39353278/85b96987-5932-4805-ac81-89820daad7e9) - -## Need any help ? - -This is the end of modeling Google Docs style permission system. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/instagram.md b/docs/versioned_docs/version-0.6.x/getting-started/examples/instagram.md deleted file mode 100644 index 9cd83163..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/instagram.md +++ /dev/null @@ -1,376 +0,0 @@ -# Instagram - -This example presents an Instagram Authorization Schema, outlining the intricate relationships between users, accounts, and posts on the platform. It defines user access levels, privacy settings, and interactions, offering insights into how followers, account owners, and post restrictions are managed within the Instagram ecosystem. - -## Schema | [Open in playground](https://play.permify.co/?s=instagram&tab=schema) - -```perm -entity user {} - -entity account { - // users have accounts - relation owner @user - - // accounts can follow other users/accounts. - relation following @user - - // other users/accounts can follow account. - relation follower @user - - // accounts can be private or public. - attribute public boolean - - // users can view an account if they're followers, owners, or if the account is not private. - action view = (owner or follower) or public - -} - -entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - // users can view the posts, if they have access to view the linked accounts. - action view = account.view - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted -} -``` - -## Brief Examination of the Model - -The Instagram Authorization Schema models the relationships between users, accounts, and posts in the Instagram platform. - -Users can own accounts, follow other accounts, and be followed by other users. Accounts can have public or private settings, and access to view an account is determined by ownership, followers, and privacy settings. Posts are associated with accounts and can have restricted comments and likes based on account privacy. - -### Entities & Relations - -- **`User`**: Represents a user on the Instagram platform. - -- **`Account`**: Represents a user account on Instagram. Accounts have owners, followers, and can follow other accounts. - -- **`Post`**: Represents a post on Instagram. Posts are linked to accounts and can have restricted comments and likes. - -### Permissions - -Users can view an account if they are the owner, a follower, or if the account is public. -Users can comment and like posts if they have access to view the linked account and the post is unrestricted. - -### Relationships and Attributes - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Relationships -// Users, Accounts and Posts: - account:1#owner@user:kevin - account:2#owner@user:george - account:1#following@user:george - account:2#follower@user:kevin - post:1#account@account:1 - post:2#account@account:2 - -// Attributes -// Accounts and Posts: - account:1$public|boolean:true - account:2$public|boolean:false - post:1$restricted|boolean:false - post:2$restricted|boolean:true -``` - -## Test & Validation - -To validate our authorization logic, let's run some tests on different scenarios using the Instagram Authorization Schema. - -### Test 1: Checking Account Viewing Permissions - -
- - Can user:kevin view account:1? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:kevin` is the owner of `account:1`. Hence, `user:kevin` should be able to view `account:1`. The expected result is `'true'`. - -

-
- -
- - Can user:kevin view account:2? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:kevin` follows `account:2`. Hence, `user:kevin` should be able to view `account:2` because he is a follower. The expected result is `'true'`. - -

-
- -
- - Can user:george view account:1? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:george` can view `account:1`, because the account is public. Hence, `user:george` should be able to view `account:1`. The expected result is `'true'`. - -

-
- -
- - Can user:george view account:2? - - -

- -```perm - entity account { - relation owner @user - relation following @user - relation follower @user - attribute public boolean - action view = (owner or follower) or public - } -``` - -According to the schema, `user:george` is the owner of `account:2`. Hence, `user:george` should be able to view `account:2`. The expected result is `'true'`. - -

-
- -### Test 2: Checking Post Viewing Permissions - -
Can user:george view post:1? - -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:1` is linked with `account:1`, and it does not have restricted access. Also, `user:george` is following `account:1`. Hence, `user:george` should be able to view `post:1`. The expected result is `'true'`. - -

-
- -
Can user:kevin view post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it has restricted access. Also, `user:george` is not following `account:1`. Hence, `user:kevin` should not be able to view `post:2`. The expected result is `'false'`. - -

-
- -
Can user:george view post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action view = account.view -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it is restricted access. Also, `user:george` can view his own `post:2`. The expected result is `'true'`. - -

-
- -### Test 3: Checking Post Commenting Permissions - -
Can user:george comment post:1? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action comment = account.following not restricted -} -``` - -According to the schema, `post:1` is linked with `account:1`, and it is not restricted. Also, `user:george` can comment on `post:1`. The expected result is `'true'`. - -

-
- -
Can user:kevin comment post:2? -

- -```perm -entity post { - relation account @account - attribute restricted boolean - action comment = account.following not restricted -} -``` - -According to the schema, `post:2` is linked with `account:2`, and it is restricted. `user:kevin` cannot comment on `post:2`. The expected result is `'false'`. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: |- - entity user {} - - entity account { - // users have accounts - relation owner @user - - // accounts can follow other users/accounts. - relation following @user - - // other users/accounts can follow account. - relation follower @user - - // accounts can be private or public. - attribute public boolean - - // users can view an account if they're followers, owners, or if the account is not private. - action view = (owner or follower) or public - - } - - entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - // users can view the posts, if they have access to view the linked accounts. - action view = account.view - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted - } -relationships: - - account:1#owner@user:kevin - - account:2#owner@user:george - - account:1#following@user:george - - account:2#follower@user:kevin - - post:1#account@account:1 - - post:2#account@account:2 -attributes: - - account:1$public|boolean:true - - account:2$public|boolean:false - - post:1$restricted|boolean:false - - post:2$restricted|boolean:true -scenarios: - - name: Account Viewing Permissions - description: Evaluate account viewing permissions for 'kevin' and 'george'. - checks: - - entity: account:1 - subject: user:kevin - assertions: - view: true - - entity: account:2 - subject: user:kevin - assertions: - view: true - - entity: account:1 - subject: user:george - assertions: - view: true - - entity: account:2 - subject: user:george - assertions: - view: true - - name: Post Viewing Permissions - description: Determine post viewing permissions for 'kevin' and 'george'. - checks: - - entity: post:1 - subject: user:george - assertions: - view: true - - entity: post:2 - subject: user:kevin - assertions: - view: true - - entity: post:2 - subject: user:george - assertions: - view: true - - name: Post Commenting Permissions - description: Evaluate post commenting permissions for 'kevin' and 'george'. - checks: - - entity: post:1 - subject: user:george - assertions: - comment: true - - entity: post:2 - subject: user:kevin - assertions: - comment: false -``` - -## Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve` - -![Running Permify](https://github.com/Permify/permify/assets/48759364/eb4cde6e-09bf-4e38-88bc-251a811f9c4f) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![test-result](https://github.com/Permify/permify/assets/48759364/2fb9a1ab-40d4-48e0-857a-3d59de575134) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/mercury.md b/docs/versioned_docs/version-0.6.x/getting-started/examples/mercury.md deleted file mode 100644 index 796c6211..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/mercury.md +++ /dev/null @@ -1,157 +0,0 @@ -# Mercury - -Explore **Mercury's Authorization Schema** in this example, delving into the intricate interplay among **users**, **organizations**, and **accounts**. Uncover the defined user roles, approval workflows, and limits, providing a snapshot of the dynamic relationships within the Mercury ecosystem. - -For those who donโ€™t know, Mercury is a bank offering both checking and savings accounts, complete with debit and credit card features. Given the delicate nature of financial transactions, Mercury has built-in access control features to ensure security. - -But today weโ€™re going to focus on approvals. Mercury allows itโ€™s users to set a number amount for multiple user approval for any action. - -For instance, an admin can decide that withdrawals above $1000 by members require approval from two designated approvers. This means, if a member wants to withdraw more than $1000, they need a green light from two admin. And if an admin tries to withdraw they need an approval form another admin. - -- Admin โ†’ Withdraw $1000 โ†’ needs an approver -- Member โ†’ Withdraw $1000 โ†’ needs 2 approvers. - -## Full Schema | [Open in playground](https://play.permify.co/?s=mercury&tab=schema) - -So letโ€™s start with building basics. We need Users, Organization, Accounts both Savings and Deposits as entities in the mercury - -```perm -entity user {} - -entity organization {} - -entity teams {} - -entity accounts {} -``` - -Then inserting relations into these entities. - -```perm -entity user {} - -entity organization { - relation admin @user - relation member @user -} - -entity accounts { - relation checkings @accounts - relation savings @accounts - - relation org @organization -} -``` - -Next step is to define actions in our use case. - -```perm -entity user {} - -entity organization { - relation admin @user - relation member @user -} - -entity account { - - relation checkings @account - relation savings @account - - relation org @organization - - action withdraw = - -} -``` - -Now we need to define our attributes which will help us create access rights via Withdraw Limit and Admin Approval of the account. - -Every organization has a set withdrawal limit. Additionally, for members and admins of the organization, there are specific approval limits in place when they attempt to withdraw amounts exceeding this limit. - -```perm -entity user {} - -entity organization { - relation admin @user - relation member @user -} - -entity account { - - relation checkings @account - relation savings @account - - relation org @organization - - attribute approval integer - attribute balance double - - action withdraw = - -} -``` - -Letโ€™s create our rules that defines our attribute-based access rights. - -- Balance of the account must be more than withdraw amount -- If withdraw amount is less than the withdraw limit we donโ€™t need approval -- Else; we need approve of two admins if weโ€™re member, and we need approve of single admin if weโ€™re another admin. - -```perm -entity user {} - -entity organization { - relation admin @user - relation member @user - - attribute admin_approval_limit integer - attribute member_approval_limit integer - attribute approval_num integer - - action approve = admin - action create_account = admin - - permission approval = (member and check_member_approval(approval_num, member_approval_limit)) or (admin and check_admin_approval(approval_num, admin_approval_limit)) -} - -entity account { - relation checkings @account - relation savings @account - - relation owner @organization - - attribute withdraw_limit double - attribute balance double - - action withdraw = check_balance(balance, request.amount) and (check_limit(withdraw_limit, request.amount) or owner.approval) -} - -rule check_balance(balance double, amount double) { - balance >= amount -} - -rule check_limit(withdraw_limit double, amount double) { - withdraw_limit >= amount -} - -rule check_admin_approval(approval_num integer, admin_approval_limit integer) { - approval_num >= admin_approval_limit -} - -rule check_member_approval(approval_num integer, member_approval_limit integer) { - approval_num >= member_approval_limit -} -``` - -At last, as you can see we use the Rules to define access rights to withdraw which basically translates into; - -- Check balance if itโ€™s over the withdraw amount. If not donโ€™t allow the action. -- Check withdraw limit; if itโ€™s less than the limit allow the actionโ€ฆ -- Else; - - Check if user is admin, and have approval more than the approval limit for admins. - - Check if user is member, and have approval more than the approval limit for members. - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. diff --git a/docs/versioned_docs/version-0.6.x/getting-started/examples/notion.md b/docs/versioned_docs/version-0.6.x/getting-started/examples/notion.md deleted file mode 100644 index 9095d464..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/examples/notion.md +++ /dev/null @@ -1,548 +0,0 @@ -# Notion - -This is a schema definition of the authorization model for Notion, a popular productivity and organization tool. - -### Schema | [Open in playground](https://play.permify.co/?s=BsCvLmd4g81sB20XJZI5p) - -```perm -entity user {} - -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin -} - -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write -} - -entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write -} - -entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter -} - -entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author -} - -entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write -} - -entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write -} -``` - -## Brief Examination of the Model - -The model defines several entities, including users, workspaces, pages, databases, blocks, and integrations. It also includes several default roles, such as Admin, Bot, Guest, and Member. Here's a breakdown of the entities: - -### Entities & Relations - -- **`user`**: Represents a user in the system. - -- **`workspace`**: Represents a workspace in which users can collaborate. Each workspace has an owner, members, guests, and bots associated with it. The owner and admin users have permission to manage the workspace. Permissions are defined for creating pages, inviting members, viewing the workspace, and managing the workspace. The read and write permissions can be inherited by child entities. - -- **`page`**: Represents a page within a workspace. Each page is associated with a workspace and has a writer and readers. The read and write permissions are defined based on the writer and readers of the page and can be inherited from the workspace. - -- **`database`**: Represents a database within a workspace. Each database is associated with a workspace and has an editor and viewers. The read and write permissions are defined based on the editor and viewers of the database and can be inherited from the workspace. Permissions are also defined for creating and deleting databases. - -- **`block`**: Represents a block within a page or database. Each block is associated with a page or database and has an editor and commenters. The read and write permissions are defined based on the editor and commenters of the block and can be inherited from the database. Commenters are users who have permission to comment on the block. - -- **`comment`**: Represents a comment on a block. Each comment is associated with a block and has an author. The read and write permissions are defined based on the author of the comment and can be inherited from the block. - -- **`template`**: Represents a template within a workspace. Each template is associated with a workspace and has a creator and viewers. The read and write permissions are defined based on the creator and viewers of the template and can be inherited from the workspace. Permissions are also defined for creating and deleting templates. - -- **`integration`**: Represents an integration within a workspace. Each integration is associated with a workspace and has an owner. Permissions are defined for reading and writing to the integration. - -### Permissions - -We have several actions attached with the entities, which are limited by certain permissions. Let's examine the **read** permission of the page entity. - -#### Page Read Permission - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - - .. - .. - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - .. -} - -entity page { - - // The workspace associated with the page - relation workspace @workspace - - .. - .. - - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - .. - .. - - // Define permissions for page actions - permission read = reader or workspace.read - - .. - .. -} -``` - -This permission specifies who can read the contents of the page at Notion. - -The `reader` relation specifies the users who are members of the workspace associated with the page (`workspace#member`) or guests of the workspace (`workspace#guest`). - -Read permission of the workspace inherited as `workspace.read` in the page entity. THis permission specifies that any user who has been granted read access to the workspace object (i.e., the workspace that the page belongs to) can also read the page. - -In summary, any user who is a member or guest of the workspace and has been granted read access to the page through the reader relation, as well as any user who has been granted read access to the workspace itself, can read the contents of the page. - -## Relationships - -Based on our schema, let's create some sample relationships to test both our schema and our authorization logic. - -```perm -// Assign users to different workspaces: -workspace:engineering_team#owner@user:alice -workspace:engineering_team#member@user:bob -workspace:engineering_team#guest@user:charlie -workspace:engineering_team#admin@user:alice -workspace:sales_team#owner@user:david -workspace:sales_team#member@user:eve -workspace:sales_team#guest@user:frank -workspace:sales_team#admin@user:david - -// Connect pages, databases, and templates to workspaces: -page:project_plan#workspace@workspace:engineering_team -page:product_spec#workspace@workspace:engineering_team -database:task_list#workspace@workspace:engineering_team -template:weekly_report#workspace@workspace:sales_team -database:customer_list#workspace@workspace:sales_team -template:marketing_campaign#workspace@workspace:sales_team - -// Set permissions for pages, databases, and templates: -page:project_plan#writer@user:frank -page:project_plan#reader@user:bob - -database:task_list#editor@user:alice -database:task_list#viewer@user:bob - -template:weekly_report#creator@user:alice -template:weekly_report#viewer@user:bob - -page:product_spec#writer@user:david -page:product_spec#reader@user:eve - -database:customer_list#editor@user:david -database:customer_list#viewer@user:eve - -template:marketing_campaign#creator@user:david -template:marketing_campaign#viewer@user:eve - -// Set relationships for blocks and comments: -block:task_list_1#database@database:task_list -block:task_list_1#editor@user:alice -block:task_list_1#commenter@user:bob -block:task_list_2#database@database:task_list -block:task_list_2#editor@user:alice -block:task_list_2#commenter@user:bob - -comment:task_list_1_comment_1#block@block:task_list_1 -comment:task_list_1_comment_1#author@user:bob -comment:task_list_1_comment_2#block@block:task_list_1 -comment:task_list_1_comment_2#author@user:charlie -comment:task_list_2_comment_1#block@block:task_list_2 -comment:task_list_2_comment_1#author@user:bob -comment:task_list_2_comment_2#block@block:task_list_2 -comment:task_list_2_comment_2#author@user:charlie -``` - -## Test & Validation - -Since we have our schema and the sample relation tuples, let's check some permissions and test our authorization logic. - -
can user:alice write database:task_list ? -

- -```perm - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - - .. - .. - - // Define permissions for database actions - .. - .. - - permission write = editor or workspace.write - - .. - .. - } -``` - -According to what we have defined for the **'write'** permission, users who are either; - -- The editor in task list database (`database:task_list`) -- Have a write permission in the engineering team workspace, which is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`) - -can edit the task list database (`database:task_list`) - -Based on the relation tuples we created, `user:alice` doesn't have the **editor** relationship with the `database:task_list`. - -Since `user:alice` is the owner and admin in the engineering team workspace (`workspace:engineering_team#admin@user:alice`) it has a write permission defined in the workspace entity, as you can see below: - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - .. - .. - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - // Define permissions that can be inherited by child entities - .. - permission write = owner or admin -} -``` - -And as we mentioned the engineering team workspace is the only workspace that task list is associated (`database:task_list#workspace@workspace:engineering_team`). Therefore, the `user:alice write database:task_list` check request should yield a **'true'** response. - -

-
- -
can user:charlie write page:product_spec ? -

- -```perm -entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - - .. - .. - - permission write = writer or workspace.write -} -``` - -`user:charlie` is guest in the workspace (`workspace:engineering_team#guest@user:charlie`) and the engineering team workspace is the only workspace that `page:product_spec` belongs to. - -As we defined, guests doesn't have write permission in a workspace. - -```perm -entity workspace { - // The owner of the workspace - relation owner @user - // Admin users who have permission to manage the workspace - relation admin @user - - .. - .. - - permission write = owner or admin -} -``` - -So that, `user:charlie` doesn't have a write relationship in the workspace. And ultimately, the `user:charlie write page:product_spec` check request should yield a **'false'** response. - -

-
- -Let's test these access checks in our local with using **permify validator**. We'll use the below schema for the schema validation file. - -```yaml -schema: >- - entity user {} - - entity workspace { - // The owner of the workspace - relation owner @user - // Members of the workspace - relation member @user - // Guests (users with read-only access) of the workspace - relation guest @user - // Bots associated with the workspace - relation bot @user - // Admin users who have permission to manage the workspace - relation admin @user - - // Define permissions for workspace actions - permission create_page = owner or member or admin - permission invite_member = owner or admin - permission view_workspace = owner or member or guest or bot - permission manage_workspace = owner or admin - - // Define permissions that can be inherited by child entities - permission read = member or guest or bot or admin - permission write = owner or admin - } - - entity page { - // The workspace associated with the page - relation workspace @workspace - // The user who can write to the page - relation writer @user - // The user(s) who can read the page (members of the workspace or guests) - relation reader @user @workspace#member @workspace#guest - - // Define permissions for page actions - permission read = reader or workspace.read - permission write = writer or workspace.write - } - - entity database { - // The workspace associated with the database - relation workspace @workspace - // The user who can edit the database - relation editor @user - // The user(s) who can view the database (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for database actions - permission read = viewer or workspace.read - permission write = editor or workspace.write - permission create = editor or workspace.write - permission delete = editor or workspace.write - } - - entity block { - // The page associated with the block - relation page @page - // The database associated with the block - - relation database @database - // The user who can edit the block - relation editor @user - // The user(s) who can comment on the block (readers of the parent object) - relation commenter @user @page#reader - - // Define permissions for block actions - permission read = database.read or commenter - permission write = editor or database.write - permission comment = commenter - } - - entity comment { - // The block associated with the comment - relation block @block - - // The author of the comment - relation author @user - - // Define permissions for comment actions - permission read = block.read - permission write = author - } - - entity template { - // The workspace associated with the template - relation workspace @workspace - // The user who creates the template - relation creator @user - - // The user(s) who can view the page (members of the workspace or guests) - relation viewer @user @workspace#member @workspace#guest - - // Define permissions for template actions - permission read = viewer or workspace.read - permission write = creator or workspace.write - permission create = creator or workspace.write - permission delete = creator or workspace.write - } - - entity integration { - // The workspace associated with the integration - relation workspace @workspace - - // The owner of the integration - relation owner @user - - // Define permissions for integration actions - permission read = workspace.read - permission write = owner or workspace.write - } - -relationships: - - workspace:engineering_team#owner@user:alice - - workspace:engineering_team#member@user:bob - - workspace:engineering_team#guest@user:charlie - - workspace:engineering_team#admin@user:alice - - workspace:sales_team#owner@user:david - - workspace:sales_team#member@user:eve - - workspace:sales_team#guest@user:frank - - workspace:sales_team#admin@user:david - - page:project_plan#workspace@workspace:engineering_team - - page:product_spec#workspace@workspace:engineering_team - - database:task_list#workspace@workspace:engineering_team - - template:weekly_report#workspace@workspace:sales_team - - database:customer_list#workspace@workspace:sales_team - - template:marketing_campaign#workspace@workspace:sales_team - - page:project_plan#writer@user:frank - - page:project_plan#reader@user:bob - - database:task_list#editor@user:alice - - database:task_list#viewer@user:bob - - template:weekly_report#creator@user:alice - - template:weekly_report#viewer@user:bob - - page:product_spec#writer@user:david - - page:product_spec#reader@user:eve - - database:customer_list#editor@user:david - - database:customer_list#viewer@user:eve - - template:marketing_campaign#creator@user:david - - template:marketing_campaign#viewer@user:eve - - block:task_list_1#database@database:task_list - - block:task_list_1#editor@user:alice - - block:task_list_1#commenter@user:bob - - block:task_list_2#database@database:task_list - - block:task_list_2#editor@user:alice - - block:task_list_2#commenter@user:bob - - comment:task_list_1_comment_1#block@block:task_list_1 - - comment:task_list_1_comment_1#author@user:bob - - comment:task_list_1_comment_2#block@block:task_list_1 - - comment:task_list_1_comment_2#author@user:charlie - - comment:task_list_2_comment_1#block@block:task_list_2 - - comment:task_list_2_comment_1#author@user:bob - - comment:task_list_2_comment_2#block@block:task_list_2 - - comment:task_list_2_comment_2#author@user:charlie - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "database:task_list" - subject: "user:alice" - assertions: - write: true - - entity: "page:product_spec" - subject: "user:charlie" - assertions: - write: false -``` - -### Using Schema Validator in Local - -After cloning [Permify](https://github.com/Permify/permify), open up a new file and copy the **schema yaml file** content inside. Then, build and run Permify instance using the command `make serve`. - -![Running Permify](https://user-images.githubusercontent.com/34595361/233155326-e1d2daf6-2406-4139-b0b3-5f7b54880593.png) - -Then run `permify validate {path of your schema validation file}` to start the test process. - -The validation result according to our example schema validation file: - -![Screen Shot 2023-04-16 at 15 53 06](https://user-images.githubusercontent.com/34595361/233154924-c31a76f4-86f5-4ed3-a1ec-750b642927e6.png) - -## Need any help ? - -This is the end of demonstration of the authorization structure for Facebook groups. To install and implement this see the [Set Up Permify](../../installation.md) section. - -If you need any kind of help, our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/getting-started/modeling.md b/docs/versioned_docs/version-0.6.x/getting-started/modeling.md deleted file mode 100644 index 0e3231f5..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/modeling.md +++ /dev/null @@ -1,608 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Modeling Authorization - -Permify was designed and structured as a true ReBAC solution, so besides roles and attributes Permify also supports indirect permission granting through relationships. - -With Permify, you can define that a user has certain permissions because of their relation to other entities. An example of this would be granting a manager the same permissions as their subordinates, or giving a user access to a resource because they belong to a certain group. - -This is facilitated by our relationship-based access control, which allows the definition of complex permission structures based on the relationships between users, roles, and resources. - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager, member and also dynamic attributes such as boolean variables, IP range, time period, etc. - -![modeling-authorization](https://raw.githubusercontent.com/Permify/permify/master/assets/permify-dsl.gif) - -You can define your entities, relations between them and access control decisions with using Permify Schema. It includes set-algebraic operators such as intersection and union for specifying potentially complex access control policies in terms of those user-object relations. - -Hereโ€™s a simple breakdown of our schema. - -![permify-schema](https://user-images.githubusercontent.com/34595361/183866396-9d2850fc-043f-4254-aa4c-ee2c4172afb8.png) - -Permify Schema can be created on our [playground](https://play.permify.co/) as well as in any IDE or text editor. We also have a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Permify.perm) to ease modeling Permify Schema with code snippets and syntax highlights. Note that on VS code the file with extension is **_".perm"_**. - -## Developing a Schema - -This guide will show how to develop a Permify Schema from scratch with a simple example, yet it will show almost every aspect of our modeling language. - -We'll follow a simplified version of the GitHub access control system, where teams and organizations have control over the viewing, editing, or deleting access rights of repositories. - -Before start I want to share the full implementation of simple Github access control example with using Permify Schema. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - - action create_repository = admin or member - action delete = admin - -} - -entity team { - - relation parent @organization - relation member @user - - action edit = member or parent.admin - -} - -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - action push = owner or maintainer - action read = org.admin and (owner or maintainer or org.member) - action delete = parent.admin or owner - -} -``` - -:::info -You can start developing Permify Schema on [VSCode]. You can install the extension by searching for **Perm** in the extensions marketplace. - -[vscode]: https://marketplace.visualstudio.com/items?itemName=Permify.perm - -::: - -## Defining Entities - -The very first step to build Permify Schema is creating your Entities. Entity is an object that defines your resources that held role in your permission system. - -Think of entities as tables in your database. We are strongly advice to name entities same as your database table name that its corresponds. In that way you can easily model and reason your authorization as well as eliminating the error possibility. - -You can create entities using `entity` keyword. Let's create some entities according to our example GitHub authorization logic." - -```perm -entity user {} - -entity organization {} - -entity team {} - -entity repository {} -``` - -Entities has 2 different attributes. These are; - -- **relations** -- **actions or permissions** - -## Defining Relations - -Relations represent relationships between entities. It's probably the most critical part of the schema because Permify mostly based on relations between resources and their permissions. - -Keyword **_relation_** need to used to create a entity relation with name and type attributes. - -**Relation Attributes:** - -- **name:** relation name. -- **type:** relation type, basically the entity itโ€™s related to (e.g. user, organization, document, etc.) - -An example relation takes form of, - -```perm -relation [name] @[type] -``` - -Lets turn back to our example and define our relations inside our entities: - -#### User Entity - -โ†’ The user entity is a mandatory entity in Permify. It generally will be empty but it will used a lot in other entities as a relation type to referencing users. - -```perm -entity user {} -``` - -### Roles and User Types - -You can define user types and roles within the entity. If you specifically want to define a global role, such as `admin`, we advise defining it at the entity with the most global hierarchy, such as an organization. Then, spread it to the rest of the entities to include it within permissions. - -For the sake of simplicity, let's define only 2 user types in an organization, these are administrators and direct members of the organization. - -```perm -entity organization { - - relation admin @user - relation member @user - -} -``` - -### Parent-Child Relationship - -โ†’ Let's say teams can belong organizations and can have a member inside of it as follows, - -```perm -entity organization { - - relation admin @user - relation member @user - -} - -entity team { - - relation parent @organization - relation member @user - -} -``` - -The parent relation is indicating the organization the team belongs to. This way we can achieve **parent-child relationship** within these entities. - -### Ownership - -In Github workflow, organizations and users can have multiple repositories, so each repository is related with an organization and with users. We can define repository relations as as follows. - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - -} -``` - -The owner relation indicates the creator of the repository, that way we can achieve **ownership** in Permify. - -### Multiple Relation Types - -As you can see we have new syntax above, - -```perm - relation maintainer @user @team#member -``` - -When we look at the maintainer relation, it indicates that the maintainer can be an `user` as well as this user can be a `team member`. - -:::info -You can use **#** to reach entities relation. When we look at the `@team#member` it specifies that if the user has a relation with the team, this relation can only be the `member`. We called that feature locking, because it basically locks the relation type according to the prefixed entity. - -Actual purpose of feature locking is to giving ability to specify the sets of users that can be assigned. - -For example: - -```perm - relation viewer @user -``` - -When you define it like this, you can only add users directly as tuples (you can find out what relation tuples is in next section): - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 - -However, if you define it as: - -```perm - relation viewer @user @organization#member -``` - -You will then be able to specify not only individual users but also members of an organization: - -- organization:1#viewer@user:U1 -- organization:1#viewer@user:U2 -- organization:1#viewer@organization:O1#member - -You can think of these definitions as a precaution taken against creating undesired user set relationships. -::: - -Defining multiple relation types totally optional. The goal behind it to improve validation and reasonability. And for complex models, it allows you to model your entities in a more structured way. - -## Defining Actions and Permissions - -Actions describe what relations, or relationโ€™s relation can do. Think of actions as permissions of the entity it belongs. So actions defines who can perform a specific action on a resource in which circumstances. - -The basic form of authorization check in Permify is **_Can the user U perform action X on a resource Y ?_**. - -### Intersection and Exclusion - -The Permify Schema supports **`and`**, **`or`** and **`not`** operators to achieve permission **intersection** and **exclusion**. The keywords **_action_** or **_permission_** can be used with those operators to form rules for your authorization logic. - -#### Intersection - -Lets get back to our github example and create a read action on repository entity to represent usage of **`and`** &, **`or`** operators, - -```perm -entity repository { - - relation parent @organization - - relation owner @user - relation maintainer @user @team#member - - - .. - .. - - action read = org.admin and (owner or maintainer or org.member) - -} -``` - -โ†’ If we examine the `read` action rules; user that is `organization admin` and following users can read the repository: `owner` of the repository, or `maintainer`, or `member` of the organization which repository belongs to. - -:::info Permission Keyword -The same `read` can also be defined using the **permission** keyword, as follows: - -```perm - permission read = org.admin and (owner or maintainer or org.member) -``` - -Using `action` and `permission` will yield the same result for defining permissions in your authorization logic. See why we have 2 keywords for defining an permission from the [Nested Hierarchies](#nested-hierarchies) section. -::: - -#### Exclusion - -After this point, we'll move beyond the GitHub example and explore more advanced abilities of Permify DSL. - -Before delving into details, let's examine the **`not`** operator and conclude [Intersection and Exclusion](#intersection-and-exclusion) section. - -Here is the **post** entity from our sample [Instagram Authorization Structure](./examples/google-docs.md)example, - -```perm -entity post { - // posts are linked with accounts. - relation account @account - - // comments are limited to people followed by the parent account. - attribute restricted boolean - - .. - .. - - // users can comment and like on unrestricted posts or posts by owners who follow them. - action comment = account.following not restricted - action like = account.following not restricted -} -``` - -As you can see from the comment and like actions, a user tagged with the `restricted` attribute โ€” details of defining attributes can be found in the [Attribute Based Permissions (ABAC)](#attribute-based-permissions-abac) section โ€” won't be able to like or comment on the specific post. - -This is a simple example to demonstrate how you can exclude users, resources, or any subject from permissions using the **`not`** operator. - -### Permission Union - -Permify allows you to set permissions that are effectively the union of multiple permission sets. - -You can define permissions as relations to union all rules that permissions have. Here is an simple demonstration how to achieve permission union in our DSL, you can use actions (or permissions) when defining another action (or permission) like relations, - -```perm - action edit = member or manager - action delete = edit or org.admin -``` - -The `delete` action inherits the rules from the `edit` action. By doing that, we'll be able to state that only organization administrators and any relation capable of performing the edit action (member or manager) can also perform the delete action. - -Permission union is super beneficial in scenarios where a user needs to have varied access across different departments or roles. - -### Nested Hierarchies - -The reason we have two keywords for defining permissions (`action` and `permission`) is that while most permissions are based on actions (such as view, read, edit, etc.), there are still cases where we need to define permissions based on roles or user types, such as admin or member. - -Additionally, there may be permissions that need to be inherited by child entities. Using the `permission` keyword in these cases is more convenient and provides better reasoning of the schema. - -Here is a simple example to demonstrate inherited permissions. - -Let's examine a small snippet from our [Facebook Groups](./examples/google-docs.md) real world example. Let's create a permission called 'view' in the comment entity (which represents the comments of the post in Facebook Groups) - -Users can only view a comment if: - -- The user is the owner of that comment -**or** -- The user is a member of the group to which the comment's post belongs. - -```perm -// Represents a post in a Facebook group -entity post { - - .. - .. - - // Relation to represent the group that the post belongs to - relation group @group - - // Permissions for the post entity - - .. - .. - permission group_member = group.member -} - -// Represents a comment on a post in a Facebook group -entity comment { - - // Relation to represent the owner of the comment - relation owner @user - - // Relation to represent the post that the comment belongs to - relation post @post - relation comment @comment - - .. - .. - - // Permissions - action view = owner or post.group_member - - .. - .. -} -``` - -The `post.group_member` refers to the members of the group to which the post belongs. We defined it as action in **post** entity as, - -```perm -permission group_member = group.member -``` - -Permissions can be inherited as relations in other entities. This allows to form nested hierarchical relationships between entities. - -In this example, a comment belongs to a post which is part of a group. Since there is a **'member'** relation defined for the group entity, we can use the **'group_member'** permission to inherit the **member** relation from the group in the post and then use it in the comment. - -### Recursive ReBAC - -With Permify DSL, you can define recursive relationship-based permissions within the same entity. - -As an example, consider a system where there are multiple organizations within a company, some of which may have a parent-child relationship between them. - -As expected, organization members are also granted permission to view their organization details. You can model that as follows: - -```perm -entity user {} - -entity organization { - relation parent @organization - relation member @user @organization#member - - action view = member or parent.member -} -``` - -Let's extend the scenario by adding a rule allowing parent organization members to view details of child organizations. Specifically, a member of **Organization Alpha** could view the details of **Organization Beta** if **Organization Beta** belongs to **Organization Alpha**. - -![modeling-authorization](https://user-images.githubusercontent.com/58391988/279456032-485a0aef-b83b-4257-af48-0fcbe6fa2e64.png) - -First authorization schema that we provide won't solve this issue because `parent.member` accommodate single upward traversal in a hierarchy. - -Instead of `parent.member` we can call the parent view permission on the same entity - `parent.view` to achieve multiple levels of upward traversal, as follows: - -```perm -entity user {} - -entity organization { - relation parent @organization - relation member @user @organization#member - - action view = member or parent.view -} -``` - -This way, we achieve a recursive relationship between parent-child organizations. - -:::note -*Credits to [Lรฉo](https://github.com/LeoFVO) for the illustration and for [highlighting](https://github.com/Permify/permify/issues/790) this use case.* -::: - -## Attribute Based Permissions (ABAC) - -To support Attribute Based Access Control (ABAC) in Permify, we've added two main components into our schema language: `attribute` and `rule`. - -### Defining Attributes - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```perm -attribute ip_range string[] -``` - -Here are the all attribute types that you use when defining an `attribute`. - -```perm -// A boolean attribute type -boolean - -// A boolean array attribute type. -boolean[] - -// A string attribute type. -string - -// A string array attribute type. -string[] - -// An integer attribute type. -integer - -// An integer array attribute type. -integer[] - -// A double attribute type. -double - -// A double array attribute type. -double[] -``` - -### Defining Rules - -Rules are structures that allow you to write specific conditions for the model. You can think rules as simple functions of every software language have. They accept parameters and are based on condition to return a true/false result. - -In the following example schema, a rule could be used to check if a given IP address falls within a specified IP range: - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute ip_range string[] - - permission view = check_ip_range(request.ip, ip_range) or admin -} - -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` - -:::info Syntax -We design our schema language based on [Common Expression Language (CEL)](https://github.com/google/cel-go). So the syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. - -Please let us know via our [Discord channel](https://discord.gg/n6KfzYxhPp) if you have questions regarding syntax, definitions or any operator you identify not working as expected. -::: - -Let's examine some of common usage of ABAC with small schema examples. - -### Boolean - True/False Conditions - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```perm -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: - -### Text & Object Based Conditions - -String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: - -### Numerical Conditions - -#### Integers - -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```perm -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: - -#### Double - Precise numerical information - -Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```perm -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: - -See more details on [Attribute Based Access Control](#attribute-based-permissions-abac) section to learn our approach on ABAC as well as how it operates in Permify. you can see more comprehensive ABAC examples in the [Example ABAC Use Cases](../use-cases/abac/#example-use-cases) section in related page. - -## More Comprehensive Examples - -You can check out more comprehensive schema examples from the [Real World Examples](../examples.md) section. - -Here is what each example focuses on, - -* [Google Docs]: how users can gain direct access to a document through **organizational roles** or through **inherited/nested permissions**. -* [Facebook Groups]: how users can perform various actions based on the **roles and permissions within the groups** they belong. -* [Notion]: how **one global entity (workspace) can manage access rights** in the child entities that belong to it. -* [Instagram]: how **public/private attributes** play role in granting access to specific users. -* [Mercury]: how **attributes and rules interact within the hierarchical relationships**. - -[Google Docs]:./examples/google-docs.md -[Facebook Groups]:./examples/facebook-groups.md -[Notion]:./examples/notion.md -[Instagram]:./examples/instagram.md -[Mercury]:./examples/mercury.md \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/getting-started/sync-data.md b/docs/versioned_docs/version-0.6.x/getting-started/sync-data.md deleted file mode 100644 index 07f0759f..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/sync-data.md +++ /dev/null @@ -1,480 +0,0 @@ ---- -sidebar_position: 2 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Managing Authorization Data - -Permify unifies your authorization data in a database of your preference, which serves as the single source of truth for all authorization queries and requests via the Permify API. - -In Permify, you can store authorization data in two different forms: as **relationships** and as **attributes**. - -Let's examine relationships first. - -## Access Control as Relationships - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. Each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -In Permify, the simplest form of relational tuple structured as: `entity # relation @ user`. Here are some relational tuples with semantics, - -![relational-tuples](https://user-images.githubusercontent.com/34595361/183959294-149fcbb9-7f10-4c1e-8d66-20a839893909.png) - -## Attributes - -Besides creating and storing your authorization-related data as relationships, you can also create attributes along with your resources and users. - -For certain use cases, using relationships (ReBAC) or roles (RBAC) might not be the best fit. For example, geo-based permissions where access is granted only if associated with a geographical or regional attribute. Or consider time-based permissions, restricting certain actions to office hours. A simpler scenario involves defining certain individuals as banned, filtering them out from access despite meeting other requirements. - -Attribute-Based Access Control takes a more contextual approach, allowing you to define access rights based on the context around subjects and objects in an application. - -In Permify, the form of attributes are similar to relational tuples but with a small syntax differentiation: - -`subject $ attribute | value` - -Here are some attributes with semantics, - -* `account:1$balance|double:4000` - account:1's balance is defined as 4000. -* `post:546$is_restricted|boolean:true` - post:546 is labeled as restricted post within the system. -* `user:122$regions|string[]:US,MEX` - user:122 is associated with regions United States and Mexico. - -## Where is the stored Authorization Data used? - -These relational tuples and attributes represents your authorization data. - -Permify stores your these data in a database you prefer. You can configure the database when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -Stored data are queried and utilized in Permify APIs, including the check API, which is an access control check request used to determine whether a user's action is authorized. - -As an example; to decide whether a user could view a protected resource, Permify looks up the relations between that specific user and the protected resource. These relation types could be ownership, parent-child relation, a role such as an admin or manager or even an attribute. - -## Creating Authorization Data - -Relationships and attributes can be created with an simple API call, Since these attributes and relations are live instances, meaning they can be affected by specific user actions within the application, they should be created/deleted with a simple Permify API call at runtime. - -Each relational tuple or attribute should be created according to its authorization model, [Permify Schema]. - -[Permify Schema]: ../modeling - -![tuple-creation](https://user-images.githubusercontent.com/34595361/186637488-30838a3b-849a-4859-ae4f-d664137bb6ba.png) - -Let's follow a simple document management system example with the following Permify Schema to see how to create relation tuples. - -```perm -entity user {} - -entity organization { - - relation admin @user - relation member @user - -} - -entity document { - - relation owner @user - relation parent @organization - relation maintainer @user @organization#member - - action view = owner or parent.member or maintainer or parent.admin - action edit = owner or maintainer or parent.admin - action delete = owner or parent.admin -} -``` - -According to the schema above; when a user creates a document in an organization, more specifically let's say, when user:1 create a document:2 we need to create the following relational tuple, - -- `document:2#owner@user:1` - -### Write Data API - -You can create relational tuples by using `Write Data API`. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "2", - }, - Relation: "owner", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "2" - }, - relation: "owner", - subject: { - type: "user", - id: "1" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "2s" - }, - "relation": "owner", - "subject":{ - "type": "user", - "id": "1", - "relation": "" - } - } - ] -}' -``` - - - -### Snap Tokens - -In Write Data API response you'll get a snap token of the operation. - -```json -{ - "snap_token": "FxHhb4CrLBc=" -} -``` - -This token consists of an encoded timestamp, which is used to ensure fresh results in access control checks. We're suggesting to use snap tokens in production to prevent data inconsistency and optimize the performance. See more on [Snap Tokens](../reference/snap-tokens.md) - -## More Examples - -Let's create more example data according to the schema we defined above. - -### Organization Admin - -**relational tuple:** organization:1#admin@user:3 - -**Semantics:** User 3 is administrator in organization 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "organization", - Id: "1", - }, - Relation: "admin", - Subject: & v1.Subject { - Type: "user", - Id: "3", - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "organization", - id: "1" - }, - relation: "admin", - subject: { - type: "user", - id: "3" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "organization", - "id": "1" - }, - "relation": "admin", - "subject":{ - "type": "user", - "id": "3", - "relation": "" - } - } - ] -}' -``` - - - -### Parent Organization - -**Relational Tuple:** document:1#parent@organization:1#โ€ฆ - -**Semantics:** Organization 1 is parent of document 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "parent", - Subject: & v1.Subject { - Type: "organization", - Id: "1", - Relation: "..." - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "parent", - subject: { - type: "organization", - id: "1", - relation: "..." - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "parent", - "subject":{ - "type": "organization", - "id": "1", - "relation": "..." - } - } - ] -}' -``` - - - -:::info -Note: `relation: โ€œ...โ€` used when subject type is different from **user** entity. **#โ€ฆ** represents a relation that does not affect the semantics of the tuple. - -Simply, the usage of ... is straightforward: if you're use user entity as an subject, you should not be using the `...` If you're using another subject rather than user entity then you need to use the `...` -::: - -### Organization Members Are Maintainers in specific Doc - -**Created relational tuple:** document:1#maintainer@organization:2#member - -**Definition:** Members of organization 2 are maintainers in document 1. - - - - -```go -rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest { - TenantId: "t1", - Metadata: &v1.DataWriteRequestMetadata { - SchemaVersion: "" - }, - Tuples: [] * v1.Tuple { - { - Entity: & v1.Entity { - Type: "document", - Id: "1", - }, - Relation: "maintainer", - Subject: & v1.Subject { - Type: "organization", - Id: "2", - Relation: "member" - }, - } - }, -}) -``` - - - - - -```javascript -client.data.write({ - tenantId: "t1", - metadata: { - schemaVersion: "" - }, - tuples: [{ - entity: { - type: "document", - id: "1" - }, - relation: "maintainer", - subject: { - type: "organization", - id: "2", - relation: "member" - } - }] -}).then((response) => { - // handle response -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata": { - "schema_version": "" - }, - "tuples": [ - { - "entity": { - "type": "document", - "id": "1" - }, - "relation": "maintainer", - "subject":{ - "type": "organization", - "id": "2", - "relation": "member" - } - } - ] -}' -``` - - - -#### Test this Example on [Playground](https://play.permify.co/?s=bCDvst-22ISFR6DV90y8_) - -## Audit Logs For Permission Changes - -Permify does support audit logs for permission changes. Leveraging the [MVCC (Multi-Version Concurrency Control)](http://mbukowicz.github.io/databases/2020/05/01/snapshot-isolation-in-postgresql.html) pattern, we maintain a history of all permission data changes. This essentially provides an audit trail, allowing users to track alterations and when they occurred. - -In cloud version, our system supports change history auditing. It automatically generates and securely stores logs for all significant actions. These logs detail who made the change, what was changed, and when the change occurred. Furthermore, your system allows for easy searching and analysis of these logs, supporting automated alerting for suspicious activities. This comprehensive approach ensures thorough and effective auditing of all changes - -## Permission Baselining (Reviewing) - -We have a strong foundation for permission baselining and review, thanks to MVCC. - -**Historical Review:** You can review the history of permissions changes as each version is stored. This enables retrospective audits and analysis. - -**Current State Review:** You can review the current state of permissions by examining the latest versions of each permission setting. - -**Cleanup:** Your system incorporates a garbage collector for managing old data, which helps keep your permissions structure clean and optimized. - -## Next - -Let's now head over to the **Access Control Check** section and learn how to perform access control in Permify to ensure that only authorized users have the right level of access to our resources. diff --git a/docs/versioned_docs/version-0.6.x/getting-started/testing.md b/docs/versioned_docs/version-0.6.x/getting-started/testing.md deleted file mode 100644 index 977fcf2d..00000000 --- a/docs/versioned_docs/version-0.6.x/getting-started/testing.md +++ /dev/null @@ -1,279 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Testing & Validation - -Testing is critical process when building and maintaining an authorization system. This page explains how to ensure the new authorization model and related authorization data works as expected in Permify. - -Assuming that you're familiar with creating an authorization model and forming relation tuples in Permify. If not, we're strongly advising you to examine them before testing. - -We provide a GitHub action repository called [permify-validate-action] for testing and validation. This repository runs the Permify validate command on the created schema validation yaml file that consists of schema (authorization model) and relationships (sample authorization data) and assertions (sample check queries and results). - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [related page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Adding Validate Action To Your Workflow - -After adding [permify-validate-action] to your Github Action workflow, you need to define the schema validation yaml file as, - -- **With local file:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "test.yaml" -``` - -- **With external url:** -```yaml -steps: -- uses: "permify/permify-validate-action@v1.0.0" - with: - validationFile: "https://gist.github.com/permify-bot/bb8f95acb64525d2a41688ae0a6f4274" -``` - -:::info -If you don't know how to create Github action workflow and add a action to it, you can examine [quickstart page](https://docs.github.com/en/actions/quickstart) on Github docs. -::: - -## Schema Validation File - -Below you can examine an example schema validation yaml file. It consists 3 parts; -- `schema` which is the authorization model you want to test, -- `relationships` sample data to test your model, -- `scenarios` to test access check queries within created scenarios. - -### Defining the Schema: - -You can define the `schema` in the YAML file in one of two ways: - -1. **Directly in the File:** Define the schema directly within the YAML file. - - ```yaml - schema: >- - entity user {} - entity organization { - ... - } - -2. **Via URL or File Path:** Specify a URL or a file path to an external schema file. - **Example with URL:** - - ```yaml - schema: https://example.com/path/to/schema.txt - ``` - - **Example with File Path:** - ```yaml - schema: /path/to/your/schema/file.txt - ``` - -Here is an example Schema Validation file, - -```yaml -schema: >- - entity user {} - - entity organization { - - relation admin @user - relation member @user - - action create_repository = (admin or member) - action delete = admin - } - - entity repository { - - relation owner @user @organization#member - relation parent @organization - - action push = owner - action read = (owner and (parent.admin and parent.member)) - action delete = (parent.member and (parent.admin or owner)) - action edit = parent.member not owner - } - -relationships: - - "organization:1#admin@user:1" - - "organization:1#member@user:1" - - "repository:1#owner@user:1" - - "repository:2#owner@user:2" - - "repository:2#owner@user:3" - - "repository:1#parent@organization:1#..." - - "organization:1#member@user:43" - - "repository:1#owner@user:43" - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - assertions: - push : true - owner : true - - entity: "repository:2" - subject: "user:1" - assertions: - push : false - - entity: "repository:3" - subject: "user:1" - context: - - "repository:3#owner@user:1" - assertions: - push : true - - entity: "repository:1" - subject: "user:43" - assertions: - edit : false - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - - "repository:3#owner@user:1" - - "repository:4#owner@user:1" - - "repository:5#owner@user:1" - assertions: - push : ["1", "3", "4", "5"] - edit : [] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - - "organization:1#member@user:58" - assertions: - push : ["1", "43"] - edit : ["58"] -``` - -Assuming that you're well-familiar with the `schema` and `relationships` sections of the above YAML file. If not, please see the previous sections to learn how to create an authorization model (schema) and generate data (relationships) according to it. - -We'll continue by examining how to create scenarios. - -## Creating Test Scenarios - -You can create multiple access checks at once to test whether your authorization logic behaves as expected or not. - -Besides simple access checks you can also test subject filtering queries and data (entity) filtering with it. - -Let's deconstruct the `scenarios`, - -### Scenarios - -```js -scenarios: - - name: // name of the scenario - description: // description of the scenario - checks: // simple access check case/cases - entity_filters: // entity (data) filtering query/queries - subject_filters: // subject filtering query/queries -``` - -### Access Check - -You can create `check` inside `scenarios` to test multiple access check cases, - -```js -checks: - - entity: "repository:3" // resource/entity that you want to check access for - subject: "user:1" // subject that performs the access check - context: // additional data provided during an access check to be evaluated - - "repository:3#owner@user:1" - assertions: // expected result/results for specific action/s or an permission/s. - push : true -``` - -Semantics for above check is: whether `user:1` can push to `repository:3`, additional to stored tuples take account that user:1 is owner of repository:3 (`repository:3#owner@user:1`). Expected result for that check it **true** - `push : true` - -:::info Contextual Tuples -We use `context` (Contextual Tuples) with simple relational tuples for simplicity in this example. However, it is primarily used for dynamic access checks, such as those involving time, date, or IP address, etc. - -To learn more about how `context` works, see the [Contextual Tuples](../../reference/contextual-tuples) section. -::: - -### Entity Filtering - -You can create `entity_filters` within `scenarios` to test your data filtering queries. - -```js -entity_filters: - - entity_type: "repository" // entity that you want to filter - subject: "user:1" // subject that you want to perform data filtering - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "3", "4", "5"] // IDs of the resources that we expected to return - edit : [] -``` - -The major difference between `check` lies in the assertions part. Since we're performing data filtering with bulk data, instead of a true-false result, we enter the IDs of the resources that we expect to be returned - -### Subject Filtering - -You can create `subject_filters` within `scenarios` to test your subject filtering queries, a.k.a which users can perform action Y or have permission X on entity:Z? - -```js -- subject_reference: "user" - entity: "repository:1" - context: null // additional data provided during an access check to be evaluated - assertions: - push : ["1", "43"] // IDs of the users that we expected to return - edit : ["58"] -``` - -:::info API Endpoints -You can find the related API endpoints for `check`, `entity_filters`, and `subject_filters` in the Permission service in the [Using The API](../../api-overview) section. -::: - -## Coverage Analysis - -By using the command `permify coverage {path of your schema validation file}`, you can measure the coverage for your schema. - -The coverage is calculated by analyzing the relationships and assertions in your created model, identifying any missing elements. - -The output of the example provided above is as follows. - -![schema-coverage](https://user-images.githubusercontent.com/39353278/236303688-15cc2673-05e6-42d3-9ad4-0c538f546fb0.png) - -## Testing in Local - -You can also test your new authorization model in your local (Permify clone) without using [permify-validate-action] at all. - -For that open up a new file and add a schema yaml file inside. Then build your project with, run `make build` command and run `./permify validate {path of your schema validation file}`. - -If we use the above example schema validation file, after running `./permify validate {path of your schema validation file}` it gives a result on the terminal as: - -![schema-validation](https://user-images.githubusercontent.com/39353278/236303542-930de83f-ebdd-4b0a-a09e-5c069744cc5c.png) - -[permify-validate-action]: https://github.com/Permify/permify-validate-action - -## AST Conversion - -By utilizing the command `permify ast {path of your schema validation file}`, you can effortlessly convert your model into an Abstract Syntax Tree (AST) representation. - -The conversion to AST provides a structured representation of your model, making it easier to navigate, modify, and analyze. This process ensures that your model is syntactically correct and can be processed by other tools without issues. - -The output after running the above example command is illustrated below. - - -![ast-conversion](https://github.com/Permify/permify/assets/39353278/822902d7-9612-46a6-95e9-1cb09bc0ebb2) - -## Unit Tests For Schema Changes - -We recommend leveraging Permify's in-memory databases for a simplified and isolated testing environment. These in-memory databases can be easily created and disposed of for each individual unit test, ensuring that your tests do not interfere with each other and each one starts with a clean slate. - -For managing permission/relation changes, we suggest storing schema in an abstracted place such as a git repo and centrally checking and approving every change before deploying it via the CI pipeline that utilizes theย **Write Schema API**. - -We recommend adding ourย [schema validator](https://github.com/Permify/permify-validate-action)ย to the pipeline to ensure that any changes are automatically validated. - -You can find more details about our suggested workflow to handle schema changes in [Write Schema](../../api-overview/schema/write-schema#suggested-workflow-for-schema-changes) section. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about it, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - - diff --git a/docs/versioned_docs/version-0.6.x/installation.md b/docs/versioned_docs/version-0.6.x/installation.md deleted file mode 100644 index c428e1f5..00000000 --- a/docs/versioned_docs/version-0.6.x/installation.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: installation -title: Setup Permify -slug: /installation ---- - -# Setup Permify - -Here is some options that you can use to set up and deploy Permify in your servers. - -```mdx-code-block -import {CardList} from '../../src/components/Card'; - - -``` - -If your deployment preference is not listed above, please let us know by joining our [Discord Community](https://discord.gg/n6KfzYxhPp)! \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/installation/_category_.json b/docs/versioned_docs/version-0.6.x/installation/_category_.json deleted file mode 100644 index 24b32a32..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Set Up Permify", - "position": 3, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/installation/aws.md b/docs/versioned_docs/version-0.6.x/installation/aws.md deleted file mode 100644 index c1c204d9..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/aws.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: AWS ECS, ECR & EC2 ---- - -# ย Deploy on AWS ECS, ECR & EC2 - -AWS is a piece of cake no one ever said! Thatโ€™s why today weโ€™re bringing this tutorial to help you deploy Permify in AWS. - -There are many ways to deploy and use Permify in AWS. Today weโ€™ll start with Elastic Container Service (ECS). - -ECS is a container management service. You can run your containers as task definitions, and Itโ€™s one of the easiest ways to deploy containers. - -If youโ€™d like to watch this tutorial rather than reading. Hereโ€™s the video version. - - - -There is no prerequisite in this tutorial. You can simply deploy permify by following this step-by-step guide. However, if you want to integrate more advanced AWS security & networking features, weโ€™ll follow up with a new tutorial guideline. - -At the end of this tutorial youโ€™ll be able to; - -1. [Create a security group](#create-an-ec2-security-group) -2. [Creating and configuring ECS Clusters](#2-creating-an-ecs-cluster) -3. [Creating and defining task definitions](#3-creating-and-running-task-definitions) -4. [Running our task definition](#4-running-our-task-definition) - -## 1. Create an EC2 Security Group - -So first thing first, letโ€™s go over into security groups and create our security group. Weโ€™ll need this security group while creating our cluster. - -![security-group-1](https://user-images.githubusercontent.com/34595361/208877994-e9461acc-4ffd-4591-b43e-db254366d25d.png) - -Search for โ€œSecurity Groupsโ€ in the search bar. And go to the EC2 security groups feature. - -![security-group-2](https://user-images.githubusercontent.com/34595361/208877493-ab11228c-1aa0-4bc5-b41d-4527737028e9.png) - -Then start creating a new security group. - -![security-group-3](https://user-images.githubusercontent.com/34595361/208877500-2c299883-6107-4b70-aa96-0f28eb00cf3d.png) - -You have to name your security group, and give a description. Also, you need to choose the same VPC that youโ€™ll going to use in EC2. So, I choose the default one. And Iโ€™m going to use same one while creating the ECS cluster. - -The next step is to configure our inbound rules. Hereโ€™s the configuration; - -```json -//for mapping HTTP request port. -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for mapping RPC request port. -type = "Custom TCP", protocol = "TCP", port_range = "3478",source = "Anywhere", ::/0 - -type = "Custom TCP", protocol = "TCP", port_range = "3476",source = "Anywhere", 0.0.0.0/0 - -//for using SSH for connecting from your local computer. -type = "Custom TCP", protocol = "TCP", port_range = "22",source = "Anywhere", 0.0.0.0/0 -``` - -We have configured the HTTP and RPC ports for Permify. Also, we added port โ€œ22โ€ for SSH connection. So, we can connect to EC2 through our local terminal. - -Now, weโ€™re good to go. You can create the security group. And itโ€™s ready to use in our ECS. - -## 2. Creating an ECS cluster - -![create-ecs-cluster-1](https://user-images.githubusercontent.com/34595361/208878666-98c5d3ce-b079-444d-bc66-53f13038a08a.png) - -The next step is to create an ECS cluster. From your AWS console search for Elastic Container Service or ECS for short. - -![create-ecs-cluster-2](https://user-images.githubusercontent.com/34595361/208878675-2f266cfc-defb-4c7f-9186-b4de39f1743b.png) - -Then go over the clusters. As you can see there are 2 types of clusters. One is for ECS and another for EKS. We need to use ECS, EKS stands for Elastic Kubernetes Service. Today weโ€™re not going to cover Kubernetes. - -Click **โ€œCreate Clusterโ€** - -![create-ecs-cluster-3](https://user-images.githubusercontent.com/34595361/208878685-3edac67b-5b3d-4f0d-b2f7-70a5ec2e4870.png) - -Letโ€™s create our first Cluster. Simply you have 3 options; Serverless(Network Only), Linux, and Windows. Weโ€™re going to cover EC2 Linux + Networking option. - -![create-ecs-cluster-4](https://user-images.githubusercontent.com/34595361/208878681-d98a77db-16b1-42af-a697-3036cc604c85.png) - -The next step is to configure our Cluster, starting with your Cluster name. Since weโ€™re deploying Permify, Iโ€™ll call it โ€œpermifyโ€. - -Then choose your instance type. You can take a look at different instances and pricing from [here](https://aws.amazon.com/ec2/pricing/on-demand/). Iโ€™m going with the t4 large. For cost purposes, you can choose t2.micro if youโ€™re just trying out. Itโ€™s free tier eligible. - -Also, if you want to connect this EC2 instance from your local computer. You need to use SSH. Thus choose a key pair. If you have no such intention, leave it โ€œnoneโ€. - -![create-ecs-cluster-5](https://user-images.githubusercontent.com/34595361/208878989-801839f5-8fce-4410-99e0-0a2dcccb47fa.png) - -Now, we need to configure networking. First, choose your VPC, we use the default VPC as we did in the security groups. And choose any subnet on that VPC. - -You want to enable auto-assigned IP to make your app reachable from the internet. - -Choose the security group we have created previously. - -And voila, you can create your cluster. Now, we need to run our container in this cluster. To do that, letโ€™s go over task definitions. And create our container definition. - -## 3. Creating and running task definitions - -Go over to ECS, and click the task definitions. - -![create-run-task-1](https://user-images.githubusercontent.com/34595361/208879726-fe5aac07-16a8-4f8c-9cc9-1c95ca191a42.png) - -And create a new task definition. - -![create-run-task-2](https://user-images.githubusercontent.com/34595361/208879733-e9aa6fa4-9f66-44e4-8c70-dfa0e33c1b73.png) - -Again, youโ€™re going to ask to choose between; FARGATE, EC2, and EXTERNAL (On-premise). Weโ€™ll continue with EC2. - -Leave everything in default under the โ€œConfigure task and container definitionsโ€ section. - -![create-run-task-3](https://user-images.githubusercontent.com/34595361/208879735-789ec411-5829-47be-9634-c09c7b0c0320.png) - -Under the IAM role section you can choose โ€œecsTaskExecutionRoleโ€ if you want to use Cloud Watch later. - -You can leave task size in default since itโ€™s optional for EC2. - -The critical part over here is to add our container. Click on the โ€œAdd Containerโ€ button. - -![create-run-task-4](https://user-images.githubusercontent.com/34595361/208879740-4515e884-1efd-46fd-8e8c-cfa86634b673.png) - -Then we need to add our container details. First, give a name. And then the most important part is our image URI. Permify is registered on the Github Registry so our image is; - -```yaml -ghcr.io/permify/permify:latest -``` - -Then we need to define memory limit for the container, I went with 1024. You can define as much as your instance allows. - -Next step is to mapping our ports. As we mentioned in security groups, Permify by default listens; - -- `3476 for HTTP port` -- `3478 for RPC port` - -![create-run-task-5](https://user-images.githubusercontent.com/34595361/208879746-5991a04c-73d5-4e35-97b0-67aa9ebf61fc.png) - -Then we need to define command under the environment section. So, in order to start permify we first need to add โ€œserveโ€ command. - -For using properly we need a few other. Hereโ€™s the commands we need. - -```yaml -serve, --database-engine=postgres, --database-uri=postgres://:@:/, --database-pool-max=20 -``` - -- `serve` โ‡’ for starting the Permify. -- `--database-engine=postgres` โ‡’ for defining the db we use. -- `--database-uri=postgres://:password@:/` โ‡’ for connecting your database with URI. -- `--database-pool-max=20` โ‡’ the depth for running in graph. - -Weโ€™re nice and clear, add the container and then just create your task definition. Weโ€™ll use this definition to run in our cluster. - -So, letโ€™s go over and run our task definition. - -## 4. Running our task definition - -![run-task-definition-1](https://user-images.githubusercontent.com/34595361/208880326-c5ecb48c-e210-47f8-bd92-d1f789be24ff.png) - -Letโ€™s go to ECS and enter into our cluster. And go over into the tasks to run our task. - -![run-task-definition-2](https://user-images.githubusercontent.com/34595361/208880332-97a5732d-bc7d-401e-bae9-216d4273c5bf.png) - -Click to โ€œRun new Taskโ€ - -![run-task-definition-3](https://user-images.githubusercontent.com/34595361/208880335-b3ce229f-33ff-4f03-90e7-6d6a306928ae.png) - -Choose EC2 as a launch type. Then pick the task definition we just created. And leave everything else in the default. You can run your task now. - -We have just deployed our container into EC2 instance with ECS. Letโ€™s test it. - -Now you can go over into EC2, and click on the running instances. Find the instance named `ECS Instance - EC2ContainerService-` in the running instances. - -![run-task-definition-4](https://user-images.githubusercontent.com/34595361/208880339-a508354c-99ee-4219-8ace-1c7fdbbe90ed.png) - -Copy the Public IPv4 DNS from the right corner, and paste it into your browser. But you need to add `:3476` to access our http endpoint. So it should be like this; - -`:3476` - -and if you add healthz at the end like this; - -`:3476/healthz` - -you should get Serving status :) - -![run-task-definition-5](https://user-images.githubusercontent.com/34595361/208880346-d19a6877-3013-4347-86c9-9f865b8a3e3c.png) - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/installation/azure.md b/docs/versioned_docs/version-0.6.x/installation/azure.md deleted file mode 100644 index 7f32ade5..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/azure.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Azure CR & Application Service ---- - -# Deploy on Azure CR, & Application Service - -## TO:DO \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/installation/brew.md b/docs/versioned_docs/version-0.6.x/installation/brew.md deleted file mode 100644 index 0212df8b..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/brew.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "Install with Brew" ---- - -# Brew With Configurations - -This section shows how to install and run Permify Service using brew. - -### Install Permify - -Open terminal and run the following line, - -```shell -brew install permify/tap/permify -``` - -### Run Permify Service - -To run the Permify Service, `permify serve` command should be run. - -By default, the service is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather then an actual database. You can override these by running the command with configuration flags. - -### Configure By Using Flags - -See all the configuration flags by running, - -```shell -permify serve --help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Configure With Using Config File - -You can also configure Permify Service by using a configuration file. - -```shell - permify serve -c=config.yaml -``` - -or - -```shell - permify serve --config=config.yaml -``` - -### Test your connection. - -You can test your connection by making an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../../api-overview/ - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with a Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/installation/container.md b/docs/versioned_docs/version-0.6.x/installation/container.md deleted file mode 100644 index aa0b7686..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/container.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: "Docker Container" ---- - -# Run using Docker - -This section shows how to run Permify using our docker container. You can run Permify using Docker with following command. - -## Run in a terminal - -```shell -docker run -p 3476:3476 -p 3478:3478 -v {YOUR-CONFIG-PATH}:/config ghcr.io/permify/permify serve -``` - -This will start a Permify server with the configuration that is in **{YOUR-CONFIG-PATH}**. - -### Configure with a YAML file - -This config path - `{YOUR-CONFIG-PATH}` - should contain the [config yaml file](../reference/configuration.md), where you can configure the Permify Server as well as define the ***database*** to store your authorization related data in. - -:::info Talk to an Permify Engineer -By default, the container is configured to listen on ports 3476 (HTTP) and 3478 (gRPC) and store the authorization data in memory rather than an actual database. -::: - -### Configure Using Flags - -Alternatively, you can set configuration options using flags when running the command. See all the configuration flags by running, - -```shell -docker run -p 3476:3476 -p 3478:3478 ghcr.io/permify/permify serve -help -``` - -:::info Environment Variables -In addition to CLI flags, Permify also supports configuration via environment variables. You can replace any flag with an environment variable by converting dashes into underscores and prefixing with PERMIFY_ (e.g. **--log-level** becomes **PERMIFY_LOG_LEVEL**). -::: - -### Test your connection. - -You can test your connection by making an HTTP GET request, - -```shell -localhost:3476/healthz -``` - -You can use our Postman Collection to work with the API. Also see the [Using the API] section for details of core functions. - -[Using the API]: ../api-overview.md - -[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/permify-dev/workspace/permify/collection) -[![View in Swagger](http://jessemillar.github.io/view-in-swagger-button/button.svg)](https://permify.github.io/permify-swagger/) - - -### Need any help ? - -Our team is happy to help you get started with Permify, [schedule a call with a Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/installation/google.md b/docs/versioned_docs/version-0.6.x/installation/google.md deleted file mode 100644 index deb30dc9..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/google.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -title: Deploy on Google Compute Engine ---- - -This guide outlines the process of deploying Permify, on Google Compute Engine. The steps include setting up Google Cloud SDK and kubectl, managing containers using Google Kubernetes Engine (GKE), deploying Permify, and implementing Permify in a distributed configuration with Serf. By following these steps, you can efficiently deploy Permify on Google's scalable and secure infrastructure. - -## Google Cloud SDK Install - -1. At the command line, run the following command: - - ```bash - curl https://sdk.cloud.google.com | bash - ``` - -2. When prompted, choose a location on your file system (usually your Home directory) to create theย `google-cloud-sdk`ย subdirectory under. -3. If you want to send anonymous usage statistics to help improve gcloud CLI, answerย `Y`ย when prompted. -4. To add gcloud CLI command-line tools to yourย `PATH`ย and enable command completion, answerย `Y`ย when prompted -5. Restart your shell: - - ```bash - exec -l $SHELL - ``` - -6. To initialize the Google Cloud CLI environment, run `gcloud init` - -## Install kubectl - -1. Install theย `kubectl`ย component: - - ```bash - gcloud components install kubectl - ``` - -2. Verify thatย `kubectl`ย is installed: - - ```bash - kubectl version - ``` - -3. Install Authn Plug-in - - ```bash - gcloud components install gke-gcloud-auth-plugin - ``` - - Check theย `gke-gcloud-auth-plugin`ย binary version: - - ```bash - gke-gcloud-auth-plugin --version - ``` - - -## Create Containers with GKE - -1. Login & Initialize Google Cloud CLI - - ```bash - gcloud init - ``` - -2. Follow configuration instructions -3. Create Container Cluster - - ```bash - gcloud container clusters create [CLUSTER_NAME] - ``` - -4. Authenticate the cluster - - ```bash - gcloud container clusters get-credentials [CLUSTER_NAME] - ``` - - -## Deploy Permify - -1. Apply deployment config - - ```bash - kubectl apply -f deployment.yaml - ``` - - - **Deployment.yaml** - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - app: permify - name: permify - spec: - replicas: 3 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always - status: {} - ``` - -2. Apply service manfiest - - ```bash - kubectl apply -f service.yaml - ``` - - - **Service Manifest** - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer - status: - loadBalancer: {} - ``` - - -## Deploying Permify in a Distributed Configuration - -If you aim to deploy Permify in a distributed configuration, you will need to create a Serf deployment. The Serf deployment can be dockerized to our Container Registry under the name permify/serf:v1.0, which is provided by Hashicorp. - -Please note: It is crucial to ensure that both Serf and Permify deployments reside within the same namespace for proper operation. - -1. Serf Service Create: - - Serf Deployment&Service yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: serf-deployment - spec: - replicas: 1 - selector: - matchLabels: - app: serf - template: - metadata: - labels: - app: serf - spec: - containers: - - name: serf - image: permify/serf:v1.0 - args: - - "-node=main-serf" - ports: - - containerPort: 7946 - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 200m - memory: 256Mi - --- - apiVersion: v1 - kind: Service - metadata: - name: serf - spec: - selector: - app: serf - ports: - - protocol: TCP - port: 7946 - targetPort: 7946 - name: serf - type: ClusterIP - ``` - -2. Apply Deployment Manifest - - Deployment.yaml - - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: permify-deployment - spec: - replicas: 3 - selector: - matchLabels: - app: permify - template: - metadata: - labels: - app: permify - spec: - containers: - - image: permify/permify:tagname - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://user:password@host:5432/db_name" - - "--database-max-open-connections=20" - - "--distributed-enabled=true" - - "--distributed-node=serf:7946" - - "--distributed-node-name=main-serf" - - "--distributed-protocol=serf" - resources: - requests: - memory: "128Mi" - cpu: "200m" - limits: - memory: "128Mi" - cpu: "400m" - ports: - - containerPort: 3476 - name: permify-port - - containerPort: 7946 - name: permify-dist - - containerPort: 6060 - name: permify-pprof - ``` - -3. Apply Service Manifest - - Service.yaml - - ```yaml - apiVersion: v1 - kind: Service - metadata: - name: permify - spec: - ports: - - name: permify-port - port: 3476 - targetPort: 3476 - - name: permify-dist - port: 7946 - targetPort: 7946 - selector: - app: permify - type: LoadBalancer - ``` - - -## Need any help ? - -Our team is happy to help you to deploy Permify, [schedule a call with an Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/installation/kubernetes.md b/docs/versioned_docs/version-0.6.x/installation/kubernetes.md deleted file mode 100644 index f2a1e21b..00000000 --- a/docs/versioned_docs/version-0.6.x/installation/kubernetes.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Kubernetes Cluster ---- - -# ย Deploy on Kubernetes Cluster - -In this section weโ€™re going to deploy Permify in AWS EKS which is Amazon Elastic Kubernetes Service. EKS is a managed service that you can easily run Kubernetes in AWS. - -Hereโ€™s what weโ€™re going to do step-by-step; - -1. [Configure our AWS IAM credentials](#configure-aws-cli-with-your-iam-account) -3. [Create EKS cluster and configure nodes](#creating-an-aws-eks-cluster) -4. [Deploy Permify to nodes](#deploying--running-permify-in-nodes) - -There are a couple of small prerequisites for this tutorial. - -### Pre-requisites - -- An AWS account. -- The AWS Command Line Interface (CLI) is installed and configured on your local machine. โ€” [Click here](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/home) to go to IAM -- The AWS IAM Authenticator for Kubernetes is installed and configured on your local machine. - -## Configure AWS CLI with your IAM account. - -The first step is to configure our AWS IAM account into our local terminal so that we can run commands. Most of you probably have a configured AWS account if you ever set up anything into AWS programmatically, so you can skip this. If you donโ€™t follow these steps. - -### Create an AWS IAM Programmatic Access Account - -First, letโ€™s create IAM credentials for ourselves. Search IAM from the AWS console. You need to write down the account ID if you want to log in AWS console with this account as well. Letโ€™s go over users and start creating our credentials. - -![kubernetes-1](https://user-images.githubusercontent.com/34595361/211697636-6e106115-bd68-4909-aea0-5a7b6f8d5e18.png) - -At Users screen click to โ€œAdd usersโ€ โ€” and youโ€™ll end up in your first screen creating user credentials. Here you can define the name of the user. Also there 2 options that you can choose simultaneously. - -But you must choose โ€œAccess key - Programmatic accessโ€ option. Itโ€™ll allow us to configure our AWS CLI on our local machine. - -You can also choose โ€œPassword - AWS Management Console accessโ€ if you want to log in to this account through the console. But youโ€™ll need the Account ID that I mentioned in the IAM console screen. - -In the next screen, youโ€™ll be asked to create or copy the user-set permissions. For this tutorial, youโ€™ll only need to access EKS resources and features. So lets create group by clicking the โ€œCreate groupโ€ โ€” and then at pop-up screen search for EKS. - -![kubernetes-2](https://user-images.githubusercontent.com/34595361/211697647-f39d73e7-b6e2-40ae-8c3b-ad68032d6b21.png) - -Iโ€™ll choose all EKS permissions but if you have certain policies internally, just stick with them. Youโ€™ll only need following permission to; - -- `AmazonEKSClusterPolicy` -- `AmazonEKSServicePolicy` -- `AmazonEKSVPCResourceController` -- `AmazonEKSWorkerNodePolicy` - -Then simply you can review and create the user. - -![kubernetes-4](https://user-images.githubusercontent.com/34595361/211697655-1b75d4f9-a2ee-4b7e-9e1e-0be0b5aaad7d.png) - -Once you created the credentials youโ€™ll prompt the โ€œAccess key IDโ€ and โ€œSecret access keyโ€, you should save this down somewhere. Weโ€™re going the use these to configure our local machine with AWS CLI. - -### **Configure AWS CLI with your IAM account** - -Letโ€™s open our local terminal - -```jsx -aws configure -``` - -Next youโ€™ll ask for the following credentials; - -- `AWS Access Key ID` -- `AWS Secret Access Key` -- `Default region name` -- `Default output format` (leave it empty) - -## Creating an AWS EKS Cluster - -For the first step, we need to install [eksctl](https://eksctl.io/) โ€” which is like kubectl but for AWS EKS. It helps us to set up and deploy our cluster and nodes within a fraction of the time. - -Letโ€™s download eksctl using brew. - - -```jsx -brew tap weaveworks/tap -``` - -While installing the eksctl, weโ€™ll end up getting kubectl and other dependencies. - -```jsx -brew install weaveworks/tap/eksctl -``` - -Now, weโ€™re ready to create our EKS cluster. You can define certain things while deploying standard the cluster beside the name and version like; the region you want to deploy, the EC2 instance type of each node, and the number of nodes you want to run. - -```bash -eksctl create cluster \ ---name \ ---version 1.24 \ ---region ย \ ---nodegroup-name permify \ ---node-type t2.small \ ---nodes 2 -``` - -## Deploying & Running Permify in Nodes - -The next stop is applying our manifests which will help us to deploy and configure our container/Permify. - -Letโ€™s create our deployment manifest first. - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: permify - name: permify -spec: - replicas: 2 - selector: - matchLabels: - app: permify - strategy: - type: Recreate - template: - metadata: - labels: - app: permify - spec: - containers: - - image: ghcr.io/permify/permify - name: permify - args: - - "serve" - - "--database-engine=postgres" - - "--database-uri=postgres://postgres:nOcodeSTIAnLAba@permify-test.ceuo5kqsxyea.us-east-1.rds.amazonaws.com:5432/demo" - - "--database-max-open-connections=20" - ports: - - containerPort: 3476 - protocol: TCP - resources: {} - restartPolicy: Always -status: {} -``` - -Now letโ€™s apply our deployment manifest - -```jsx -kubectl apply -f deployment.yaml -``` - -The next step is to create a service manifest, this will allow us to configure our container app. - -```jsx -apiVersion: v1 -kind: Service -metadata: - name: permify -spec: - ports: - - name: 3476-tcp - port: 3476 - protocol: TCP - targetPort: 3476 - selector: - app: permify - type: LoadBalancer -status: - loadBalancer: {} -``` - -Letโ€™s apply service.yaml to our nodes. - -```jsx -kubectl apply -f service.yaml -``` - -Last but not least, we can check our pods & nodes. And we can start using the container with load balancer \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.6.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be..00000000 --- a/docs/versioned_docs/version-0.6.x/permify-overview/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "First Glance", - "position": 1, - "collapsed": false -} diff --git a/docs/versioned_docs/version-0.6.x/permify-overview/authorization-service.md b/docs/versioned_docs/version-0.6.x/permify-overview/authorization-service.md deleted file mode 100644 index 55a6906d..00000000 --- a/docs/versioned_docs/version-0.6.x/permify-overview/authorization-service.md +++ /dev/null @@ -1,40 +0,0 @@ - -# Authorization As A Service - -An authorization service is a module that allows you to manage access to your application and ease the development and maintenance of your authorization system. It works in run time and respond to all authorization questions from any of your apps. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify] is a fully open source **authorization service** that offers a variety of binding and crafting options to secure your applications. It's designed to be deployed as a authorization service rather than a library compiled into an application. - -[Permify]: https://github.com/Permify/permify - -## Benefits of using an Authorization Service - -### Move & Iterate Faster -Avoid the hassle of building your a new authorization system, save time and money by leveraging existing, battle-tested code that has been developed by a team rather than starting from scratch. You can get started quickly with a simple API that you can easily integrate into your application to move and iterate faster. - -### Scale As You Wish -Permify based on [Google Zanzibar], which is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. Building a scalable and robust authorization system is hard and needs a quite engineering time. Zanzibar system achieved more than 95% of the access checks responded in 10 milliseconds and has maintained more than 99.999% availability for the 3 year period. Permify applies proven techniques that Google used. Weโ€™re trying to make Zanzibar available to everyone to use and benefit in their applications and services. - -[Google Zanzibar]: https://permify.co/post/google-zanzibar-in-a-nutshell/ - -### Gain Visibility Across Teams -Enterprise-grade authorizations require robust and fine-grained permissions as well as being able to observe and work on these permissions as a group. Yet, code-level authorization logic and distributed authorization data among multiple services make it harder to change permissions and keep them up to date all the time. Permify is designed to abstract authorization logic from your code and make authorization available to everyone including non-technical people in your organization. - -### Be Extendable, At Any Time -Products quickly changes due to never-ending user requirements as the company scales. It's so common that oldest authorization systems will fall short and needs to be changed in the road. Refactoring existing authorization systems is hard because generally these systems sit at the heart of your product. Permify has an extendable authorization language that allows you to update the current authorization model easily, securely, and without affecting production. After it's tested and ready to go, you can switch new version of your model without breaking a sweat. - -### Audit Your Authorization and Ensure Security -Protect your data, prevent unauthorized access and ensure your customers security. Permify can help you with things like fraud detection, real-time transaction monitoring, and even risk assessment with various functions that can be used easily with single API calls. - -## Cases that can benefit from An Authorization Service: - -- If you already have an identity/auth solution and want to plug in fine-grained authorization on top of that. -- If you want to create a unified access control mechanism to use across your individual applications. -- If you want to make future-proof authorization system and don't want to spend engineering effort for it. -- If youโ€™re managing authorization for growing micro-service infrastructure. -- If your authorization logic is cluttering your code base. -- If your data model is getting too complicated to handle your authorization within the service. -- If your authorization is growing too complex to handle within code or API gateway. - diff --git a/docs/versioned_docs/version-0.6.x/permify-overview/infrastructure.md b/docs/versioned_docs/version-0.6.x/permify-overview/infrastructure.md deleted file mode 100644 index 0f29223d..00000000 --- a/docs/versioned_docs/version-0.6.x/permify-overview/infrastructure.md +++ /dev/null @@ -1,79 +0,0 @@ - -# Architecture & Deployment - -Permify is a infrastructure for ease the process of creating and managing scalable authorization systems in your environment. - -This section shows where and how does Permify fit into your environment with examining Permify's high level design, internal architecture, deployment patterns and the usage with the authentication and identity providers. - -## High Level Design - -You can model your authorization logic with **Permify's domain specific language** and your applications can interpolate with Permify API over REST API or GRPC Service to perform access control checks, read or query authorization-related data and more! - -Permify stores access control relations in a **database of your choice**, and each API request evaluates and takes into account access decisions based on the stored relations. - -So this preferred database behaves as a **centralized data source** for your authorization system. - -![relational-tuples](https://user-images.githubusercontent.com/34595361/186108668-4c6cb98c-e777-472b-bf05-d8760add82d2.png) - -### Permify vs Authentication - -Authentication involves verifying that the person actually is who they purport to be, while authorization refers to what a person or service is allowed to do once inside the system. - -To clear out, Permify doesn't handle authentication or user management. Permify behave as you have a different place to handle authentication and store relevant data. Authentication or user management solutions (AWS Cognito, Auth0, etc) only can feed Permify with user information (attributes, identities, etc) to provide more consistent authorization across your stack. - -### Permify with Identity Providers - -Identity providers help you store and control your usersโ€™ and employeesโ€™ identities in a single place. - -Letโ€™s say you build a project management application. And a client wants to connect this application via SSO. You need to connect your app to Okta. And your client can control who can access the application, and which group of authorization types they can have. But as a maker of this project management app. You need to build the permissions and then map to Okta. - -What we do is, help you build these permissions and eventually map anywhere you want. - -## Architecture - -Permify supports both HTTP and GRPC. HTTP requests are converted to GRPC and then transferred to Permify servers. - -There are 4 servers in a Permify Instance: Permission, Relationship, Schema, and Watch. - -- **Permission Server:** The permission server forwards the request to the invoker. The invoker checks for any missing parts of the query, letโ€™s say if no snapshot is provided, it finds the head snapshot. It then hashes the request (with snapshot and schema version) and forwards it to the most convenient Permify instance. If the hash matches its own, it directs it to the local cache. If the cache does not contain the request, it proceeds to the engine. The engine breaks down the query into sub-queries and returns it to the invoker. This process continues until a final decision is made. -- **Relationship Server:** After validating the request, it passes it to the database access layer. -- **Schema Server:** After validating the request, it passes it to the database access layer. -- **Watch Server:** It broadcasts changes in relationships based on their snapshots. - -![architecture](https://github.com/Permify/permify/assets/34595361/b943bc0d-5faf-4a06-abb9-fbd70eb42ea0) - -Database abstractions for the reader and writer can use a database like Aurora Postgres. - -When deploying, separate hosts can be used in the Permify config for the reader and writer. This way, different Permify instances can read from different read replicas. - -**Note:** we are using serf (https://github.com/hashicorp/serf) agent for node discovery on hashring. - -## Deployment Patterns - -There are two main deployment patterns that you can follow, integrate Permify into your applications as a sidecar or using Permify as a service across your applications. Despite for both of these deployment patterns implementation is same - running Permify API in a environment you choose - the architectural aspects and usages differs. So let's examine them both. - -### Permify As A Service - -Permify can be deployed as a sole service that abstracts authorization logic from core applications and behaves as a single source of truth for authorization. - -Gathering authorization logic in a central place offers important advantages over maintaining separate access control mechanisms for individual applications. - -See the [What is Authorization Service] Section for a detailed explanation of those advantages. - -[What is Authorization Service]: ../authorization-service - -![load-balancer](https://user-images.githubusercontent.com/34595361/201173835-6f6b67cd-d65b-4239-b695-04ecf1bad5bc.png) - -Since multiple applications could interact with the Permify Service on that pattern, preventing bottleneck for Permify endpoints and providing high availability is important. - -As shown from above schema, you can horizontally scale Permify Service with positioning Permify instances behind of a load balancer. - -### Using Permify as a Sidecar - -Permify can be used as a sidecar as well. In this deployment model, each application uses its own Permify instance and manages its own specific authorization. - -![load-balancer](https://user-images.githubusercontent.com/34595361/201466158-951d5111-843d-4ed2-a4e6-82f2f8edf16a.png) - -Although unified authorization offers many advantages, using the sidecar model ensures high performance and availability plus avoids the risk of a single point of failure of the centered authorization mechanism. - - diff --git a/docs/versioned_docs/version-0.6.x/permify-overview/intro.md b/docs/versioned_docs/version-0.6.x/permify-overview/intro.md deleted file mode 100644 index 69c135f2..00000000 --- a/docs/versioned_docs/version-0.6.x/permify-overview/intro.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_position: 1 ---- - -# What is Permify? - -[Permify](https://github.com/Permify/permify) is an **open source authorization service** for creating fine-grained and scalable authorization systems. - -With Permify, you can easily structure your authorization model, store authorization data in your preferred database, and interact with the Permify API to handle all authorization queries from your applications or services. - -Permify is inspired by Googleโ€™s consistent, global authorization system, [Google Zanzibar](https://permify.co/post/google-zanzibar-in-a-nutshell/). - -### Motivation - -Our goal is to make **Google's Zanzibar** available to everyone and help them to build robust, flexible, and easily auditable authorization system that establishes a [natural linkage between permissions](https://permify.co/post/relationship-based-access-control-rebac/) across the business units, functions, and entities of an organization. - -## Key Features - -๐Ÿ›ก๏ธ **Production ready** authorization API that serve as **gRPC** and **REST**. - -๐Ÿ”ฎ Domain Specific Authorization Language to **easily model** your authorization. Supporting RBAC, ReBAC, ABAC and more. - -๐Ÿ” Database Configuration to store your permissions with **high availability** and **low latency**. - -โœ… Perform access control checks and get answers **down to 10ms** with our various cache mechanisms that we operate. - -๐Ÿ’ช Battle tested, robust **authorization architecture and data model** based on [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf). - -โš™๏ธ Create custom permissions for your **tenants**, and manage them in a single place with **Multi Tenancy**. - -โšก Analyze **performance and behavior** of your authorization with tracing tools [jaeger], [signoz] or [zipkin]. - -[jaeger]: https://www.jaegertracing.io/ -[signoz]: https://signoz.io/ -[zipkin]: https://zipkin.io/ - -## Getting Started - -In Permify, authorization is divided into 3 core aspects; **modeling**, **storing authorization data** and **access checks**. - -- See how to [Model your Authorization] using Permify Schema. -- Learn how Permify will [Store Authorization Data] as relations. -- Perform [Access Checks] anywhere in your stack. - -[Model your Authorization]: ../../getting-started/modeling -[Store Authorization Data]: ../../getting-started/sync-data -[Access Checks]: ../../getting-started/enforcement - -This document explains how Permify handles these aspects to provide a robust and scalable authorization system for your applications. For the ones that want to try it out and examine it instantly, - - - -## Community & Support - -We would love to hear from you :heart: - -You can get immediate help on our Discord channel. This can be any kind of question-related to Permify, authorization, or authentication and identity management. We'd love to discuss anything related to access control space. - -For feature requests, bugs, or any improvements you can always open an [issue](https://github.com/permify/permify/issues). - -### Want to Contribute? Here are the ways to contribute to Permify - -* **Contribute to codebase:** We're collaboratively working with our community to make Permify the best it can be! You can develop new features, fix existing issues or make third-party integrations/packages. -* **Improve documentation:** Alongside our codebase, documentation is an important part of our open-source journey. We're trying to give the best DX possible to explain ourselves and Permify. And you can help with that by importing resources or adding new ones. -* **Contribute to playground:** Permify playground allows you to visualize and test your authorization logic. You can contribute to our playground by improving its user interface, fixing glitches, or adding new features. - -You can find more details about contributions on [CONTRIBUTING.md](https://github.com/Permify/permify/blob/master/CONTRIBUTING.md). - -## Communication Channels - -If you like Permify, please consider giving us a :star: on [github](https://github.com/permify/permify) - -

- - permify | Discord - - - permify | Twitter - - - permify | Linkedin - -

- -## Roadmap - -You can find Permify's Public Roadmap [here](https://github.com/orgs/Permify/projects/1)! - -## Need any help on Authorization ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify or how it might fit into your authorization workflow, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - diff --git a/docs/versioned_docs/version-0.6.x/playground.md b/docs/versioned_docs/version-0.6.x/playground.md deleted file mode 100644 index 384c3485..00000000 --- a/docs/versioned_docs/version-0.6.x/playground.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Using Permify Playground - -You can use our [Playground] to create and test your authorization schema in a browser. - -Our playground consists 3 main sections, - -- [Schema (Authorization Model)](#schema-authorization-model) -- [Authorization Data](#authorization-data) -- [Enforcement](#enforcement-access-check-scenarios) - -Let's examine these sections by following a simple example. - -[Playground]: https://play.permify.co/ - -## Schema (Authorization Model) - -You can create your authorization model in this section with using our domain specific language. - -You can define your entities, relations between them and access control decisions with using Permify Schema. We already have a couple of use cases and example that you can choose to see how authorization can be structured. Also, you can check our docs to [learn more about how to model authorization](./getting-started/modeling.md) in Permify. - -To demonstrate how the playground works, let's create a simple authorization model as follows. This model should be selected as the default when you open the playground. - -```perm -entity user {} - -entity organization { - - // organizational roles - relation admin @user - relation member @user -} - -entity repository { - - // represents repositories parent organization - relation parent @organization - - // represents owner of this repository - relation owner @user - - // permissions - permission edit = parent.admin or owner - permission delete = owner -} -``` - -We have 2 permissions, `edit` for access of editing repository and `delete` for access of deleting repository. - -Repositories has parent child relation with organizations. The `parent` relation in the repository entity represents that parent child association, while ownership of the repository is represented with the `owner` relation. - -Organizations can have organizational wide roles such as admin and member, which defined as `admin` and `member` relation in organization entity. - -:::info Automatic Saving for Schema Changes -Schema changes are captured automatically, and other sections update accordingly. Some delays may occur at times; please feel free to reach out if these delays hinder your testing process. -::: - -### Visualizer - -We get loads of feedback about the observability and reasonability of the authorization model across teams and colleagues. - -So we put a simple visualizer that shows how your authorization structure looks at a high level. In particular, you can examine relations between entities and their permissions. - -![relational-tuples](https://github.com/Permify/permify/assets/34595361/f8b77c18-dd46-461c-9408-392b642cc900) - -## Authorization Data - -You can create sample authorization data to test your authorization logic. In Permify, authorization data stored as tuples and these tuples stored in a database that you preferred. - -The basic tuple takes the form of: - -`โ€entity # relation @ user` - -So the entity can be any entity that you defined in your model. If we look up our example it can be an organization or repository (since the user is empty). The relation can be one of the defined relations in the selected entity. - -The user is basically the user or user set in our system. Let's say we want make the **user 1** `admin` in **organization 1** then we need to create an example relational tuple according to our model as follows: - -`โ€organization:1#admin@user:1` - -To create a relation tuple in playground just hit the **Add Relationship** button. - -![create-tuple-empty](https://github.com/Permify/permify/assets/34595361/33b85fe7-25e2-400d-8055-94d305023d8c) - -You can choose entity, relation and the subject (user or user set) with entering identifier to create sample data. Let's create the relation tuple `โ€organization:1#admin@user:1` as follows. - -![create-tuple-user](https://github.com/Permify/permify/assets/34595361/016d6f9e-955a-4c39-ab55-21a9fd6dffd9) - -Let's add one more relation tuple to perform a sample access check. I want to add repository:1 into organization:1 - `โ€repository:1#parent@organization:1#...` as follows: - -![create-tuple-parent](https://github.com/Permify/permify/assets/34595361/42daf251-818a-4bd2-8790-1c8656cd497f) - -Created tuples shown in the **Data** section as follows. - -![authorization-data](https://github.com/Permify/permify/assets/34595361/ccc25da1-5212-425d-b604-6a31a8f9555f) - -## Enforcement (Access Check Scenarios) - -Finally as we have a sample data let's perform an access check! - -The YAML in the Enforcement section represents a test scenario for conducting access checks. This scenario-based testing process provides the ability to execute complex access scenarios in a single place. - -Let's name our scenario **"admin_access_test"** and create tests to check: - -- Whether user:1 (admin) can edit repository:1? -- Whether user:1 (admin) can delete repository:1? - -Below is the YAML scenario covering these two tests: - -![scenario-check](https://github.com/Permify/permify/assets/34595361/934add02-6b6a-45ed-9b5b-6a2539778fcf) - -In the above YAML structure, - -#### entity - -Represents the resource for which we want to check access - `repository:1` - -#### subject - -Represents the subject that performs the action or grants access - `user:1`. - -#### assertions - -Assertions stands for defining the expected result for specific action or an permission. In our case we're evaluating access for edit action. - -Since organization:1 is parent of repository:1 ( `โ€repository:1#parent@organization:1#...` ) and user:1 has an admin role in organization:1 ( `โ€organization:1#admin@user:1` ) user:1 should allow to edit the repository:1 because the we define rule of the edit permission as: - -`โ€permission edit = parent.admin or owner` - -:::note -which `โ€parent.admin`โ€ indicates admin in the organization that repository belongs to. -::: - -So user:1 should be able to edit resource:1, therefore expected outcome for that access request is true. -- `edit: true` - -On the other hand, user:1 should't be able to delete resource:1, because only owners can. Therefore expected outcome for that is false. -- `delete: false` - -:::info Create More Advanced Scenarios -For simplicity, we've created a basic scenario. However, you can create more advanced scenarios using our validation YAML structure. - -To learn how to use this syntax for complex scenarios, refer to the [Creating Test Scenarios](../getting-started/testing#creating-test-scenarios) section in [Testing & Validation](./getting-started/testing.md) page. -::: - -Let's click the Run button to execute our scenario. - -![scenario-check-true](https://github.com/Permify/permify/assets/34595361/a90c042f-e0f8-46a0-9800-383620226acd) - -Let's change the expected outcome as false (`edit: false`) and hit the **Run** button again we'll see an error message. - -![scenario-check-false](https://github.com/Permify/permify/assets/34595361/9f9768bf-c534-4b1d-9447-e55cab2dafca) - -As we seen above this is how you can model your authorization and test it with sample data in Permify Playground. - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). diff --git a/docs/versioned_docs/version-0.6.x/reference/_category_.json b/docs/versioned_docs/version-0.6.x/reference/_category_.json deleted file mode 100644 index b55d99d8..00000000 --- a/docs/versioned_docs/version-0.6.x/reference/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Reference", - "position": 8, - "collapsed": true -} diff --git a/docs/versioned_docs/version-0.6.x/reference/cache.md b/docs/versioned_docs/version-0.6.x/reference/cache.md deleted file mode 100644 index 1910705a..00000000 --- a/docs/versioned_docs/version-0.6.x/reference/cache.md +++ /dev/null @@ -1,95 +0,0 @@ -# Cache Mechanisms - -This section showcases the cache mechanisms that Permify uses. - -## Schema Cache - -Schemas are stored in an in-memory cache based on their versions. If a version is specified in the request metadata, it will be searched for in the in-memory cache. If not found, it will query the database for the version and store it in the cache. If no version information is given in the metadata, versions will be assumed to be alphanumeric and sorted in that order, and Permify will request the head version and check if it exists in the memory cache. - -The size of this can be determined through the Permify configuration. Here is an example configuration: -service: - -```yaml -โ€ฆ - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB -โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -## Data Cache - -Permify applies the MVCC (Multi Version Concurrency Control) pattern for Postgres, creating a separate database snapshot for each write and delete operation. This both enhances performance and provides a consistent cache. - -An example of a cache key is: -check_{tenant_id}_{schema_version}:{snapshot_token}:{check_request} - -Permify hashes each request and searches for the same key. If it cannot find it, it runs the check engine and writes to the cache, thus creating a consistently working hash. - -The size of this can also be determined via the Permify configuration. Hereโ€™s an example: -service: - -```yaml - โ€ฆ - permission: - bulk_limit: 100 - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - โ€ฆ -``` - -The cache library used is: https://github.com/dgraph-io/ristretto - -Note: Another advantage of the MVCC pattern is the ability to historically store data. However, it has a downside of accumulation of too many relationships. For this, we have developed a garbage collector that will delete old data at a time period you specify. - -## Distributed Cache - -Permify does provide a distributed cache across availability zones (within an AWS region) via **Consistent Hashing**. Permify uses Consistent Hashing across its distributed instances for more efficient use of their individual caches. - -This would allow for high availability and resilience in the face of individual nodes or even entire availability zone failure, as well as improved performance due to data locality benefits. - -Consistent Hashing is a distributed hashing scheme that operates independently of the number of objects in a distributed hash table. This method hashes according to the nodesโ€™ peers, estimating which node a key would be on and thereby ensuring the most suitable request goes to the most suitable node, effectively creating a natural load balancer. - -### How Consistent Hashing Operates in Permify - -With a single instance, when an API request is made, request and corresponding response stored in its corresponding local cache. - -If we have more than one Permify instance consistent hashing activates on API calls, hashes the request, and outputs a unique key representing the node/instance that will store the request's data. Suppose it stored in the instance 2, subsequent API calls with the same hash will retrieve the response from the instance 2, regardless of which instance that API called from. - -Using this consistent hashing approach, we can effectively utilize individual cache capacities. Adding more instances automatically increases the total cache capacity in Permify. - -You can learn more about consistent hashing from the following blog post: [Introducing Consistent Hashing](https://itnext.io/introducing-consistent-hashing-9a289769052e) - -:::info -Note, however, that while the consistent hashing approach will distribute keys evenly across the cache nodes, it's up to the application logic to ensure the cache is used effectively (i.e., that it reads from and writes to the cache appropriately). -::: - -Here is an example configuration: - -```yaml -distributed: - # Indicates whether the distributed mode is enabled or not - enabled: true - - # The address of the distributed service. - # Using a Kubernetes DNS name suggests this service runs in a Kubernetes cluster - # under the 'default' namespace and is named 'permify' - address: "kubernetes:///permify.default:5000" - - # The port on which the service is exposed - port: "5000" -``` - -Additional to that weโ€™re using a [circuit breaker](https://blog.bitsrc.io/circuit-breaker-pattern-in-microservices-26bf6e5b21ff) pattern to detect and handle failures when the underlying database is unavailable. It prevents unnecessary calls when the database is down and handles the process on the rebooting phase. - -## Need any help ? - -Our team is happy help you to structure right architecture for your permission system. Feel free to [schedule a call with one of our Permify engineer](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). - - - diff --git a/docs/versioned_docs/version-0.6.x/reference/configuration.md b/docs/versioned_docs/version-0.6.x/reference/configuration.md deleted file mode 100644 index 184366ab..00000000 --- a/docs/versioned_docs/version-0.6.x/reference/configuration.md +++ /dev/null @@ -1,553 +0,0 @@ -# Configuration File - -Permify offers various options for configuring your Permify API Server. - -Here is the example configuration YAML file with glossary below. You can also find -this [example config file](https://github.com/Permify/permify/blob/master/example.config.yaml) in Permify repo. - -***Example config.yaml file*** - -```yaml -# The server section specifies the HTTP and gRPC server settings, -# including whether or not TLS is enabled and the certificate and -# key file locations. -server: - rate_limit: 100 - http: - enabled: true - port: 3476 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - grpc: - port: 3478 - tls: - enabled: true - cert: /etc/letsencrypt/live/yourdomain.com/fullchain.pem - key: /etc/letsencrypt/live/yourdomain.com/privkey.pem - -# The logger section sets the logging level for the service. -logger: - level: info - -# The profiler section enables or disables the pprof profiler and -# sets the port number for the profiler endpoint. -profiler: - enabled: true - port: 6060 - -# The authn section specifies the authentication method for the service. -authn: - enabled: true - method: preshared - preshared: - keys: [] - -# The tracer section enables or disables distributed tracing and sets the -# exporter and endpoint for the tracing data. -tracer: - exporter: zipkin - endpoint: http://localhost:9411/api/v2/spans - enabled: true - -# The meter section enables or disables metrics collection and sets the -# exporter and endpoint for the collected metrics. -meter: - exporter: otlp - endpoint: localhost:4318 - enabled: true - -# The service section sets various service-level settings, including whether -# or not to use a circuit breaker, and cache sizes for schema, permission, -# and relationship data. -service: - circuit_breaker: false - watch: - enabled: false - schema: - cache: - number_of_counters: 1_000 - max_cost: 10MiB - permission: - bulk_limit: 100 - concurrency_limit: 100 - cache: - number_of_counters: 10_000 - max_cost: 10MiB - -# The database section specifies the database engine and connection settings, -# including the URI for the database, whether or not to auto-migrate the database, -# and connection pool settings. -database: - engine: postgres - uri: postgres://user:password@host:5432/db_name - auto_migrate: false - max_open_connections: 20 - max_idle_connections: 1 - max_connection_lifetime: 300s - max_connection_idle_time: 60s - garbage_collection: - enabled: true - interval: 200h - window: 200h - timeout: 5m - -# distributed configuration settings -distributed: - # Indicates whether the distributed mode is enabled or not - enabled: true - - # The address of the distributed service. - # Using a Kubernetes DNS name suggests this service runs in a Kubernetes cluster - # under the 'default' namespace and is named 'permify' - address: "kubernetes:///permify.default" - - # The port on which the service is exposed - port: "5000" - -``` - -## Options - -
server | Server Configurations -

- -#### Definition - -Server options to run Permify. (`grpc` and `http` available for now.) - -#### Structure - -``` -โ”œโ”€โ”€ server - โ”œโ”€โ”€ rate_limit - โ”œโ”€โ”€ (`grpc` or `http`) - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ port - โ”‚ โ””โ”€โ”€ tls - โ”‚ โ”œโ”€โ”€ enabled - โ”‚ โ”œโ”€โ”€ cert - โ”‚ โ””โ”€โ”€ key -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------|---------|---------------------------------------------------------------------| -| [ ] | rate_limit | 100 | the maximum number of requests the server should handle per second. | -| [x] | [ server_type ] | - | server option type can either be `grpc` or `http`. | -| [ ] | enabled (for server type) | true | switch option for server. | -| [x] | port | - | port that server run on. | -| [x] | tls | - | transport layer security options. | -| [ ] | enabled (for tls) | false | switch option for tls | -| [ ] | cert | - | tls certificate path. | -| [ ] | key | - | tls key pat | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|-----------------------------------|--------------| -| rate_limit | PERMIFY_RATE_LIMIT | int | -| grpc-port | PERMIFY_GRPC_PORT | string | -| grpc-tls-enabled | PERMIFY_GRPC_TLS_ENABLED | boolean | -| grpc-tls-key-path | PERMIFY_GRPC_TLS_KEY_PATH | string | -| grpc-tls-cert-path | PERMIFY_GRPC_TLS_CERT_PATH | string | -| http-enabled | PERMIFY_HTTP_ENABLED | boolean | -| http-port | PERMIFY_HTTP_PORT | string | -| http-tls-key-path | PERMIFY_HTTP_TLS_KEY_PATH | string | -| http-tls-cert-path | PERMIFY_HTTP_TLS_CERT_PATH | string | -| http-cors-allowed-origins | PERMIFY_HTTP_CORS_ALLOWED_ORIGINS | string array | -| http-cors-allowed-headers | PERMIFY_HTTP_CORS_ALLOWED_HEADERS | string array | - -

-
- -
logger | Logging Options -

- -#### Definition - -Real time logs of authorization. Permify uses [zerolog] as a logger. - -[zerolog]: https://github.com/rs/zerolog - -#### Structure - -``` -โ”œโ”€โ”€ logger - โ”œโ”€โ”€ level -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------| -| [x] | level | info | logger levels: `error`, `warn`, `info` , `debug` | -| [x] | output | text | logger output: `json`, `text` | - -#### ENV - -| Argument | ENV | Type | -|---------------------------|---------------------------------|--------| -| log-level | PERMIFY_LOG_LEVEL | string | -| log-output | PERMIFY_LOG_OUTPUT | string | - -

-
- -
authn | Server Authentication -

- -#### Definition - -You can choose to authenticate users to interact with Permify API. - -There are 2 authentication method you can choose: - -* [Pre Shared Keys](#pre-shared-keys) -* [OpenID Connect](#openid-connect) - -#### Pre Shared Keys - -On this method, you must provide a pre shared keys in order to identify yourself. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ keys -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | true | switch option authentication config | -| [x] | keys | - | Private key/keys for server authentication. Permify does not provide this key, so it must be generated by the users. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-preshared-keys | PERMIFY_AUTHN_PRESHARED_KEYS | string array | - - -#### OpenID Connect - -Permify supports OpenID Connect (OIDC). OIDC provides an identity layer on top of OAuth 2.0 to address the shortcomings -of using OAuth 2.0 for establishing identity. - -With this authentication method, you be able to integrate your existing Identity Provider (IDP) to validate JSON Web -Tokens (JWTs) using JSON Web Keys (JWKs). By doing so, only trusted tokens from the IDP will be accepted for -authentication. - -#### Structure - -``` -โ”œโ”€โ”€ authn -| โ”œโ”€โ”€ method -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ client-id -| โ”œโ”€โ”€ issuer -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | false | switch option authentication config | -| [x] | client_id | - | This is the client ID of the application you're developing. It is a unique identifier that is assigned to your application by the OpenID Connect provider, and it should be included in the JWTs that are issued by the provider. | -| [x] | issuer | - | This is the URL of the provider that is responsible for authenticating users. You will use this URL to discover information about the provider in step 1 of the authentication process. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------|-------------------------------|--------------| -| authn-enabled | PERMIFY_AUTHN_ENABLED | boolean | -| authn-method | PERMIFY_AUTHN_METHOD | string | -| authn-oidc-issuer | PERMIFY_AUTHN_OIDC_ISSUER | string | -| authn-oidc-client-id | PERMIFY_AUTHN_OIDC_CLIENT_ID | string | - -

-
- - -
tracer | Tracing Configurations -

- -#### Definition - -Permify integrated with [jaeger], [otlp], [signoz], and [zipkin] tacing tools to analyze performance and behavior of your -authorization when using Permify. - -#### Structure - -``` -โ”œโ”€โ”€ tracer -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ insecure -| โ”œโ”€โ”€ urlpath -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|----------------------------------------------------------------------------| -| [x] | exporter | - | Tracer exporter, the options are `jaeger`, `otlp`, `signoz`, and `zipkin`. | -| [x] | endpoint | - | export uri for tracing data. | -| [ ] | enabled | false | switch option for tracing. | -| [ ] | urlpath | | allows one to override the default URL path for otlp, used for sending traces. If unset, default ("/v1/traces") will be used. | -| [ ] | insecure | false | Whether to use HTTP instead of HTTPs for exporting the traces. | - -#### ENV - -| Argument | ENV | Type | -|----------------------|-------------------------------|--------------| -| tracer-enabled | PERMIFY_TRACER_ENABLED | boolean | -| tracer-exporter | PERMIFY_TRACER_EXPORTER | string | -| tracer-endpoint | PERMIFY_TRACER_ENDPOINT | string | -| tracer-urlpath | PERMIFY_TRACER_URL_PATH | string | -| tracer-insecure | PERMIFY_TRACER_INSECURE | boolean | - -

-
- -
meter | Meter Configurations -

- -#### Definition - -Configuration for observing metrics; check count, cache check count and session information; Permify version, hostname, -os, arch. - -#### Structure - -``` -โ”œโ”€โ”€ meter -| โ”œโ”€โ”€ exporter -| โ”œโ”€โ”€ endpoint -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ insecure -| โ”œโ”€โ”€ urlpath -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | -| [x] | endpoint | - | export uri for metric observation | -| [ ] | enabled | true | switch option for meter tracing. | - -#### ENV - -| Argument | ENV | Type | -|--------------------|-------------------------|--------------| -| meter-enabled | PERMIFY_METER_ENABLED | boolean | -| meter-exporter | PERMIFY_METER_EXPORTER | string | -| meter-endpoint | PERMIFY_METER_ENDPOINT | string | -| meter-urlpath | PERMIFY_METER_URL_PATH | string | -| meter-insecure | PERMIFY_METER_INSECURE | boolean | - -

-
- -
database | Database Configurations -

- -#### Definition - -Configurations for the database that points out where your want to store your authorization data (relation tuples, -audits, decision logs, authorization model) - -#### Structure - -``` -โ”œโ”€โ”€ database -| โ”œโ”€โ”€ engine -| โ”œโ”€โ”€ uri -| โ”œโ”€โ”€ auto_migrate -| โ”œโ”€โ”€ max_open_connections -| โ”œโ”€โ”€ max_idle_connections -| โ”œโ”€โ”€ max_connection_lifetime -| โ”œโ”€โ”€ max_connection_idle_time -| โ”œโ”€โ”€garbage_collection -| โ”œโ”€โ”€enable: true -| โ”œโ”€โ”€interval: 3m -| โ”œโ”€โ”€timeout: 3m -| โ”œโ”€โ”€window: 720h -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|---------------------------------|---------|-------------------------------------------------------------------------------------------------------------------| -| [x] | engine | memory | Data source. Permify supports **PostgreSQL**(`'postgres'`) for now. Contact with us for your preferred database. | -| [x] | uri | - | Uri of your data source. | -| [ ] | auto_migrate | true | When its configured as false migrating flow won't work. | -| [ ] | max_open_connections | 20 | Configuration parameter determines the maximum number of concurrent connections to the database that are allowed. | -| [ ] | max_idle_connections | 1 | Determines the maximum number of idle connections that can be held in the connection pool. | -| [ ] | max_connection_lifetime | 300s | Determines the maximum lifetime of a connection in seconds. | -| [ ] | max_connection_idle_time | 60s | Determines the maximum time in seconds that a connection can remain idle before it is closed. | -| [ ] | enable (for garbage collection) | false | Switch option for garbage collection. | -| [ ] | interval | 3m | Determines the run period of a Garbage Collection operation. | -| [ ] | timeout | 3m | Sets the duration of the Garbage Collection timeout. | -| [ ] | window | 720h | Determines how much backward cleaning the Garbage Collection process will perform. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------------------------------|--------------------------------------------------------|----------| -| database-engine | PERMIFY_DATABASE_ENGINE | string | -| database-uri | PERMIFY_DATABASE_URI | string | -| database-auto-migrate | PERMIFY_DATABASE_AUTO_MIGRATE | boolean | -| database-max-open-connections | PERMIFY_DATABASE_MAX_OPEN_CONNECTIONS | int | -| database-max-idle-connections | PERMIFY_DATABASE_MAX_IDLE_CONNECTIONS | int | -| database-max-connection-lifetime | PERMIFY_DATABASE_MAX_CONNECTION_LIFETIME | duration | -| database-max-connection-idle-time | PERMIFY_DATABASE_MAX_CONNECTION_IDLE_TIME | duration | -| database-garbage-collection-enabled | PERMIFY_DATABASE_GARBAGE_ENABLED | boolean | -| database-garbage-collection-interval | PERMIFY_DATABASE_GARBAGE_COLLECTION_INTERVAL | duration | -| database-garbage-collection-timeout | PERMIFY_DATABASE_GARBAGE_COLLECTION_TIMEOUT | duration | -| database-garbage-collection-window | PERMIFY_DATABASE_GARBAGE_COLLECTION_WINDOW | duration | - -

-
- -
service | Service Configurations -

- -#### Definition - -Configurations for the permify service and how it should behave. You can configure the circuit breaker pattern, configuration watcher, and service specific options for permission and schema services (rate limiting, concurrency limiting, cache size). - -#### Structure - -``` -โ”œโ”€โ”€ service -| โ”œโ”€โ”€ circuit_breaker -| โ”œโ”€โ”€ watch: -| | โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ schema: -| | โ”œโ”€โ”€ cache: -| | | โ”œโ”€โ”€ number_of_counters -| | | โ”œโ”€โ”€ max_cost -| | permission: -| | | โ”œโ”€โ”€ bulk_limit -| | | โ”œโ”€โ”€ concurrency_limit -| | | โ”œโ”€โ”€ cache: -| | | | โ”œโ”€โ”€ number_of_counters -| | | | โ”œโ”€โ”€ max_cost -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-------------------------------------|---------|---------------------------------------------------| -| [ ] | circuit_breaker | false | switch option to use the circuit breaker pattern. | -| [ ] | watch | false | switch option for configuration watcher. | -| [ ] | schema.cache.number_of_counters | 1_000 | number of counters for schema service. | -| [ ] | schema.cache.max_cost | 10MiB | max cost for schema cache. | -| [ ] | permission.bulk_limit | 100 | bulk operations limit for permission service. | -| [ ] | permission.concurrency_limit | 100 | concurrency limit for permission service. | -| [ ] | permission.cache.max_cost | 10MiB | max cost for permission service. | - -#### ENV - -| Argument | ENV | Type | -|-----------------------------------------------|--------------------------------------------------------|----------| -| service-circuit-breaker | PERMIFY_SERVICE_CIRCUIT_BREAKER | boolean | -| service-watch-enabled | PERMIFY_SERVICE_WATCH_ENABLED | boolean | -| service-schema-cache-number-of-counters | PERMIFY_SERVICE_SCHEMA_CACHE_NUMBER_OF_COUNTERS | int | -| service-schema-cache-max-cost | PERMIFY_SERVICE_SCHEMA_CACHE_MAX_COST | int | -| service-permission-bulk-limit | PERMIFY_SERVICE_PERMISSION_BULK_LIMIT | int | -| service-permission-concurrency-limit | PERMIFY_SERVICE_PERMISSION_CONCURRENCY_LIMIT | int | -| service-permission-cache-max-cost | PERMIFY_SERVICE_PERMISSION_CACHE_MAX_COST | int | - -

-
- -
profiler | Performance Profiler Configurations -

- -#### Definition - -pprof is a performance profiler for Go programs. It allows developers to analyze and understand the performance -characteristics of their code by generating detailed profiles of program execution - -#### Structure - -``` -โ”œโ”€โ”€ profiler -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|-----------------------------------------------| -| [ ] | enabled | true | switch option for profiler. | -| [x] | port | - | port that profiler runs on *(default: 6060)*. | - -#### ENV - -| Argument | ENV | Type | -|------------------|----------------------------|--------------| -| profiler-enabled | PERMIFY_PROFILER_ENABLED | boolean | -| profiler-port | PERMIFY_PROFILER_PORT | string | - -

-
- -
Distributed | Consistent hashing Configurations -

- -#### Definition - -A consistent hashing ring ensures data distribution that minimizes reorganization when nodes are added or removed, improving scalability and performance in distributed systems." - -#### Structure - -``` -โ”œโ”€โ”€ distributed -| โ”œโ”€โ”€ enabled -| โ”œโ”€โ”€ address -| โ”œโ”€โ”€ port -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|-------------|---------|--------------------------------------| -| [x] | enabled | false | switch option for distributed. | -| [] | address | - | address of the distributed service | -| [] | port | 5000 | port on which the service is exposed | - - -#### ENV - -| Argument | ENV | Type | -|----------------------|-----------------------------|---------| -| distributed-enabled | PERMIFY_DISTRIBUTED_ENABLED | boolean | -| distributed-address | PERMIFY_DISTRIBUTED_ADDRESS | string | -| distributed-port | PERMIFY_DISTRIBUTED_PORT | string | - -

-
- -[jaeger]: https://www.jaegertracing.io/ - -[otlp]: https://opentelemetry.io/ - -[zipkin]: https://zipkin.io/ - -[signoz]: https://signoz.io/ diff --git a/docs/versioned_docs/version-0.6.x/reference/glossary.md b/docs/versioned_docs/version-0.6.x/reference/glossary.md deleted file mode 100644 index d005f259..00000000 --- a/docs/versioned_docs/version-0.6.x/reference/glossary.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Glossary - -This section explains the basic concepts that commonly mentioned in Permify, as well as in this document. You can find the whole context on right menu. - -## Google Zanzibar (or just Zanzibar) - -[Google Zanzibar] is the global authorization system used at Google for handling authorization for hundreds of its services and products including; YouTube, Drive, Calendar, Cloud and Maps. - -Google published Zanzibar back in 2019, and in a short time it gained attention quickly. In fact some big tech companies started to shift their legacy authorization structure to Zanzibar style systems. Additionaly, Zanzibar based solutions increased over the time. All disclosure; [Permify] is an authorization system based on Zanzibar. - -For more about Zanzibar check our blog post, [Google Zanzibar In A Nutshell] - -[Google Zanzibar In A Nutshell]: https://permify.co/post/google-zanzibar-in-a-nutshell/ -[Google Zanzibar]: https://research.google/pubs/pub48190/ -[Permify]: https://permify.co/ - -## Permify Schema - -Permify has its own language that you can model your authorization logic with it, we called it Permify Schema. The language allows to define arbitrary relations between users and objects, such as owner, editor, commenter or roles like admin, manager etc. You can define your entities, relations between them and access control decisions with using Permify Schema. - -It includes set-algebraic operators such as inter- section and union for specifying potentially complex access control policies in terms of those user-object relations. - -## Relational Tuples - -In Permify, relationship between your entities, objects, and users builds up a collection of access control lists (ACLs). - -These ACLs called relational tuples: the underlying data form that represents object-to-object and object-to-subject relations. The simplest form of relational tuple structured as `entity # relation @ user` and each relational tuple represents an action that a specific user or user set can do on a resource and takes form of `user U has relation R to object O`, where user U could be a simple user or a user set such as team X members. - -## Permission Database - -Permify stores your relational tuples (authorization data) in a database you prefer. You can configure it when running Permify Service with using both [configuration flags](../../installation/brew#configuration-flags) or [configuration YAML file](https://github.com/Permify/permify/blob/master/example.config.yaml). - -## Relationship Based Access Control (ReBAC) - -ReBAC is an access control model that defines permissions based on the relationships between entities and subjects of your system. Although ReBAC is best known for social networks because its core concept is about the network of relations, it can be applied beyond that. - -Check out [Relationship Based Access Control (ReBAC)](https://permify.co/post/relationship-based-access-control-rebac/) post learn more about ReBAC and its common usage. - -## Domain Specific Language (DSL) - -Domain Specific Language is a language that specialized to a particular application domain. Permify has its DSL basically an authorization language which you can model and structure your authorization with it. We called it Permify Schema. \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/use-cases.md b/docs/versioned_docs/version-0.6.x/use-cases.md deleted file mode 100644 index 76f7bb33..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: use-cases -title: Common Use Cases -slug: /use-cases ---- - -# Common Use Cases - -Common modeling patterns and uses cases we have seen so far from the users from small startups with simple RBAC to multi-regional enterprises that run tens of Permify instances with deeply nested relationships. - -:::success Missing a specific use case? -No problem, let's discuss it together! just open an [issue](https://github.com/Permify/permify/issues) about it or join our conversation at [discord](https://discord.gg/n6KfzYxhPp)! -::: - -```mdx-code-block -import {CaseList} from '@site/src/components/Case'; -import list from './use-cases/_list.json'; - - -``` diff --git a/docs/versioned_docs/version-0.6.x/use-cases/_category_.json b/docs/versioned_docs/version-0.6.x/use-cases/_category_.json deleted file mode 100644 index 9f9db2d4..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Common Use Cases", - "position": 8, - "collapsed": true -} - \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/use-cases/_list.json b/docs/versioned_docs/version-0.6.x/use-cases/_list.json deleted file mode 100644 index 7b6e7fb1..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases/_list.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 1, - "title": "Role Based Access Control (RBAC)", - "description": "Want to implement role to your application ? Define an entity and manage your roles throught your applications.", - "link": "./simple-rbac" - }, - { - "id": 2, - "title": "Attribute Based Access Control (ABAC)", - "description": "Grant access what based on specific characteristics or attributes.", - "link": "./abac" - }, - { - "id": 3, - "title": "Relationship Based Access Control (ReBAC)", - "description": "Define permissions based on the relationships between resources and subjects in your system", - "link": "./rebac" - }, - { - "id": 4, - "title": "Custom Roles", - "description": "Assign specific permissions to users based on the custom roles that they are assigned within the system.", - "link": "./custom-roles" - }, - { - "id": 5, - "title": "Multi Tenancy", - "description": "Create custom authorization schema and relation tuples for the different tenants and manage them in a single place.", - "link": "./multi-tenancy" - } -] \ No newline at end of file diff --git a/docs/versioned_docs/version-0.6.x/use-cases/abac.md b/docs/versioned_docs/version-0.6.x/use-cases/abac.md deleted file mode 100644 index 23a6cb4d..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases/abac.md +++ /dev/null @@ -1,631 +0,0 @@ -# Attribute Based Access Control - -This page explains the design approach of Permify's ABAC support as well as demonstrates how to create and use attribute based permissions in Permify. - -## What is Attribute Based Access Control (ABAC)? - -Attribute-Based Access Control (ABAC) is like a security guard that decides who gets to access what based on specific characteristics or "attributes". - -These attributes can be associated with users, resources, or the environment, and their values can influence the outcome of an access request. - -Letโ€™s make an analogy, itโ€™s the best way to understand complex ideas. - -Think about an amusement park, and there are 3 different rides. In order to access each ride, you need to have different qualities. For example: - -1. ride one you need to be over 6ft tall. -2. ride two you need to be under 200lbs. -3. ride three you need to be between 12 - 18 years old. - -Similar to this ABAC checks certain qualities that you have defined on users, resources, or the environment. - -## Why Would Need ABAC? - -Itโ€™s obvious but the simple answer is โ€œuse casesโ€โ€ฆ Sometimes, using ReBAC and RBAC isn't the best fit for the job. It's like using winter tires on a hot desert road, or summer tires in a snowstorm - they're just not the right tools for the conditions. - -1. **Geographically Restricted:** Think of ABAC like a bouncer at a club who only lets in people from certain towns. For example, a movie streaming service might only show certain movies in certain countries because of rules about who can watch what and where. -2. **Time-Based:** ABAC can also act like a parent setting rules about when you can use the computer. For example, a system might only let you do certain things during office hours. -3. **Compliance with Privacy Regulations:** ABAC can help follow rules about privacy. For example, a hospital system might need to limit who can see a patient's data based on the patient's permission, why they want to see it, and who the person is. -4. **Limit Range:** ABAC can help you create a rules defining a number limit or range. For instance, a banking system might have limits for wiring or withdrawing money. -5. **Device Information:** ABAC can control access based on attributes of the device, such as the device type, operating system version, or whether the device has the latest security patches. - -As you can see ABAC has a more contextual approach. You can define access rights regarding context around subjects and objects in an application. - -## Modeling ABAC - -To support ABAC in Permify, we've added two main components into our DSL: attributes and rules. - -### Defining Attributes - -Attributes are used to define properties for entities in specific data types. For instance, an attribute could be an IP range associated with an organization, defined as a string array: - -```perm -attribute ip_range string[] -``` - -Here are the all attribute types that you use when defining an `attribute`. - -```perm -// A boolean attribute type -boolean - -// A boolean array attribute type. -boolean[] - -// A string attribute type. -string - -// A string array attribute type. -string[] - -// An integer attribute type. -integer - -// An integer array attribute type. -integer[] - -// A double attribute type. -double - -// A double array attribute type. -double[] -``` - -### Defining Rules - -Rules are structures that allow you to write specific conditions for the model. You can think rules as simple functions of every software language have. They accept parameters and are based on condition to return a true/false result. - -In the following example schema, a rule could be used to check if a given IP address falls within a specified IP range: - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute ip_range string[] - - permission view = check_ip_range(request.ip, ip_range) or admin -} - -rule check_ip_range(ip string, ip_range string[]) { - ip in ip_range -} -``` - -:::info Syntax -We design our schema language based on [Common Expression Language (CEL)](https://github.com/google/cel-go). So the syntax looks nearly identical to equivalent expressions in C++, Go, Java, and TypeScript. - -Please let us know via our [Discord channel](https://discord.gg/n6KfzYxhPp) if you have questions regarding syntax, definitions or any operator you identify not working as expected. -::: - -Let's examine some of common usage of ABAC with small schema examples. - -### Boolean - True/False Conditions - -For attributes that represent a binary choice or state, such as a yes/no question, the `Boolean` data type is an excellent choice. - -```perm -entity post { - attribute is_public boolean - - permission view = is_public -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts boolean as `FALSE` -::: - -### Text & Object Based Conditions - -String can be used as attribute data type in a variety of scenarios where text-based information is needed to make access control decisions. Here are a few examples: - -- **Location:** If you need to control access based on geographical location, you might have a location attribute (e.g., "USA", "EU", "Asia") stored as a string. -- **Device Type**: If access control decisions need to consider the type of device being used, a device type attribute (e.g., "mobile", "desktop", "tablet") could be stored as a string. -- **Time Zone**: If access needs to be controlled based on time zones, a time zone attribute (e.g., "EST", "PST", "GMT") could be stored as a string. -- **Day of the Week:** In a scenario where access to certain resources is determined by the day of the week, the string data type can be used to represent these days (e.g., "Monday", "Tuesday", etc.) as attributes! - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute location string[] - - permission view = check_location(request.current_location, location) or admin -} - -rule check_location(current_location string, location string[]) { - current_location in location -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts string as `""` -::: - -### Numerical Conditions - -#### Integers - -Integer can be used as attribute data type in several scenarios where numerical information is needed to make access control decisions. Here are a few examples: - -- **Age:** If access to certain resources is age-restricted, an age attribute stored as an integer can be used to control access. -- **Security Clearance Level:** In a system where users have different security clearance levels, these levels can be stored as integer attributes (e.g., 1, 2, 3 with 3 being the highest clearance). -- **Resource Size or Length:** If access to resources is controlled based on their size or length (like a document's length or a file's size), these can be stored as integer attributes. -- **Version Number:** If access control decisions need to consider the version number of a resource (like a software version or a document revision), these can be stored as integer attributes. - -```perm -entity content { - permission view = check_age(request.age) -} - -rule check_age(age integer) { - age >= 18 -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts integer as `0` -::: - -#### Double - Precise numerical information - -Double can be used as attribute data type in several scenarios where precise numerical information is needed to make access control decisions. Here are a few examples: - -- **Usage Limit:** If a user has a usage limit (like the amount of storage they can use or the amount of data they can download), and this limit needs to be represented with decimal precision, it can be stored as a double attribute. -- **Transaction Amount:** In a financial system, if access control decisions need to consider the amount of a transaction, and this amount needs to be represented with decimal precision (like $100.50), these amounts can be stored as double attributes. -- **User Rating:** If access control decisions need to consider a user's rating (like a rating out of 5 with decimal points, such as 4.7), these ratings can be stored as double attributes. -- **Geolocation:** If access control decisions need to consider precise geographical coordinates (like latitude and longitude, which are often represented with decimal points), these coordinates can be stored as double attributes. - -```perm -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -:::caution -โ›” If you donโ€™t create the related attribute data, Permify accounts double as `0.0` -::: - -## Example Use Cases - -### Example of Public/Private Repository - -In this example, **`is_public`** is defined as a boolean attribute. If an attribute is a boolean, it can be directly used without the need for a rule. This is only applicable for boolean types. - -```perm -entity user {} - -entity post { - - relation owner @user - - attribute is_public boolean - - permission view = is_public or owner - permission edit = owner -} -``` - -In this context, if the **`is_public`** attribute of the repository is set to true, everyone can view it. If it's not public (i.e., **`is_public`** is false), only the owner, in this case **`user:1`**, can view it. - -The permissions in this model are defined as such: - -**`permission view = is_public or owner`** - -This means that the 'view' permission is granted if either the repository is public (**`is_public`** is true) or if the current user is the owner of the repository. - -**relationships:** - -- post:1#owner@user:1 - -**attributes:** - -- post:1$is_public|boolean:true - -**Check Evolution Sub Queries Post View** -โ†’ post:1#is_public โ†’ true -โ†’ post:1#admin@user:1 โ†’ true - -**Request keys before hash** - -- check*{snapshot}*{schema*version}*{context}\_post:1$is_public โ†’ true -- check*{snapshot}*{schema*version}*{context}\_post:1#admin@user:1 โ†’ true - -### Example of Weekday - -In this example, to be able to view the repository it must not be a weekend, and the user must be a member of the organization. - -```perm -entity user {} - -entity organization { - - relation member @user - - permission view = is_weekday(request.day_of_week) and member -} - -entity repository { - - relation organization @organization - - permission view = organization.view -} - -rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' -} -``` - -The permissions in this model state that to 'view' the repository, the user must fulfill two conditions: the current day (according to the context data **`day_of_week`**) must not be a weekend (determined by the **`is_weekday`** rule), and the user must be a member of the organization that owns the repository. - -**Relationships:** - -- organization:1#member@user:1 - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$is_weekday(context.day_of_week) โ†’ true -โ†’ organization:1#member@user:1 โ†’ true - -**Request keys before hash** - -- check*{snapshot}*{schema*version}*{context}\_organization:1$is_weekday(context.day_of_week) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_post:1#member@user:1 โ†’ true - -### Example of Banking System - -This model represents a banking system with two entities: **`user`** and **`account`**. - -1. **`user`**: Represents a customer of the bank. -2. **`account`**: Represents a bank account that has an **`owner`** (which is a **`user`**), and a **`balance`** (amount of money in the account). - -```perm -entity user {} - -entity account { - relation owner @user - attribute balance double - - permission withdraw = check_balance(request.amount, balance) and owner -} - -rule check_balance(amount double, balance double) { - (balance >= amount) && (amount <= 5000) -} -``` - -**The check_balance rule:** This rule verifies if the withdrawal amount is less than or equal to the account's balance and doesn't exceed 5000 (the maximum amount allowed for a withdrawal). It accepts two parameters, the withdrawal amount (amount) and the account's current balance (balance). -**The owner check:** This condition checks if the person requesting the withdrawal is the owner of the account. - -Both of these conditions need to be true for the **`withdraw`** permission to be granted. In other words, a user can withdraw money from an account only if they are the owner of that account, and the amount they want to withdraw is within the account balance and doesn't exceed 5000. - -**Relationships** - -- account:1#owner@user:1 - -**Attributes** - -- account:1$balance|double:4000 - -**Check Evolution Sub Queries For Account Withdraw** -โ†’ account:1$check_balance(context.amount,balance) โ†’ true -โ†’ account:1#owner@user:1 โ†’ true - -**Request keys before hash** - -- check*{snapshot}*{schema*version}*{context}\_account:1$check_balance(context.amount,balance) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_account:1#owner@user:1 โ†’ true - -### Hierarchical Usage - -In this model: - -1. **`employee`**: Represents an individual worker. It has no specific attributes or relations in this case. -2. **`organization`**: Represents an entire organization, which has a **`founding_year`** attribute. The **`view`** permission is granted if the **`check_founding_year`** rule (which checks if the organization was founded after 2000) returns true. -3. **`department`**: Represents a department within the organization. It has a **`budget`** attribute and a relation to its parent **`organization`**. The **`view`** permission is granted if the department's budget is more than 10,000 (checked by the **`check_budget`** rule) and if the **`organization.view`** permission is true. - -Note: In this model, permissions can refer to higher-level permissions (like **`organization.view`**). However, you cannot use the attribute of a relation in this way. For example, you cannot directly reference **`organization.founding_year`** in a permission expression. Permissions can depend on permissions in a related entity, but not directly on the related entity's attributes. - -```perm -entity employee {} - -entity organization { - attribute founding_year integer - - permission view = check_founding_year(founding_year) -} - -entity department { - relation organization @organization - attribute budget double - - permission view = check_budget(budget) and organization.view -} - -rule check_founding_year(founding_year integer) { - founding_year > 2000 -} - -rule check_budget(budget double) { - budget > 10000 -} -``` - -**Relationships** - -- department:1#organization@organization:1 -- department:1#organization@organization:2 - -**Attributes** - -- department:1$budget|double:20000 -- organization:1$organization|integer:2021 - -**Check Evolution Sub Queries For Department View** -โ†’ department:1$check_budget(budget) โ†’ true -โ†’ department:1#organization@user:1 โ†’ true - โ†’ organization:2$check_founding_year(founding_year) โ†’ false -โ†’ organization:1$check_founding_year(founding_year) โ†’ true - -**Request keys before hash** - -- check*{snapshot}*{schema*version}*{context}\_department:1$check_budget(budget) โ†’ true -- check*{snapshot}*{schema*version}*{context}\_organization:2$check_founding_year(founding_year) โ†’ false -- check*{snapshot}*{schema*version}*{context}\_organization:1$check_founding_year(founding_year) โ†’ true - -## Evaluation of ABAC Access Checks - -**Model** - -```perm -entity user {} - -entity organization { - - relation admin @user - - attribute ip_range string[] - - permission view = check_ip_range(request.ip_address, ip_range) or admin -} - -rule check_ip_range(ip_address string, ip_range string[]) { - ip in ip_range -} -``` - -In this case, the part written as 'context' refers to the context within the request. Any type of data can be added from within the request and can be called within the model. - -For instance, - -```json -"context": { - "data": { - "ip_address": "187.182.51.206", - "day_of_week": "monday" - }, -} -``` - -**Relationships** - -- organization:1#admin@user:1 - -**Attributes** - -- organization:1$ip_range|string[]:[โ€˜187.182.51.206โ€™, โ€˜250.89.38.115โ€™] - -**Check request** - -```json -{ - "entity": { - "type": "organization", - "id": "1" - }, - "permission": "view", - "subject": { - "type": "user", - "id": "1" - }, - "context": { - "data": { - "ip_address": "187.182.51.206" - } - } -} -``` - -**Check Evolution Sub Queries Organization View** -โ†’ organization:1$check_ip_range(context.ip_address,ip_range) โ†’ true -โ†’ organization:1#admin@user:1 โ†’ true - -**Cache Mechanism** -The cache mechanism works by hashing the snapshot of the database, schema version, and sub-queries as keys and adding their results, so it will operate in the same way in calls as in relationships. For example, - -**Request keys before hash** - -- check*{snapshot}*{schema*version}*{context}\_organization:1#admin@user:1 โ†’ true -- check*{snapshot}*{schema*version}*{context}\_organization:1$check_ip_range(ip_range) โ†’ true - -## How To Use ABAC - -**Install Permify** - -```yaml -docker pull **ghcr.io/permify/permify:latest** -``` - -**Validation Yaml Structure** - -```yaml -schema: >- - {string schema} - -relationships: - - entity_name:entity_id#relation@subject_type:subject_id - -attributes: - - entity_name:entity_id#attribute@attribute_type:attribute_value - -scenarios: - - name: "name" - description: "description" - checks: - - entity: "entity_name:entity_id" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result - entity_filters: - - entity_type: "entity_name" - subject: "subject_name:subject_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array - subject_filters: - - subject_reference: "subject_name" - entity: "entity_name:entity_id" - context: - tuples: [] - attributes: [] - data: - key: {value} - assertions: - permission: result_array -``` - -**Note:** The 'data' field within the 'context' can be assigned a desired value as a key-value pair. Later, this value can be retrieved within the model using 'request.key'. - -**Example in validation file:** - -```yaml -context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" -``` - -This YAML snippet specifies a validation context with no tuples or attributes, and a data field indicating the day of the week is Saturday. - -**Example in model** - -```yaml -permission delete = is_weekday(request.day_of_week) -``` - -In the model, a **`delete`** permission rule is set. It calls the function **`is_weekday`** with the value of **`day_of_week`** from the context. If **`is_weekday("saturday")`** is true, the delete permission is granted. - -**Create Validation File** - -```yaml -schema: >- - entity user {} - - entity organization { - - relation member @user - - attribute credit integer - - permission view = check_credit(credit) and member - } - - entity repository { - - relation organization @organization - - attribute is_public boolean - - permission view = is_public - permission edit = organization.view - permission delete = is_weekday(request.day_of_week) - } - - rule check_credit(credit integer) { - credit > 5000 - } - - rule is_weekday(day_of_week string) { - day_of_week != 'saturday' && day_of_week != 'sunday' - } - -relationships: - - organization:1#member@user:1 - - repository:1#organization@organization:1 - -attributes: - - organization:1$credit|integer:6000 - - repository:1$is_public|boolean:true - -scenarios: - - name: "scenario 1" - description: "test description" - checks: - - entity: "repository:1" - subject: "user:1" - context: - assertions: - view: true - - entity: "repository:1" - subject: "user:1" - context: - tuples: [] - attributes: [] - data: - day_of_week: "saturday" - assertions: - view: true - delete: false - - entity: "organization:1" - subject: "user:1" - context: - assertions: - view: true - entity_filters: - - entity_type: "repository" - subject: "user:1" - context: - assertions: - view: ["1"] - subject_filters: - - subject_reference: "user" - entity: "repository:1" - context: - assertions: - view: ["1"] - edit: ["1"] -``` - -**Run validation command** - -```yaml -docker run -v {your_config_folder}:/config **ghcr.io/permify/permify-beta:latest validate /config/validation.yaml** -``` - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. diff --git a/docs/versioned_docs/version-0.6.x/use-cases/custom-roles.md b/docs/versioned_docs/version-0.6.x/use-cases/custom-roles.md deleted file mode 100644 index 4ddf1658..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases/custom-roles.md +++ /dev/null @@ -1,74 +0,0 @@ - -# Custom Roles - -This document highlights a solution for custom roles with the [Permify Schema]. In this tutorial, we will create custom **admin** and **member** roles in a project. Then set the permissions of these roles according to their capabilities on the dashboard and tasks. - -[Permify Schema]: ../../getting-started/modeling - -Before we get started, here's the final schema that we will create in this tutorial. - -```perm -entity user {} - -entity role { - relation assignee @user -} - -entity dashboard { - relation view @role#assignee - relation edit @role#assignee -} - -entity task { - relation view @role#assignee - relation edit @role#assignee -} -``` - -This schema encompasses several crucial elements to structure a custom role-based access control system. The role entity serves as a particularly important component, as it enables the creation of multiple custom roles. These roles may vary according to the needs of the application and could include roles like **admin**, **editor**, or **member**, among others. - -Once these custom roles have been established, they can be assigned to other entities in the system. Specifically, in this schema, these roles are attached to the dashboard and task entities. Each of these entities, dashboard and task, has pre-defined permissions associated with them. These permissions, defined within the schema or model, could represent various operations such as **view**, **edit**, and so forth. - -With this setup, it's possible to map these pre-defined permissions of the dashboard and task entities to the custom roles that have been created. This implies that specific permissions, for instance, **view** and **edit** for a dashboard or a task, could be assigned to a particular custom role. - -Based on this model, the example relationships are as follows. With these relationships, custom roles such as **admin** and **member** have been created. - -## Relationships - -dashboard:project-progress#view@role:admin#assignee - -dashboard:project-progress#view@role:member#assignee - -dashboard:project-progress#edit@role:admin#assignee - -task:website-design-review#view@role:admin#assignee - -task:website-design-review#view@role:member#assignee - -task:website-design-review#edit@role:admin#assignee - -Together with these relationships and the model, a view has been created for the **project-progress** dashboard and the **website-design-review** task as shown in the table below. - -| permission | admin | member | -|--------------------|-------|---------| -| **dashboard:view** | โœ… | โœ… | -| **dashboard:edit** | โœ… | โ›” | -| **task:view** | โœ… | โœ… | -| **task:edit** | โœ… | โ›” | - - -Subsequently, you can make authorization decisions by assigning these custom roles to the users that you have created. - -role:member#assignee@user:1 - -When we write these relationship, the final situation will be as follows. - -`Can user:1 view dashboard:project-progress?` gives **Allow** result since the `user:1` is assignee of `role:member` and `role:member` has `dashboard:project-progress#view` permission. - -`Can user:1 view task:website-design-review?` gives **Denied** result since the `user:1` is not assignee of `role:admin`. - - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. - diff --git a/docs/versioned_docs/version-0.6.x/use-cases/multi-tenancy.md b/docs/versioned_docs/version-0.6.x/use-cases/multi-tenancy.md deleted file mode 100644 index c8e5be9a..00000000 --- a/docs/versioned_docs/version-0.6.x/use-cases/multi-tenancy.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: "Multi Tenancy" ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -With version 0.3.x Permify moved to a tenancy-based infrastructure, which affects almost all of the API operations. - -## Multi Tenancy on Permify - -Multi-tenancy in Permify refers to an authorization architecture where a single Permify authorization service serves multiple applications/organizations (tenants). - -This allows customization of the authorization for each tenant's specific needs. With Multi-Tenancy support, you can create a custom authorization schema and relation tuples for the different tenants and manage them in a single place. - -For the users that don't have/need multi-tenancy in their authorization structure, we created a pre-inserted tenant (id: **t1**) that comes default when you serve a Permify service. - -Several things changed when we moved to tenant based infrastructure, these are: - -- [Multi Tenancy on Permify](#multi-tenancy-on-permify) - - [API endpoints now have Tenant ID field](#api-endpoints-now-have-tenant-id-field) - - [Check API](#check-api) - - [Added Tenancy Service](#added-tenancy-service) - - [Permission Database Tenancy Table and Tenant Id column](#permission-database-tenancy-table-and-tenant-id-column) - - [Tenant Table](#tenant-table) - - [Tenant ID Column](#tenant-id-column) -- [Need any help ?](#need-any-help-) - -### API endpoints now have Tenant ID field - -All API endpoints now have a `โ€tenant_id` mandatory field. Let's examine a check request below, - -#### Check API - - - - -```go -cr, err: = client.Permission.Check(context.Background(), & v1.PermissionCheckRequest { - TenantId: "t1", - Metadata: & v1.PermissionCheckRequestMetadata { - SnapToken: "" - SchemaVersion: "" - Depth: 20, - }, - Entity: & v1.Entity { - Type: "repository", - Id: "1", - }, - Permission: "edit", - Subject: & v1.Subject { - Type: "user", - Id: "1", - }, - - if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - // RESULT_ALLOWED - } else { - // RESULT_DENIED - } -}) -``` - - - - -```javascript -client.permission.check({ - tenantId: "t1", - metadata: { - snapToken: "", - schemaVersion: "", - depth: 20 - }, - entity: { - type: "repository", - id: "1" - }, - permission: "edit", - subject: { - type: "user", - id: "1" - } -}).then((response) => { - if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) { - console.log("RESULT_ALLOWED") - } else { - console.log("RESULT_DENIED") - } -}) -``` - - - - -```curl -curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "metadata":{ - "snap_token": "", - "schema_version": "", - "depth": 20 - }, - "entity": { - "type": "repository", - "id": "1" - }, - "permission": "edit", - "subject": { - "type": "user", - "id": "1", - "relation": "" - }, -}' -``` - - - -Users that come from version 0.2.x and users that have a single tenant can enter **t1** as tenant id. See changes on the other endpoints from [API Overview Section](../api-overview.md). - -### Added Tenancy Service - -To manage tenants we have added a Tenancy service; you can create, delete and list tenants. See the [Tenancy Service](../../api-overview/tenancy) in Using The API section. - -### Permission Database Tenancy Table and Tenant Id column - -#### Tenant Table - -A tenants table has been added to the Permission database to store tenant's details. The new folder structure changed as follows: -``` -tables -โ”œโ”€โ”€ migrations -โ”œโ”€โ”€ relation_tuples -โ”œโ”€โ”€ schema_definitions -โ”œโ”€โ”€ tenants -โ”œโ”€โ”€ transactions -``` - -#### Tenant ID Column - -Relation tuples and schema definition tables now have a tenant_id column, which stores the id of the tenant that the data belongs. - -Let's take a look at a snapshot of the demo table on an example Permission Database. - -Example Relation Tuples data table: -![tenant-id-tuples](https://user-images.githubusercontent.com/34595361/214724165-a3775756-0649-4869-b994-d837fadd271d.png) - -Example Schema Definitions data table -![tenant-id-schema](https://user-images.githubusercontent.com/34595361/214724727-01eadad3-720c-4c10-a88d-6ee293ecf4a8.png) - -## Need any help ? - -Our team is happy to help you get started with Permify. If you'd like to learn more about using Permify in your app or have any questions about this example, [schedule a call with one of our Permify engineers](https://meetings-eu1.hubspot.com/ege-aytin/call-with-an-expert). Alternatively you can join our [discord community](https://discord.com/invite/MJbUjwskdH) to discuss. diff --git a/docs/versioned_sidebars/version-0.2.x-sidebars.json b/docs/versioned_sidebars/version-0.2.x-sidebars.json deleted file mode 100644 index 0a0e671f..00000000 --- a/docs/versioned_sidebars/version-0.2.x-sidebars.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "someSidebar": [ - { - "type": "category", - "label": "First Glance", - "link": { - "type": "generated-index", - "title": "First Glance", - "slug": "/permify-overview" - }, - "items": [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Getting Started", - "link": { - "type": "generated-index", - "title": "Getting Started", - "slug": "/getting-started" - }, - "items": [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Set Up Permify", - "link": { - "type": "generated-index", - "title": "Set Up Permify", - "slug": "/installation" - }, - "items": [ - "installation/overview", - "installation/brew", - "installation/container" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Deployment", - "link": { - "type": "generated-index", - "title": "Deployment", - "slug": "/deployment" - }, - "items": [ - "deployment/aws", - "deployment/azure", - "deployment/google", - "deployment/kubernetes" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Using the API", - "link": { - "type": "doc", - "id": "api-overview" - }, - "items": [ - "api-overview/write-schema", - "api-overview/write-relationships", - "api-overview/read-api", - "api-overview/check-api", - "api-overview/delete-relationships", - "api-overview/expand-api", - "api-overview/schema-lookup" - ], - "collapsed": true - }, - { - "type": "doc", - "id": "playground", - "label": "Permify Playground" - }, - { - "type": "category", - "label": "Common Use Cases", - "link": { - "type": "generated-index", - "title": "Common Use Cases", - "slug": "/use-cases" - }, - "items": [ - "example-use-cases/simple-rbac", - "example-use-cases/organizational", - "example-use-cases/ownership", - "example-use-cases/parent-child", - "example-use-cases/sharing", - "example-use-cases/user-groups" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Reference", - "link": { - "type": "generated-index", - "title": "Reference", - "slug": "/reference" - }, - "items": [ - "reference/glossary", - "reference/snap-tokens", - "reference/tracing" - ], - "collapsed": true - } - ] -} diff --git a/docs/versioned_sidebars/version-0.3.x-sidebars.json b/docs/versioned_sidebars/version-0.3.x-sidebars.json deleted file mode 100644 index 7ef0942b..00000000 --- a/docs/versioned_sidebars/version-0.3.x-sidebars.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "someSidebar": [ - { - "type": "category", - "label": "First Glance", - "link": { - "type": "generated-index", - "title": "First Glance", - "slug": "/permify-overview" - }, - "items": [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Getting Started", - "link": { - "type": "generated-index", - "title": "Getting Started", - "slug": "/getting-started" - }, - "items": [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing", - { - "type": "category", - "label": "Real World Examples", - "link": { - "type": "generated-index", - "title": "Real World Examples", - "slug": "/getting-started/examples" - }, - "items": [ - "getting-started/examples/google-docs", - "getting-started/examples/facebook-groups", - "getting-started/examples/notion" - ] - } - ], - "collapsed": false - }, - { - "type": "category", - "label": "Set Up Permify", - "link": { - "type": "doc", - "id": "installation" - }, - "items": [ - "installation/overview", - "installation/brew", - "installation/container", - "installation/aws", - "installation/azure", - "installation/google", - "installation/kubernetes" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Using the API", - "link": { - "type": "doc", - "id": "api-overview" - }, - "items": [ - { - "type": "category", - "label": "Schema Service", - "link": { - "type": "generated-index", - "title": "Schema Service", - "slug": "/api-overview/schema" - }, - "items": [ - "api-overview/schema/write-schema" - ] - }, - { - "type": "category", - "label": "Relationship Service", - "link": { - "type": "generated-index", - "title": "Relationship Service", - "slug": "/api-overview/relationship" - }, - "items": [ - "api-overview/relationship/write-relationships", - "api-overview/relationship/read-api", - "api-overview/relationship/delete-relationships" - ] - }, - { - "type": "category", - "label": "Permission Service", - "link": { - "type": "generated-index", - "title": "Permission Service", - "slug": "/api-overview//permission" - }, - "items": [ - "api-overview/permission/check-api", - "api-overview/permission/lookup-entity", - "api-overview/permission/expand-api", - "api-overview/permission/schema-lookup" - ] - }, - { - "type": "category", - "label": "Tenancy Service", - "link": { - "type": "generated-index", - "title": "Tenancy Service", - "slug": "/api-overview/tenancy" - }, - "items": [ - "api-overview/tenancy/create-tenant", - "api-overview/tenancy/delete-tenant" - ] - } - ], - "collapsed": true - }, - { - "type": "doc", - "id": "playground", - "label": "Permify Playground" - }, - { - "type": "doc", - "id": "migrating", - "label": "Migrating From 0.2.x to 0.3.x" - }, - { - "type": "category", - "label": "Common Use Cases", - "link": { - "type": "doc", - "id": "use-cases" - }, - "items": [ - "use-cases/simple-rbac", - "use-cases/organizational", - "use-cases/ownership", - "use-cases/nested-hierarchies", - "use-cases/user-groups", - "use-cases/sharing" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Reference", - "link": { - "type": "generated-index", - "title": "Reference", - "slug": "/reference" - }, - "items": [ - "reference/glossary", - "reference/configuration", - "reference/snap-tokens", - "reference/tracing" - ], - "collapsed": true - } - ] -} diff --git a/docs/versioned_sidebars/version-0.4.x-sidebars.json b/docs/versioned_sidebars/version-0.4.x-sidebars.json deleted file mode 100644 index fa2857fa..00000000 --- a/docs/versioned_sidebars/version-0.4.x-sidebars.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "someSidebar": [ - { - "type": "category", - "label": "First Glance", - "link": { - "type": "generated-index", - "title": "First Glance", - "slug": "/permify-overview" - }, - "items": [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Getting Started", - "link": { - "type": "generated-index", - "title": "Getting Started", - "slug": "/getting-started" - }, - "items": [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing", - { - "type": "category", - "label": "Real World Examples", - "link": { - "type": "generated-index", - "title": "Real World Examples", - "slug": "/getting-started/examples" - }, - "items": [ - "getting-started/examples/google-docs", - "getting-started/examples/facebook-groups", - "getting-started/examples/notion" - ] - } - ], - "collapsed": false - }, - { - "type": "category", - "label": "Set Up Permify", - "link": { - "type": "doc", - "id": "installation" - }, - "items": [ - "installation/overview", - "installation/brew", - "installation/container", - "installation/aws", - "installation/azure", - "installation/google", - "installation/kubernetes" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Using the API", - "link": { - "type": "doc", - "id": "api-overview" - }, - "items": [ - { - "type": "category", - "label": "Schema Service", - "link": { - "type": "generated-index", - "title": "Schema Service", - "slug": "/api-overview/schema" - }, - "items": [ - "api-overview/schema/write-schema" - ] - }, - { - "type": "category", - "label": "Relationship Service", - "link": { - "type": "generated-index", - "title": "Relationship Service", - "slug": "/api-overview/relationship" - }, - "items": [ - "api-overview/relationship/write-relationships", - "api-overview/relationship/read-api", - "api-overview/relationship/delete-relationships" - ] - }, - { - "type": "category", - "label": "Permission Service", - "link": { - "type": "generated-index", - "title": "Permission Service", - "slug": "/api-overview/permission" - }, - "items": [ - "api-overview/permission/check-api", - "api-overview/permission/lookup-entity", - "api-overview/permission/lookup-subject", - "api-overview/permission/expand-api", - "api-overview/permission/subject-permission" - ] - }, - { - "type": "category", - "label": "Tenancy Service", - "link": { - "type": "generated-index", - "title": "Tenancy Service", - "slug": "/api-overview/tenancy" - }, - "items": [ - "api-overview/tenancy/create-tenant", - "api-overview/tenancy/delete-tenant" - ] - }, - { - "type": "category", - "label": "Watch Service", - "link": { - "type": "generated-index", - "title": "Watch Service", - "slug": "/api-overview/watch" - }, - "items": [ - "api-overview/watch/watch-changes" - ] - } - ], - "collapsed": true - }, - { - "type": "doc", - "id": "playground", - "label": "Permify Playground" - }, - { - "type": "category", - "label": "Common Use Cases", - "link": { - "type": "doc", - "id": "use-cases" - }, - "items": [ - "use-cases/simple-rbac", - "use-cases/abac", - "use-cases/custom-roles", - "use-cases/multi-tenancy", - "use-cases/rebac" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Reference", - "link": { - "type": "generated-index", - "title": "Reference", - "slug": "/reference" - }, - "items": [ - "reference/glossary", - "reference/configuration", - "reference/contextual-tuples", - "reference/snap-tokens", - "reference/cache", - "reference/tracing" - ], - "collapsed": true - } - ] -} diff --git a/docs/versioned_sidebars/version-0.5.x-sidebars.json b/docs/versioned_sidebars/version-0.5.x-sidebars.json deleted file mode 100644 index 2390f457..00000000 --- a/docs/versioned_sidebars/version-0.5.x-sidebars.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "someSidebar": [ - { - "type": "category", - "label": "First Glance", - "link": { - "type": "generated-index", - "title": "First Glance", - "slug": "/permify-overview" - }, - "items": [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Getting Started", - "link": { - "type": "generated-index", - "title": "Getting Started", - "slug": "/getting-started" - }, - "items": [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing", - { - "type": "category", - "label": "Real World Examples", - "link": { - "type": "generated-index", - "title": "Real World Examples", - "slug": "/getting-started/examples" - }, - "items": [ - "getting-started/examples/google-docs", - "getting-started/examples/facebook-groups", - "getting-started/examples/notion", - "getting-started/examples/instagram", - "getting-started/examples/mercury" - ] - } - ], - "collapsed": false - }, - { - "type": "category", - "label": "Set Up Permify", - "link": { - "type": "doc", - "id": "installation" - }, - "items": [ - "installation/overview", - "installation/brew", - "installation/container", - "installation/aws", - "installation/azure", - "installation/google", - "installation/kubernetes" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Using the API", - "link": { - "type": "doc", - "id": "api-overview" - }, - "items": [ - { - "type": "category", - "label": "Schema Service", - "link": { - "type": "generated-index", - "title": "Schema Service", - "slug": "/api-overview/schema" - }, - "items": [ - "api-overview/schema/write-schema" - ] - }, - { - "type": "category", - "label": "Data Service", - "link": { - "type": "generated-index", - "title": "Data Service", - "slug": "/api-overview/data" - }, - "items": [ - "api-overview/data/write-data", - "api-overview/data/read-relationships", - "api-overview/data/read-attributes", - "api-overview/data/delete-data" - ] - }, - { - "type": "category", - "label": "Permission Service", - "link": { - "type": "generated-index", - "title": "Permission Service", - "slug": "/api-overview/permission" - }, - "items": [ - "api-overview/permission/check-api", - "api-overview/permission/lookup-entity", - "api-overview/permission/lookup-subject", - "api-overview/permission/expand-api", - "api-overview/permission/subject-permission" - ] - }, - { - "type": "category", - "label": "Tenancy Service", - "link": { - "type": "generated-index", - "title": "Tenancy Service", - "slug": "/api-overview/tenancy" - }, - "items": [ - "api-overview/tenancy/create-tenant", - "api-overview/tenancy/delete-tenant" - ] - }, - { - "type": "category", - "label": "Watch Service", - "link": { - "type": "generated-index", - "title": "Watch Service", - "slug": "/api-overview/watch" - }, - "items": [ - "api-overview/watch/watch-changes" - ] - } - ], - "collapsed": true - }, - { - "type": "doc", - "id": "playground", - "label": "Permify Playground" - }, - { - "type": "category", - "label": "Common Use Cases", - "link": { - "type": "doc", - "id": "use-cases" - }, - "items": [ - "use-cases/simple-rbac", - "use-cases/abac", - "use-cases/custom-roles", - "use-cases/multi-tenancy", - "use-cases/rebac" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Reference", - "link": { - "type": "generated-index", - "title": "Reference", - "slug": "/reference" - }, - "items": [ - "reference/glossary", - "reference/configuration", - "reference/contextual-tuples", - "reference/snap-tokens", - "reference/cache", - "reference/tracing" - ], - "collapsed": true - } - ] -} diff --git a/docs/versioned_sidebars/version-0.6.x-sidebars.json b/docs/versioned_sidebars/version-0.6.x-sidebars.json deleted file mode 100644 index f3bed79b..00000000 --- a/docs/versioned_sidebars/version-0.6.x-sidebars.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "someSidebar": [ - { - "type": "category", - "label": "First Glance", - "link": { - "type": "generated-index", - "title": "First Glance", - "slug": "/permify-overview" - }, - "items": [ - "permify-overview/intro", - "permify-overview/authorization-service", - "permify-overview/infrastructure" - ], - "collapsed": false - }, - { - "type": "category", - "label": "Getting Started", - "link": { - "type": "generated-index", - "title": "Getting Started", - "slug": "/getting-started" - }, - "items": [ - "getting-started/modeling", - "getting-started/sync-data", - "getting-started/enforcement", - "getting-started/testing", - { - "type": "category", - "label": "Real World Examples", - "link": { - "type": "doc", - "id": "examples" - }, - "items": [ - "getting-started/examples/google-docs", - "getting-started/examples/facebook-groups", - "getting-started/examples/notion", - "getting-started/examples/instagram", - "getting-started/examples/mercury" - ] - } - ], - "collapsed": false - }, - { - "type": "category", - "label": "Set Up Permify", - "link": { - "type": "doc", - "id": "installation" - }, - "items": [ - "installation/overview", - "installation/brew", - "installation/container", - "installation/aws", - "installation/azure", - "installation/google", - "installation/kubernetes" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Using the API", - "link": { - "type": "doc", - "id": "api-overview" - }, - "items": [ - { - "type": "category", - "label": "Schema Service", - "link": { - "type": "generated-index", - "title": "Schema Service", - "slug": "/api-overview/schema" - }, - "items": [ - "api-overview/schema/write-schema" - ] - }, - { - "type": "category", - "label": "Data Service", - "link": { - "type": "generated-index", - "title": "Data Service", - "slug": "/api-overview/data" - }, - "items": [ - "api-overview/data/write-data", - "api-overview/data/read-relationships", - "api-overview/data/read-attributes", - "api-overview/data/run-bundle", - "api-overview/data/delete-data" - ] - }, - { - "type": "category", - "label": "Bundle Service", - "link": { - "type": "doc", - "id": "bundle" - }, - "items": [ - "api-overview/bundle/write-bundle", - "api-overview/bundle/read-bundle", - "api-overview/bundle/delete-bundle" - ] - }, - { - "type": "category", - "label": "Permission Service", - "link": { - "type": "generated-index", - "title": "Permission Service", - "slug": "/api-overview/permission" - }, - "items": [ - "api-overview/permission/check-api", - "api-overview/permission/lookup-entity", - "api-overview/permission/lookup-subject", - "api-overview/permission/expand-api", - "api-overview/permission/subject-permission" - ] - }, - { - "type": "category", - "label": "Tenancy Service", - "link": { - "type": "generated-index", - "title": "Tenancy Service", - "slug": "/api-overview/tenancy" - }, - "items": [ - "api-overview/tenancy/create-tenant", - "api-overview/tenancy/delete-tenant" - ] - }, - { - "type": "category", - "label": "Watch Service", - "link": { - "type": "generated-index", - "title": "Watch Service", - "slug": "/api-overview/watch" - }, - "items": [ - "api-overview/watch/watch-changes" - ] - } - ], - "collapsed": true - }, - { - "type": "doc", - "id": "playground", - "label": "Permify Playground" - }, - { - "type": "category", - "label": "Common Use Cases", - "link": { - "type": "doc", - "id": "use-cases" - }, - "items": [ - "use-cases/simple-rbac", - "use-cases/abac", - "use-cases/custom-roles", - "use-cases/multi-tenancy", - "use-cases/rebac" - ], - "collapsed": true - }, - { - "type": "category", - "label": "Reference", - "link": { - "type": "generated-index", - "title": "Reference", - "slug": "/reference" - }, - "items": [ - "reference/glossary", - "reference/configuration", - "reference/contextual-tuples", - "reference/snap-tokens", - "reference/cache", - "reference/tracing" - ], - "collapsed": true - } - ] -} diff --git a/docs/versions.json b/docs/versions.json deleted file mode 100644 index 841e5a8d..00000000 --- a/docs/versions.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - "0.6.x", - "0.5.x", - "0.4.x", - "0.3.x", - "0.2.x" -] diff --git a/docs/yarn.lock b/docs/yarn.lock deleted file mode 100644 index 89b2054f..00000000 --- a/docs/yarn.lock +++ /dev/null @@ -1,9157 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@algolia/autocomplete-core@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.11.0.tgz#9db68f2aa38fe3149507d214082a1926b6b91fac" - integrity sha512-kFtn8XPMdE1QGDxyMTObGgaUpq5lcG2fLVsda6E88MoZZsfYkC8Oua6dwa0b06/GpgEWaliby/7AksUqz05uzw== - dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.11.0" - "@algolia/autocomplete-shared" "1.11.0" - -"@algolia/autocomplete-core@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7" - integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw== - dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.9.3" - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-js@^1.8.2": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.11.0.tgz#1e05480dd14a4068791013a7587884ca962d93db" - integrity sha512-+INNaRwwztxUboAoTnDSAm7INPcyLOu4SANYTZihyQiVRr6ZeJd7/AlifMnonJxrEH7j5RgX7WhjUm5xMN+r8A== - dependencies: - "@algolia/autocomplete-core" "1.11.0" - "@algolia/autocomplete-preset-algolia" "1.11.0" - "@algolia/autocomplete-shared" "1.11.0" - htm "^3.1.1" - preact "^10.13.2" - -"@algolia/autocomplete-plugin-algolia-insights@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.11.0.tgz#edae2ebe5d88afe62ce3efa1723289e5ae376ce3" - integrity sha512-TsJ5vs1jR9IbYDRWnd0tHLF/y54quoSAV7fDbyDdfUdkuI9bVP0bzulxT+POezPT5+6Ya5IJNCrg4DViA3Dm0Q== - dependencies: - "@algolia/autocomplete-shared" "1.11.0" - -"@algolia/autocomplete-plugin-algolia-insights@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587" - integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg== - dependencies: - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-preset-algolia@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.11.0.tgz#4a90d130c6667643809f8c20cba46f6d21dad579" - integrity sha512-a2Tg6TOXN75xIzcx9P7srTNIH8kFjap6IEDHiMYWwa3V4qWNZjbE3e07HxwD3Pme8zp700y3EiYTQMBaYETe6g== - dependencies: - "@algolia/autocomplete-shared" "1.11.0" - -"@algolia/autocomplete-preset-algolia@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da" - integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA== - dependencies: - "@algolia/autocomplete-shared" "1.9.3" - -"@algolia/autocomplete-shared@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.11.0.tgz#2ba14b056e695ad2bbd0eb04f5d6dcbd3584c751" - integrity sha512-ug1HYGQfe8+bvGuVJ3Fbdxn+YvR6MHPD36vQ5kv+5WWnBiW+QTyGk5yiluS9+i81l9wxH34Zl3XN/6MQ68MAgw== - -"@algolia/autocomplete-shared@1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" - integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== - -"@algolia/autocomplete-theme-classic@^1.8.2": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.11.0.tgz#3523744fc244f1979850560a898f5c562664ee08" - integrity sha512-R6k8D/6rwI5EQliVweK+JvX6JAF2cnzJvWhfgwOkdkVHYX3RT9yXR8aE7m6Rxv8wtQpivGsCKeTEJl2jD5goEw== - -"@algolia/cache-browser-local-storage@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz#357318242fc542ffce41d6eb5b4a9b402921b0bb" - integrity sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ== - dependencies: - "@algolia/cache-common" "4.20.0" - -"@algolia/cache-common@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.20.0.tgz#ec52230509fce891091ffd0d890618bcdc2fa20d" - integrity sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ== - -"@algolia/cache-in-memory@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz#5f18d057bd6b3b075022df085c4f83bcca4e3e67" - integrity sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg== - dependencies: - "@algolia/cache-common" "4.20.0" - -"@algolia/client-account@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.20.0.tgz#23ce0b4cffd63100fb7c1aa1c67a4494de5bd645" - integrity sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q== - dependencies: - "@algolia/client-common" "4.20.0" - "@algolia/client-search" "4.20.0" - "@algolia/transporter" "4.20.0" - -"@algolia/client-analytics@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.20.0.tgz#0aa6bef35d3a41ac3991b3f46fcd0bf00d276fa9" - integrity sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug== - dependencies: - "@algolia/client-common" "4.20.0" - "@algolia/client-search" "4.20.0" - "@algolia/requester-common" "4.20.0" - "@algolia/transporter" "4.20.0" - -"@algolia/client-common@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.20.0.tgz#ca60f04466515548651c4371a742fbb8971790ef" - integrity sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ== - dependencies: - "@algolia/requester-common" "4.20.0" - "@algolia/transporter" "4.20.0" - -"@algolia/client-personalization@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.20.0.tgz#ca81308e8ad0db3b27458b78355f124f29657181" - integrity sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ== - dependencies: - "@algolia/client-common" "4.20.0" - "@algolia/requester-common" "4.20.0" - "@algolia/transporter" "4.20.0" - -"@algolia/client-search@4.20.0", "@algolia/client-search@^4.12.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.20.0.tgz#3bcce817ca6caedc835e0eaf6f580e02ee7c3e15" - integrity sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg== - dependencies: - "@algolia/client-common" "4.20.0" - "@algolia/requester-common" "4.20.0" - "@algolia/transporter" "4.20.0" - -"@algolia/events@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" - integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== - -"@algolia/logger-common@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.20.0.tgz#f148ddf67e5d733a06213bebf7117cb8a651ab36" - integrity sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ== - -"@algolia/logger-console@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.20.0.tgz#ac443d27c4e94357f3063e675039cef0aa2de0a7" - integrity sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA== - dependencies: - "@algolia/logger-common" "4.20.0" - -"@algolia/requester-browser-xhr@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz#db16d0bdef018b93b51681d3f1e134aca4f64814" - integrity sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw== - dependencies: - "@algolia/requester-common" "4.20.0" - -"@algolia/requester-common@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.20.0.tgz#65694b2263a8712b4360fef18680528ffd435b5c" - integrity sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng== - -"@algolia/requester-node-http@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz#b52b182b52b0b16dec4070832267d484a6b1d5bb" - integrity sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng== - dependencies: - "@algolia/requester-common" "4.20.0" - -"@algolia/transporter@4.20.0": - version "4.20.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.20.0.tgz#7e5b24333d7cc9a926b2f6a249f87c2889b944a9" - integrity sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg== - dependencies: - "@algolia/cache-common" "4.20.0" - "@algolia/logger-common" "4.20.0" - "@algolia/requester-common" "4.20.0" - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.8.3": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" - integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== - -"@babel/core@7.12.9": - version "7.12.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" - integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.5" - "@babel/parser" "^7.12.7" - "@babel/template" "^7.12.7" - "@babel/traverse" "^7.12.9" - "@babel/types" "^7.12.7" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.18.6", "@babel/core@^7.19.6": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7" - integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.22.15" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.20" - "@babel/helpers" "^7.22.15" - "@babel/parser" "^7.22.16" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.20" - "@babel/types" "^7.22.19" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" - integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== - dependencies: - "@babel/types" "^7.22.15" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" - integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" - integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.15" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" - integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - regexpu-core "^5.3.1" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" - integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - -"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" - integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.20", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e" - integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-plugin-utils@7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" - integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-wrap-function" "^7.22.20" - -"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" - integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.22.15" - "@babel/helper-optimise-call-expression" "^7.22.5" - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== - -"@babel/helper-wrap-function@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" - integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== - dependencies: - "@babel/helper-function-name" "^7.22.5" - "@babel/template" "^7.22.15" - "@babel/types" "^7.22.19" - -"@babel/helpers@^7.12.5", "@babel/helpers@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" - integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": - version "7.22.16" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" - integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== - -"@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" - integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" - integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.15" - -"@babel/plugin-proposal-object-rest-spread@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" - integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-import-attributes@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" - integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-jsx@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" - integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" - integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" - integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-async-generator-functions@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" - integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-transform-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" - integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== - dependencies: - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" - -"@babel/plugin-transform-block-scoped-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" - integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-block-scoping@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz#494eb82b87b5f8b1d8f6f28ea74078ec0a10a841" - integrity sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-class-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" - integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-class-static-block@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" - integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.11" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-transform-classes@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" - integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.9" - "@babel/helper-split-export-declaration" "^7.22.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" - integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/template" "^7.22.5" - -"@babel/plugin-transform-destructuring@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz#e7404ea5bb3387073b9754be654eecb578324694" - integrity sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-dotall-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" - integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-duplicate-keys@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" - integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-dynamic-import@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" - integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" - integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-export-namespace-from@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" - integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29" - integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" - integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== - dependencies: - "@babel/helper-compilation-targets" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-json-strings@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" - integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-transform-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" - integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-logical-assignment-operators@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" - integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" - integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-modules-amd@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" - integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-modules-commonjs@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz#b11810117ed4ee7691b29bd29fd9f3f98276034f" - integrity sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg== - dependencies: - "@babel/helper-module-transforms" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - -"@babel/plugin-transform-modules-systemjs@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" - integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== - dependencies: - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - -"@babel/plugin-transform-modules-umd@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" - integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== - dependencies: - "@babel/helper-module-transforms" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" - integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-new-target@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" - integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" - integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-transform-numeric-separator@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" - integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-transform-object-rest-spread@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" - integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.15" - -"@babel/plugin-transform-object-super@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" - integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" - -"@babel/plugin-transform-optional-catch-binding@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" - integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-transform-optional-chaining@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz#d7a5996c2f7ca4ad2ad16dbb74444e5c4385b1ba" - integrity sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" - integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-private-methods@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" - integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-private-property-in-object@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" - integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.11" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-transform-property-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" - integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-constant-elements@^7.18.12": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" - integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-display-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" - integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-react-jsx-development@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" - integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== - dependencies: - "@babel/plugin-transform-react-jsx" "^7.22.5" - -"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6" - integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/types" "^7.22.15" - -"@babel/plugin-transform-react-pure-annotations@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" - integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-regenerator@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" - integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - regenerator-transform "^0.15.2" - -"@babel/plugin-transform-reserved-words@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" - integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-runtime@^7.18.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9" - integrity sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g== - dependencies: - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.5" - babel-plugin-polyfill-corejs3 "^0.8.3" - babel-plugin-polyfill-regenerator "^0.5.2" - semver "^6.3.1" - -"@babel/plugin-transform-shorthand-properties@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" - integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" - integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - -"@babel/plugin-transform-sticky-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" - integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-template-literals@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" - integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-typeof-symbol@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" - integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-typescript@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127" - integrity sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-typescript" "^7.22.5" - -"@babel/plugin-transform-unicode-escapes@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" - integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-property-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" - integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" - integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-transform-unicode-sets-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" - integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" - integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg== - dependencies: - "@babel/compat-data" "^7.22.20" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.15" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.22.5" - "@babel/plugin-syntax-import-attributes" "^7.22.5" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.15" - "@babel/plugin-transform-async-to-generator" "^7.22.5" - "@babel/plugin-transform-block-scoped-functions" "^7.22.5" - "@babel/plugin-transform-block-scoping" "^7.22.15" - "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.11" - "@babel/plugin-transform-classes" "^7.22.15" - "@babel/plugin-transform-computed-properties" "^7.22.5" - "@babel/plugin-transform-destructuring" "^7.22.15" - "@babel/plugin-transform-dotall-regex" "^7.22.5" - "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.11" - "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.11" - "@babel/plugin-transform-for-of" "^7.22.15" - "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.11" - "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" - "@babel/plugin-transform-member-expression-literals" "^7.22.5" - "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.15" - "@babel/plugin-transform-modules-systemjs" "^7.22.11" - "@babel/plugin-transform-modules-umd" "^7.22.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" - "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" - "@babel/plugin-transform-numeric-separator" "^7.22.11" - "@babel/plugin-transform-object-rest-spread" "^7.22.15" - "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.11" - "@babel/plugin-transform-optional-chaining" "^7.22.15" - "@babel/plugin-transform-parameters" "^7.22.15" - "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.11" - "@babel/plugin-transform-property-literals" "^7.22.5" - "@babel/plugin-transform-regenerator" "^7.22.10" - "@babel/plugin-transform-reserved-words" "^7.22.5" - "@babel/plugin-transform-shorthand-properties" "^7.22.5" - "@babel/plugin-transform-spread" "^7.22.5" - "@babel/plugin-transform-sticky-regex" "^7.22.5" - "@babel/plugin-transform-template-literals" "^7.22.5" - "@babel/plugin-transform-typeof-symbol" "^7.22.5" - "@babel/plugin-transform-unicode-escapes" "^7.22.10" - "@babel/plugin-transform-unicode-property-regex" "^7.22.5" - "@babel/plugin-transform-unicode-regex" "^7.22.5" - "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" - "@babel/preset-modules" "0.1.6-no-external-plugins" - "@babel/types" "^7.22.19" - babel-plugin-polyfill-corejs2 "^0.4.5" - babel-plugin-polyfill-corejs3 "^0.8.3" - babel-plugin-polyfill-regenerator "^0.5.2" - core-js-compat "^3.31.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/preset-react@^7.18.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.15.tgz#9a776892b648e13cc8ca2edf5ed1264eea6b6afc" - integrity sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.15" - "@babel/plugin-transform-react-display-name" "^7.22.5" - "@babel/plugin-transform-react-jsx" "^7.22.15" - "@babel/plugin-transform-react-jsx-development" "^7.22.5" - "@babel/plugin-transform-react-pure-annotations" "^7.22.5" - -"@babel/preset-typescript@^7.18.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz#43db30516fae1d417d748105a0bc95f637239d48" - integrity sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.15" - "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.15" - "@babel/plugin-transform-typescript" "^7.22.15" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime-corejs3@^7.18.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.22.15.tgz#7aeb9460598a997b0fe74982a5b02fb9e5d264d9" - integrity sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ== - dependencies: - core-js-pure "^3.30.2" - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" - integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.12.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20", "@babel/traverse@^7.4.5": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.12.7", "@babel/types@^7.20.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.19" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" - integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.19" - to-fast-properties "^2.0.0" - -"@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@cmfcmf/docusaurus-search-local@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@cmfcmf/docusaurus-search-local/-/docusaurus-search-local-1.1.0.tgz#3db5d7d6e05985cc3b06cec10436385c53033c69" - integrity sha512-0IVb/aA0IK8ZlktuxmgXmluXfcSpo6Vdd2nG21y1aOH9nVYnPP231Dn0H8Ng9Qf9ronQQCDWHnuWpYOr9rUrEQ== - dependencies: - "@algolia/autocomplete-js" "^1.8.2" - "@algolia/autocomplete-theme-classic" "^1.8.2" - "@algolia/client-search" "^4.12.0" - algoliasearch "^4.12.0" - cheerio "^1.0.0-rc.9" - clsx "^1.1.1" - lunr-languages "^1.4.0" - mark.js "^8.11.1" - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@0.5.7": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@docsearch/css@3.5.2": - version "3.5.2" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac" - integrity sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA== - -"@docsearch/react@^3.1.1": - version "3.5.2" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.5.2.tgz#2e6bbee00eb67333b64906352734da6aef1232b9" - integrity sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng== - dependencies: - "@algolia/autocomplete-core" "1.9.3" - "@algolia/autocomplete-preset-algolia" "1.9.3" - "@docsearch/css" "3.5.2" - algoliasearch "^4.19.1" - -"@docusaurus/core@2.4.3", "@docusaurus/core@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.3.tgz#d86624901386fd8164ce4bff9cc7f16fde57f523" - integrity sha512-dWH5P7cgeNSIg9ufReX6gaCl/TmrGKD38Orbwuz05WPhAQtFXHd5B8Qym1TiXfvUNvwoYKkAJOJuGe8ou0Z7PA== - dependencies: - "@babel/core" "^7.18.6" - "@babel/generator" "^7.18.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.18.6" - "@babel/preset-env" "^7.18.6" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@babel/runtime" "^7.18.6" - "@babel/runtime-corejs3" "^7.18.6" - "@babel/traverse" "^7.18.8" - "@docusaurus/cssnano-preset" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - "@slorber/static-site-generator-webpack-plugin" "^4.0.7" - "@svgr/webpack" "^6.2.1" - autoprefixer "^10.4.7" - babel-loader "^8.2.5" - babel-plugin-dynamic-import-node "^2.3.3" - boxen "^6.2.1" - chalk "^4.1.2" - chokidar "^3.5.3" - clean-css "^5.3.0" - cli-table3 "^0.6.2" - combine-promises "^1.1.0" - commander "^5.1.0" - copy-webpack-plugin "^11.0.0" - core-js "^3.23.3" - css-loader "^6.7.1" - css-minimizer-webpack-plugin "^4.0.0" - cssnano "^5.1.12" - del "^6.1.1" - detect-port "^1.3.0" - escape-html "^1.0.3" - eta "^2.0.0" - file-loader "^6.2.0" - fs-extra "^10.1.0" - html-minifier-terser "^6.1.0" - html-tags "^3.2.0" - html-webpack-plugin "^5.5.0" - import-fresh "^3.3.0" - leven "^3.1.0" - lodash "^4.17.21" - mini-css-extract-plugin "^2.6.1" - postcss "^8.4.14" - postcss-loader "^7.0.0" - prompts "^2.4.2" - react-dev-utils "^12.0.1" - react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - react-loadable-ssr-addon-v5-slorber "^1.0.1" - react-router "^5.3.3" - react-router-config "^5.1.1" - react-router-dom "^5.3.3" - rtl-detect "^1.0.4" - semver "^7.3.7" - serve-handler "^6.1.3" - shelljs "^0.8.5" - terser-webpack-plugin "^5.3.3" - tslib "^2.4.0" - update-notifier "^5.1.0" - url-loader "^4.1.1" - wait-on "^6.0.1" - webpack "^5.73.0" - webpack-bundle-analyzer "^4.5.0" - webpack-dev-server "^4.9.3" - webpack-merge "^5.8.0" - webpackbar "^5.0.2" - -"@docusaurus/cssnano-preset@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.3.tgz#1d7e833c41ce240fcc2812a2ac27f7b862f32de0" - integrity sha512-ZvGSRCi7z9wLnZrXNPG6DmVPHdKGd8dIn9pYbEOFiYihfv4uDR3UtxogmKf+rT8ZlKFf5Lqne8E8nt08zNM8CA== - dependencies: - cssnano-preset-advanced "^5.3.8" - postcss "^8.4.14" - postcss-sort-media-queries "^4.2.1" - tslib "^2.4.0" - -"@docusaurus/logger@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.3.tgz#518bbc965fb4ebe8f1d0b14e5f4161607552d34c" - integrity sha512-Zxws7r3yLufk9xM1zq9ged0YHs65mlRmtsobnFkdZTxWXdTYlWWLWdKyNKAsVC+D7zg+pv2fGbyabdOnyZOM3w== - dependencies: - chalk "^4.1.2" - tslib "^2.4.0" - -"@docusaurus/mdx-loader@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.3.tgz#e8ff37f30a060eaa97b8121c135f74cb531a4a3e" - integrity sha512-b1+fDnWtl3GiqkL0BRjYtc94FZrcDDBV1j8446+4tptB9BAOlePwG2p/pK6vGvfL53lkOsszXMghr2g67M0vCw== - dependencies: - "@babel/parser" "^7.18.8" - "@babel/traverse" "^7.18.8" - "@docusaurus/logger" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@mdx-js/mdx" "^1.6.22" - escape-html "^1.0.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - image-size "^1.0.1" - mdast-util-to-string "^2.0.0" - remark-emoji "^2.2.0" - stringify-object "^3.3.0" - tslib "^2.4.0" - unified "^9.2.2" - unist-util-visit "^2.0.3" - url-loader "^4.1.1" - webpack "^5.73.0" - -"@docusaurus/module-type-aliases@2.4.3", "@docusaurus/module-type-aliases@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.3.tgz#d08ef67e4151e02f352a2836bcf9ecde3b9c56ac" - integrity sha512-cwkBkt1UCiduuvEAo7XZY01dJfRn7UR/75mBgOdb1hKknhrabJZ8YH+7savd/y9kLExPyrhe0QwdS9GuzsRRIA== - dependencies: - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/types" "2.4.3" - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router-config" "*" - "@types/react-router-dom" "*" - react-helmet-async "*" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - -"@docusaurus/plugin-client-redirects@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.4.3.tgz#0da7e6facadbca3bd7cb8d0453f21bea7f4f1721" - integrity sha512-iCwc/zH8X6eNtLYdyUJFY6+GbsbRgMgvAC/TmSmCYTmwnoN5Y1Bc5OwUkdtoch0XKizotJMRAmGIAhP8sAetdQ== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - eta "^2.0.0" - fs-extra "^10.1.0" - lodash "^4.17.21" - tslib "^2.4.0" - -"@docusaurus/plugin-content-blog@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.3.tgz#6473b974acab98e967414d8bbb0d37e0cedcea14" - integrity sha512-PVhypqaA0t98zVDpOeTqWUTvRqCEjJubtfFUQ7zJNYdbYTbS/E/ytq6zbLVsN/dImvemtO/5JQgjLxsh8XLo8Q== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - cheerio "^1.0.0-rc.12" - feed "^4.2.2" - fs-extra "^10.1.0" - lodash "^4.17.21" - reading-time "^1.5.0" - tslib "^2.4.0" - unist-util-visit "^2.0.3" - utility-types "^3.10.0" - webpack "^5.73.0" - -"@docusaurus/plugin-content-docs@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.3.tgz#aa224c0512351e81807adf778ca59fd9cd136973" - integrity sha512-N7Po2LSH6UejQhzTCsvuX5NOzlC+HiXOVvofnEPj0WhMu1etpLEXE6a4aTxrtg95lQ5kf0xUIdjX9sh3d3G76A== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/module-type-aliases" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - "@types/react-router-config" "^5.0.6" - combine-promises "^1.1.0" - fs-extra "^10.1.0" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - lodash "^4.17.21" - tslib "^2.4.0" - utility-types "^3.10.0" - webpack "^5.73.0" - -"@docusaurus/plugin-content-pages@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.3.tgz#7f285e718b53da8c8d0101e70840c75b9c0a1ac0" - integrity sha512-txtDVz7y3zGk67q0HjG0gRttVPodkHqE0bpJ+7dOaTH40CQFLSh7+aBeGnPOTl+oCPG+hxkim4SndqPqXjQ8Bg== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - fs-extra "^10.1.0" - tslib "^2.4.0" - webpack "^5.73.0" - -"@docusaurus/plugin-debug@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.3.tgz#2f90eb0c9286a9f225444e3a88315676fe02c245" - integrity sha512-LkUbuq3zCmINlFb+gAd4ZvYr+bPAzMC0hwND4F7V9bZ852dCX8YoWyovVUBKq4er1XsOwSQaHmNGtObtn8Av8Q== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - fs-extra "^10.1.0" - react-json-view "^1.21.3" - tslib "^2.4.0" - -"@docusaurus/plugin-google-analytics@2.4.3", "@docusaurus/plugin-google-analytics@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.3.tgz#0d19993136ade6f7a7741251b4f617400d92ab45" - integrity sha512-KzBV3k8lDkWOhg/oYGxlK5o9bOwX7KpPc/FTWoB+SfKhlHfhq7qcQdMi1elAaVEIop8tgK6gD1E58Q+XC6otSQ== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - tslib "^2.4.0" - -"@docusaurus/plugin-google-gtag@2.4.3", "@docusaurus/plugin-google-gtag@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.3.tgz#e1a80b0696771b488562e5b60eff21c9932d9e1c" - integrity sha512-5FMg0rT7sDy4i9AGsvJC71MQrqQZwgLNdDetLEGDHLfSHLvJhQbTCUGbGXknUgWXQJckcV/AILYeJy+HhxeIFA== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - tslib "^2.4.0" - -"@docusaurus/plugin-google-tag-manager@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.3.tgz#e41fbf79b0ffc2de1cc4013eb77798cff0ad98e3" - integrity sha512-1jTzp71yDGuQiX9Bi0pVp3alArV0LSnHXempvQTxwCGAEzUWWaBg4d8pocAlTpbP9aULQQqhgzrs8hgTRPOM0A== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - tslib "^2.4.0" - -"@docusaurus/plugin-sitemap@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.3.tgz#1b3930900a8f89670ce7e8f83fb4730cd3298c32" - integrity sha512-LRQYrK1oH1rNfr4YvWBmRzTL0LN9UAPxBbghgeFRBm5yloF6P+zv1tm2pe2hQTX/QP5bSKdnajCvfnScgKXMZQ== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - fs-extra "^10.1.0" - sitemap "^7.1.1" - tslib "^2.4.0" - -"@docusaurus/preset-classic@^2.4.0": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.3.tgz#074c57ebf29fa43d23bd1c8ce691226f542bc262" - integrity sha512-tRyMliepY11Ym6hB1rAFSNGwQDpmszvWYJvlK1E+md4SW8i6ylNHtpZjaYFff9Mdk3i/Pg8ItQq9P0daOJAvQw== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/plugin-content-blog" "2.4.3" - "@docusaurus/plugin-content-docs" "2.4.3" - "@docusaurus/plugin-content-pages" "2.4.3" - "@docusaurus/plugin-debug" "2.4.3" - "@docusaurus/plugin-google-analytics" "2.4.3" - "@docusaurus/plugin-google-gtag" "2.4.3" - "@docusaurus/plugin-google-tag-manager" "2.4.3" - "@docusaurus/plugin-sitemap" "2.4.3" - "@docusaurus/theme-classic" "2.4.3" - "@docusaurus/theme-common" "2.4.3" - "@docusaurus/theme-search-algolia" "2.4.3" - "@docusaurus/types" "2.4.3" - -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" - integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== - dependencies: - "@types/react" "*" - prop-types "^15.6.2" - -"@docusaurus/theme-classic@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.3.tgz#29360f2eb03a0e1686eb19668633ef313970ee8f" - integrity sha512-QKRAJPSGPfDY2yCiPMIVyr+MqwZCIV2lxNzqbyUW0YkrlmdzzP3WuQJPMGLCjWgQp/5c9kpWMvMxjhpZx1R32Q== - dependencies: - "@docusaurus/core" "2.4.3" - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/module-type-aliases" "2.4.3" - "@docusaurus/plugin-content-blog" "2.4.3" - "@docusaurus/plugin-content-docs" "2.4.3" - "@docusaurus/plugin-content-pages" "2.4.3" - "@docusaurus/theme-common" "2.4.3" - "@docusaurus/theme-translations" "2.4.3" - "@docusaurus/types" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - "@mdx-js/react" "^1.6.22" - clsx "^1.2.1" - copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.43" - lodash "^4.17.21" - nprogress "^0.2.0" - postcss "^8.4.14" - prism-react-renderer "^1.3.5" - prismjs "^1.28.0" - react-router-dom "^5.3.3" - rtlcss "^3.5.0" - tslib "^2.4.0" - utility-types "^3.10.0" - -"@docusaurus/theme-common@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.3.tgz#bb31d70b6b67d0bdef9baa343192dcec49946a2e" - integrity sha512-7KaDJBXKBVGXw5WOVt84FtN8czGWhM0lbyWEZXGp8AFfL6sZQfRTluFp4QriR97qwzSyOfQb+nzcDZZU4tezUw== - dependencies: - "@docusaurus/mdx-loader" "2.4.3" - "@docusaurus/module-type-aliases" "2.4.3" - "@docusaurus/plugin-content-blog" "2.4.3" - "@docusaurus/plugin-content-docs" "2.4.3" - "@docusaurus/plugin-content-pages" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-common" "2.4.3" - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router-config" "*" - clsx "^1.2.1" - parse-numeric-range "^1.3.0" - prism-react-renderer "^1.3.5" - tslib "^2.4.0" - use-sync-external-store "^1.2.0" - utility-types "^3.10.0" - -"@docusaurus/theme-search-algolia@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz#32d4cbefc3deba4112068fbdb0bde11ac51ece53" - integrity sha512-jziq4f6YVUB5hZOB85ELATwnxBz/RmSLD3ksGQOLDPKVzat4pmI8tddNWtriPpxR04BNT+ZfpPUMFkNFetSW1Q== - dependencies: - "@docsearch/react" "^3.1.1" - "@docusaurus/core" "2.4.3" - "@docusaurus/logger" "2.4.3" - "@docusaurus/plugin-content-docs" "2.4.3" - "@docusaurus/theme-common" "2.4.3" - "@docusaurus/theme-translations" "2.4.3" - "@docusaurus/utils" "2.4.3" - "@docusaurus/utils-validation" "2.4.3" - algoliasearch "^4.13.1" - algoliasearch-helper "^3.10.0" - clsx "^1.2.1" - eta "^2.0.0" - fs-extra "^10.1.0" - lodash "^4.17.21" - tslib "^2.4.0" - utility-types "^3.10.0" - -"@docusaurus/theme-translations@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz#91ac73fc49b8c652b7a54e88b679af57d6ac6102" - integrity sha512-H4D+lbZbjbKNS/Zw1Lel64PioUAIT3cLYYJLUf3KkuO/oc9e0QCVhIYVtUI2SfBCF2NNdlyhBDQEEMygsCedIg== - dependencies: - fs-extra "^10.1.0" - tslib "^2.4.0" - -"@docusaurus/types@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.3.tgz#4aead281ca09f721b3c0a9b926818450cfa3db31" - integrity sha512-W6zNLGQqfrp/EoPD0bhb9n7OobP+RHpmvVzpA+Z/IuU3Q63njJM24hmT0GYboovWcDtFmnIJC9wcyx4RVPQscw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - commander "^5.1.0" - joi "^17.6.0" - react-helmet-async "^1.3.0" - utility-types "^3.10.0" - webpack "^5.73.0" - webpack-merge "^5.8.0" - -"@docusaurus/utils-common@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.3.tgz#30656c39ef1ce7e002af7ba39ea08330f58efcfb" - integrity sha512-/jascp4GbLQCPVmcGkPzEQjNaAk3ADVfMtudk49Ggb+131B1WDD6HqlSmDf8MxGdy7Dja2gc+StHf01kiWoTDQ== - dependencies: - tslib "^2.4.0" - -"@docusaurus/utils-validation@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.3.tgz#8122c394feef3e96c73f6433987837ec206a63fb" - integrity sha512-G2+Vt3WR5E/9drAobP+hhZQMaswRwDlp6qOMi7o7ZypB+VO7N//DZWhZEwhcRGepMDJGQEwtPv7UxtYwPL9PBw== - dependencies: - "@docusaurus/logger" "2.4.3" - "@docusaurus/utils" "2.4.3" - joi "^17.6.0" - js-yaml "^4.1.0" - tslib "^2.4.0" - -"@docusaurus/utils@2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.3.tgz#52b000d989380a2125831b84e3a7327bef471e89" - integrity sha512-fKcXsjrD86Smxv8Pt0TBFqYieZZCPh4cbf9oszUq/AMhZn3ujwpKaVYZACPX8mmjtYx0JOgNx52CREBfiGQB4A== - dependencies: - "@docusaurus/logger" "2.4.3" - "@svgr/webpack" "^6.2.1" - escape-string-regexp "^4.0.0" - file-loader "^6.2.0" - fs-extra "^10.1.0" - github-slugger "^1.4.0" - globby "^11.1.0" - gray-matter "^4.0.3" - js-yaml "^4.1.0" - lodash "^4.17.21" - micromatch "^4.0.5" - resolve-pathname "^3.0.0" - shelljs "^0.8.5" - tslib "^2.4.0" - url-loader "^4.1.1" - webpack "^5.73.0" - -"@emotion/babel-plugin@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" - integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/serialize" "^1.1.2" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.2.0" - -"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== - dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/sheet" "^1.2.2" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - stylis "4.2.0" - -"@emotion/hash@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" - integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== - -"@emotion/is-prop-valid@^1.1.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== - dependencies: - "@emotion/memoize" "^0.8.1" - -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== - -"@emotion/react@^11.8.1": - version "11.11.1" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" - integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" - integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== - dependencies: - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/unitless" "^0.8.1" - "@emotion/utils" "^1.2.1" - csstype "^3.0.2" - -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== - -"@emotion/stylis@^0.8.4": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== - -"@emotion/unitless@^0.7.4": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@emotion/unitless@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== - -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== - -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== - -"@floating-ui/core@^1.4.2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" - integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== - dependencies: - "@floating-ui/utils" "^0.1.3" - -"@floating-ui/dom@^1.0.1": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" - integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== - dependencies: - "@floating-ui/core" "^1.4.2" - "@floating-ui/utils" "^0.1.3" - -"@floating-ui/utils@^0.1.3": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" - integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== - -"@glidejs/glide@^3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@glidejs/glide/-/glide-3.6.0.tgz#d13fd57de8473a2d86022ac601b5cbc98c9dcfe5" - integrity sha512-47Aa+JmYjY4xTFpTtYCwrqirmI1arnp1UZETwtWpbTPisXUAuxrdJxKJLH8KHFWMsSrLi9+AcfyfzDIuO75rEA== - -"@hapi/hoek@^9.0.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" - integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.19" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" - integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@mdx-js/mdx@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" - integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== - dependencies: - "@babel/core" "7.12.9" - "@babel/plugin-syntax-jsx" "7.12.1" - "@babel/plugin-syntax-object-rest-spread" "7.8.3" - "@mdx-js/util" "1.6.22" - babel-plugin-apply-mdx-type-prop "1.6.22" - babel-plugin-extract-import-names "1.6.22" - camelcase-css "2.0.1" - detab "2.0.4" - hast-util-raw "6.0.1" - lodash.uniq "4.5.0" - mdast-util-to-hast "10.0.1" - remark-footnotes "2.0.0" - remark-mdx "1.6.22" - remark-parse "8.0.3" - remark-squeeze-paragraphs "4.0.0" - style-to-object "0.3.0" - unified "9.2.0" - unist-builder "2.0.3" - unist-util-visit "2.0.3" - -"@mdx-js/react@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" - integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== - -"@mdx-js/util@1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" - integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@polka/url@^1.0.0-next.20": - version "1.0.0-next.23" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c" - integrity sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg== - -"@rc-component/portal@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71" - integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg== - dependencies: - "@babel/runtime" "^7.18.0" - classnames "^2.3.2" - rc-util "^5.24.4" - -"@reach/accordion@^0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@reach/accordion/-/accordion-0.18.0.tgz#7bd76caaa8394b2e67cbf6de7892b02d9cbd7eb1" - integrity sha512-jOQ1jj0Oc4yYu58R7WzSCkDSNO8kpy8ovQTjKk0Bo3pbmmkX7QQq292TTl8dMz0RXGe0pmLViubRsD0GkrBFIA== - dependencies: - "@reach/auto-id" "0.18.0" - "@reach/descendants" "0.18.0" - "@reach/polymorphic" "0.18.0" - "@reach/utils" "0.18.0" - -"@reach/auto-id@0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.18.0.tgz#4b97085cd1cf1360a9bedc6e9c78e97824014f0d" - integrity sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg== - dependencies: - "@reach/utils" "0.18.0" - -"@reach/descendants@0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.18.0.tgz#16fe52a5154da262994b0b8768baff4f670922d1" - integrity sha512-GXUxnM6CfrX5URdnipPIl3Tlc6geuz4xb4n61y4tVWXQX1278Ra9Jz9DMRN8x4wheHAysvrYwnR/SzAlxQzwtA== - dependencies: - "@reach/utils" "0.18.0" - -"@reach/polymorphic@0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@reach/polymorphic/-/polymorphic-0.18.0.tgz#2fe42007a774e06cdbc8e13e0d46f2dc30f2f1ed" - integrity sha512-N9iAjdMbE//6rryZZxAPLRorzDcGBnluf7YQij6XDLiMtfCj1noa7KyLpEc/5XCIB/EwhX3zCluFAwloBKdblA== - -"@reach/utils@0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.18.0.tgz#4f3cebe093dd436eeaff633809bf0f68f4f9d2ee" - integrity sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A== - -"@redq/reuse-modal@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@redq/reuse-modal/-/reuse-modal-2.0.0.tgz#96d829e04a0d03ea7f1b2e7d0063850f92112df2" - integrity sha512-pvnGDDsVvyadQy9KALK20FHZV0mjjlAqK2pEF+ZX7wdZ6L3/b8cocESfU3aNAClGQUVr65RRo83nQLjR3H4pcA== - dependencies: - react-rnd "^9.0.4" - react-spring "^8.0.18" - -"@rollup/plugin-babel@^6.0.3": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz#07ccde15de278c581673034ad6accdb4a153dfeb" - integrity sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@rollup/pluginutils" "^5.0.1" - -"@rollup/pluginutils@^5.0.1": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.4.tgz#74f808f9053d33bafec0cc98e7b835c9667d32ba" - integrity sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g== - dependencies: - "@types/estree" "^1.0.0" - estree-walker "^2.0.2" - picomatch "^2.3.1" - -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@slorber/static-site-generator-webpack-plugin@^4.0.7": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3" - integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA== - dependencies: - eval "^0.1.8" - p-map "^4.0.0" - webpack-sources "^3.2.2" - -"@styled-system/background@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/background/-/background-5.1.2.tgz#75c63d06b497ab372b70186c0bf608d62847a2ba" - integrity sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/border@^5.1.5": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@styled-system/border/-/border-5.1.5.tgz#0493d4332d2b59b74bb0d57d08c73eb555761ba6" - integrity sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/color@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/color/-/color-5.1.2.tgz#b8d6b4af481faabe4abca1a60f8daa4ccc2d9f43" - integrity sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/core@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/core/-/core-5.1.2.tgz#b8b7b86455d5a0514f071c4fa8e434b987f6a772" - integrity sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw== - dependencies: - object-assign "^4.1.1" - -"@styled-system/css@^5.1.5": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@styled-system/css/-/css-5.1.5.tgz#0460d5f3ff962fa649ea128ef58d9584f403bbbc" - integrity sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A== - -"@styled-system/flexbox@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/flexbox/-/flexbox-5.1.2.tgz#077090f43f61c3852df63da24e4108087a8beecf" - integrity sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/grid@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/grid/-/grid-5.1.2.tgz#7165049877732900b99cd00759679fbe45c6c573" - integrity sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/layout@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/layout/-/layout-5.1.2.tgz#12d73e79887e10062f4dbbbc2067462eace42339" - integrity sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/position@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/position/-/position-5.1.2.tgz#56961266566836f57a24d8e8e33ce0c1adb59dd3" - integrity sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/shadow@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/shadow/-/shadow-5.1.2.tgz#beddab28d7de03cd0177a87ac4ed3b3b6d9831fd" - integrity sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/space@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/space/-/space-5.1.2.tgz#38925d2fa29a41c0eb20e65b7c3efb6e8efce953" - integrity sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/theme-get@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/theme-get/-/theme-get-5.1.2.tgz#b40a00a44da63b7a6ed85f73f737c4defecd6049" - integrity sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/typography@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@styled-system/typography/-/typography-5.1.2.tgz#65fb791c67d50cd2900d234583eaacdca8c134f7" - integrity sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg== - dependencies: - "@styled-system/core" "^5.1.2" - -"@styled-system/variant@^5.1.5": - version "5.1.5" - resolved "https://registry.yarnpkg.com/@styled-system/variant/-/variant-5.1.5.tgz#8446d8aad06af3a4c723d717841df2dbe4ddeafd" - integrity sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw== - dependencies: - "@styled-system/core" "^5.1.2" - "@styled-system/css" "^5.1.5" - -"@svgr/babel-plugin-add-jsx-attribute@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" - integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== - -"@svgr/babel-plugin-remove-jsx-attribute@*": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" - integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== - -"@svgr/babel-plugin-remove-jsx-empty-expression@*": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" - integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== - -"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" - integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== - -"@svgr/babel-plugin-svg-dynamic-title@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" - integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== - -"@svgr/babel-plugin-svg-em-dimensions@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" - integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== - -"@svgr/babel-plugin-transform-react-native-svg@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" - integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== - -"@svgr/babel-plugin-transform-svg-component@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" - integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== - -"@svgr/babel-preset@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" - integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1" - "@svgr/babel-plugin-remove-jsx-attribute" "*" - "@svgr/babel-plugin-remove-jsx-empty-expression" "*" - "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1" - "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1" - "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1" - "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" - "@svgr/babel-plugin-transform-svg-component" "^6.5.1" - -"@svgr/core@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" - integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - camelcase "^6.2.0" - cosmiconfig "^7.0.1" - -"@svgr/hast-util-to-babel-ast@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" - integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw== - dependencies: - "@babel/types" "^7.20.0" - entities "^4.4.0" - -"@svgr/plugin-jsx@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" - integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw== - dependencies: - "@babel/core" "^7.19.6" - "@svgr/babel-preset" "^6.5.1" - "@svgr/hast-util-to-babel-ast" "^6.5.1" - svg-parser "^2.0.4" - -"@svgr/plugin-svgo@^6.5.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" - integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ== - dependencies: - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - svgo "^2.8.0" - -"@svgr/webpack@^6.2.1": - version "6.5.1" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8" - integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA== - dependencies: - "@babel/core" "^7.19.6" - "@babel/plugin-transform-react-constant-elements" "^7.18.12" - "@babel/preset-env" "^7.19.4" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@svgr/core" "^6.5.1" - "@svgr/plugin-jsx" "^6.5.1" - "@svgr/plugin-svgo" "^6.5.1" - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@tsconfig/docusaurus@^1.0.4": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.7.tgz#a3ee3c8109b3fec091e3d61a61834e563aeee3c3" - integrity sha512-ffTXxGIP/IRMCjuzHd6M4/HdIrw1bMfC7Bv8hMkTadnePkpe0lG0oDSdbRpSDZb2rQMAgpbWiR10BvxvNYwYrg== - -"@types/body-parser@*": - version "1.19.3" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.3.tgz#fb558014374f7d9e56c8f34bab2042a3a07d25cd" - integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.11" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.11.tgz#fbaa46a1529ea5c5e46cde36e4be6a880db55b84" - integrity sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz#6e5e3602d93bda975cebc3449e1a318340af9e20" - integrity sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.36" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" - integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a" - integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.36" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz#baa9022119bdc05a4adfe740ffc97b5f9360e545" - integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/hast@^2.0.0": - version "2.3.6" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.6.tgz#bb8b05602112a26d22868acb70c4b20984ec7086" - integrity sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg== - dependencies: - "@types/unist" "^2" - -"@types/history@^4.7.11": - version "4.7.11" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" - integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== - -"@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== - -"@types/http-errors@*": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" - integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== - -"@types/http-proxy@^1.17.8": - version "1.17.12" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.12.tgz#86e849e9eeae0362548803c37a0a1afc616bd96b" - integrity sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - -"@types/mdast@^3.0.0": - version "3.0.12" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.12.tgz#beeb511b977c875a5b0cc92eab6fcac2f0895514" - integrity sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg== - dependencies: - "@types/unist" "^2" - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/node@*": - version "20.6.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.3.tgz#5b763b321cd3b80f6b8dde7a37e1a77ff9358dd9" - integrity sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA== - -"@types/node@^17.0.5": - version "17.0.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" - integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/parse5@^5.0.0": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" - integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== - -"@types/prop-types@*": - version "15.7.6" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" - integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== - -"@types/qs@*": - version "6.9.8" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" - integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/react-router-config@*", "@types/react-router-config@^5.0.6": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.7.tgz#36207a3fe08b271abee62b26993ee932d13cbb02" - integrity sha512-pFFVXUIydHlcJP6wJm7sDii5mD/bCmmAY0wQzq+M+uX7bqS95AQqHZWP1iNMKrWVQSuHIzj5qi9BvrtLX2/T4w== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "^5.1.0" - -"@types/react-router-dom@*": - version "5.3.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" - integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*", "@types/react-router@^5.1.0": - version "5.1.20" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" - integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - -"@types/react-transition-group@^4.4.0": - version "4.4.6" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" - integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== - dependencies: - "@types/react" "*" - -"@types/react@*": - version "18.2.22" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" - integrity sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/sax@^1.2.1": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.4.tgz#8221affa7f4f3cb21abd22f244cfabfa63e6a69e" - integrity sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw== - dependencies: - "@types/node" "*" - -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - -"@types/send@*": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" - integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" - integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== - dependencies: - "@types/node" "*" - -"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c" - integrity sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw== - -"@types/ws@^8.5.5": - version "8.5.5" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" - integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== - dependencies: - "@types/node" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - -"@webassemblyjs/floating-point-hex-parser@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" - integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== - -"@webassemblyjs/helper-api-error@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" - integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== - -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== - -"@webassemblyjs/helper-numbers@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" - integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" - integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== - -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - -"@webassemblyjs/ieee754@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" - integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" - integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" - integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-api-error" "1.11.6" - "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/ieee754" "1.11.6" - "@webassemblyjs/leb128" "1.11.6" - "@webassemblyjs/utf8" "1.11.6" - -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== - dependencies: - "@webassemblyjs/ast" "1.11.6" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - -acorn-walk@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^5.2.1: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -address@^1.0.1, address@^1.1.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -algoliasearch-helper@^3.10.0: - version "3.14.2" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.14.2.tgz#c34cfe6cefcfecd65c60bcb8bf9b68134472d28c" - integrity sha512-FjDSrjvQvJT/SKMW74nPgFpsoPUwZCzGbCqbp8HhBFfSk/OvNFxzCaCmuO0p7AWeLy1gD+muFwQEkBwcl5H4pg== - dependencies: - "@algolia/events" "^4.0.1" - -algoliasearch@^4.12.0, algoliasearch@^4.13.1, algoliasearch@^4.19.1: - version "4.20.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.20.0.tgz#700c2cb66e14f8a288460036c7b2a554d0d93cf4" - integrity sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g== - dependencies: - "@algolia/cache-browser-local-storage" "4.20.0" - "@algolia/cache-common" "4.20.0" - "@algolia/cache-in-memory" "4.20.0" - "@algolia/client-account" "4.20.0" - "@algolia/client-analytics" "4.20.0" - "@algolia/client-common" "4.20.0" - "@algolia/client-personalization" "4.20.0" - "@algolia/client-search" "4.20.0" - "@algolia/logger-common" "4.20.0" - "@algolia/logger-console" "4.20.0" - "@algolia/requester-browser-xhr" "4.20.0" - "@algolia/requester-common" "4.20.0" - "@algolia/requester-node-http" "4.20.0" - "@algolia/transporter" "4.20.0" - -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== - -animate.css@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075" - integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ== - -ansi-align@^3.0.0, ansi-align@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== - -ast-types@0.9.6: - version "0.9.6" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" - integrity sha512-qEdtR2UH78yyHX/AUNfXmJTlM48XoFZKBdwi1nzkI1mJL21cmbu0cvjxjpkXJ5NENMq42H+hNs8VLJcqXLerBQ== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -autoprefixer@^10.4.12, autoprefixer@^10.4.7: - version "10.4.16" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" - integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== - dependencies: - browserslist "^4.21.10" - caniuse-lite "^1.0.30001538" - fraction.js "^4.3.6" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" - -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== - dependencies: - follow-redirects "^1.14.7" - -babel-loader@^8.2.5: - version "8.3.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" - integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-apply-mdx-type-prop@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" - integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - "@mdx-js/util" "1.6.22" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-extract-import-names@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" - integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - -babel-plugin-polyfill-corejs2@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" - integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.4.2" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" - integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.2" - core-js-compat "^3.31.0" - -babel-plugin-polyfill-regenerator@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" - integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.2" - -"babel-plugin-styled-components@>= 1.12.0": - version "2.1.4" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" - integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.22.5" - lodash "^4.17.21" - picomatch "^2.3.1" - -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base16@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" - integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== - -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - -batch-processor@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" - integrity sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -bezier-easing@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86" - integrity sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135" - integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - -boxen@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" - integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== - dependencies: - ansi-align "^3.0.1" - camelcase "^6.2.0" - chalk "^4.1.2" - cli-boxes "^3.0.0" - string-width "^5.0.1" - type-fest "^2.5.0" - widest-line "^4.0.1" - wrap-ansi "^8.0.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" - integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== - dependencies: - no-case "^2.2.0" - upper-case "^1.1.1" - -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -camelcase-css@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -camelize@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" - integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001538: - version "1.0.30001538" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" - integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== - -ccount@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" - integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -character-entities-legacy@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" - integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== - -character-entities@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" - integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== - -character-reference-invalid@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" - integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== - -cheerio-select@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" - integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== - dependencies: - boolbase "^1.0.0" - css-select "^5.1.0" - css-what "^6.1.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.0.1" - -cheerio@^1.0.0-rc.12, cheerio@^1.0.0-rc.9: - version "1.0.0-rc.12" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" - integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== - dependencies: - cheerio-select "^2.1.0" - dom-serializer "^2.0.0" - domhandler "^5.0.3" - domutils "^3.0.1" - htmlparser2 "^8.0.1" - parse5 "^7.0.0" - parse5-htmlparser2-tree-adapter "^7.0.0" - -chokidar@^3.4.2, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -classnames@2.x, classnames@^2.0.0, classnames@^2.2.1, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== - -clean-css@^5.2.2, clean-css@^5.3.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" - integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== - dependencies: - source-map "~0.6.0" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-boxes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" - integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== - -cli-table3@^0.6.2: - version "0.6.3" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" - integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - -clsx@^1.1.1, clsx@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - -clsx@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" - integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== - -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colord@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" - integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== - -colorette@^2.0.10: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -combine-promises@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.2.0.tgz#5f2e68451862acf85761ded4d9e2af7769c2ca6a" - integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ== - -comma-separated-tokens@^1.0.0: - version "1.0.8" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" - integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== - -commander@^2.20.0, commander@^2.5.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha512-3/qHkNMM6o/KGXHITA14y78PcfmXh4+AOCJpSoF73h4VY1JpdGv3CHMS5+JW6SwLhfJt4RhNmLAa7+RRX/62EQ== - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -consola@^2.15.3: - version "2.15.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" - integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== - -"consolidated-events@^1.1.0 || ^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/consolidated-events/-/consolidated-events-2.0.2.tgz#da8d8f8c2b232831413d9e190dc11669c79f4a91" - integrity sha512-2/uRVMdRypf5z/TW/ncD/66l75P5hH2vM/GR8Jf8HLc2xnfJtmina6F6du8+v4Z2vTrMo7jC+W1tmEEuuELgkQ== - -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.5.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -copy-text-to-clipboard@^3.0.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b" - integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q== - -copy-webpack-plugin@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" - integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== - dependencies: - fast-glob "^3.2.11" - glob-parent "^6.0.1" - globby "^13.1.1" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - -core-js-compat@^3.31.0: - version "3.32.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.2.tgz#8047d1a8b3ac4e639f0d4f66d4431aa3b16e004c" - integrity sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ== - dependencies: - browserslist "^4.21.10" - -core-js-pure@^3.30.2: - version "3.32.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.32.2.tgz#b7dbdac528625cf87eb0523b532eb61551b9a6d1" - integrity sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ== - -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - integrity sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA== - -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - -core-js@^3.23.3, core-js@^3.6.5: - version "3.32.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7" - integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -cosmiconfig@^8.2.0: - version "8.3.6" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" - integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== - dependencies: - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - path-type "^4.0.0" - -country-flag-icons@^1.5.4: - version "1.5.7" - resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.7.tgz#f1f2ddf14f3cbf01cba6746374aeba94db35d4b4" - integrity sha512-AdvXhMcmSp7nBSkpGfW4qR/luAdRUutJqya9PuwRbsBzuoknThfultbv7Ib6fWsHXC43Es/4QJ8gzQQdBNm75A== - -countup.js@^2.5.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/countup.js/-/countup.js-2.8.0.tgz#64951f2df3ede28839413d654d8fef28251c32a8" - integrity sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ== - -create-react-class@^15.6.2: - version "15.7.0" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e" - integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng== - dependencies: - loose-envify "^1.3.1" - object-assign "^4.1.1" - -cross-fetch@^3.1.5: - version "3.1.8" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" - integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== - dependencies: - node-fetch "^2.6.12" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -css-color-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" - integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== - -css-declaration-sorter@^6.3.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" - integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== - -css-loader@^6.7.1: - version "6.8.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" - integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.21" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.3" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.8" - -css-mediaquery@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" - integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q== - -css-minimizer-webpack-plugin@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35" - integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA== - dependencies: - cssnano "^5.1.8" - jest-worker "^29.1.2" - postcss "^8.4.17" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-select@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" - integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== - dependencies: - boolbase "^1.0.0" - css-what "^6.1.0" - domhandler "^5.0.2" - domutils "^3.0.1" - nth-check "^2.0.1" - -css-to-react-native@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" - integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== - dependencies: - camelize "^1.0.0" - css-color-keywords "^1.0.0" - postcss-value-parser "^4.0.2" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1, css-what@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-advanced@^5.3.8: - version "5.3.10" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz#25558a1fbf3a871fb6429ce71e41be7f5aca6eef" - integrity sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ== - dependencies: - autoprefixer "^10.4.12" - cssnano-preset-default "^5.2.14" - postcss-discard-unused "^5.1.0" - postcss-merge-idents "^5.1.1" - postcss-reduce-idents "^5.2.0" - postcss-zindex "^5.1.0" - -cssnano-preset-default@^5.2.14: - version "5.2.14" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" - integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== - dependencies: - css-declaration-sorter "^6.3.1" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.1" - postcss-convert-values "^5.1.3" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.7" - postcss-merge-rules "^5.1.4" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.4" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.1" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.2" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@^5.1.12, cssnano@^5.1.8: - version "5.1.15" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" - integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== - dependencies: - cssnano-preset-default "^5.2.14" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -csstype@^3.0.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - -debug@2.6.9, debug@^2.6.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decode-uri-component@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" - integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -define-data-property@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" - integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-properties@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -defined@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" - integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== - -del@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" - integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== - dependencies: - globby "^11.0.1" - graceful-fs "^4.2.4" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.2" - p-map "^4.0.0" - rimraf "^3.0.2" - slash "^3.0.0" - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -desandro-matches-selector@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1" - integrity sha512-+1q0nXhdzg1IpIJdMKalUwvvskeKnYyEe3shPRwedNcWtnhEKT3ZxvFjzywHDeGcKViIxTCAoOYQWP1qD7VNyg== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detab@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" - integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== - dependencies: - repeat-string "^1.5.4" - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -detect-port-alt@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - dependencies: - address "^1.0.1" - debug "^2.6.0" - -detect-port@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== - dependencies: - address "^1.0.1" - debug "4" - -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-align@^1.7.0: - version "1.12.4" - resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" - integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== - -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -dom-serializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" - integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.2" - entities "^4.2.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domhandler@^5.0.2, domhandler@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" - integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - dependencies: - domelementtype "^2.3.0" - -domutils@^2.5.2, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -domutils@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" - integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== - dependencies: - dom-serializer "^2.0.0" - domelementtype "^2.3.0" - domhandler "^5.0.3" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -duplexer3@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" - integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - -duplexer@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.477: - version "1.4.526" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz#1bcda5f2b8238e497c20fcdb41af5da907a770e2" - integrity sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q== - -element-resize-detector@^1.1.9: - version "1.2.4" - resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz#3e6c5982dd77508b5fa7e6d5c02170e26325c9b1" - integrity sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg== - dependencies: - batch-processor "1.0.0" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -emoticon@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" - integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enquire.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814" - integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw== - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^4.2.0, entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha512-XLiBFsLtNF0MOZl+vWU59yPb3C2JtrQY2CNJn22KH75zPlHWY5ChcAQuf4knJeWT/lLkrx3sqvhP/J349bt4Bw== - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-module-lexer@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" - integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-html@^1.0.3, escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha512-59dDGQo2b3M/JfKIws0/z8dcXH2mnVHkfSPRhCYS91JNGfGNwr7GsSF6qzWZuOGvw5Ii0w9TtylrX07MGmlOoQ== - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esprima@~3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg== - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -estree-walker@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eta@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" - integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -ev-emitter@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" - integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q== - -eval@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" - integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== - dependencies: - "@types/node" "*" - require-like ">= 0.1.1" - -eventemitter3@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-memoize@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" - integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== - -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== - dependencies: - punycode "^1.3.2" - -fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== - dependencies: - reusify "^1.0.4" - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fbemitter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" - integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== - dependencies: - fbjs "^3.0.0" - -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== - -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha512-4KW7tT33ytfazK3Ekvesbsa4A5J79hUrdXONQGZ0wM6i3PFc70YknF9kj1eyx3mDupgJ7Z+ifFhcMJ+ps2eZIw== - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - -fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.5" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d" - integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg== - dependencies: - cross-fetch "^3.1.5" - fbjs-css-vars "^1.0.0" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^1.0.35" - -feed@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" - integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== - dependencies: - xml-js "^1.6.11" - -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -filesize@^8.0.6: - version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" - integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -fizzy-ui-utils@^2.0.0: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz#7df45dcc4eb374a08b65d39bb9a4beedf7330505" - integrity sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg== - dependencies: - desandro-matches-selector "^2.0.0" - -flux@^4.0.1: - version "4.0.4" - resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572" - integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw== - dependencies: - fbemitter "^3.0.0" - fbjs "^3.0.1" - -focus-group@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/focus-group/-/focus-group-0.3.1.tgz#e0f32ed86b0dabdd6ffcebdf898ecb32e47fedce" - integrity sha512-IA01dzk2cStQso/qnt2rWhXCFBZlBfjZmohB9mXUx9feEaJcORAK0FQGvwaApsNNGwzEnqrp/2qTR4lq8PXfnQ== - -follow-redirects@^1.0.0, follow-redirects@^1.14.7: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== - -fork-ts-checker-webpack-plugin@^6.5.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3" - integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fraction.js@^4.3.6: - version "4.3.6" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" - integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747" - integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" - integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-proto "^1.0.1" - has-symbols "^1.0.3" - -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== - -get-size@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/get-size/-/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef" - integrity sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q== - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -github-buttons@^2.22.0: - version "2.27.0" - resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.27.0.tgz#bbebea3d1c4f8c302b7d8432fd25a679242597e2" - integrity sha512-PmfRMI2Rttg/2jDfKBeSl621sEznrsKF019SuoLdoNlO7qRUZaOyEI5Li4uW+79pVqnDtKfIEVuHTIJ5lgy64w== - -github-slugger@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" - integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^5.0.15: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA== - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -globby@^13.1.1: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -gray-matter@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" - integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== - dependencies: - js-yaml "^3.13.1" - kind-of "^6.0.2" - section-matter "^1.0.0" - strip-bom-string "^1.0.0" - -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hast-to-hyperscript@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" - integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== - dependencies: - "@types/unist" "^2.0.3" - comma-separated-tokens "^1.0.0" - property-information "^5.3.0" - space-separated-tokens "^1.0.0" - style-to-object "^0.3.0" - unist-util-is "^4.0.0" - web-namespaces "^1.0.0" - -hast-util-from-parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" - integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== - dependencies: - "@types/parse5" "^5.0.0" - hastscript "^6.0.0" - property-information "^5.0.0" - vfile "^4.0.0" - vfile-location "^3.2.0" - web-namespaces "^1.0.0" - -hast-util-parse-selector@^2.0.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" - integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== - -hast-util-raw@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" - integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== - dependencies: - "@types/hast" "^2.0.0" - hast-util-from-parse5 "^6.0.0" - hast-util-to-parse5 "^6.0.0" - html-void-elements "^1.0.0" - parse5 "^6.0.0" - unist-util-position "^3.0.0" - vfile "^4.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - -hast-util-to-parse5@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" - integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== - dependencies: - hast-to-hyperscript "^9.0.0" - property-information "^5.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - -hastscript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" - integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== - dependencies: - "@types/hast" "^2.0.0" - comma-separated-tokens "^1.0.0" - hast-util-parse-selector "^2.0.0" - property-information "^5.0.0" - space-separated-tokens "^1.0.0" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -htm@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78" - integrity sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ== - -html-entities@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" - integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== - -html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== - dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" - -html-tags@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" - integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== - -html-void-elements@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" - integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== - -html-webpack-plugin@^5.5.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz#72270f4a78e222b5825b296e5e3e1328ad525a3e" - integrity sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg== - dependencies: - "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" - -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" - -htmlparser2@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" - integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== - dependencies: - domelementtype "^2.3.0" - domhandler "^5.0.3" - domutils "^3.0.1" - entities "^4.4.0" - -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -hyphenate-style-name@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" - integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== - -iconv-lite@0.4.24, iconv-lite@^0.4.5: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ignore@^5.2.0, ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== - -image-size@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486" - integrity sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg== - dependencies: - queue "6.0.2" - -imagesloaded@^4.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7" - integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA== - dependencies: - ev-emitter "^1.0.0" - -immer@^9.0.7: - version "9.0.21" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" - integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== - -import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infima@0.2.0-alpha.43: - version "0.2.0-alpha.43" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" - integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - -ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== - -input-format@^0.3.8: - version "0.3.8" - resolved "https://registry.yarnpkg.com/input-format/-/input-format-0.3.8.tgz#9445b0cab2f0457fbe36d77d607e942fd37345c5" - integrity sha512-tLR0XRig1xIcG1PtIpMd/uoltvkAI62CN9OIbtj4/tEJAkqTCQLNHUZ9N4M46w0dopny7Rlt/lRH5Xzp7e6F+g== - dependencies: - prop-types "^15.8.1" - -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== - -is-alphabetical@1.0.4, is-alphabetical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" - integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== - -is-alphanumerical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" - integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-decimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" - integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-hexadecimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" - integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== - -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== - -is-root@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest-worker@^29.1.2: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jiti@^1.18.2: - version "1.20.0" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" - integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== - -joi@^17.6.0: - version "17.10.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.10.2.tgz#4ecc348aa89ede0b48335aad172e0f5591e55b29" - integrity sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== - -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json2mq@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" - integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== - dependencies: - string-convert "^0.2.0" - -json5@^2.1.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha512-LGm87w0A8E92RrcXt94PnNHkFqHmgDy3mKHvNZOG7QepKCTCH/VB6S+IEN+bT4uLN3gVpOT0vvOOVd96osG71g== - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -launch-editor@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7" - integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ== - dependencies: - picocolors "^1.0.0" - shell-quote "^1.7.3" - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -libphonenumber-js@^1.10.44: - version "1.10.44" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.44.tgz#6709722461173e744190494aaaec9c1c690d8ca8" - integrity sha512-svlRdNBI5WgBjRC20GrCfbFiclbF0Cx+sCcQob/C1r57nsoq0xg8r65QbTyVyweQIlB33P+Uahyho6EMYgcOyQ== - -lilconfig@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== - -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -loader-utils@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576" - integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.curry@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" - integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.escape@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" - integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== - -lodash.flow@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" - integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== - -lodash.invokemap@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" - integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.pullall@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" - integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== - -lodash.uniq@4.5.0, lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash.uniqby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" - integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== - -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lower-case@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" - integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -lunr-languages@^1.4.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/lunr-languages/-/lunr-languages-1.13.0.tgz#81840f0e2abe2e4cbc5aeb793477b7616dca2bdf" - integrity sha512-qgTOarcnAtVFKr0aJ2GuiqbBdhKF61jpF8OgFbnlSAb1t6kOiQW67q0hv0UQzzB+5+OwPpnZyFT/L0L9SQG1/A== - -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -mark.js@^8.11.1: - version "8.11.1" - resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" - integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== - -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -masonry-layout@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/masonry-layout/-/masonry-layout-4.2.2.tgz#d57b44af13e601bfcdc423f1dd8348b5524de348" - integrity sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA== - dependencies: - get-size "^2.0.2" - outlayer "^2.1.0" - -matchmediaquery@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.1.tgz#8247edc47e499ebb7c58f62a9ff9ccf5b815c6d7" - integrity sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ== - dependencies: - css-mediaquery "^0.1.2" - -mdast-squeeze-paragraphs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" - integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== - dependencies: - unist-util-remove "^2.0.0" - -mdast-util-definitions@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" - integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== - dependencies: - unist-util-visit "^2.0.0" - -mdast-util-to-hast@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" - integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - -mdast-util-to-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" - integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdurl@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.1.2, memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -memoize-one@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" - integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== - dependencies: - mime-db "~1.33.0" - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mini-css-extract-plugin@^2.6.1: - version "2.7.6" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d" - integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw== - dependencies: - schema-utils "^4.0.0" - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -"minimatch@2 || 3", minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^0.5.0: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mrmime@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" - integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -no-case@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" - integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== - dependencies: - lower-case "^1.1.1" - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-emoji@^1.10.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== - dependencies: - lodash "^4.17.21" - -node-fetch@^2.6.12: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nprogress@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" - integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g== - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9, open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -opener@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -outlayer@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/outlayer/-/outlayer-2.1.1.tgz#29863b6de10ea5dadfffcadfa0d728907387e9a2" - integrity sha512-+GplXsCQ3VrbGujAeHEzP9SXsBmJxzn/YdDSQZL0xqBmAWBmortu2Y9Gwdp9J0bgDQ8/YNIPMoBM13nTwZfAhw== - dependencies: - ev-emitter "^1.0.0" - fizzy-ui-utils "^2.0.0" - get-size "^2.0.2" - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -parallax-controller@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/parallax-controller/-/parallax-controller-1.7.0.tgz#659e8176922034ac548009ac544951432330aa0a" - integrity sha512-tIU/LgH9oIrvC6o+rvGQis8/NXxsHNuF5LODmcc5TjvEDUi2qiR/iisFmyaqI9LImQ3psdAv28k2ZEAnFekiig== - dependencies: - bezier-easing "^2.1.0" - -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" - integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== - dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" - -parse-json@^5.0.0, parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-numeric-range@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" - integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== - -parse5-htmlparser2-tree-adapter@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" - integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== - dependencies: - domhandler "^5.0.2" - parse5 "^7.0.0" - -parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parse5@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== - dependencies: - entities "^4.4.0" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-is-inside@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - -polished@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1" - integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ== - dependencies: - "@babel/runtime" "^7.17.8" - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" - integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" - integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-discard-unused@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" - integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-loader@^7.0.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd" - integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA== - dependencies: - cosmiconfig "^8.2.0" - jiti "^1.18.2" - semver "^7.3.8" - -postcss-merge-idents@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" - integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-merge-longhand@^5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" - integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.1" - -postcss-merge-rules@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" - integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" - integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== - dependencies: - browserslist "^4.21.4" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" - integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" - integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-idents@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" - integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" - integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.13" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-sort-media-queries@^4.2.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz#04a5a78db3921eb78f28a1a781a2e68e65258128" - integrity sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw== - dependencies: - sort-css-media-queries "2.1.0" - -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss-zindex@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" - integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== - -postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.21: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -preact@^10.13.2: - version "10.17.1" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.17.1.tgz#0a1b3c658c019e759326b9648c62912cf5c2dde1" - integrity sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA== - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== - -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== - dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" - -pretty-time@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" - integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== - -prism-react-renderer@^1.2.1, prism-react-renderer@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" - integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== - -prismjs@^1.28.0: - version "1.29.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" - integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== - -private@^0.1.6, private@~0.1.5: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -promise@^7.0.3, promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -property-information@^5.0.0, property-information@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" - integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== - dependencies: - xtend "^4.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - -pure-color@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" - integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA== - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -query-string@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-8.1.0.tgz#e7f95367737219544cd360a11a4f4ca03836e115" - integrity sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw== - dependencies: - decode-uri-component "^0.4.1" - filter-obj "^5.1.0" - split-on-first "^3.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -queue@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" - integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== - dependencies: - inherits "~2.0.3" - -raf@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc-align@^4.0.0: - version "4.0.15" - resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.15.tgz#2bbd665cf85dfd0b0244c5a752b07565e9098577" - integrity sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - dom-align "^1.7.0" - rc-util "^5.26.0" - resize-observer-polyfill "^1.5.1" - -rc-collapse@^3.5.2: - version "3.7.1" - resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.7.1.tgz#bda1f7f80adccf3433c1c15d4d9f9ca09910c727" - integrity sha512-N/7ejyiTf3XElNJBBpxqnZBUuMsQWEOPjB2QkfNvZ/Ca54eAvJXuOD1EGbCWCk2m7v/MSxku7mRpdeaLOCd4Gg== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.3.4" - rc-util "^5.27.0" - -rc-drawer@^6.1.3: - version "6.5.0" - resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-6.5.0.tgz#1875bfd6fc502173358b556fefeacf8ee4b32d24" - integrity sha512-wzSVdAbUh2LG7Is9Fh08RCbRtDO6VXEbUUE1cbEs8zAeryS0nxme5Zy9w5rFfLBOeAAqunrcJb2DL24UPQljeg== - dependencies: - "@babel/runtime" "^7.10.1" - "@rc-component/portal" "^1.1.1" - classnames "^2.2.6" - rc-motion "^2.6.1" - rc-util "^5.36.0" - -rc-dropdown@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-4.0.1.tgz#f65d9d3d89750241057db59d5a75e43cd4576b68" - integrity sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.6" - rc-trigger "^5.3.1" - rc-util "^5.17.0" - -rc-menu@~9.8.0: - version "9.8.4" - resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.8.4.tgz#58bf19d471e3c74ff4bcfdb0f02a3826ebe2553b" - integrity sha512-lmw2j8I2fhdIzHmC9ajfImfckt0WDb2KVJJBBRIsxPEw2kGkEfjLMUoB1NgiNT/Q5cC8PdjGOGQjHJIJMwyNMw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "2.x" - rc-motion "^2.4.3" - rc-overflow "^1.2.8" - rc-trigger "^5.1.2" - rc-util "^5.27.0" - -rc-motion@^2.0.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.6.1, rc-motion@^2.6.2: - version "2.9.0" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.9.0.tgz#9e18a1b8d61e528a97369cf9a7601e9b29205710" - integrity sha512-XIU2+xLkdIr1/h6ohPZXyPBMvOmuyFZQ/T0xnawz+Rh+gh4FINcnZmMT5UTIj6hgI0VLDjTaPeRd+smJeSPqiQ== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.21.0" - -rc-overflow@^1.2.8: - version "1.3.2" - resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.3.2.tgz#72ee49e85a1308d8d4e3bd53285dc1f3e0bcce2c" - integrity sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw== - dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-resize-observer "^1.0.0" - rc-util "^5.37.0" - -rc-progress@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.5.1.tgz#a3cdfd2fe04eb5c3d43fa1c69e7dd70c73b102ae" - integrity sha512-V6Amx6SbLRwPin/oD+k1vbPrO8+9Qf8zW1T8A7o83HdNafEVvAxPV5YsgtKFP+Ud5HghLj33zKOcEHrcrUGkfw== - dependencies: - "@babel/runtime" "^7.10.1" - classnames "^2.2.6" - rc-util "^5.16.1" - -rc-resize-observer@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz#b61b9f27048001243617b81f95e53d7d7d7a6a3d" - integrity sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg== - dependencies: - "@babel/runtime" "^7.20.7" - classnames "^2.2.1" - rc-util "^5.27.0" - resize-observer-polyfill "^1.5.1" - -rc-tabs@12.5.7: - version "12.5.7" - resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.5.7.tgz#9175f5e27341416d3f572c8d9a40e300d0e2de6b" - integrity sha512-i9gY2TcwCNmBM+bXCDDTvb6mnRYIDkkNm+UGoIqrLOFnRRbAqjsSf+tgyvzhBvbK8XcSrMhzKKLaOMbGyND8YA== - dependencies: - "@babel/runtime" "^7.11.2" - classnames "2.x" - rc-dropdown "~4.0.0" - rc-menu "~9.8.0" - rc-motion "^2.6.2" - rc-resize-observer "^1.0.0" - rc-util "^5.16.0" - -rc-trigger@^5.1.2, rc-trigger@^5.3.1: - version "5.3.4" - resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.3.4.tgz#6b4b26e32825677c837d1eb4d7085035eecf9a61" - integrity sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw== - dependencies: - "@babel/runtime" "^7.18.3" - classnames "^2.2.6" - rc-align "^4.0.0" - rc-motion "^2.0.0" - rc-util "^5.19.2" - -rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.19.2, rc-util@^5.21.0, rc-util@^5.24.4, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.36.0, rc-util@^5.37.0: - version "5.37.0" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.37.0.tgz#6df9a55cb469b41b6995530a45b5f3dd3219a4ea" - integrity sha512-cPMV8DzaHI1KDaS7XPRXAf4J7mtBqjvjikLpQieaeOO7+cEbqY2j7Kso/T0R0OiEZTNcLS/8Zl9YrlXiO9UbjQ== - dependencies: - "@babel/runtime" "^7.18.3" - react-is "^16.12.0" - -rc@1.2.8, rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -re-resizable@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-5.0.1.tgz#66756a7aa0583b199e23e23f12a71c546031146d" - integrity sha512-Iy8v5li7bhNBDxCN1DbA4l6G2Hk8NCZtcExoI1D+5pfvKyQcH8LH2P5h3DGoEfHhs0uyyRC1Qx8bHBomfrmxgA== - dependencies: - fast-memoize "^2.5.1" - -re-resizable@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.6.tgz#b95d37e3821481b56ddfb1e12862940a791e827d" - integrity sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ== - dependencies: - fast-memoize "^2.5.1" - -react-accessible-accordion@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/react-accessible-accordion/-/react-accessible-accordion-5.0.0.tgz#5b61d06aec38906a99f977c10324d9bddec0f64c" - integrity sha512-MT2obYpTgLIIfPr9d7hEyvPB5rg8uJcHpgA83JSRlEUHvzH48+8HJPvzSs+nM+XprTugDgLfhozO5qyJpBvYRQ== - -react-anchor-link-smooth-scroll@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/react-anchor-link-smooth-scroll/-/react-anchor-link-smooth-scroll-1.0.12.tgz#5cd49c73e74be6d20b4c05a5e5c72d07ebbedac7" - integrity sha512-aaY+9X0yh8YnC0jBfoTKpsiCLdO/Y6pCltww+VB+NnTBPDOvnIdnp1AlazajsDitc1j+cVSQ+yNtaVeTIMQbxw== - -react-aria-menubutton@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/react-aria-menubutton/-/react-aria-menubutton-7.0.3.tgz#9f3c96e8f0b9469dc1d5dab7cf244b73069f2882" - integrity sha512-Ql4W3rNiZmuVJ1wQ0UUeV4OZX3IZq2evsfEqJGefSMdfkK6o8X/6Ufxrzu0wL+/Dr7JUY3xnrnIQimSCFghlCQ== - dependencies: - focus-group "^0.3.1" - prop-types "^15.6.0" - teeny-tap "^0.2.0" - -react-base16-styling@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" - integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== - dependencies: - base16 "^1.0.0" - lodash.curry "^4.0.1" - lodash.flow "^3.3.0" - pure-color "^1.2.0" - -react-collapser@^1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/react-collapser/-/react-collapser-1.5.10.tgz#f3d2962df65f12546b36520f432159333bd06258" - integrity sha512-J4rYmaaidi3CI+KLobfrtPBJjAgygp7bltPWX0V4NbUALCBqoHt+nj02r6VzelxBYHmJF278yqZd5vJ163etxA== - -react-content-loader@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/react-content-loader/-/react-content-loader-6.2.1.tgz#8feb733c2d2495002e1b216f13707f2b5f2a8ead" - integrity sha512-6ONbFX+Hi3SHuP66JB8CPvJn372pj+qwltJV0J8z/8MFrq98I1cbFdZuhDWeQXu3CFxiiDTXJn7DFxx2ZvrO7g== - -react-countdown-now@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/react-countdown-now/-/react-countdown-now-2.1.2.tgz#b93d153fbb7a8d359beb32ecbf1de29d15f026c7" - integrity sha512-BgRnsxV2vnvyJAefDBigTX+ngX6eGd2xl6DIFdZPIZkbx0mzxlMEeoEQG01JyeHDjqaXFUpIonIbx1yYyaMwzg== - dependencies: - lodash.isequal "^4.5.0" - prop-types "^15.7.2" - -react-countup@^6.4.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/react-countup/-/react-countup-6.4.2.tgz#cf8564c9381958a36c7c25f7c0769f7a472e4c99" - integrity sha512-wdDrNb2lPFGbLb+i0FTgswPbWziubS6KZRII8NRpXmUCoZsi15PFbIHgBz60Dyxd4KPuRvwsK5aawIU4OPP3jA== - dependencies: - "@rollup/plugin-babel" "^6.0.3" - countup.js "^2.5.0" - -react-dev-utils@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" - integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== - dependencies: - "@babel/code-frame" "^7.16.0" - address "^1.1.2" - browserslist "^4.18.1" - chalk "^4.1.2" - cross-spawn "^7.0.3" - detect-port-alt "^1.1.6" - escape-string-regexp "^4.0.0" - filesize "^8.0.6" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.5.0" - global-modules "^2.0.0" - globby "^11.0.4" - gzip-size "^6.0.0" - immer "^9.0.7" - is-root "^2.1.0" - loader-utils "^3.2.0" - open "^8.4.0" - pkg-up "^3.1.0" - prompts "^2.4.2" - react-error-overlay "^6.0.11" - recursive-readdir "^2.2.2" - shell-quote "^1.7.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -react-dom@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" - -react-draggable@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.0.tgz#2ed7ea3f92e7d742d747f9e6324860606cd4d997" - integrity sha512-U7/jD0tAW4T0S7DCPK0kkKLyL0z61sC/eqU+NUfDjnq+JtBKaYKDHpsK2wazctiA4alEzCXUnzkREoxppOySVw== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - -react-draggable@4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c" - integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== - dependencies: - clsx "^1.1.1" - prop-types "^15.8.1" - -react-error-overlay@^6.0.11: - version "6.0.11" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" - integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== - -react-fast-compare@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" - integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== - -react-github-btn@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/react-github-btn/-/react-github-btn-1.4.0.tgz#92654107e92658e60dd977c7a92b212f806da78d" - integrity sha512-lV4FYClAfjWnBfv0iNlJUGhamDgIq6TayD0kPZED6VzHWdpcHmPfsYOZ/CFwLfPv4Zp+F4m8QKTj0oy2HjiGXg== - dependencies: - github-buttons "^2.22.0" - -react-helmet-async@*, react-helmet-async@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" - integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== - dependencies: - "@babel/runtime" "^7.12.5" - invariant "^2.2.4" - prop-types "^15.7.2" - react-fast-compare "^3.2.0" - shallowequal "^1.1.0" - -react-icons-kit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/react-icons-kit/-/react-icons-kit-2.0.0.tgz#9d605c687a79422705a863b5cbcf36b9892a3cc9" - integrity sha512-3vgaRrmJrAkRfaxBknyWJVdokvq4n8O0LexrH3hTkV2aMTxtW7VwP9MsD6BZYGC1EkVJL2GpDK8viaa4fgQejg== - dependencies: - camel-case "^3.0.0" - prop-types "^15.5.8" - -react-icons@^4.7.1: - version "4.11.0" - resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.11.0.tgz#4b0e31c9bfc919608095cc429c4f1846f4d66c65" - integrity sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA== - -react-id-swiper@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/react-id-swiper/-/react-id-swiper-4.0.0.tgz#eed938c5a4bf464a5cd7316a1bf6778581f47f2f" - integrity sha512-BFY8VQYgc1ECkT1Ek6MYSF8MADjWYZctdeRlMA202mjGcdjq1Bugfhg207KHTjGWKWZlY/7/nxFShqQo74RslA== - dependencies: - object-assign "^4.1.1" - -react-image-gallery@1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/react-image-gallery/-/react-image-gallery-1.2.11.tgz#707331e71b02964f00c1f3ab98e9efe071ff9f15" - integrity sha512-YLMCdNSCf3YPhRmnjEOdEZGEXghTMx4o3dlAqd0rz0nAyebUvTz/xJnIHPActbPSHCvVJgt5A09EPHYbGqw++w== - -react-image@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/react-image/-/react-image-4.1.0.tgz#92f2d4a809a178b3bf69acd7bad7da7aa5e7364c" - integrity sha512-qwPNlelQe9Zy14K2pGWSwoL+vHsAwmJKS6gkotekDgRpcnRuzXNap00GfibD3eEPYu3WCPlyIUUNzcyHOrLHjw== - -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -"react-is@^17.0.1 || ^18.0.0", react-is@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -react-json-view@^1.21.3: - version "1.21.3" - resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" - integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== - dependencies: - flux "^4.0.1" - react-base16-styling "^0.6.0" - react-lifecycles-compat "^3.0.4" - react-textarea-autosize "^8.3.2" - -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - -react-loadable-ssr-addon-v5-slorber@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" - integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== - dependencies: - "@babel/runtime" "^7.10.3" - -react-loader-spinner@^5.3.4: - version "5.4.5" - resolved "https://registry.yarnpkg.com/react-loader-spinner/-/react-loader-spinner-5.4.5.tgz#246a9d943deb20b528ec3a889f3236cef6f9af68" - integrity sha512-32f+sb/v2tnNfyvnCCOS4fpyVHsGXjSyNo6QLniHcaj1XjKLxx14L2z0h6szRugOL8IEJ+53GPwNAdbkDqmy4g== - dependencies: - react-is "^18.2.0" - styled-components "^5.3.5" - styled-tools "^1.7.2" - -react-masonry-component@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-masonry-component/-/react-masonry-component-6.3.0.tgz#edd7593f9f0d4bfa29249ea1e4d2d07757eba65d" - integrity sha512-4ZI78nxMfCpU5yQiS6Rup07Ju++YzcOGAyvbMcl2GfpZTw8GRwT548lkKr0PJarNicRV1qE2D/NiT26UPg1T7A== - dependencies: - create-react-class "^15.6.2" - element-resize-detector "^1.1.9" - imagesloaded "^4.0.0" - lodash "^4.17.4" - masonry-layout "^4.2.0" - prop-types "^15.5.8" - -react-parallax-component@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/react-parallax-component/-/react-parallax-component-1.0.6.tgz#da2ce22e4910eac759cb4a0bc3dc25062d3ddcdf" - integrity sha512-ZU+hHzsmAhqiaICaGgMd4r6+FCpVmA4v4LakNYxzxNEcDI14aGBDH/SnnI3vxUMOB+TxvZMOfpFP84q1IpYRqg== - dependencies: - react "^0.14.2" - -react-parallax@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/react-parallax/-/react-parallax-3.5.1.tgz#73071679371b7738e21ffac8daa0bc3cb7e5174d" - integrity sha512-p5zPsPsqELlIOGaPS01O0IRx8R2bxcBAtrdF/RHf9nIxxk5hijbM2y89tk4rJQBcNH6ESSLe7J2NV4/ms7FLFw== - -react-phone-number-input@^3.2.18: - version "3.3.6" - resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.3.6.tgz#5d2a2bbfb2600c4c0428e4cd4a725d69f40fe2dd" - integrity sha512-P6q5jSwTyyIPshNhY+JhohjifXwgwLnjsd3IrjQ1vo3ZGlx1ndABXrrfau943iTAaf+tO6os+G6NiJhfHhjLoQ== - dependencies: - classnames "^2.3.1" - country-flag-icons "^1.5.4" - input-format "^0.3.8" - libphonenumber-js "^1.10.44" - prop-types "^15.8.1" - -react-responsive@^9.0.0: - version "9.0.2" - resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-9.0.2.tgz#34531ca77a61e7a8775714016d21241df7e4205c" - integrity sha512-+4CCab7z8G8glgJoRjAwocsgsv6VA2w7JPxFWHRc7kvz8mec1/K5LutNC2MG28Mn8mu6+bu04XZxHv5gyfT7xQ== - dependencies: - hyphenate-style-name "^1.0.0" - matchmediaquery "^0.3.0" - prop-types "^15.6.1" - shallow-equal "^1.2.1" - -react-reveal@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/react-reveal/-/react-reveal-1.2.2.tgz#f47fbc44debc4c185ae2163a215a9e822c7adfef" - integrity sha512-JCv3fAoU6Z+Lcd8U48bwzm4pMZ79qsedSXYwpwt6lJNtj/v5nKJYZZbw3yhaQPPgYePo3Y0NOCoYOq/jcsisuw== - dependencies: - prop-types "^15.5.10" - -react-rnd@^10.3.7: - version "10.4.1" - resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.4.1.tgz#9e1c3f244895d7862ef03be98b2a620848c3fba1" - integrity sha512-0m887AjQZr6p2ADLNnipquqsDq4XJu/uqVqI3zuoGD19tRm6uB83HmZWydtkilNp5EWsOHbLGF4IjWMdd5du8Q== - dependencies: - re-resizable "6.9.6" - react-draggable "4.4.5" - tslib "2.3.1" - -react-rnd@^9.0.4: - version "9.2.0" - resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-9.2.0.tgz#0642a9f788ee2f16267d13cefe21d0e94da99668" - integrity sha512-Sp2vCUcvnHcdwwODbL50FJqZL6yt/ElPGVIaennOulsRWWJZ+dMWo0eJl+5qlh8uxRaLzpi9VQoq+ADTkvytsg== - dependencies: - re-resizable "5.0.1" - react-draggable "3.3.0" - tslib "1.9.3" - -react-router-config@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" - integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== - dependencies: - "@babel/runtime" "^7.1.2" - -react-router-dom@^5.3.3: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" - integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.3.4" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.3.4, react-router@^5.3.3: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" - integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-scroll-motion@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/react-scroll-motion/-/react-scroll-motion-0.3.2.tgz#78213b0ca454352b7fa86dfcd9b6189466fed76d" - integrity sha512-w/6btLiY4I1wh1hxRhVKN0Vd3Hrpb+ZfSBZxxMh+mMtc9oXeR+VvfLfx2VRTNl+0JyjTCciwFxtDTK6vg09Mhw== - -react-scroll-parallax@^3.3.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/react-scroll-parallax/-/react-scroll-parallax-3.4.2.tgz#4f987804581115f3fc50600341611ef5a7ea65d3" - integrity sha512-jNltdM1a6y2TRHW4X4nyNT9BAzbLoISTvXaoB9YKGudVcAsGWManMI6UZ6PMd2si6bbWtTROgF/A9IoplZ2OLA== - dependencies: - parallax-controller "^1.7.0" - -react-scrollspy@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/react-scrollspy/-/react-scrollspy-3.4.3.tgz#12bccebc7e2dfe228b873288b3f473904edb3854" - integrity sha512-c2QZpMPWxm1HF71h1EqaxBldx2zLYO0aZ24Bcuo2mUWF79T+F6qOtr7XJCzUDm99NOwhVKQD01a7A8VC6c90CQ== - dependencies: - babel-runtime "^6.26.0" - classnames "^2.2.5" - prop-types "^15.5.10" - -react-select@^5.6.0: - version "5.7.5" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.5.tgz#d2d0f29994e0f06000147bfb2adf58324926c2fd" - integrity sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA== - dependencies: - "@babel/runtime" "^7.12.0" - "@emotion/cache" "^11.4.0" - "@emotion/react" "^11.8.1" - "@floating-ui/dom" "^1.0.1" - "@types/react-transition-group" "^4.4.0" - memoize-one "^6.0.0" - prop-types "^15.6.0" - react-transition-group "^4.3.0" - use-isomorphic-layout-effect "^1.1.2" - -react-slick@^0.29.0: - version "0.29.0" - resolved "https://registry.yarnpkg.com/react-slick/-/react-slick-0.29.0.tgz#0bed5ea42bf75a23d40c0259b828ed27627b51bb" - integrity sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA== - dependencies: - classnames "^2.2.5" - enquire.js "^2.1.6" - json2mq "^0.2.0" - lodash.debounce "^4.0.8" - resize-observer-polyfill "^1.5.0" - -react-spring@^8.0.18: - version "8.0.27" - resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a" - integrity sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g== - dependencies: - "@babel/runtime" "^7.3.1" - prop-types "^15.5.8" - -react-stickynode@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/react-stickynode/-/react-stickynode-4.1.0.tgz#ecd80987f64b98f999c589cd4b992eee6bed9562" - integrity sha512-zylWgfad75jLfh/gYIayDcDWIDwO4weZrsZqDpjZ/axhF06zRjdCWFBgUr33Pvv2+htKWqPSFksWTyB6aMQ1ZQ== - dependencies: - classnames "^2.0.0" - core-js "^3.6.5" - prop-types "^15.6.0" - shallowequal "^1.0.0" - subscribe-ui-event "^2.0.6" - -react-tabs@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-6.0.2.tgz#bc1065c3828561fee285a8fd045f22e0fcdde1eb" - integrity sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ== - dependencies: - clsx "^2.0.0" - prop-types "^15.5.0" - -react-textarea-autosize@^8.3.2: - version "8.5.3" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" - integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== - dependencies: - "@babel/runtime" "^7.20.13" - use-composed-ref "^1.3.0" - use-latest "^1.2.1" - -react-transition-group@^4.3.0: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react-tsparticles@^2.9.3: - version "2.12.2" - resolved "https://registry.yarnpkg.com/react-tsparticles/-/react-tsparticles-2.12.2.tgz#679054f229650db8398a646e2bb1c4a750f2a7de" - integrity sha512-/nrEbyL8UROXKIMXe+f+LZN2ckvkwV2Qa+GGe/H26oEIc+wq/ybSG9REDwQiSt2OaDQGu0MwmA4BKmkL6wAWcA== - dependencies: - tsparticles-engine "^2.12.0" - -react-waypoint@10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-10.3.0.tgz#fcc60e86c6c9ad2174fa58d066dc6ae54e3df71d" - integrity sha512-iF1y2c1BsoXuEGz08NoahaLFIGI9gTUAAOKip96HUmylRT6DUtpgoBPjk/Y8dfcFVmfVDvUzWjNXpZyKTOV0SQ== - dependencies: - "@babel/runtime" "^7.12.5" - consolidated-events "^1.1.0 || ^2.0.0" - prop-types "^15.0.0" - react-is "^17.0.1 || ^18.0.0" - -react@^0.14.2: - version "0.14.10" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.10.tgz#c10d7750f1c5b34eee2a123915ac4c14c01c1081" - integrity sha512-yxMw5aorZG4qsLVBfjae4wGFvd5708DhcxaXLJ3IOTgr1TCs8k9+ZheGgLGr5OfwWMhSahNbGvvoEDzrxVWouA== - dependencies: - envify "^3.0.0" - fbjs "^0.6.1" - -react@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -reading-time@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" - integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== - -recast@^0.11.17: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha512-+nixG+3NugceyR8O1bLU45qs84JgI3+8EauyRZafLgC9XbdAOIVgwV1Pe2da0YzGo62KzWoZwUpVEQf6qNAXWA== - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - -recursive-readdir@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" - integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== - dependencies: - minimatch "^3.0.5" - -regenerate-unicode-properties@^10.1.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" - integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== - -regenerator-transform@^0.15.2: - version "0.15.2" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" - integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -registry-auth-token@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" - integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== - dependencies: - rc "1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -remark-emoji@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" - integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== - dependencies: - emoticon "^3.2.0" - node-emoji "^1.10.0" - unist-util-visit "^2.0.3" - -remark-footnotes@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" - integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== - -remark-mdx@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" - integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== - dependencies: - "@babel/core" "7.12.9" - "@babel/helper-plugin-utils" "7.10.4" - "@babel/plugin-proposal-object-rest-spread" "7.12.1" - "@babel/plugin-syntax-jsx" "7.12.1" - "@mdx-js/util" "1.6.22" - is-alphabetical "1.0.4" - remark-parse "8.0.3" - unified "9.2.0" - -remark-parse@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" - integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== - dependencies: - ccount "^1.0.0" - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^2.0.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^2.0.0" - vfile-location "^3.0.0" - xtend "^4.0.1" - -remark-squeeze-paragraphs@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" - integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== - dependencies: - mdast-squeeze-paragraphs "^4.0.0" - -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== - dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" - -repeat-string@^1.5.4: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -"require-like@>= 0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" - integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - -resolve@^1.1.6, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.3.2: - version "1.22.6" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" - integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== - dependencies: - lowercase-keys "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rtl-detect@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" - integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== - -rtlcss@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" - integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== - dependencies: - find-up "^5.0.0" - picocolors "^1.0.0" - postcss "^8.3.11" - strip-json-comments "^3.1.1" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.5.4: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.5: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" - integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -section-matter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" - integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== - dependencies: - extend-shallow "^2.0.1" - kind-of "^6.0.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== - dependencies: - node-forge "^1" - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.4.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== - dependencies: - randombytes "^2.1.0" - -serve-handler@^6.1.3: - version "6.1.5" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375" - integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.1.2" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shallow-equal@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" - integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== - -shallowequal@^1.0.0, shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.7.3: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - -shelljs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sirv@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" - integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== - dependencies: - "@polka/url" "^1.0.0-next.20" - mrmime "^1.0.0" - totalist "^3.0.0" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -sitemap@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" - integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== - dependencies: - "@types/node" "^17.0.5" - "@types/sax" "^1.2.1" - arg "^5.0.0" - sax "^1.2.4" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -sort-css-media-queries@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" - integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A== - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.0, source-map@^0.5.7, source-map@~0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -space-separated-tokens@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" - integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-on-first@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" - integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -ssr-window@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be" - integrity sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ== - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -std-env@^3.0.1: - version "3.4.3" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" - integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== - -string-convert@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" - integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringify-object@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -style-to-object@0.3.0, style-to-object@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" - integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== - dependencies: - inline-style-parser "0.1.1" - -styled-components@^5.3.5, styled-components@^5.3.6: - version "5.3.11" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" - integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/traverse" "^7.4.5" - "@emotion/is-prop-valid" "^1.1.0" - "@emotion/stylis" "^0.8.4" - "@emotion/unitless" "^0.7.4" - babel-plugin-styled-components ">= 1.12.0" - css-to-react-native "^3.0.0" - hoist-non-react-statics "^3.0.0" - shallowequal "^1.1.0" - supports-color "^5.5.0" - -styled-system@5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-5.1.5.tgz#e362d73e1dbb5641a2fd749a6eba1263dc85075e" - integrity sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A== - dependencies: - "@styled-system/background" "^5.1.2" - "@styled-system/border" "^5.1.5" - "@styled-system/color" "^5.1.2" - "@styled-system/core" "^5.1.2" - "@styled-system/flexbox" "^5.1.2" - "@styled-system/grid" "^5.1.2" - "@styled-system/layout" "^5.1.2" - "@styled-system/position" "^5.1.2" - "@styled-system/shadow" "^5.1.2" - "@styled-system/space" "^5.1.2" - "@styled-system/typography" "^5.1.2" - "@styled-system/variant" "^5.1.5" - object-assign "^4.1.1" - -styled-tools@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/styled-tools/-/styled-tools-1.7.2.tgz#a8f71198535cf785d7db0927cc1c1b88337c4440" - integrity sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg== - -stylehacks@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" - integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== - dependencies: - browserslist "^4.21.4" - postcss-selector-parser "^6.0.4" - -stylis@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - -subscribe-ui-event@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/subscribe-ui-event/-/subscribe-ui-event-2.0.7.tgz#8d18b6339c35b25246a5335775573f0e5dc461f8" - integrity sha512-Acrtf9XXl6lpyHAWYeRD1xTPUQHDERfL4GHeNuYAtZMc4Z8Us2iDBP0Fn3xiRvkQ1FO+hx+qRLmPEwiZxp7FDQ== - dependencies: - eventemitter3 "^3.0.0" - lodash "^4.17.15" - raf "^3.0.0" - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svg-parser@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== - -svgo@^2.7.0, svgo@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -swiper@^9.1.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/swiper/-/swiper-9.4.1.tgz#2f48bcd6ab4b4fcf4ae93eaee53980531d42fd42" - integrity sha512-1nT2T8EzUpZ0FagEqaN/YAhRj33F2x/lN6cyB0/xoYJDMf8KwTFT3hMOeoB8Tg4o3+P/CKqskP+WX0Df046fqA== - dependencies: - ssr-window "^4.0.2" - -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -teeny-tap@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/teeny-tap/-/teeny-tap-0.2.0.tgz#167e645182d06ac222d62bb2ab67947a70a58a68" - integrity sha512-HnA3I2sxRQe/SZgQTQgQvvA17DhfzhBJ1LfSOXZ5VUTbxGLvnAqUef84ZGNNSEbk1ZMEIDeghTHZagJ7LifAgg== - -terser-webpack-plugin@^5.3.3, terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.17" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.1" - terser "^5.16.8" - -terser@^5.10.0, terser@^5.16.8: - version "5.20.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.20.0.tgz#ea42aea62578703e33def47d5c5b93c49772423e" - integrity sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" - commander "^2.20.0" - source-map-support "~0.5.20" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -through@~2.3.4: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -tiny-invariant@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" - integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== - -tiny-warning@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -totalist@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" - integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== - -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== - -tslib@1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== - -tslib@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - -tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - -tsparticles-engine@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/tsparticles-engine/-/tsparticles-engine-2.12.0.tgz#4a52a8de4ab6085180abf27f4720f47caa1455fc" - integrity sha512-ZjDIYex6jBJ4iMc9+z0uPe7SgBnmb6l+EJm83MPIsOny9lPpetMsnw/8YJ3xdxn8hV+S3myTpTN1CkOVmFv0QQ== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^2.5.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== - -ua-parser-js@^0.7.9: - version "0.7.36" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.36.tgz#382c5d6fc09141b6541be2cae446ecfcec284db2" - integrity sha512-CPPLoCts2p7D8VbybttE3P2ylv0OBZEAy7a12DsulIEcAiMtWJy+PBgMXgWDI80D5UwqE8oQPHYnk13tm38M2Q== - -ua-parser-js@^1.0.35: - version "1.0.36" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c" - integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw== - -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -unified@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" - integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" - -unified@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" - integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -unist-builder@2.0.3, unist-builder@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" - integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== - -unist-util-generated@^1.0.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" - integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== - -unist-util-is@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" - integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== - -unist-util-position@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" - integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== - -unist-util-remove-position@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" - integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== - dependencies: - unist-util-visit "^2.0.0" - -unist-util-remove@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" - integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== - dependencies: - unist-util-is "^4.0.0" - -unist-util-stringify-position@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" - integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== - dependencies: - "@types/unist" "^2.0.2" - -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.0.11: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -upper-case@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" - integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-loader@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" - integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== - dependencies: - loader-utils "^2.0.0" - mime-types "^2.1.27" - schema-utils "^3.0.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== - dependencies: - prepend-http "^2.0.0" - -use-composed-ref@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" - integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== - -use-isomorphic-layout-effect@^1.1.1, use-isomorphic-layout-effect@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" - integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== - -use-latest@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" - integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== - dependencies: - use-isomorphic-layout-effect "^1.1.1" - -use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== - -utility-types@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" - integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vfile-location@^3.0.0, vfile-location@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" - integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== - -vfile-message@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" - integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^2.0.0" - -vfile@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" - integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^2.0.0" - vfile-message "^2.0.0" - -wait-on@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" - integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== - dependencies: - axios "^0.25.0" - joi "^17.6.0" - lodash "^4.17.21" - minimist "^1.2.5" - rxjs "^7.5.4" - -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -web-namespaces@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" - integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webpack-bundle-analyzer@^4.5.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" - integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== - dependencies: - "@discoveryjs/json-ext" "0.5.7" - acorn "^8.0.4" - acorn-walk "^8.0.0" - commander "^7.2.0" - escape-string-regexp "^4.0.0" - gzip-size "^6.0.0" - is-plain-object "^5.0.0" - lodash.debounce "^4.0.8" - lodash.escape "^4.0.1" - lodash.flatten "^4.4.0" - lodash.invokemap "^4.6.0" - lodash.pullall "^4.2.0" - lodash.uniqby "^4.7.0" - opener "^1.5.2" - picocolors "^1.0.0" - sirv "^2.0.3" - ws "^7.3.1" - -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^4.9.3: - version "4.15.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7" - integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.5" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - launch-editor "^2.6.0" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.13.0" - -webpack-merge@^5.8.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826" - integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.2, webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@^5.73.0: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -webpackbar@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" - integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== - dependencies: - chalk "^4.1.0" - consola "^2.15.3" - pretty-time "^1.1.0" - std-env "^3.0.1" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha512-DIuh7/cloHxHYwS/oRXGgkALYAntijL63nsgMQsNSnBj825AysosAqA2ZbYXGRqpPRiNH7335dTqV364euRpZw== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -widest-line@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" - integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== - dependencies: - string-width "^5.0.1" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@^7.3.1: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -ws@^8.13.0: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml-js@^1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" - integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== - dependencies: - sax "^1.2.4" - -xtend@^4.0.0, xtend@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zwitch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" - integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== diff --git a/go.mod b/go.mod index dfabaaa4..4d64de4c 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,16 @@ go 1.21 require ( github.com/Masterminds/squirrel v1.5.4 - github.com/MicahParks/keyfunc v1.9.0 github.com/cenkalti/backoff/v4 v4.2.1 github.com/cespare/xxhash/v2 v2.2.0 github.com/dgraph-io/ristretto v0.1.1 github.com/dustin/go-humanize v1.0.1 - github.com/envoyproxy/protoc-gen-validate v1.0.2 + github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/fatih/color v1.16.0 - github.com/go-jose/go-jose/v3 v3.0.1 + github.com/go-jose/go-jose/v3 v3.0.3 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.3 - github.com/google/cel-go v0.18.2 + github.com/google/cel-go v0.20.1 github.com/gookit/color v1.5.4 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 @@ -23,9 +22,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.5 github.com/jackc/pgio v1.0.0 - github.com/jackc/pgtype v1.14.0 - github.com/jackc/pgx/v5 v5.5.1 + github.com/jackc/pgtype v1.14.2 + github.com/jackc/pgx/v5 v5.5.4 github.com/juju/ratelimit v1.0.2 + github.com/lestrrat-go/jwx v1.2.29 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 @@ -37,8 +37,8 @@ require ( github.com/sony/gobreaker v0.5.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 - github.com/stretchr/testify v1.8.4 - github.com/testcontainers/testcontainers-go v0.27.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.29.1 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 go.opentelemetry.io/contrib/instrumentation/host v0.46.1 go.opentelemetry.io/contrib/instrumentation/runtime v0.46.0 @@ -54,16 +54,29 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/net v0.20.0 - golang.org/x/sync v0.5.0 + golang.org/x/net v0.21.0 + golang.org/x/sync v0.6.0 google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 google.golang.org/grpc v1.60.1 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 resenje.org/singleflight v0.4.1 ) -require github.com/mattn/go-runewidth v0.0.9 // indirect +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/moby/sys/user v0.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect +) require ( dario.cat/mergo v1.0.0 // indirect @@ -72,13 +85,12 @@ require ( github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/containerd/containerd v1.7.11 // indirect + github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/docker v25.0.5+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.3.0 // indirect @@ -89,7 +101,7 @@ require ( github.com/golang/glog v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -114,8 +126,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc5 // indirect - github.com/opencontainers/runc v1.1.7 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -126,7 +137,7 @@ require ( github.com/prometheus/procfs v0.11.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/shirou/gopsutil/v3 v3.23.11 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -134,7 +145,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect @@ -143,9 +154,9 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.16.1 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect diff --git a/go.sum b/go.sum index 9a144418..d713e197 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= -github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= @@ -79,8 +77,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -95,16 +93,19 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= +github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -116,10 +117,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -127,8 +130,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -141,10 +144,11 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -179,8 +183,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cel-go v0.18.2 h1:L0B6sNBSVmt0OyECi8v6VOS74KOc9W/tLiWKfZABvf4= -github.com/google/cel-go v0.18.2/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -212,8 +216,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -290,15 +294,15 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.2 h1:QBdZQTKpPdBlw2AdKwHEyqUcm/lrl2cwWAHjCMyln/o= +github.com/jackc/pgtype v1.14.2/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c h1:Dznn52SgVIVst9UyOT9brctYUgxs+CvVfPaC3jKrA50= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= -github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -329,6 +333,19 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -359,6 +376,8 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -371,10 +390,8 @@ github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/uoCk= -github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= @@ -422,8 +439,8 @@ github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aep github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= -github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= -github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -454,8 +471,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -465,12 +483,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/testcontainers/testcontainers-go v0.27.0 h1:IeIrJN4twonTDuMuBNQdKZ+K97yd7VrmNGu+lDpYcDk= -github.com/testcontainers/testcontainers-go v0.27.0/go.mod h1:+HgYZcd17GshBUZv9b+jKFJ198heWPQq3KQIp2+N+7U= +github.com/testcontainers/testcontainers-go v0.29.1 h1:z8kxdFlovA2y97RWx98v/TQ+tR+SXZm6p35M+xB92zk= +github.com/testcontainers/testcontainers-go v0.29.1/go.mod h1:SnKnKQav8UcgtKqjp/AD8bE1MqZm+3TDb/B8crE3XnI= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -481,6 +500,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= @@ -494,6 +514,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= go.opentelemetry.io/contrib/instrumentation/host v0.46.1 h1:jLPv7OPP2CROWQ8PaUx3zONn5S4HjCJnH1syT3fnEEc= go.opentelemetry.io/contrib/instrumentation/host v0.46.1/go.mod h1:7PhaLiZ6K9zbeZNxOdr+DB8tzxWsrjVa9BcCMGuMPeA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.0 h1:dRj4IGqk65IHPLsur40gajPeQXxWWjprjeNq6aMJorU= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.0/go.mod h1:LD/bFNptUlSeHOX/6FMaAvjfvralTgFd09/EaZtV8X4= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= @@ -546,16 +568,17 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -591,8 +614,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -626,8 +651,11 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -649,8 +677,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -697,18 +727,27 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -717,6 +756,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -779,8 +820,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -888,8 +931,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index b02519a1..76ce03f3 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -8,12 +8,11 @@ import ( "io" "net/http" "strings" - "time" - "github.com/MicahParks/keyfunc" "github.com/golang-jwt/jwt/v4" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/hashicorp/go-retryablehttp" + "github.com/lestrrat-go/jwx/jwk" "github.com/Permify/permify/internal/config" base "github.com/Permify/permify/pkg/pb/base/v1" @@ -24,134 +23,126 @@ type Authenticator interface { Authenticate(ctx context.Context) error } -// Authn holds configuration for OIDC authentication, including issuer, audience, and key details. type Authn struct { - // IssuerURL is the URL of the OIDC issuer. + // URL of the issuer. This is typically the base URL of the identity provider. IssuerURL string - - // Audience is the intended audience of the tokens, typically the client ID. + // Audience for which the token is intended. It must match the audience in the JWT. Audience string - - // JwksURI is the URL to fetch the JSON Web Key Set (JWKS) from. + // URL of the JSON Web Key Set (JWKS). This URL hosts public keys used to verify JWT signatures. JwksURI string - - // JWKs holds the JWKS fetched from JwksURI for validating tokens. - JWKs *keyfunc.JWKS - - // httpClient is used to make HTTP requests, e.g., to fetch the JWKS. - httpClient *http.Client + // Pointer to an AutoRefresh object from the JWKS library. It helps in automatically refreshing the JWKS at predefined intervals. + jwksSet *jwk.AutoRefresh + // List of valid signing methods. Specifies which signing algorithms are considered valid for the JWTs. + validMethods []string + // Pointer to a JWT parser object. This is used to parse and validate the JWT tokens. + jwtParser *jwt.Parser } -// NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. -// It initializes the HTTP client with retry capabilities, sets up the OIDC issuer and audience, -// and attempts to fetch the JWKS keys from the issuer's JWKsURI. -func NewOidcAuthn(_ context.Context, audience config.Oidc) (*Authn, error) { - // Initialize a new retryable HTTP client to handle transient network errors - // by retrying failed HTTP requests. The logger is disabled for cleaner output. +// NewOidcAuthn initializes a new instance of the Authn struct with OpenID Connect (OIDC) configuration. +// It takes in a context for managing cancellation and a configuration object. It returns a pointer to an Authn instance or an error. +func NewOidcAuthn(ctx context.Context, conf config.Oidc) (*Authn, error) { + // Create a new HTTP client with retry capabilities. This client is used for making HTTP requests, particularly for fetching OIDC configuration. client := retryablehttp.NewClient() - client.Logger = nil // Disabling logging for the HTTP client + client.Logger = nil // Disable logging for the HTTP client to avoid noisy logs. + + // Fetch the OIDC configuration from the issuer's well-known configuration endpoint. + oidcConf, err := fetchOIDCConfiguration(client.StandardClient(), strings.TrimSuffix(conf.Issuer, "/")+"/.well-known/openid-configuration") + if err != nil { + // If there is an error fetching the OIDC configuration, return nil and the error. + return nil, fmt.Errorf("failed to fetch OIDC configuration: %w", err) + } + + // Set up automatic refresh of the JSON Web Key Set (JWKS) to ensure the public keys are always up-to-date. + ar := jwk.NewAutoRefresh(ctx) // Create a new AutoRefresh instance for the JWKS. + ar.Configure(oidcConf.JWKsURI, jwk.WithHTTPClient(client.StandardClient()), jwk.WithRefreshInterval(conf.RefreshInterval)) // Configure the auto-refresh parameters. - // Create a new instance of Authn with the provided issuer URL and audience. - // The httpClient is set to the standard net/http client wrapped with retry logic. + // Initialize the Authn struct with the OIDC configuration details and other relevant settings. oidc := &Authn{ - IssuerURL: audience.Issuer, - Audience: audience.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + IssuerURL: conf.Issuer, // URL of the token issuer. + Audience: conf.Audience, // Intended audience of the token. + JwksURI: oidcConf.JWKsURI, // URL of the JWKS endpoint. + validMethods: conf.ValidMethods, // List of acceptable signing methods for the tokens. + jwtParser: jwt.NewParser(jwt.WithValidMethods(conf.ValidMethods)), // JWT parser configured with the valid signing methods. + jwksSet: ar, // Set the JWKS auto-refresh instance. } - // Attempt to fetch the JWKS keys from the OIDC provider. - // This is crucial for setting up OIDC authentication as it enables token validation. - err := oidc.fetchKeys() + // Attempt to fetch the JWKS immediately to ensure it's available and valid. + _, err = oidc.jwksSet.Fetch(ctx, oidc.JwksURI) if err != nil { - // If fetching keys fails, return an error to prevent initialization of a non-functional Authn instance. - return nil, err + // If there is an error fetching the JWKS, return nil and the error. + return nil, fmt.Errorf("failed to fetch JWKS: %w", err) } - // Return the initialized Authn instance, ready for use in OIDC authentication. + // Return the initialized OIDC authentication object and no error. return oidc, nil } -// Authenticate validates the authentication token from the request context. +// Authenticate validates the JWT token found in the authorization header of the incoming request. +// It uses the OIDC configuration to validate the token against the issuer's public keys. func (oidc *Authn) Authenticate(requestContext context.Context) error { - // Extract the authentication header from the metadata in the request context. + // Extract the authorization header from the metadata of the incoming gRPC request. authHeader, err := grpcauth.AuthFromMD(requestContext, "Bearer") if err != nil { - // Return an error if the bearer token is missing from the authentication header. + // If the authorization header is missing or does not start with "Bearer", return an error. return errors.New(base.ErrorCode_ERROR_CODE_MISSING_BEARER_TOKEN.String()) } - // Initialize a new JWT parser with the RS256 signing method. - jwtParser := jwt.NewParser(jwt.WithValidMethods([]string{"RS256"})) - - // Parse and validate the JWT from the authentication header. - token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { - // Use the JWKS from oidc to validate the JWT's signature. - return oidc.JWKs.Keyfunc(token) + // Parse and validate the JWT token extracted from the authorization header. + parsedToken, err := oidc.jwtParser.Parse(authHeader, func(token *jwt.Token) (interface{}, error) { + // Fetch the public keys from the JWKS endpoint configured for the OIDC. + jwks, err := oidc.jwksSet.Fetch(requestContext, oidc.JwksURI) + if err != nil { + // If fetching the JWKS fails, return an error. + return nil, fmt.Errorf("failed to fetch JWKS: %w", err) + } + + // Retrieve the key ID from the JWT header and find the corresponding key in the JWKS. + if keyID, ok := token.Header["kid"].(string); ok { + if key, found := jwks.LookupKeyID(keyID); found { + // If the key is found, convert it to a usable format. + var k interface{} + if err := key.Raw(&k); err != nil { + return nil, fmt.Errorf("failed to get raw public key: %w", err) + } + return k, nil // Return the public key for JWT signature verification. + } + // If the specified key ID is not found in the JWKS, return an error. + return nil, fmt.Errorf("kid %s not found", keyID) + } + // If the JWT does not contain a key ID, return an error. + return nil, errors.New("kid must be specified in the token header") }) if err != nil { - // Return an error if the token is invalid (e.g., expired, wrong signature). + // If token parsing or validation fails, return an error indicating the token is invalid. return errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } - // Check if the parsed token is valid. - if !token.Valid { - // Return an error if the token is not valid. + // Ensure the token is valid. + if !parsedToken.Valid { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } // Extract the claims from the token. - claims, ok := token.Claims.(jwt.MapClaims) + claims, ok := parsedToken.Claims.(jwt.MapClaims) if !ok { + // If the claims are in an incorrect format, return an error. return errors.New(base.ErrorCode_ERROR_CODE_INVALID_CLAIMS.String()) } + // Verify the issuer of the token matches the expected issuer. if ok := claims.VerifyIssuer(oidc.IssuerURL, true); !ok { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_ISSUER.String()) } + // Verify the audience of the token matches the expected audience. if ok := claims.VerifyAudience(oidc.Audience, true); !ok { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_AUDIENCE.String()) } - // If all checks pass, the token is considered valid, and the function returns nil. + // If all validations pass, return nil indicating the token is valid. return nil } -func (oidc *Authn) fetchKeys() error { - oidcConfig, err := oidc.fetchOIDCConfiguration() - if err != nil { - return fmt.Errorf("error fetching OIDC configuration: %w", err) - } - - oidc.JwksURI = oidcConfig.JWKsURI - - jwks, err := oidc.GetKeys() - if err != nil { - return fmt.Errorf("error fetching OIDC keys: %w", err) - } - - oidc.JWKs = jwks - - return nil -} - -// GetKeys fetches the JSON Web Key Set (JWKS) from the configured JWKS URI. -func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { - // Use the keyfunc package to fetch the JWKS from the JWKS URI. - // The keyfunc.Options struct is used to configure the HTTP client used for the request - // and set a refresh interval for the keys. - jwks, err := keyfunc.Get(oidc.JwksURI, keyfunc.Options{ - Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. - RefreshInterval: 48 * time.Hour, // Set the interval to refresh the keys every 48 hours. - }) - if err != nil { - return nil, fmt.Errorf("failed to fetch keys from '%s': %s", oidc.JwksURI, err) - } - - // Return the fetched JWKS and nil for the error if successful. - return jwks, nil -} - // Config holds OpenID Connect (OIDC) configuration details. type Config struct { // Issuer is the OIDC provider's unique identifier URL. @@ -160,29 +151,31 @@ type Config struct { JWKsURI string `json:"jwks_uri"` } -// Fetches OIDC configuration using the well-known endpoint. -func (oidc *Authn) fetchOIDCConfiguration() (*Config, error) { - wellKnownURL := oidc.getWellKnownURL() - body, err := oidc.doHTTPRequest(wellKnownURL) +// fetchOIDCConfiguration sends an HTTP request to the given URL to fetch the OpenID Connect (OIDC) configuration. +// It requires an HTTP client and the URL from which to fetch the configuration. +func fetchOIDCConfiguration(client *http.Client, url string) (*Config, error) { + // Send an HTTP GET request to the provided URL to fetch the OIDC configuration. + // This typically points to the well-known configuration endpoint of the OIDC provider. + body, err := doHTTPRequest(client, url) if err != nil { + // If there is an error in fetching the configuration (network error, bad response, etc.), return nil and the error. return nil, err } + // Parse the JSON response body into an OIDC Config struct. + // This involves unmarshalling the JSON into a struct that matches the expected fields of the OIDC configuration. oidcConfig, err := parseOIDCConfiguration(body) if err != nil { + // If there is an error in parsing the JSON response (missing fields, incorrect format, etc.), return nil and the error. return nil, err } + // Return the parsed OIDC configuration and nil as the error (indicating success). return oidcConfig, nil } -// Constructs the well-known URL for fetching OIDC configuration. -func (oidc *Authn) getWellKnownURL() string { - return strings.TrimSuffix(oidc.IssuerURL, "/") + "/.well-known/openid-configuration" -} - // doHTTPRequest makes an HTTP GET request to the specified URL and returns the response body. -func (oidc *Authn) doHTTPRequest(url string) ([]byte, error) { +func doHTTPRequest(client *http.Client, url string) ([]byte, error) { // Create a new HTTP GET request. req, err := http.NewRequest("GET", url, nil) if err != nil { @@ -190,7 +183,7 @@ func (oidc *Authn) doHTTPRequest(url string) ([]byte, error) { } // Send the request using the configured HTTP client. - res, err := oidc.httpClient.Do(req) + res, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to execute HTTP request for OIDC configuration: %s", err) } @@ -226,12 +219,9 @@ func parseOIDCConfiguration(body []byte) (*Config, error) { } if oidcConfig.JWKsURI == "" { + return nil, errors.New("JWKsURI value is required but missing in OIDC configuration") } // Return the successfully parsed configuration. return &oidcConfig, nil } - -func (oidc *Authn) Close() { - oidc.JWKs.EndBackground() -} diff --git a/internal/config/config.go b/internal/config/config.go index 721436cb..8d4411e4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,8 +69,10 @@ type ( // Oidc contains configuration for OIDC authentication. Oidc struct { - Issuer string `mapstructure:"issuer"` // OIDC issuer URL - Audience string `mapstructure:"audience"` // OIDC client ID + Issuer string `mapstructure:"issuer"` // OIDC issuer URL + Audience string `mapstructure:"audience"` // OIDC client ID + RefreshInterval time.Duration `mapstructure:"refresh_interval"` + ValidMethods []string `mapstructure:"valid_methods"` } // Profiler contains configuration for the profiler. @@ -300,7 +302,10 @@ func DefaultConfig() *Config { Authn: Authn{ Enabled: false, Preshared: Preshared{}, - Oidc: Oidc{}, + Oidc: Oidc{ + RefreshInterval: time.Minute * 15, + ValidMethods: []string{"RS256", "HS256"}, + }, }, Database: Database{ Engine: "memory", diff --git a/internal/info.go b/internal/info.go index 70aa4f22..cc29803d 100644 --- a/internal/info.go +++ b/internal/info.go @@ -23,7 +23,7 @@ var Identifier = "" */ const ( // Version is the last release of the Permify (e.g. v0.1.0) - Version = "v0.7.6" + Version = "v0.7.8" ) // Function to create a single line of the ASCII art with centered content and color diff --git a/internal/storage/postgres/postgres_test.go b/internal/storage/postgres/postgres_test.go index 2fec9386..c56dc779 100644 --- a/internal/storage/postgres/postgres_test.go +++ b/internal/storage/postgres/postgres_test.go @@ -3,13 +3,13 @@ package postgres import ( "context" "fmt" - "sort" - "testing" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "sort" + "testing" + `time` "github.com/Permify/permify/internal/config" "github.com/Permify/permify/internal/storage" @@ -36,32 +36,30 @@ func postgresDB(postgresVersion string) database.Database { }, Started: true, }) - if err != nil { - Expect(err).ShouldNot(HaveOccurred()) - } + Expect(err).ShouldNot(HaveOccurred()) // Execute the command in the container _, _, execErr := postgres.Exec(ctx, []string{"psql", "-U", "postgres", "-c", "ALTER SYSTEM SET track_commit_timestamp = on;"}) - if execErr != nil { - Expect(execErr).ShouldNot(HaveOccurred()) - } + Expect(execErr).ShouldNot(HaveOccurred()) + + stopTimeout := 2 * time.Second + err = postgres.Stop(context.Background(), &stopTimeout) + Expect(err).ShouldNot(HaveOccurred()) + + err = postgres.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) cmd := []string{"sh", "-c", "export PGPASSWORD=postgres" + "; psql -U postgres -d permify -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'"} _, _, err = postgres.Exec(ctx, cmd) - if err != nil { - Expect(err).ShouldNot(HaveOccurred()) - } + Expect(err).ShouldNot(HaveOccurred()) host, err := postgres.Host(ctx) - if err != nil { - Expect(err).ShouldNot(HaveOccurred()) - } + Expect(err).ShouldNot(HaveOccurred()) port, err := postgres.MappedPort(ctx, "5432") - if err != nil { - Expect(err).ShouldNot(HaveOccurred()) - } + Expect(err).ShouldNot(HaveOccurred()) + dbAddr := fmt.Sprintf("%s:%s", host, port.Port()) postgresDSN := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", "postgres", "postgres", dbAddr, "permify") diff --git a/internal/storage/postgres/utils/common.go b/internal/storage/postgres/utils/common.go index a8291d78..8d95bc3c 100644 --- a/internal/storage/postgres/utils/common.go +++ b/internal/storage/postgres/utils/common.go @@ -123,17 +123,12 @@ func GenerateGCQuery(table string, value uint64) squirrel.DeleteBuilder { // HandleError records an error in the given span, logs the error, and returns a standardized error. // This function is used for consistent error handling across different parts of the application. func HandleError(ctx context.Context, span trace.Span, err error, errorCode base.ErrorCode) error { - // Record the error on the span - span.RecordError(err) - // Check if the error is context-related if IsContextRelatedError(ctx, err) || IsSerializationRelatedError(err) { - // Set the status of the span - span.SetStatus(codes.Unset, err.Error()) // Use debug level logging for context or serialization-related errors slog.Debug("an error related to context or serialization was encountered during the operation", slog.String("error", err.Error())) } else { - // Set the status of the span + span.RecordError(err) span.SetStatus(codes.Error, err.Error()) // Use error level logging for all other errors slog.Error("error encountered", slog.Any("error", err)) diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index 802ffdd6..9307bfbf 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -1,9 +1,11 @@ package cmd import ( + "fmt" "os" "strings" + "github.com/Permify/permify/internal/config" "github.com/Permify/permify/pkg/cmd/flags" "github.com/gookit/color" @@ -15,12 +17,9 @@ import ( func NewConfigCommand() *cobra.Command { cmd := &cobra.Command{ Use: "config", - Short: "Inspect permify configuration and environment variables ", - RunE: func(cmd *cobra.Command, args []string) error { - data := prepareConfigData(cmd) - renderConfigTable(data) - return nil - }, + Short: "Inspect permify configuration and environment variables", + RunE: conf(), + Args: cobra.NoArgs, } flags.RegisterServeFlags(cmd) @@ -28,71 +27,162 @@ func NewConfigCommand() *cobra.Command { return cmd } -func prepareConfigData(cmd *cobra.Command) [][]string { - data := [][]string{} - for _, key := range viper.AllKeys() { - viperKey, viperValue, source := getConfigDetails(key, cmd) - data = append(data, []string{color.FgCyan.Render(viperKey), viperValue, source}) - } - return data -} +func conf() func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var cfg *config.Config + var err error + cfgFile := viper.GetString("config.file") + if cfgFile != "" { + cfg, err = config.NewConfigWithFile(cfgFile) + if err != nil { + return fmt.Errorf("failed to create new config: %w", err) + } -func getConfigDetails(key string, cmd *cobra.Command) (string, string, string) { - envKey := strings.ToUpper(strings.ReplaceAll(key, ".", "_")) - viperKey := "PERMIFY_" + envKey - viperValue := viper.GetString(key) - envValue, envExists := os.LookupEnv(viperKey) + if err = viper.Unmarshal(cfg); err != nil { + return fmt.Errorf("failed to unmarshal config: %w", err) + } + } else { + // Load configuration + cfg, err = config.NewConfig() + if err != nil { + return fmt.Errorf("failed to create new config: %w", err) + } - source := getKeyOrigin(envExists, cmd, key) - value := viperValue - if envExists { - value = envValue + if err = viper.Unmarshal(cfg); err != nil { + return fmt.Errorf("failed to unmarshal config: %w", err) + } + } + + var data [][]string + + data = append(data, + []string{"account_id", cfg.AccountID, getKeyOrigin(cmd, "account-id", "PERMIFY_ACCOUNT_ID")}, + // SERVER + []string{"server.rate_limit", fmt.Sprintf("%v", cfg.Server.RateLimit), getKeyOrigin(cmd, "server-rate-limit", "PERMIFY_RATE_LIMIT")}, + []string{"server.grpc.port", cfg.Server.GRPC.Port, getKeyOrigin(cmd, "grpc-port", "PERMIFY_GRPC_PORT")}, + []string{"server.grpc.tls.enabled", fmt.Sprintf("%v", cfg.Server.GRPC.TLSConfig.Enabled), getKeyOrigin(cmd, "grpc-tls-enabled", "PERMIFY_GRPC_TLS_ENABLED")}, + []string{"server.grpc.tls.cert", cfg.Server.GRPC.TLSConfig.CertPath, getKeyOrigin(cmd, "grpc-tls-cert-path", "PERMIFY_GRPC_TLS_CERT_PATH")}, + []string{"server.http.enabled", fmt.Sprintf("%v", cfg.Server.HTTP.Enabled), getKeyOrigin(cmd, "http-enabled", "PERMIFY_HTTP_ENABLED")}, + []string{"server.http.tls.enabled", fmt.Sprintf("%v", cfg.Server.HTTP.TLSConfig.Enabled), getKeyOrigin(cmd, "http-tls-enabled", "PERMIFY_HTTP_TLS_ENABLED")}, + []string{"server.http.tls.key", HideSecret(cfg.Server.HTTP.TLSConfig.KeyPath), getKeyOrigin(cmd, "http-tls-key-path", "PERMIFY_HTTP_TLS_KEY_PATH")}, + []string{"server.http.tls.cert", HideSecret(cfg.Server.HTTP.TLSConfig.CertPath), getKeyOrigin(cmd, "http-tls-cert-path", "PERMIFY_HTTP_TLS_CERT_PATH")}, + []string{"server.http.cors_allowed_origins", fmt.Sprintf("%v", cfg.Server.HTTP.CORSAllowedOrigins), getKeyOrigin(cmd, "http-cors-allowed-origins", "PERMIFY_HTTP_CORS_ALLOWED_ORIGINS")}, + []string{"server.http.cors_allowed_headers", fmt.Sprintf("%v", cfg.Server.HTTP.CORSAllowedHeaders), getKeyOrigin(cmd, "http-cors-allowed-headers", "PERMIFY_HTTP_CORS_ALLOWED_HEADERS")}, + // PROFILER + []string{"profiler.enabled", fmt.Sprintf("%v", cfg.Profiler.Enabled), getKeyOrigin(cmd, "profiler-enabled", "PERMIFY_PROFILER_ENABLED")}, + []string{"profiler.port", cfg.Profiler.Port, getKeyOrigin(cmd, "profiler-port", "PERMIFY_PROFILER_PORT")}, + // LOG + []string{"logger.level", cfg.Log.Level, getKeyOrigin(cmd, "log-level", "PERMIFY_LOG_LEVEL")}, + []string{"logger.output", cfg.Log.Level, getKeyOrigin(cmd, "log-output", "PERMIFY_LOG_OUTPUT")}, + // AUTHN + []string{"authn.enabled", fmt.Sprintf("%v", cfg.Authn.Enabled), getKeyOrigin(cmd, "authn-enabled", "PERMIFY_AUTHN_ENABLED")}, + []string{"authn.method", cfg.Authn.Method, getKeyOrigin(cmd, "authn-method", "PERMIFY_AUTHN_METHOD")}, + []string{"authn.preshared.keys", fmt.Sprintf("%v", HideSecrets(cfg.Authn.Preshared.Keys...)), getKeyOrigin(cmd, "authn-preshared-keys", "PERMIFY_AUTHN_PRESHARED_KEYS")}, + []string{"authn.oidc.issuer", HideSecret(cfg.Authn.Oidc.Issuer), getKeyOrigin(cmd, "authn-oidc-issuer", "PERMIFY_AUTHN_OIDC_ISSUER")}, + []string{"authn.oidc.audience", HideSecret(cfg.Authn.Oidc.Audience), getKeyOrigin(cmd, "authn-oidc-audience", "PERMIFY_AUTHN_OIDC_AUDIENCE")}, + // TRACER + []string{"tracer.enabled", fmt.Sprintf("%v", cfg.Tracer.Enabled), getKeyOrigin(cmd, "tracer-enabled", "PERMIFY_TRACER_ENABLED")}, + []string{"tracer.exporter", cfg.Tracer.Exporter, getKeyOrigin(cmd, "tracer-exporter", "PERMIFY_TRACER_EXPORTER")}, + []string{"tracer.endpoint", HideSecret(cfg.Tracer.Exporter), getKeyOrigin(cmd, "tracer-endpoint", "PERMIFY_TRACER_ENDPOINT")}, + []string{"tracer.insecure", fmt.Sprintf("%v", cfg.Tracer.Insecure), getKeyOrigin(cmd, "tracer-insecure", "PERMIFY_TRACER_INSECURE")}, + []string{"tracer.urlpath", cfg.Tracer.URLPath, getKeyOrigin(cmd, "tracer-urlpath", "PERMIFY_TRACER_URL_PATH")}, + // METER + []string{"meter.enabled", fmt.Sprintf("%v", cfg.Meter.Enabled), getKeyOrigin(cmd, "meter-enabled", "PERMIFY_METER_ENABLED")}, + []string{"meter.exporter", cfg.Meter.Exporter, getKeyOrigin(cmd, "meter-exporter", "PERMIFY_METER_EXPORTER")}, + []string{"meter.endpoint", HideSecret(cfg.Meter.Exporter), getKeyOrigin(cmd, "meter-endpoint", "PERMIFY_METER_ENDPOINT")}, + []string{"meter.insecure", fmt.Sprintf("%v", cfg.Meter.Insecure), getKeyOrigin(cmd, "meter-insecure", "PERMIFY_METER_INSECURE")}, + []string{"meter.urlpath", cfg.Meter.URLPath, getKeyOrigin(cmd, "meter-urlpath", "PERMIFY_METER_URL_PATH")}, + // SERVICE + []string{"service.circuit_breaker", fmt.Sprintf("%v", cfg.Service.CircuitBreaker), getKeyOrigin(cmd, "service-circuit-breaker", "PERMIFY_SERVICE_CIRCUIT_BREAKER")}, + []string{"service.schema.cache.number_of_counters", fmt.Sprintf("%v", cfg.Service.Schema.Cache.NumberOfCounters), getKeyOrigin(cmd, "service-schema-cache-number-of-counters", "PERMIFY_SERVICE_WATCH_ENABLED")}, + []string{"service.schema.cache.max_cost", cfg.Service.Schema.Cache.MaxCost, getKeyOrigin(cmd, "service-schema-cache-max-cost", "PERMIFY_SERVICE_SCHEMA_CACHE_MAX_COST")}, + []string{"service.permission.bulk_limit", fmt.Sprintf("%v", cfg.Service.Permission.BulkLimit), getKeyOrigin(cmd, "service-permission-bulk-limit", "PERMIFY_SERVICE_PERMISSION_BULK_LIMIT")}, + []string{"service.permission.concurrency_limit", fmt.Sprintf("%v", cfg.Service.Permission.ConcurrencyLimit), getKeyOrigin(cmd, "service-permission-concurrency-limit", "PERMIFY_SERVICE_PERMISSION_CONCURRENCY_LIMIT")}, + []string{"service.permission.cache.number_of_counters", fmt.Sprintf("%v", cfg.Service.Permission.Cache.NumberOfCounters), getKeyOrigin(cmd, "service-permission-cache-number-of-counters", "PERMIFY_SERVICE_PERMISSION_CACHE_NUMBER_OF_COUNTERS")}, + []string{"service.permission.cache.max_cost", fmt.Sprintf("%v", cfg.Service.Permission.Cache.MaxCost), getKeyOrigin(cmd, "service-permission-cache-max-cost", "PERMIFY_SERVICE_PERMISSION_CACHE_MAX_COST")}, + // DATABASE + []string{"database.engine", cfg.Database.Engine, getKeyOrigin(cmd, "database-engine", "PERMIFY_DATABASE_ENGINE")}, + []string{"database.uri", HideSecret(cfg.Database.URI), getKeyOrigin(cmd, "database-uri", "PERMIFY_DATABASE_URI")}, + []string{"database.auto_migrate", fmt.Sprintf("%v", cfg.Database.AutoMigrate), getKeyOrigin(cmd, "database-auto-migrate", "PERMIFY_DATABASE_AUTO_MIGRATE")}, + []string{"database.max_open_connections", fmt.Sprintf("%v", cfg.Database.MaxOpenConnections), getKeyOrigin(cmd, "database-max-open-connections", "PERMIFY_DATABASE_MAX_OPEN_CONNECTIONS")}, + []string{"database.max_idle_connections", fmt.Sprintf("%v", cfg.Database.MaxIdleConnections), getKeyOrigin(cmd, "database-max-idle-connections", "PERMIFY_DATABASE_MAX_IDLE_CONNECTIONS")}, + []string{"database.max_connection_lifetime", fmt.Sprintf("%v", cfg.Database.MaxConnectionLifetime), getKeyOrigin(cmd, "database-max-connection-lifetime", "PERMIFY_DATABASE_MAX_CONNECTION_LIFETIME")}, + []string{"database.max_connection_idle_time", fmt.Sprintf("%v", cfg.Database.MaxConnectionIdleTime), getKeyOrigin(cmd, "database-max-connection-idle-time", "PERMIFY_DATABASE_MAX_CONNECTION_IDLE_TIME")}, + []string{"database.garbage_collection.enabled", fmt.Sprintf("%v", cfg.Database.GarbageCollection.Enabled), getKeyOrigin(cmd, "database-garbage-collection-enabled", "PERMIFY_DATABASE_GARBAGE_COLLECTION_ENABLED")}, + []string{"database.garbage_collection.interval", fmt.Sprintf("%v", cfg.Database.GarbageCollection.Interval), getKeyOrigin(cmd, "database-garbage-collection-interval", "PERMIFY_DATABASE_GARBAGE_COLLECTION_INTERVAL")}, + []string{"database.garbage_collection.timeout", fmt.Sprintf("%v", cfg.Database.GarbageCollection.Timeout), getKeyOrigin(cmd, "database-garbage-collection-timeout", "PERMIFY_DATABASE_GARBAGE_COLLECTION_TIMEOUT")}, + []string{"database.garbage_collection.window", fmt.Sprintf("%v", cfg.Database.GarbageCollection.Window), getKeyOrigin(cmd, "database-garbage-collection-window", "PERMIFY_DATABASE_GARBAGE_COLLECTION_WINDOW")}, + // DISTRIBUTED + []string{"distributed.enabled", fmt.Sprintf("%v", cfg.Distributed.Enabled), getKeyOrigin(cmd, "distributed-enabled", "PERMIFY_DISTRIBUTED_ENABLED")}, + []string{"distributed.address", cfg.Distributed.Address, getKeyOrigin(cmd, "distributed-address", "PERMIFY_DISTRIBUTED_ADDRESS")}, + []string{"distributed.port", cfg.Distributed.Port, getKeyOrigin(cmd, "distributed-port", "PERMIFY_DISTRIBUTED_PORT")}, + ) + + renderConfigTable(data) + return nil } - return viperKey, obfuscateSecrets(viperKey, value), source } -func getKeyOrigin(envExists bool, cmd *cobra.Command, key string) string { - if cmd.Flags().Changed(strings.ReplaceAll(key, ".", "-")) { - return color.FgLightGreen.Render("flag") +// getKeyOrigin determines the source of a configuration value. +// It checks whether a value was set via a command-line flag, an environment variable, or defaults to file. +func getKeyOrigin(cmd *cobra.Command, flagKey, envKey string) string { + // Check if the command-line flag (specified by flagKey) was explicitly set by the user. + if cmd.Flags().Changed(flagKey) { + // If the flag was set, return "FLAG" with light green color. + return color.FgLightGreen.Render("FLAG") } - if envExists { - return color.FgLightBlue.Render("env") + + // Check if the environment variable (specified by envKey) exists. + _, exists := os.LookupEnv(envKey) + if exists { + // If the environment variable exists, return "ENV" with light blue color. + return color.FgLightBlue.Render("ENV") } - return color.FgYellow.Render("file") + + // If neither the command-line flag nor the environment variable was set, + // assume the value came from a configuration file. + return color.FgYellow.Render("FILE") } +// renderConfigTable displays configuration data in a formatted table on the console. +// It takes a 2D slice of strings where each inner slice represents a row in the table. func renderConfigTable(data [][]string) { + // Create a new table writer object, writing to standard output. table := tablewriter.NewWriter(os.Stdout) + + // Set the headers of the table. Each header cell is a column title. table.SetHeader([]string{"Key", "Value", "Source"}) + + // Align the columns of the table: left-aligned for keys, centered for values and sources. table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER}) + + // Set the center separator character for the table, which appears between columns. table.SetCenterSeparator("|") + + // Loop through the data and add each row to the table. for _, v := range data { table.Append(v) } + + // Render the table to standard output, displaying it to the user. table.Render() } -func obfuscateSecrets(key, value string) string { - secrets := []string{ - "PERMIFY_DATABASE_URI", +// HideSecret replaces all but the first and last characters of a string with asterisks +func HideSecret(secret string) string { + if len(secret) <= 2 { + // If the secret is too short, just return asterisks + return strings.Repeat("*", len(secret)) } + // Keep first and last character visible; replace the rest with asterisks + return string(secret[0]) + strings.Repeat("*", len(secret)-2) + string(secret[len(secret)-1]) +} - for _, wKey := range secrets { - if key == wKey { - if len(value) < 3 { - return value - } - if lastColon := strings.LastIndex(value, ":"); lastColon != -1 { - typeEnd := strings.Index(value[lastColon:], "/") - if typeEnd != -1 { - if len(value)-3 > lastColon+typeEnd+1 { - return value[:lastColon+typeEnd+1] + "***" + value[len(value)-3:] - } - return value - } - } - return value[:len(value)-3] + "***" - } +// HideSecrets obscures each string in a given list. +func HideSecrets(secrets ...string) (rv []string) { + // Convert each secret to its hidden version and collect them. + for _, secret := range secrets { + rv = append(rv, HideSecret(secret)) // Hide each secret. } - return value + return } diff --git a/pkg/cmd/flags/serve.go b/pkg/cmd/flags/serve.go index 17a57f7b..2e255566 100644 --- a/pkg/cmd/flags/serve.go +++ b/pkg/cmd/flags/serve.go @@ -195,7 +195,7 @@ func RegisterServeFlags(cmd *cobra.Command) { panic(err) } - flags.String("authn-oidc-audience", conf.Authn.Oidc.Audience, "") + flags.String("authn-oidc-audience", conf.Authn.Oidc.Audience, "intended audience of the OpenID Connect token") if err = viper.BindPFlag("authn.oidc.audience", flags.Lookup("authn-oidc-audience")); err != nil { panic(err) } @@ -203,6 +203,22 @@ func RegisterServeFlags(cmd *cobra.Command) { panic(err) } + flags.Duration("authn-oidc-refresh-interval", conf.Authn.Oidc.RefreshInterval, "refresh interval for the OpenID Connect configuration") + if err = viper.BindPFlag("authn.oidc.refresh_interval", flags.Lookup("authn-oidc-refresh-interval")); err != nil { + panic(err) + } + if err = viper.BindEnv("authn.oidc.refresh_interval", "PERMIFY_AUTHN_OIDC_REFRESH_INTERVAL"); err != nil { + panic(err) + } + + flags.StringSlice("authn-oidc-valid-methods", conf.Authn.Oidc.ValidMethods, "list of valid JWT signing methods for OpenID Connect") + if err = viper.BindPFlag("authn.oidc.valid_methods", flags.Lookup("authn-oidc-valid-methods")); err != nil { + panic(err) + } + if err = viper.BindEnv("authn.oidc.valid_methods", "PERMIFY_AUTHN_OIDC_VALID_METHODS"); err != nil { + panic(err) + } + // TRACER flags.Bool("tracer-enabled", conf.Tracer.Enabled, "switch option for tracing") if err = viper.BindPFlag("tracer.enabled", flags.Lookup("tracer-enabled")); err != nil { @@ -439,7 +455,7 @@ func RegisterServeFlags(cmd *cobra.Command) { panic(err) } - // Distributed + // DISTRIBUTED flags.Bool("distributed-enabled", conf.Distributed.Enabled, "enable distributed") if err = viper.BindPFlag("distributed.enabled", flags.Lookup("distributed-enabled")); err != nil { panic(err) diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index 220e93fc..d4ecf6b7 100644 --- a/pkg/cmd/serve.go +++ b/pkg/cmd/serve.go @@ -52,6 +52,9 @@ func NewServeCommand() *cobra.Command { Args: cobra.NoArgs, } + // SilenceUsage is set to true to suppress usage when an error occurs + command.SilenceUsage = true + // register flags for serve flags.RegisterServeFlags(command) diff --git a/pkg/pb/base/v1/openapi.pb.go b/pkg/pb/base/v1/openapi.pb.go index c6e84253..a1fa8e87 100644 --- a/pkg/pb/base/v1/openapi.pb.go +++ b/pkg/pb/base/v1/openapi.pb.go @@ -46,7 +46,7 @@ var file_base_v1_openapi_proto_rawDesc = []byte{ 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, - 0x32, 0x06, 0x76, 0x30, 0x2e, 0x37, 0x2e, 0x36, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, + 0x32, 0x06, 0x76, 0x30, 0x2e, 0x37, 0x2e, 0x38, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x23, 0x0a, 0x21, 0x0a, 0x0a, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, diff --git a/pkg/pb/base/v1/service.pb.go b/pkg/pb/base/v1/service.pb.go index 7046607a..59b8b8e4 100644 --- a/pkg/pb/base/v1/service.pb.go +++ b/pkg/pb/base/v1/service.pb.go @@ -126,7 +126,7 @@ func (x *PermissionCheckRequest) GetArguments() []*Argument { return nil } -// PermissionCheckRequestMetadata is the metadata associated with a PermissionCheckRequest. +// PermissionCheckRequestMetadata metadata for the PermissionCheckRequest. type PermissionCheckRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -251,7 +251,7 @@ func (x *PermissionCheckResponse) GetMetadata() *PermissionCheckResponseMetadata return nil } -// PermissionCheckResponseMetadata is the metadata associated with a PermissionCheckResponse. +// PermissionCheckResponseMetadata metadata for the PermissionCheckResponse. type PermissionCheckResponseMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -394,7 +394,7 @@ func (x *PermissionExpandRequest) GetArguments() []*Argument { return nil } -// PermissionExpandRequestMetadata is the metadata associated with a PermissionExpandRequest. +// PermissionExpandRequestMetadata metadata for the PermissionExpandRequest. type PermissionExpandRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -595,7 +595,7 @@ func (x *PermissionLookupEntityRequest) GetContext() *Context { return nil } -// PermissionLookupEntityRequestMetadata is the metadata associated with a PermissionLookupEntityRequest. +// PermissionLookupEntityRequestMetadata metadata for the PermissionLookupEntityRequest. type PermissionLookupEntityRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -845,7 +845,7 @@ func (x *PermissionEntityFilterRequest) GetContext() *Context { return nil } -// PermissionEntityFilterRequestMetadata is the metadata associated with a PermissionEntityFilterRequest. +// PermissionEntityFilterRequestMetadata metadata for the PermissionEntityFilterRequest. type PermissionEntityFilterRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1006,7 +1006,7 @@ func (x *PermissionLookupSubjectRequest) GetContext() *Context { return nil } -// PermissionLookupSubjectRequestMetadata is the metadata associated with a PermissionLookupSubjectRequest. +// PermissionLookupSubjectRequestMetadata metadata for the PermissionLookupSubjectRequest. type PermissionLookupSubjectRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1207,7 +1207,7 @@ func (x *PermissionSubjectPermissionRequest) GetContext() *Context { return nil } -// PermissionSubjectPermissionRequestMetadata is the metadata associated with a PermissionSubjectPermissionRequest. +// PermissionSubjectPermissionRequestMetadata metadata for the PermissionSubjectPermissionRequest. type PermissionSubjectPermissionRequestMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2935,7 +2935,7 @@ type BundleRunResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SnapToken string `protobuf:"bytes,1,opt,name=snap_token,proto3" json:"snap_token,omitempty"` // Token related to the bundle execution. + SnapToken string `protobuf:"bytes,1,opt,name=snap_token,proto3" json:"snap_token,omitempty"` } func (x *BundleRunResponse) Reset() { @@ -3624,324 +3624,562 @@ var file_base_v1_service_proto_rawDesc = []byte{ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x03, 0x0a, 0x16, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x07, 0x0a, 0x16, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, - 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, - 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, - 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, - 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xfa, 0x42, 0x1a, 0x72, - 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5f, 0x5d, 0x7b, - 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, - 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x2f, 0x0a, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x61, 0x73, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x61, - 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x87, 0x01, 0x0a, 0x1e, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, - 0x74, 0x68, 0x22, 0x87, 0x01, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, - 0x0a, 0x03, 0x63, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x03, 0x63, 0x61, 0x6e, 0x12, 0x44, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, 0x0a, 0x1f, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, + 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, + 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, + 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, + 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, + 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x12, 0x4d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x20, 0x0a, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x22, 0xf2, 0x02, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x28, 0x40, 0x32, 0x0e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, - 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, - 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, + 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x44, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x42, 0x1b, 0x92, 0x41, 0x10, 0x4a, 0x0e, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x3a, 0x31, 0x22, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, + 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x76, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x56, 0x92, 0x41, + 0x36, 0x32, 0x34, 0x54, 0x68, 0x65, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, + 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, + 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0xc4, 0x01, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x42, 0x97, 0x01, 0x92, 0x41, 0x93, + 0x01, 0x32, 0x90, 0x01, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x20, 0x64, + 0x61, 0x74, 0x61, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, + 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x20, 0x44, 0x61, 0x74, 0x61, 0x5d, + 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x2d, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x73, 0x29, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x2f, 0x0a, + 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xb2, + 0x02, 0x0a, 0x1e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, + 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, + 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, + 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, + 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, + 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, + 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, + 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x42, 0x46, 0x92, 0x41, 0x3c, 0x32, 0x3a, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x66, 0x20, 0x72, + 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x20, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x69, 0x6e, + 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, + 0x70, 0x74, 0x68, 0x22, 0x87, 0x01, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x26, 0x0a, 0x03, 0x63, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x03, 0x63, 0x61, 0x6e, 0x12, 0x44, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, - 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, - 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, - 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x01, 0x52, 0x0a, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x2f, 0x0a, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x61, 0x72, 0x67, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x1f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x3f, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, - 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, - 0x04, 0x74, 0x72, 0x65, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x04, 0x74, 0x72, - 0x65, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x1d, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, - 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, - 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, - 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x12, 0x54, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xfa, - 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, - 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0b, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xfa, - 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, - 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0a, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, - 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, - 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x8e, 0x01, 0x0a, 0x25, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x05, - 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, - 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x40, 0x0a, 0x1e, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, - 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x44, 0x0a, - 0x24, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, - 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x5f, 0x69, 0x64, 0x22, 0xe3, 0x02, 0x0a, 0x1d, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, 0x0a, + 0x1f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0xe5, 0x04, 0x0a, 0x17, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, + 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, + 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, + 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, + 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, + 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, + 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, + 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, + 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1d, 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, + 0x01, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x2f, 0x0a, 0x09, 0x61, 0x72, 0x67, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xd6, 0x01, 0x0a, 0x1f, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, + 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, + 0x32, 0x65, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, + 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x3f, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x23, 0x0a, 0x04, 0x74, 0x72, 0x65, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x04, + 0x74, 0x72, 0x65, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x1d, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, + 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, + 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, + 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x54, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x10, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x0a, - 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x8e, 0x01, 0x0a, 0x25, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x73, - 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x64, - 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, - 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0xad, 0x03, 0x0a, 0x1e, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, - 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, - 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, - 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x55, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, - 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, - 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xfa, 0x42, 0x1a, 0x72, 0x18, - 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5f, 0x5d, 0x7b, 0x31, - 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x11, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2a, - 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x8f, 0x01, 0x0a, 0x26, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, 0x0a, 0x0b, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, + 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0b, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, + 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x0a, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xbb, 0x02, 0x0a, 0x25, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, - 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x43, 0x0a, 0x1f, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, - 0x73, 0x22, 0xe2, 0x02, 0x0a, 0x22, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, - 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, - 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, - 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x59, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, - 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, - 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x8a, 0x01, + 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, 0x32, 0x65, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, + 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, + 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, + 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, + 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, + 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x2e, 0x52, 0x0a, + 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5d, 0x0a, 0x05, 0x64, 0x65, + 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x47, 0x92, 0x41, 0x3d, 0x32, 0x3b, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, + 0x20, 0x69, 0x66, 0x20, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x20, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x67, + 0x6f, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0xfa, 0x42, 0x04, 0x1a, 0x02, + 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x40, 0x0a, 0x1e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x44, 0x0a, 0x24, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, + 0x64, 0x22, 0xc2, 0x04, 0x0a, 0x1d, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, + 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, + 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, + 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, + 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, + 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, + 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, + 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, + 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, + 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, + 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, + 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x12, 0x54, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x10, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x10, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2a, + 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xbd, 0x01, 0x0a, 0x2a, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, - 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x28, 0x0a, - 0x0f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, - 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0xcc, 0x01, 0x0a, 0x23, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, - 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x39, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x1a, 0x50, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x0c, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x28, - 0x40, 0x32, 0x0e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, - 0x2b, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, - 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, - 0x3f, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, - 0x22, 0x7a, 0x0a, 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xbb, 0x02, 0x0a, 0x25, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, + 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, + 0x41, 0x67, 0x32, 0x65, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, + 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, + 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, + 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, + 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, + 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5d, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x42, 0x47, 0x92, 0x41, 0x3d, 0x32, 0x3b, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x66, 0x20, 0x72, + 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x20, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x69, 0x6e, + 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, + 0x65, 0x70, 0x74, 0x68, 0x22, 0x8c, 0x05, 0x0a, 0x1e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, + 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, + 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, + 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, + 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, + 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, + 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, + 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, + 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0x3d, 0x0a, 0x13, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xab, 0x01, 0x0a, 0x11, - 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, - 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, - 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, - 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, - 0x48, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, 0x0a, 0x19, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x47, - 0x0a, 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0xc2, 0x01, 0x0a, 0x11, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, + 0x74, 0x5f, 0x69, 0x64, 0x12, 0x55, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, + 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3d, + 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x1d, 0xfa, 0x42, 0x1a, 0x72, 0x18, 0x28, 0x40, 0x32, 0x11, 0x5e, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x5f, 0x5d, 0x7b, 0x31, 0x2c, 0x36, 0x34, 0x7d, 0x24, 0xd0, 0x01, + 0x00, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, + 0x11, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x52, 0x11, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x22, 0xbc, 0x02, 0x0a, 0x26, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, + 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, + 0x32, 0x65, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, + 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x5d, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x42, 0x47, 0x92, 0x41, 0x3d, 0x32, 0x3b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x20, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x66, 0x20, 0x72, 0x65, 0x63, + 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x20, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x67, 0x6f, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x6c, + 0x6f, 0x6f, 0x70, 0x2e, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, + 0x74, 0x68, 0x22, 0x43, 0x0a, 0x1f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0xc1, 0x04, 0x0a, 0x22, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, + 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, + 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, + 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, + 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, + 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, - 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0b, - 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, - 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, - 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, - 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, - 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x46, 0x0a, 0x0a, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x22, 0xab, 0x02, 0x0a, 0x10, 0x44, - 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, + 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x59, 0x0a, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, + 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, + 0x01, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x2a, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xea, 0x02, 0x0a, 0x2a, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, 0x32, 0x65, 0x54, 0x68, + 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, + 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, + 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x28, 0x0a, 0x0f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x5d, 0x0a, 0x05, 0x64, 0x65, 0x70, + 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x47, 0x92, 0x41, 0x3d, 0x32, 0x3b, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, + 0x69, 0x66, 0x20, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x20, 0x64, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x20, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x67, 0x6f, + 0x74, 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x2e, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, + 0x03, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0xcc, 0x01, 0x0a, 0x23, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x53, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x39, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x1a, 0x50, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc8, 0x03, 0x0a, 0x0c, 0x57, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, + 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, + 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, + 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, + 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, + 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, + 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, + 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, + 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, + 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, + 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, + 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, 0x32, + 0x65, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, + 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x3f, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x73, 0x22, 0xd9, 0x02, 0x0a, 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, + 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, + 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, + 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, + 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, + 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, + 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, + 0x3d, 0x0a, 0x13, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8a, + 0x03, 0x0a, 0x11, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, + 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, + 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, + 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, + 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, + 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, + 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, + 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, + 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, + 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, + 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x12, 0x48, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, + 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x43, 0x0a, 0x19, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x47, 0x0a, 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0xa1, 0x03, 0x0a, 0x11, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, + 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, + 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, + 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, + 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, + 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, + 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, + 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, + 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, + 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x0b, 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, + 0x0a, 0x12, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x46, 0x0a, 0x0a, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x22, 0x8a, 0x04, 0x0a, 0x10, + 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, + 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, + 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, + 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, + 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, + 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x47, 0x0a, @@ -3961,500 +4199,2227 @@ var file_base_v1_service_proto_rawDesc = []byte{ 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x33, 0x0a, 0x11, - 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xf4, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, - 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, - 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, - 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, - 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, - 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4f, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, - 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, - 0x06, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x42, 0x11, 0xfa, - 0x42, 0x0e, 0x92, 0x01, 0x0b, 0x08, 0x01, 0x10, 0x64, 0x22, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, - 0x52, 0x06, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x20, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xd0, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, - 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, - 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, - 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, - 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, - 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, - 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x06, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0b, 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, - 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, - 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, - 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x41, 0x0a, 0x1f, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, - 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6e, 0x0a, 0x18, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, - 0x70, 0x6c, 0x65, 0x52, 0x06, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, - 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, - 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xce, 0x02, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, - 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, - 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, - 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4b, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x25, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, - 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x06, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, - 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0b, 0xfa, 0x42, 0x08, 0x2a, - 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, - 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, - 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3e, 0x0a, 0x1c, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, - 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x77, 0x0a, 0x15, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x32, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, - 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xf5, 0x01, 0x0a, 0x11, 0x44, 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, - 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, - 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, - 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x42, 0x0a, 0x0c, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x5f, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x74, 0x75, 0x70, - 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x10, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0xfa, - 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x10, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x34, 0x0a, 0x12, 0x44, 0x61, 0x74, - 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, - 0x97, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, + 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa0, 0x01, 0x0a, + 0x11, 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x8a, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x6a, 0x92, 0x41, 0x67, 0x32, 0x65, 0x54, 0x68, + 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, + 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, + 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x73, 0x29, 0x2e, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0xd3, 0x03, 0x0a, 0x18, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, - 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, - 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, - 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x2c, 0x0a, 0x06, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x3c, 0x0a, 0x1a, 0x52, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, - 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xfa, 0x01, 0x0a, 0x10, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, - 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, - 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, - 0x0a, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x72, 0x67, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x61, 0x72, 0x67, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x3c, 0x0a, 0x0e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x33, 0x0a, 0x11, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x6e, 0x61, - 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, - 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x91, 0x01, 0x0a, 0x12, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, - 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, - 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, - 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x2d, - 0x0a, 0x07, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x07, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x22, 0x2b, 0x0a, - 0x13, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x75, 0x0a, 0x11, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x4c, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, - 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, - 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, - 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x22, 0x41, 0x0a, 0x12, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x62, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x06, 0x62, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x77, 0x0a, 0x13, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4c, 0x0a, 0x09, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, + 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, + 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, + 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, + 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, - 0x14, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x61, 0x0a, 0x13, 0x54, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, - 0x17, 0x72, 0x15, 0x28, 0x40, 0x32, 0x0e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, - 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0xd0, 0x01, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x72, - 0x05, 0x28, 0x40, 0xd0, 0x01, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3f, 0x0a, 0x14, - 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x22, 0x2f, 0x0a, - 0x13, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x00, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3f, - 0x0a, 0x14, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x22, - 0x74, 0x0a, 0x11, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0b, 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, - 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, - 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, - 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6b, 0x0a, 0x12, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, - 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, - 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x32, 0xf7, 0x0b, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x88, 0x02, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1f, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x62, - 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbb, - 0x01, 0x92, 0x41, 0x83, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x62, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x72, - 0x65, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x20, 0x61, 0x20, 0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, - 0x75, 0x73, 0x65, 0x72, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, - 0x20, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6f, - 0x6e, 0x20, 0x61, 0x20, 0x63, 0x65, 0x72, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x2a, 0x11, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x3a, 0x01, - 0x2a, 0x22, 0x29, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0xd2, 0x01, 0x0a, - 0x06, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4f, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x06, 0x74, 0x75, + 0x70, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x42, 0x11, 0xfa, 0x42, 0x0e, 0x92, + 0x01, 0x0b, 0x08, 0x01, 0x10, 0x64, 0x22, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, + 0x75, 0x70, 0x6c, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0xa7, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, 0x68, 0x65, 0x20, 0x73, + 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, + 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, + 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x52, + 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xaf, 0x04, 0x0a, 0x17, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, + 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, + 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, + 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, + 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, + 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, + 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, + 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, + 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, + 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, + 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, + 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, + 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, + 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x0b, 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x01, + 0x0a, 0x1f, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, + 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, 0x68, 0x65, + 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, + 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, + 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x29, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6e, 0x0a, + 0x18, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x06, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x52, 0x06, 0x74, 0x75, 0x70, 0x6c, 0x65, + 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xad, 0x04, + 0x0a, 0x14, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, + 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, + 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, + 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, + 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, + 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x12, 0x4b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, + 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x0b, 0xfa, 0x42, 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xaa, 0x01, + 0x0a, 0x1c, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x89, + 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, + 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, + 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, + 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, + 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, + 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x52, 0x0a, + 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x77, 0x0a, 0x15, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0xd4, 0x03, 0x0a, 0x11, 0x44, 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, + 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, + 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, + 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, + 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, + 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, + 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, + 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, + 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, + 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x42, 0x0a, 0x0c, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x5f, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x74, 0x75, + 0x70, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x10, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, + 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x10, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xa0, 0x01, 0x0a, 0x12, 0x44, + 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, 0x68, 0x65, + 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x61, + 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, 0x68, 0x65, + 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x29, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xf6, 0x02, + 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, + 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, + 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, + 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, + 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, + 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, + 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x2c, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xa8, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, + 0x64, 0x54, 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, + 0x74, 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x29, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x22, 0xd9, 0x03, 0x0a, 0x10, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, + 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, + 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, + 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, + 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, + 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x1a, + 0x3c, 0x0a, 0x0e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9f, 0x01, + 0x0a, 0x11, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x69, 0x92, 0x41, 0x66, 0x32, 0x64, 0x54, + 0x68, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x74, 0x6f, + 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x73, 0x74, 0x61, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x63, + 0x68, 0x65, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x5b, 0x53, 0x6e, 0x61, 0x70, 0x20, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x5d, 0x28, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x2d, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x29, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0xf0, 0x02, 0x0a, 0x12, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, + 0x01, 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x20, 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x3c, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, + 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, + 0x74, 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, + 0x20, 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, + 0x80, 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, + 0x5f, 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, + 0x7c, 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x12, 0x2d, 0x0a, 0x07, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x07, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x13, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, + 0xd4, 0x02, 0x0a, 0x11, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, + 0x32, 0xd6, 0x01, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, + 0x79, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, + 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, + 0x28, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, + 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, + 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x6e, 0x20, 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, + 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, + 0x36, 0x34, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, + 0x01, 0x32, 0x21, 0x5e, 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, + 0x5c, 0x2d, 0x40, 0x5c, 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, + 0x5c, 0x2a, 0x29, 0x24, 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x12, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, + 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x52, 0x06, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0xd6, 0x02, 0x0a, 0x13, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0xaa, 0x02, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8b, 0x02, 0x92, 0x41, 0xd9, 0x01, 0x32, 0xd6, 0x01, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x28, 0x68, 0x61, 0x76, + 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x29, 0x20, 0x75, 0x73, 0x65, 0x20, 0x70, 0x72, 0x65, 0x2d, 0x69, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x3c, 0x63, 0x6f, 0x64, 0x65, + 0x3e, 0x74, 0x31, 0x3c, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x3e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6d, 0x61, + 0x74, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, + 0x5c, 0xe2, 0x80, 0x9c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, + 0x5d, 0x2b, 0x5c, 0xe2, 0x80, 0x9c, 0x2c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x36, 0x34, 0x20, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x2e, 0xfa, 0x42, 0x2b, 0x72, 0x29, 0x28, 0x80, 0x01, 0x32, 0x21, 0x5e, + 0x28, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x5c, 0x2d, 0x40, 0x5c, + 0x2e, 0x3a, 0x2b, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x32, 0x38, 0x7d, 0x7c, 0x5c, 0x2a, 0x29, 0x24, + 0xd0, 0x01, 0x00, 0x52, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x14, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x61, + 0x0a, 0x13, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x28, 0x40, 0x32, 0x0e, 0x5b, 0x61, 0x2d, 0x7a, + 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x2d, 0x2c, 0x5d, 0x2b, 0xd0, 0x01, 0x00, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0a, 0xfa, 0x42, 0x07, 0x72, 0x05, 0x28, 0x40, 0xd0, 0x01, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x3f, 0x0a, 0x14, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x13, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x00, 0x52, + 0x02, 0x69, 0x64, 0x22, 0x3f, 0x0a, 0x14, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x52, 0x06, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x22, 0x74, 0x0a, 0x11, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x0b, 0xfa, 0x42, + 0x08, 0x2a, 0x06, 0x18, 0x64, 0x28, 0x01, 0x40, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x34, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, + 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, + 0xfa, 0x42, 0x05, 0x72, 0x03, 0xd0, 0x01, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, + 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6b, 0x0a, 0x12, 0x54, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x29, 0x0a, 0x07, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x52, 0x07, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, + 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, + 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xc0, 0x49, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0xfd, 0x0d, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x12, 0x1f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xb0, 0x0d, 0x92, 0x41, 0xf8, 0x0c, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x61, 0x70, + 0x69, 0x2a, 0x11, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x6a, 0xcb, 0x0c, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb9, 0x0c, 0x32, 0xb6, 0x0c, 0x0a, 0xd5, 0x04, 0x2a, + 0xd2, 0x04, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, + 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0xb2, 0x04, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xa7, 0x04, 0x1a, 0xa4, 0x04, + 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, + 0x63, 0x72, 0x2e, 0x63, 0x61, 0x6e, 0x20, 0x3d, 0x3d, 0x3d, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, + 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x41, 0x4c, + 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x52, 0x45, + 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x7d, 0x29, 0x0a, 0x9b, 0x04, 0x2a, 0x98, 0x04, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, + 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x0a, 0xee, 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xe3, 0x03, 0x1a, + 0xe0, 0x03, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x20, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x70, 0x74, 0x68, + 0x3a, 0x20, 0x32, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, + 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x63, 0x61, 0x6e, 0x20, 0x3d, 0x3d, 0x3d, + 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, + 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x29, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x5f, 0x41, 0x4c, + 0x4c, 0x4f, 0x57, 0x45, 0x44, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, + 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, + 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, + 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x7d, 0x29, 0x0a, 0xbd, 0x03, 0x2a, 0xba, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x96, 0x03, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x8b, 0x03, 0x1a, 0x88, 0x03, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, + 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, + 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, + 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, + 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x3a, 0x20, 0x32, 0x30, 0x0a, 0x20, 0x20, + 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x72, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x69, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x22, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x65, + 0x64, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, + 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, + 0x22, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x3a, 0x01, 0x2a, 0x22, 0x29, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0xb0, 0x09, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x61, 0x6e, + 0x64, 0x12, 0x20, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe0, 0x08, 0x92, 0x41, 0xa7, 0x08, 0x0a, 0x0a, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x65, 0x78, 0x70, 0x61, 0x6e, + 0x64, 0x20, 0x61, 0x70, 0x69, 0x2a, 0x12, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x6a, 0xf8, 0x07, 0x0a, 0x0d, 0x78, 0x2d, + 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xe6, 0x07, 0x32, 0xe3, + 0x07, 0x0a, 0xee, 0x02, 0x2a, 0xeb, 0x02, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, + 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xcb, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0xc0, 0x02, 0x1a, 0xbd, 0x02, 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, + 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, + 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x61, - 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x78, - 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x82, 0x01, 0x92, - 0x41, 0x4a, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, - 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x68, 0x69, 0x70, 0x73, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, - 0x6f, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2a, 0x12, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x22, 0x2a, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x61, 0x6e, - 0x64, 0x12, 0xee, 0x01, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x12, 0x26, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x62, 0x61, 0x73, + 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, + 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x70, 0x75, 0x73, 0x68, 0x22, 0x2c, 0x0a, + 0x7d, 0x29, 0x0a, 0x89, 0x02, 0x2a, 0x86, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, + 0xdc, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd1, 0x01, 0x1a, 0xce, 0x01, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, + 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x70, 0x75, 0x73, 0x68, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xe3, + 0x02, 0x2a, 0xe0, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, + 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, + 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xbc, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0xb1, 0x02, 0x1a, 0xae, 0x02, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, + 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, + 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, + 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x27, 0x20, + 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, + 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6e, 0x61, 0x70, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, + 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x70, 0x75, 0x73, 0x68, + 0x22, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x22, 0x2a, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0xb0, 0x0b, 0x0a, 0x0c, 0x4c, 0x6f, + 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x26, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, - 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x8c, 0x01, 0x92, 0x41, 0x4d, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, - 0x61, 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, - 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x2a, 0x18, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x36, 0x3a, 0x01, 0x2a, 0x22, - 0x31, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, - 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x12, 0x89, 0x02, 0x0a, 0x12, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x26, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, - 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xce, 0x0a, 0x92, 0x41, + 0x8e, 0x0a, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, + 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2a, 0x18, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x6a, 0xd6, 0x09, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, + 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xc4, 0x09, 0x32, 0xc1, 0x09, 0x0a, + 0xae, 0x03, 0x2a, 0xab, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, + 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, + 0x67, 0x6f, 0x0a, 0x8b, 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x80, 0x03, + 0x1a, 0xfd, 0x02, 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x28, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, + 0x29, 0x2c, 0x20, 0x26, 0x20, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x20, 0x76, 0x31, 0x2e, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, + 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x3a, + 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, + 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x26, 0x20, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, + 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, + 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x29, + 0x0a, 0xfa, 0x02, 0x2a, 0xf7, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, + 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, + 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0xcd, 0x02, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc2, 0x02, 0x1a, 0xbf, 0x02, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x28, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, + 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x70, + 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x29, + 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, + 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x29, 0x0a, 0x7d, 0x29, 0x0a, 0x90, 0x03, + 0x2a, 0x8d, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, + 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, + 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xe9, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0xde, 0x02, 0x1a, 0xdb, 0x02, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, + 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, + 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, + 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, + 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x3a, 0x20, 0x32, 0x30, + 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x27, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x36, 0x3a, 0x01, 0x2a, 0x22, 0x31, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0xf1, 0x0c, 0x0a, + 0x12, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x12, 0x26, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x81, 0x0c, 0x92, 0x41, 0xba, + 0x0b, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x6c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x2a, 0x1e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x6a, 0xf5, 0x0a, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xe3, 0x0a, 0x32, 0xe0, 0x0a, 0x0a, 0xa1, 0x04, 0x2a, 0x9e, + 0x04, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, + 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xfe, + 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xf3, 0x03, 0x1a, 0xf0, 0x03, 0x73, + 0x74, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x6f, + 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x28, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x99, 0x01, 0x92, 0x41, 0x53, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x25, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x69, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x2a, 0x1e, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x3a, - 0x01, 0x2a, 0x22, 0x38, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, - 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0xf3, - 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x12, 0x27, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, - 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x8e, 0x01, 0x92, 0x41, 0x4e, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, - 0x61, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, - 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x2a, 0x19, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x37, 0x3a, 0x01, 0x2a, - 0x22, 0x32, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x73, 0x75, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x95, 0x02, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x62, 0x61, 0x73, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa4, 0x01, 0x92, 0x41, 0x60, 0x0a, 0x0a, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, - 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, - 0x66, 0x69, 0x63, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x2a, 0x1d, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x3b, 0x3a, 0x01, 0x2a, 0x22, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, + 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x35, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x3a, + 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x76, 0x69, + 0x65, 0x77, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, + 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, + 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, 0x29, + 0x0a, 0x0a, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x66, 0x6f, 0x72, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, + 0x3d, 0x20, 0x73, 0x74, 0x72, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x28, 0x29, 0x0a, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x3d, 0x20, 0x69, 0x6f, 0x2e, 0x45, + 0x4f, 0x46, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, 0x65, + 0x61, 0x6b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x72, 0x65, 0x73, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x0a, 0x7d, 0x0a, + 0xb9, 0x06, 0x2a, 0xb6, 0x06, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, + 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x8c, 0x06, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x81, 0x06, 0x1a, 0xfe, 0x05, 0x63, 0x6f, 0x6e, + 0x73, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x40, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x63, + 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x7b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x7d, 0x20, 0x3d, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x40, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x64, 0x69, 0x73, + 0x74, 0x2f, 0x73, 0x72, 0x63, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6e, 0x65, + 0x77, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x6e, + 0x65, 0x77, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x22, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x38, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x72, + 0x65, 0x73, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, + 0x76, 0x69, 0x65, 0x77, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, + 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x61, + 0x6e, 0x64, 0x6c, 0x65, 0x28, 0x72, 0x65, 0x73, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x61, 0x73, 0x79, + 0x6e, 0x63, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x61, 0x6e, 0x64, + 0x6c, 0x65, 0x28, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x49, 0x74, 0x65, + 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3c, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x3e, 0x29, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x77, 0x61, 0x69, 0x74, 0x20, 0x28, 0x63, 0x6f, + 0x6e, 0x73, 0x74, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x72, 0x65, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x49, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x3d, 0x3a, 0x01, 0x2a, 0x22, 0x38, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2d, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x82, 0x01, 0x0a, - 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x79, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, - 0x15, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, - 0x92, 0x41, 0x14, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x2a, 0x0b, 0x77, 0x61, 0x74, 0x63, - 0x68, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, 0x2a, - 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x30, - 0x01, 0x32, 0x8f, 0x04, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0xae, 0x01, 0x0a, - 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x2d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, + 0x12, 0xda, 0x0c, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x12, 0x27, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x62, 0x61, + 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf5, 0x0b, 0x92, 0x41, 0xb4, 0x0b, 0x0a, 0x0a, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x2d, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2a, 0x19, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x6a, 0xfa, 0x0a, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xe8, 0x0a, 0x32, 0xe5, 0x0a, 0x0a, 0xf4, 0x03, 0x2a, 0xf1, + 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, + 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xd1, + 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc6, 0x03, 0x1a, 0xc3, 0x03, 0x63, + 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, + 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, + 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, + 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, + 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, 0x65, 0x70, + 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, + 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x7d, 0x29, 0x0a, 0xae, 0x03, 0x2a, 0xab, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, + 0x81, 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xf6, 0x02, 0x1a, 0xf3, 0x02, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, + 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, + 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, + 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, + 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x3a, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x29, + 0x0a, 0x7d, 0x29, 0x0a, 0xba, 0x03, 0x2a, 0xb7, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, + 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x93, 0x03, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x88, 0x03, 0x1a, 0x85, 0x03, 0x63, 0x75, 0x72, 0x6c, 0x20, + 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, + 0x6b, 0x75, 0x70, 0x2d, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x27, 0x20, 0x5c, 0x0a, 0x2d, + 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, + 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6e, 0x61, + 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x70, 0x74, + 0x68, 0x22, 0x3a, 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x27, 0x0a, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, + 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x27, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x37, 0x3a, 0x01, 0x2a, 0x22, 0x32, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x6c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2d, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0xfa, 0x0c, + 0x0a, 0x11, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x89, + 0x0c, 0x92, 0x41, 0xc4, 0x0b, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x12, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2a, 0x1d, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x6a, 0x82, 0x0b, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xf0, 0x0a, 0x32, 0xed, 0x0a, 0x0a, 0xf5, 0x03, 0x2a, + 0xf2, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, + 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0xd2, 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc7, 0x03, 0x1a, 0xc4, 0x03, + 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, + 0x26, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4f, 0x6e, 0x6c, 0x79, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x44, 0x65, 0x70, 0x74, 0x68, 0x3a, + 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, + 0x65, 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xa0, 0x03, 0x2a, 0x9d, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, + 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x0a, 0xf3, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xe8, 0x02, 0x1a, + 0xe5, 0x02, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x20, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, + 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x7d, 0x29, 0x0a, 0xcf, 0x03, 0x2a, 0xcc, 0x03, 0x0a, 0x0f, 0x0a, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, + 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xa8, + 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x9d, 0x03, 0x1a, 0x9a, 0x03, 0x63, + 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, + 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2d, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, + 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, + 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, + 0x0a, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x3a, 0x20, 0x32, 0x30, 0x0a, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x72, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, + 0x64, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x22, + 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, + 0x22, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3b, 0x3a, + 0x01, 0x2a, 0x22, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, + 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2d, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x83, 0x08, 0x0a, 0x05, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x12, 0xf9, 0x07, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x15, + 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbe, 0x07, + 0x92, 0x41, 0x92, 0x07, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0d, 0x77, 0x61, 0x74, + 0x63, 0x68, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2a, 0x0b, 0x77, 0x61, 0x74, 0x63, + 0x68, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x6a, 0xec, 0x06, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, + 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xda, 0x06, 0x32, 0xd7, 0x06, 0x0a, + 0x9e, 0x02, 0x2a, 0x9b, 0x02, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, + 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, + 0x67, 0x6f, 0x0a, 0xfb, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xf0, 0x01, + 0x1a, 0xed, 0x01, 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x0a, 0x7d, 0x29, 0x0a, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x66, 0x6f, + 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x2c, 0x20, 0x65, 0x72, 0x72, + 0x20, 0x3a, 0x3d, 0x20, 0x63, 0x72, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x28, 0x29, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x3d, 0x20, 0x69, 0x6f, 0x2e, + 0x45, 0x4f, 0x46, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x72, 0x65, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x0a, 0x7d, 0x0a, + 0x0a, 0xb3, 0x04, 0x2a, 0xb0, 0x04, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, + 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, + 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x86, 0x04, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xfb, 0x03, 0x1a, 0xf8, 0x03, 0x63, 0x6f, + 0x6e, 0x73, 0x74, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x3d, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x40, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x3b, 0x0a, + 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x7b, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x7d, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, + 0x22, 0x40, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, + 0x79, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x64, 0x69, 0x73, 0x74, 0x2f, 0x73, 0x72, 0x63, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x62, + 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, + 0x3b, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x61, 0x69, 0x6e, + 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x66, 0x79, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x6e, 0x65, 0x77, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x3a, 0x20, 0x22, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, + 0x74, 0x3a, 0x33, 0x34, 0x37, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x72, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x77, 0x61, 0x74, 0x63, + 0x68, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, + 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x28, 0x72, 0x65, 0x73, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x61, + 0x73, 0x79, 0x6e, 0x63, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x61, + 0x6e, 0x64, 0x6c, 0x65, 0x28, 0x72, 0x65, 0x73, 0x3a, 0x20, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x49, + 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3c, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x3e, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x61, 0x77, 0x61, 0x69, 0x74, 0x20, 0x28, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x72, 0x65, 0x73, 0x29, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, 0x2a, 0x22, + 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x30, 0x01, + 0x32, 0xff, 0x1d, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x82, 0x11, 0x0a, 0x05, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0xbd, 0x10, 0x92, 0x41, 0x89, 0x10, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, + 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2a, 0x0d, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6a, 0xe1, 0x0f, 0x0a, + 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xcf, + 0x0f, 0x32, 0xcc, 0x0f, 0x0a, 0x8a, 0x05, 0x2a, 0x87, 0x05, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xe7, 0x04, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0xdc, 0x04, 0x1a, 0xd9, 0x04, 0x73, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, + 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, + 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x6a, 0x92, 0x41, 0x37, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x1e, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2a, 0x0d, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0xa8, 0x01, - 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x67, 0x92, 0x41, 0x35, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x1d, 0x72, 0x65, - 0x61, 0x64, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2a, 0x0c, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, + 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x3a, 0x20, 0x60, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x7b, 0x7d, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x3d, 0x20, 0x28, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x29, 0x5c, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5c, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x7b, + 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, + 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x40, 0x6f, 0x72, 0x67, 0x61, 0x6e, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x75, 0x73, 0x68, 0x20, 0x3d, + 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x28, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x2e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x29, 0x29, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x20, 0x3d, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x29, 0x29, 0x5c, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x60, 0x2c, 0x0a, 0x7d, + 0x29, 0x0a, 0x8a, 0x05, 0x2a, 0x87, 0x05, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, + 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0xdd, + 0x04, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd2, 0x04, 0x1a, 0xcf, 0x04, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x77, 0x72, 0x69, + 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, + 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x3a, 0x20, 0x60, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x7b, 0x7d, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x3d, 0x20, 0x28, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x29, 0x5c, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5c, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x7b, 0x5c, + 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x40, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x75, 0x73, 0x68, 0x20, 0x3d, 0x20, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x28, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x2e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x29, 0x29, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x20, 0x3d, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x29, 0x29, 0x5c, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x60, 0x0a, 0x7d, 0x29, 0x2e, + 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, + 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, + 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0xaf, + 0x05, 0x2a, 0xac, 0x05, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, + 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, + 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x88, 0x05, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0xfd, 0x04, 0x1a, 0xfa, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, + 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, + 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, + 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x73, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, + 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x22, 0x3a, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x75, 0x73, 0x65, + 0x72, 0x20, 0x7b, 0x7d, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x7b, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x40, 0x75, 0x73, 0x65, + 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, + 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x79, 0x20, 0x3d, 0x20, 0x28, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, + 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x29, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, + 0x3d, 0x20, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x5c, 0x6e, + 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x7b, 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x20, 0x40, 0x75, 0x73, 0x65, 0x72, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x20, 0x40, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5c, 0x6e, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x70, 0x75, 0x73, 0x68, 0x20, 0x3d, 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5c, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x28, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x29, 0x29, 0x5c, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x3d, 0x20, 0x28, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x28, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x20, 0x6f, 0x72, + 0x20, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x29, 0x29, 0x5c, 0x6e, 0x20, 0x7d, 0x22, 0x0a, 0x7d, 0x27, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x12, 0xf5, 0x06, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0xb3, 0x06, 0x92, 0x41, 0x80, 0x06, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x12, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2a, 0x0c, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0xda, 0x05, 0x0a, + 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xc8, + 0x05, 0x32, 0xc5, 0x05, 0x0a, 0xf6, 0x01, 0x2a, 0xf3, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xd3, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0xc8, 0x01, 0x1a, 0xc5, 0x01, 0x73, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, + 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x52, 0x65, 0x61, 0x64, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, + 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x63, 0x6e, 0x62, + 0x65, 0x36, 0x73, 0x65, 0x35, 0x66, 0x6d, 0x61, 0x6c, 0x31, 0x38, 0x67, 0x70, 0x63, 0x36, 0x36, + 0x67, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xb6, 0x01, + 0x2a, 0xb3, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, + 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, + 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x89, 0x01, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x7f, 0x1a, 0x7d, 0x6c, 0x65, 0x74, 0x20, 0x72, 0x65, 0x73, + 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x72, 0x65, 0x61, 0x64, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x73, + 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x90, 0x02, 0x2a, 0x8d, 0x02, 0x0a, 0x0f, 0x0a, 0x05, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, + 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xe9, 0x01, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xde, 0x01, 0x1a, 0xdb, 0x01, 0x63, 0x75, + 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, + 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, + 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x63, 0x6e, 0x62, 0x65, 0x36, + 0x73, 0x65, 0x35, 0x66, 0x6d, 0x61, 0x6c, 0x31, 0x38, 0x67, 0x70, 0x63, 0x36, 0x36, 0x67, 0x22, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xa8, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x73, + 0x6d, 0x61, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xf7, 0x05, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x67, 0x92, 0x41, 0x35, 0x0a, - 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x1d, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6c, - 0x6c, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, - 0x6c, 0x69, 0x73, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, - 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x6c, - 0x69, 0x73, 0x74, 0x32, 0xf4, 0x09, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x8f, 0x01, 0x0a, - 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x92, - 0x41, 0x1f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb5, 0x05, 0x92, 0x41, 0x82, + 0x05, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x20, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, + 0x6c, 0x69, 0x73, 0x74, 0x6a, 0xdc, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xca, 0x04, 0x32, 0xc7, 0x04, 0x0a, 0xc0, 0x01, 0x2a, + 0xbd, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, + 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0x9d, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x92, 0x01, 0x1a, 0x8f, 0x01, + 0x73, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x28, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x22, 0x31, + 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, + 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, + 0x85, 0x01, 0x2a, 0x82, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, + 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x59, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4f, 0x1a, 0x4d, 0x6c, 0x65, 0x74, 0x20, 0x72, 0x65, + 0x73, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x7d, 0x29, 0x0a, 0xf9, 0x01, 0x2a, 0xf6, 0x01, 0x0a, 0x0f, 0x0a, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, + 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xd2, + 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc7, 0x01, 0x1a, 0xc4, 0x01, 0x63, + 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, + 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x72, 0x65, 0x61, + 0x64, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, + 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x20, + 0x22, 0x31, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, + 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, + 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2f, 0x6c, 0x69, + 0x73, 0x74, 0x32, 0xe3, 0x43, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x8d, 0x15, 0x0a, 0x05, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcc, 0x14, 0x92, + 0x41, 0x9b, 0x14, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0a, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0xcb, - 0x01, 0x0a, 0x12, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x73, 0x12, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x65, 0x6a, 0xfa, 0x13, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x73, 0x12, 0xe8, 0x13, 0x32, 0xe5, 0x13, 0x0a, 0xbb, 0x07, 0x2a, 0xb8, 0x07, 0x0a, + 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, + 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x98, 0x07, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x8d, 0x07, 0x1a, 0x8a, 0x07, 0x2f, 0x2f, 0x20, + 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, + 0x70, 0x65, 0x64, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x61, 0x6e, 0x79, 0x70, 0x62, 0x2e, 0x4e, + 0x65, 0x77, 0x28, 0x26, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x44, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x21, + 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x48, + 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x0a, 0x7d, 0x0a, 0x0a, 0x63, + 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x28, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, + 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x75, 0x70, 0x6c, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x2a, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x3a, 0x20, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, + 0x64, 0x3a, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x2a, 0x76, 0x31, 0x2e, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x3a, 0x20, 0x22, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0x80, 0x06, 0x2a, 0xfd, 0x05, 0x0a, 0x0f, 0x0a, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, + 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x0a, 0xd3, 0x05, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0xc8, 0x05, 0x1a, 0xc5, 0x05, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x65, + 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, + 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x4a, 0x53, 0x4f, 0x4e, 0x28, + 0x7b, 0x20, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x7d, 0x29, 0x3b, + 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, + 0x41, 0x6e, 0x79, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x4a, 0x53, 0x4f, 0x4e, 0x28, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x55, 0x72, 0x6c, 0x3a, 0x20, 0x27, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x27, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x20, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, + 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x28, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x29, 0x2e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x28, 0x29, 0x0a, 0x7d, 0x29, + 0x3b, 0x0a, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, + 0x74, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x3a, 0x20, 0x22, 0x69, + 0x73, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x5d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, + 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0xa1, 0x06, 0x2a, 0x9e, 0x06, + 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, + 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, + 0x6c, 0x0a, 0xfa, 0x05, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xef, 0x05, 0x1a, + 0xec, 0x05, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, + 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x77, 0x72, 0x69, + 0x74, 0x65, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, + 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, + 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x31, + 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x40, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3a, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, + 0x70, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x7d, 0x0a, 0x7d, 0x27, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0xc6, 0x01, 0x0a, 0x12, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, + 0x70, 0x73, 0x12, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x57, - 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6e, 0x92, 0x41, - 0x35, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, - 0x6e, 0x65, 0x77, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, - 0x73, 0x2a, 0x13, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, - 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x22, - 0x2b, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, - 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0xce, 0x01, 0x0a, - 0x11, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x73, 0x12, 0x20, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x74, 0x92, 0x41, 0x37, 0x0a, 0x04, 0x44, 0x61, - 0x74, 0x61, 0x12, 0x16, 0x72, 0x65, 0x61, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x29, 0x2a, 0x17, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2e, 0x72, - 0x65, 0x61, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, 0x3a, 0x01, 0x2a, 0x22, 0x2f, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xba, 0x01, - 0x0a, 0x0e, 0x52, 0x65, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x12, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x69, 0x92, 0x41, 0x2f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x11, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x73, 0x29, 0x2a, 0x14, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x72, - 0x65, 0x61, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x3a, 0x01, 0x2a, 0x22, 0x2c, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0x94, 0x01, 0x0a, 0x06, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, - 0x92, 0x41, 0x20, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, 0x2f, 0x76, - 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x12, 0xcc, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x12, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x6c, 0x92, 0x41, 0x32, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x14, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, - 0x69, 0x70, 0x73, 0x2a, 0x14, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, - 0x70, 0x73, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x3a, - 0x01, 0x2a, 0x22, 0x2c, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, - 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x97, 0x01, 0x0a, 0x09, 0x52, 0x75, 0x6e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x19, - 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, - 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x92, 0x41, 0x1e, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x0a, 0x72, 0x75, 0x6e, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0a, 0x62, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x72, 0x75, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, - 0x2a, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, - 0x72, 0x75, 0x6e, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x32, 0xdf, 0x03, 0x0a, 0x06, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x9a, 0x01, 0x0a, 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, - 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, - 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x92, 0x41, 0x24, 0x0a, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x92, 0x41, 0x30, 0x0a, 0x04, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x13, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2a, 0x13, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x22, 0x2b, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x12, 0xb5, 0x0c, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, + 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xda, 0x0b, 0x92, 0x41, 0x9c, 0x0b, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x72, 0x65, + 0x61, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, + 0x2a, 0x17, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0xe6, 0x0a, 0x0a, 0x0d, 0x78, 0x2d, + 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xd4, 0x0a, 0x32, 0xd1, + 0x0a, 0x0a, 0x86, 0x04, 0x2a, 0x83, 0x04, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, + 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xe3, 0x03, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0xd8, 0x03, 0x1a, 0xd5, 0x03, 0x72, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x61, 0x64, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x28, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x28, 0x29, 0x2c, 0x20, 0x26, 0x20, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, + 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x26, 0x76, + 0x31, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, + 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, + 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x5d, + 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x7b, 0x22, 0x31, 0x22, 0x7d, 0x20, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x5b, 0x5d, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x7b, 0x22, 0x22, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x7d, 0x0a, 0x7d, 0x29, 0x0a, 0x87, 0x03, 0x2a, 0x84, 0x03, + 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, + 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0xda, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0xcf, 0x02, 0x1a, 0xcc, 0x02, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x68, 0x69, 0x70, 0x73, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, + 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, + 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, + 0x3a, 0x20, 0x5b, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, + 0x20, 0x7d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0xbb, 0x03, 0x2a, 0xb8, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, + 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x94, 0x03, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x89, 0x03, 0x1a, 0x86, 0x03, 0x63, 0x75, 0x72, 0x6c, + 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, + 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, 0x5b, + 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x7d, 0x0a, + 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, 0x3a, 0x01, 0x2a, 0x22, 0x2f, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xd6, 0x0a, 0x0a, + 0x0e, 0x52, 0x65, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, + 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, + 0x0a, 0x92, 0x41, 0xc9, 0x09, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0f, 0x72, 0x65, 0x61, + 0x64, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2a, 0x14, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x72, 0x65, + 0x61, 0x64, 0x6a, 0x99, 0x09, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x12, 0x87, 0x09, 0x32, 0x84, 0x09, 0x0a, 0xa5, 0x03, 0x2a, 0xa2, 0x03, + 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x82, 0x03, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xf7, 0x02, 0x1a, 0xf4, 0x02, 0x72, 0x72, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x20, 0x76, 0x31, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x65, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, + 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x26, 0x76, + 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x73, + 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x7b, 0x22, 0x31, 0x22, 0x7d, + 0x20, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x20, 0x7b, 0x22, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0x7d, 0x2c, 0x0a, + 0x7d, 0x29, 0x0a, 0xd0, 0x02, 0x2a, 0xcd, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, + 0xa3, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x98, 0x02, 0x1a, 0x95, 0x02, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x65, 0x61, 0x64, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, + 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0x86, 0x03, 0x2a, 0x83, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, + 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xdf, 0x02, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd4, 0x02, 0x1a, 0xd1, 0x02, 0x63, 0x75, 0x72, + 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, + 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, + 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, + 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x22, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x27, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x31, 0x3a, 0x01, 0x2a, 0x22, 0x2c, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xf1, 0x0b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, + 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xad, 0x0b, 0x92, 0x41, 0xfb, 0x0a, + 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x64, + 0x61, 0x74, 0x61, 0x2a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x6a, 0xd8, 0x0a, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x73, 0x12, 0xc6, 0x0a, 0x32, 0xc3, 0x0a, 0x0a, 0xee, 0x03, 0x2a, 0xeb, 0x03, 0x0a, 0x0d, + 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, + 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xcb, 0x03, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc0, 0x03, 0x1a, 0xbd, 0x03, 0x72, 0x72, 0x2c, 0x20, + 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, + 0x26, 0x20, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x6e, 0x61, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 0x75, 0x70, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x54, 0x75, 0x70, + 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x7b, 0x22, 0x31, 0x22, 0x7d, 0x20, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x3a, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x20, 0x7b, 0x22, 0x31, 0x22, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x7d, 0x0a, 0x7d, 0x29, 0x0a, 0x97, 0x03, 0x2a, 0x94, 0x03, 0x0a, + 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, + 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0xea, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0xdf, 0x02, 0x1a, 0xdc, 0x02, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x73, 0x6e, 0x61, 0x70, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, + 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x22, 0x75, 0x73, 0x65, 0x72, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, 0x73, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, + 0x7d, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, + 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x0a, 0x7d, 0x29, 0x0a, 0xb5, 0x03, 0x2a, 0xb2, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, + 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x8e, 0x03, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x83, 0x03, 0x1a, 0x80, 0x03, 0x63, 0x75, 0x72, 0x6c, 0x20, + 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x27, 0x20, 0x5c, 0x0a, + 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, + 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x69, 0x64, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3a, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x75, 0x73, + 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x73, 0x22, + 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x31, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, + 0x74, 0x61, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0xcc, 0x01, 0x0a, 0x13, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, + 0x73, 0x12, 0x22, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6c, 0x92, 0x41, 0x32, 0x0a, + 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x14, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2a, 0x14, 0x72, 0x65, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x3a, 0x01, 0x2a, 0x22, 0x2c, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, + 0x73, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0xad, 0x08, 0x0a, 0x09, 0x52, 0x75, 0x6e, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe8, 0x07, + 0x92, 0x41, 0xb2, 0x07, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0a, 0x72, 0x75, 0x6e, 0x20, + 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x72, + 0x75, 0x6e, 0x6a, 0x91, 0x07, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x12, 0xff, 0x06, 0x32, 0xfc, 0x06, 0x0a, 0xa5, 0x02, 0x2a, 0xa2, 0x02, + 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x82, 0x02, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xf7, 0x01, 0x1a, 0xf4, 0x01, 0x72, 0x72, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x2e, 0x52, 0x75, 0x6e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x28, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, + 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x3a, 0x20, 0x6d, 0x61, 0x70, 0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5d, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x22, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x35, 0x36, 0x34, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, + 0x3a, 0x20, 0x22, 0x37, 0x38, 0x39, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, + 0x7d, 0x29, 0x0a, 0x8a, 0x02, 0x2a, 0x87, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, + 0xdd, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd2, 0x01, 0x1a, 0xcf, 0x01, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, 0x75, 0x6e, 0x42, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, + 0x3a, 0x20, 0x22, 0x35, 0x36, 0x34, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x3a, + 0x20, 0x22, 0x37, 0x38, 0x39, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x29, + 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, + 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, + 0xc4, 0x02, 0x2a, 0xc1, 0x02, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, + 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0x9d, 0x02, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x92, 0x02, 0x1a, 0x8f, 0x02, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, + 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, + 0x61, 0x2f, 0x72, 0x75, 0x6e, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x27, 0x20, 0x5c, 0x0a, + 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, + 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, + 0x72, 0x49, 0x44, 0x22, 0x3a, 0x20, 0x22, 0x35, 0x36, 0x34, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x3a, 0x20, 0x22, 0x37, 0x38, 0x39, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x22, + 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x75, + 0x6e, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x32, 0xc8, 0x21, 0x0a, 0x06, 0x42, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x12, 0x86, 0x15, 0x0a, 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1b, 0x2e, + 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc1, 0x14, 0x92, 0x41, 0x8e, 0x14, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0c, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, + 0x69, 0x74, 0x65, 0x6a, 0xe7, 0x13, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xd5, 0x13, 0x32, 0xd2, 0x13, 0x0a, 0xd3, 0x06, 0x2a, 0xd0, + 0x06, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, + 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0xb0, + 0x06, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xa5, 0x06, 0x1a, 0xa2, 0x06, 0x72, + 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x28, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x2a, + 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, + 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x5b, 0x5d, 0x2a, 0x76, 0x31, 0x2e, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x23, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x40, 0x75, + 0x73, 0x65, 0x72, 0x3a, 0x7b, 0x7b, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, + 0x7d, 0x7d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, + 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x23, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x40, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x7b, 0x7b, 0x2e, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x7d, 0x7d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x20, 0x5b, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x24, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x7c, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x3a, 0x66, 0x61, 0x6c, 0x73, + 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x7d, + 0x29, 0x0a, 0x9b, 0x06, 0x2a, 0x98, 0x06, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, + 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0xee, + 0x05, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xe3, 0x05, 0x1a, 0xe0, 0x05, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x77, 0x72, 0x69, + 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, + 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, + 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, + 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x7d, 0x7d, 0x23, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x40, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x7b, + 0x7b, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x7d, 0x7d, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x23, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x40, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x7b, 0x7b, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, + 0x72, 0x49, 0x44, 0x7d, 0x7d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, + 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, + 0x24, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x7c, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x3a, + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x0a, 0x7d, 0x29, + 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, + 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, + 0xdb, 0x06, 0x2a, 0xd8, 0x06, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, + 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xb4, 0x06, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0xa9, 0x06, 0x1a, 0xa6, 0x06, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, + 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, + 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, + 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, + 0x49, 0x44, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x68, 0x69, 0x70, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x23, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x40, + 0x75, 0x73, 0x65, 0x72, 0x3a, 0x7b, 0x7b, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, + 0x44, 0x7d, 0x7d, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x23, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x40, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x7b, 0x7b, 0x2e, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x44, 0x7d, 0x7d, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x7b, 0x7b, 0x2e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x7d, 0x7d, 0x24, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x7c, + 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0x8e, 0x06, 0x0a, + 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcc, + 0x05, 0x92, 0x41, 0x9a, 0x05, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0b, 0x72, + 0x65, 0x61, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0b, 0x62, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0xf5, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, + 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xe3, 0x04, 0x32, 0xe0, 0x04, 0x0a, + 0xb8, 0x01, 0x2a, 0xb5, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, + 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, + 0x67, 0x6f, 0x0a, 0x95, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x8a, 0x01, + 0x1a, 0x87, 0x01, 0x72, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, + 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xb5, 0x01, 0x2a, 0xb2, 0x01, + 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, + 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x88, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x7e, 0x1a, 0x7c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, + 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, + 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, + 0x7d, 0x29, 0x0a, 0xea, 0x01, 0x2a, 0xe7, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, + 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xc3, 0x01, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0xb8, 0x01, 0x1a, 0xb5, 0x01, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, + 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, + 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x27, 0x20, 0x5c, 0x0a, 0x2d, + 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, + 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x7d, 0x27, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xa3, 0x06, + 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xdb, 0x05, 0x92, 0x41, 0xa7, 0x05, 0x0a, 0x06, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x2a, 0x0d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x6a, 0xfe, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x12, 0xec, 0x04, 0x32, 0xe9, 0x04, 0x0a, 0xbc, 0x01, 0x2a, 0xb9, 0x01, + 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, + 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x99, 0x01, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x8e, 0x01, 0x1a, 0x8b, 0x01, 0x72, 0x72, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xb8, 0x01, 0x2a, 0xb5, 0x01, 0x0a, + 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, 0x65, + 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, 0x61, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x8b, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x80, 0x01, 0x1a, 0x7e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x62, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x74, 0x31, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, + 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0xec, 0x01, 0x2a, 0xe9, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, 0x6c, + 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xc5, 0x01, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xba, 0x01, 0x1a, 0xb7, 0x01, 0x63, 0x75, 0x72, 0x6c, + 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x27, + 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, + 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x2c, + 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1a, 0x2e, 0x62, 0x61, - 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x92, 0x41, 0x22, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x12, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0b, - 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, - 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x62, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0xa0, 0x01, 0x0a, 0x06, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x59, 0x92, 0x41, 0x26, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0d, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2a, 0x0d, 0x62, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x62, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x32, 0xb3, 0x03, 0x0a, - 0x07, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x93, 0x01, 0x0a, 0x06, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, - 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x4c, 0x92, 0x41, 0x2c, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x11, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x8a, - 0x01, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2f, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x32, 0xc2, 0x0f, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, + 0xbc, 0x05, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf4, 0x04, 0x92, 0x41, 0xd3, 0x04, 0x0a, 0x07, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x6a, 0xa8, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, + 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x96, 0x04, 0x32, 0x93, 0x04, 0x0a, 0x99, + 0x01, 0x2a, 0x96, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, + 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, + 0x6f, 0x0a, 0x77, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x6d, 0x1a, 0x6b, 0x72, + 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x28, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4e, + 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x7d, 0x29, 0x0a, 0x98, 0x01, 0x2a, 0x95, 0x01, + 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, 0x61, 0x76, + 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x6c, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x62, 0x1a, 0x60, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x79, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3a, + 0x20, 0x22, 0x22, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0xd9, 0x01, 0x2a, 0xd6, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, + 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xb2, 0x01, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xa7, 0x01, 0x1a, 0xa4, 0x01, 0x63, 0x75, 0x72, + 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, + 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, + 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x7d, + 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0xbb, + 0x04, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x92, 0x41, 0x28, 0x0a, 0x07, 0x54, 0x65, 0x6e, - 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x84, 0x01, 0x0a, 0x04, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x92, - 0x41, 0x25, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0c, 0x6c, 0x69, 0x73, - 0x74, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2a, 0x0c, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x73, 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, - 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x6c, 0x69, - 0x73, 0x74, 0x42, 0x8a, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, - 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, - 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x62, 0x61, - 0x73, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x42, 0x61, 0x73, - 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x42, 0x61, 0x73, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, - 0x13, 0x42, 0x61, 0x73, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x42, 0x61, 0x73, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf3, 0x03, 0x92, 0x41, 0xd7, 0x03, 0x0a, 0x07, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0xac, 0x03, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9a, 0x03, 0x32, 0x97, 0x03, 0x0a, 0x8c, 0x01, + 0x2a, 0x89, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, + 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, + 0x0a, 0x6a, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x60, 0x1a, 0x5e, 0x72, 0x72, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x49, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x7d, 0x29, 0x0a, 0x8c, 0x01, 0x2a, + 0x89, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x6e, + 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, 0x6a, + 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x60, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x56, 0x1a, 0x54, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x28, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x2e, 0x74, 0x68, 0x65, + 0x6e, 0x28, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x29, 0x20, 0x3d, 0x3e, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x0a, 0x7d, 0x29, 0x0a, 0x77, 0x2a, 0x75, 0x0a, + 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, + 0x0a, 0x0e, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, + 0x0a, 0x52, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x48, 0x1a, 0x46, 0x63, 0x75, + 0x72, 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x20, 0x27, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, + 0x2f, 0x74, 0x31, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x05, 0x0a, + 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf7, + 0x04, 0x92, 0x41, 0xd8, 0x04, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0c, + 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2a, 0x0c, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x6a, 0xb0, 0x04, 0x0a, 0x0d, 0x78, + 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9e, 0x04, 0x32, + 0x9b, 0x04, 0x0a, 0xa8, 0x01, 0x2a, 0xa5, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x12, 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x0c, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, + 0x04, 0x1a, 0x02, 0x67, 0x6f, 0x0a, 0x85, 0x01, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x7b, 0x1a, 0x79, 0x63, 0x72, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x29, 0x2c, 0x20, 0x26, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x50, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0x85, 0x01, + 0x2a, 0x82, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, + 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x14, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x0c, 0x1a, 0x0a, + 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x0a, 0x59, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x4f, 0x1a, 0x4d, 0x6c, 0x65, 0x74, 0x20, 0x72, 0x65, 0x73, 0x20, + 0x3d, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x28, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x0a, 0x7d, 0x29, 0x0a, 0xe5, 0x01, 0x2a, 0xe2, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x55, 0x52, 0x4c, 0x0a, 0x0e, 0x0a, 0x04, + 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x06, 0x1a, 0x04, 0x63, 0x75, 0x72, 0x6c, 0x0a, 0xbe, 0x01, 0x0a, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xb3, 0x01, 0x1a, 0xb0, 0x01, 0x63, 0x75, 0x72, + 0x6c, 0x20, 0x2d, 0x2d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x2d, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x27, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 0x3a, 0x33, 0x34, 0x37, 0x36, 0x2f, 0x76, 0x31, 0x2f, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x27, 0x20, 0x5c, 0x0a, 0x2d, + 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x20, 0x5c, 0x0a, 0x2d, 0x2d, 0x64, 0x61, 0x74, + 0x61, 0x2d, 0x72, 0x61, 0x77, 0x20, 0x27, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x30, 0x22, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3a, 0x20, 0x22, 0x22, 0x0a, 0x7d, 0x27, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x73, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x42, 0x8a, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, + 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x62, 0x61, 0x73, 0x65, + 0x2f, 0x76, 0x31, 0x3b, 0x62, 0x61, 0x73, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, + 0xaa, 0x02, 0x07, 0x42, 0x61, 0x73, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x42, 0x61, 0x73, + 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x42, 0x61, 0x73, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x42, 0x61, 0x73, + 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/pb/base/v1/service.pb.validate.go b/pkg/pb/base/v1/service.pb.validate.go index f1b5867f..fe7b6f59 100644 --- a/pkg/pb/base/v1/service.pb.validate.go +++ b/pkg/pb/base/v1/service.pb.validate.go @@ -746,10 +746,10 @@ func (m *PermissionExpandRequest) validate(all bool) error { var errors []error - if len(m.GetTenantId()) > 64 { + if len(m.GetTenantId()) > 128 { err := PermissionExpandRequestValidationError{ field: "TenantId", - reason: "value length must be at most 64 bytes", + reason: "value length must be at most 128 bytes", } if !all { return err @@ -760,7 +760,7 @@ func (m *PermissionExpandRequest) validate(all bool) error { if !_PermissionExpandRequest_TenantId_Pattern.MatchString(m.GetTenantId()) { err := PermissionExpandRequestValidationError{ field: "TenantId", - reason: "value does not match regex pattern \"[a-zA-Z0-9-,]+\"", + reason: "value does not match regex pattern \"^([a-zA-Z0-9_\\\\-@\\\\.:+]{1,128}|\\\\*)$\"", } if !all { return err @@ -1017,7 +1017,7 @@ var _ interface { ErrorName() string } = PermissionExpandRequestValidationError{} -var _PermissionExpandRequest_TenantId_Pattern = regexp.MustCompile("[a-zA-Z0-9-,]+") +var _PermissionExpandRequest_TenantId_Pattern = regexp.MustCompile("^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$") var _PermissionExpandRequest_Permission_Pattern = regexp.MustCompile("^[a-zA-Z_]{1,64}$") @@ -3290,10 +3290,10 @@ func (m *WatchRequest) validate(all bool) error { var errors []error - if len(m.GetTenantId()) > 64 { + if len(m.GetTenantId()) > 128 { err := WatchRequestValidationError{ field: "TenantId", - reason: "value length must be at most 64 bytes", + reason: "value length must be at most 128 bytes", } if !all { return err @@ -3304,7 +3304,7 @@ func (m *WatchRequest) validate(all bool) error { if !_WatchRequest_TenantId_Pattern.MatchString(m.GetTenantId()) { err := WatchRequestValidationError{ field: "TenantId", - reason: "value does not match regex pattern \"[a-zA-Z0-9-,]+\"", + reason: "value does not match regex pattern \"^([a-zA-Z0-9_\\\\-@\\\\.:+]{1,128}|\\\\*)$\"", } if !all { return err @@ -3391,7 +3391,7 @@ var _ interface { ErrorName() string } = WatchRequestValidationError{} -var _WatchRequest_TenantId_Pattern = regexp.MustCompile("[a-zA-Z0-9-,]+") +var _WatchRequest_TenantId_Pattern = regexp.MustCompile("^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$") // Validate checks the field values on WatchResponse with the rules defined in // the proto definition for this message. If any rules are violated, the first diff --git a/proto/base/v1/openapi.proto b/proto/base/v1/openapi.proto index ca7b292a..c08a73e3 100644 --- a/proto/base/v1/openapi.proto +++ b/proto/base/v1/openapi.proto @@ -9,7 +9,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { info: { title: "Permify API"; description: "Permify is an open source authorization service for creating fine-grained and scalable authorization systems."; - version: "v0.7.6"; + version: "v0.7.8"; contact: { name: "API Support"; url: "https://github.com/Permify/permify/issues"; diff --git a/proto/base/v1/service.proto b/proto/base/v1/service.proto index 7abe541d..1e606b31 100644 --- a/proto/base/v1/service.proto +++ b/proto/base/v1/service.proto @@ -24,11 +24,67 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "This method returns a decision about whether user can perform an permission on a certain resource." + summary: "check api" tags: [ "Permission" ] operation_id: "permissions.check" + description: "", + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err := client.Permission.Check(context.Background(), &v1.PermissionCheckRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionCheckRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: &v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"edit\",\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n\n if (cr.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n // RESULT_ALLOWED\n } else {\n // RESULT_DENIED\n }\n})" }, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.permission.check({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n if (response.can === PermissionCheckResponse_Result.RESULT_ALLOWED) {\n console.log(\"RESULT_ALLOWED\")\n } else {\n console.log(\"RESULT_DENIED\")\n }\n})" }, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/check' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"edit\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'"}, + } + } + } + } + } + } }; } @@ -42,11 +98,67 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "expand relationships according to schema" + summary: "expand api" tags: [ "Permission" ] operation_id: "permissions.expand" + description: "", + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err: = client.Permission.Expand(context.Background(), &v1.PermissionExpandRequest{\n TenantId: \"t1\",\n Metadata: &v1.PermissionExpandRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n },\n Entity: &v1.Entity{\n Type: \"repository\",\n Id: \"1\",\n },\n Permission: \"push\",\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.permission.expand({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n permission: \"push\",\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/expand' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"\",\n \"snap_token\": \"\"\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"permission\": \"push\"\n}'"}, + } + } + } + } + } + } }; } @@ -60,11 +172,67 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "Retrieve an entity by its identifier." + summary: "lookup entity" tags: [ "Permission" ] operation_id: "permissions.lookupEntity" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err: = client.Permission.LookupEntity(context.Background(), & v1.PermissionLookupEntityRequest {\n TenantId: \"t1\",\n Metadata: & v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\"\n SchemaVersion: \"\"\n Depth: 20,\n },\n EntityType: \"document\",\n Permission: \"edit\",\n Subject: & v1.Subject {\n Type: \"user\",\n Id: \"1\",\n }\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.permission.lookupEntity({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entity_type: \"document\",\n permission: \"edit\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n console.log(response.entity_ids)\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-entity' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"depth\": 20\n },\n \"entity_type\": \"document\",\n \"permission\": \"edit\",\n \"subject\": {\n \"type\":\"user\",\n \"id\":\"1\"\n }\n}'"}, + } + } + } + } + } + } }; } @@ -78,11 +246,51 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "Stream entities by their identifiers." + summary: "lookup entity stream" tags: [ "Permission" ] operation_id: "permissions.lookupEntityStream" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "str, err: = client.Permission.LookupEntityStream(context.Background(), &v1.PermissionLookupEntityRequest {\n Metadata: &v1.PermissionLookupEntityRequestMetadata {\n SnapToken: \"\", \n SchemaVersion: \"\" \n Depth: 50,\n },\n EntityType: \"document\",\n Permission: \"view\",\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})\n\n// handle stream response\nfor {\n res, err: = str.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.EntityId\n}"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "const permify = require(\"@permify/permify-node\");\nconst {PermissionLookupEntityStreamResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.permission.lookupEntityStream({\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n depth: 20\n },\n entityType: \"document\",\n permission: \"view\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable) {\n for await (const response of res) {\n // response.entityId\n }\n}"}, + } + } + } + } + } + } }; } @@ -96,11 +304,67 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "Retrieve a subject by its identifier." + summary: "lookup-subject" tags: [ "Permission" ] operation_id: "permissions.lookupSubject" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err: = client.Permission.LookupSubject(context.Background(), &v1.PermissionLookupSubjectRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionLookupSubjectRequestMetadata{\n SnapToken: \"\",\n SchemaVersion: \"\",\n Depth: 20,\n },\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Permission: \"edit\",\n SubjectReference: &v1.RelationReference{\n Type: \"user\",\n Relation: \"\",\n }\n})" }, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.permission.lookupSubject({\n tenantId: \"t1\",\n metadata: {\n snapToken: \"\",\n schemaVersion: \"\"\n depth: 20,\n },\n Entity: {\n Type: \"document\",\n Id: \"1\",\n },\n permission: \"edit\",\n subject_reference: {\n type: \"user\",\n relation: \"\"\n }\n}).then((response) => {\n console.log(response.subject_ids)\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/lookup-subject' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\"\n \"depth\": 20,\n },\n \"entity\": {\n type: \"document\",\n id: \"1'\n },\n \"permission\": \"edit\",\n \"subject_reference\": {\n \"type\": \"user\",\n \"relation\": \"\"\n }\n}'"}, + } + } + } + } + } + } }; } @@ -114,11 +378,67 @@ service Permission { }; // OpenAPI annotations for this method option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "Retrieve permissions related to a specific subject." + summary: "subject permission" tags: [ "Permission" ] operation_id: "permissions.subjectPermission" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err: = client.Permission.SubjectPermission(context.Background(), &v1.PermissionSubjectPermissionRequest {\n TenantId: \"t1\",\n Metadata: &v1.PermissionSubjectPermissionRequestMetadata {\n SnapToken: \"\",\n SchemaVersion: \"\",\n OnlyPermission: false,\n Depth: 20,\n },\n Entity: &v1.Entity {\n Type: \"repository\",\n Id: \"1\",\n },\n Subject: &v1.Subject {\n Type: \"user\",\n Id: \"1\",\n },\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.permission.subjectPermission({\n tenantId: \"t1\", \n metadata: {\n snapToken: \"\",\n schemaVersion: \"\",\n onlyPermission: true,\n depth: 20\n },\n entity: {\n type: \"repository\",\n id: \"1\"\n },\n subject: {\n type: \"user\",\n id: \"1\"\n }\n}).then((response) => {\n console.log(response);\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/permissions/subject-permission' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\":{\n \"snap_token\": \"\",\n \"schema_version\": \"\",\n \"only_permission\": true,\n \"depth\": 20\n },\n \"entity\": {\n \"type\": \"repository\",\n \"id\": \"1\"\n },\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\",\n \"relation\": \"\"\n },\n}'"}, + } + } + } + } + } + } }; } } @@ -129,46 +449,63 @@ service Permission { message PermissionCheckRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern : "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes : 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionCheckRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; // Entity on which the permission needs to be checked, required. - Entity entity = 3 [json_name = "entity", (validate.rules).message.required = true]; + Entity entity = 3 [ + json_name = "entity", + (validate.rules).message.required = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"repository:1\""} + ]; // Name of the permission or relation, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes. - string permission = 4 [json_name = "permission", (validate.rules).string = { - pattern : "^[a-zA-Z_]{1,64}$", - max_bytes : 64, - ignore_empty: false, - }]; + string permission = 4 [ + json_name = "permission", + (validate.rules).string = {pattern : "^[a-zA-Z_]{1,64}$", max_bytes : 64, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The action the user wants to perform on the resource"} + ]; // Subject for which the permission needs to be checked, required. - Subject subject = 5 [json_name = "subject", (validate.rules).message.required = true]; + Subject subject = 5 [ + json_name = "subject", + (validate.rules).message.required = true + ]; // Context associated with this request. - Context context = 6 [json_name = "context"]; + Context context = 6 [ + json_name = "context", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../operations/contextual-tuples)"} + ]; // Additional arguments associated with this request. repeated Argument arguments = 7 [json_name = "arguments"]; } -// PermissionCheckRequestMetadata is the metadata associated with a PermissionCheckRequest. +// PermissionCheckRequestMetadata metadata for the PermissionCheckRequest. message PermissionCheckRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; // Depth of the check, must be greater than or equal to 3. - int32 depth = 3 [json_name = "depth", (validate.rules).int32.gte = 3]; + int32 depth = 3 [ + json_name = "depth", + (validate.rules).int32.gte = 3, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Query limit when if recursive database queries got in loop"} + ]; } // PermissionCheckResponse is the response message for the Check method in the Permission service. @@ -181,7 +518,7 @@ message PermissionCheckResponse { PermissionCheckResponseMetadata metadata = 2 [json_name = "metadata"]; } -// PermissionCheckResponseMetadata is the metadata associated with a PermissionCheckResponse. +// PermissionCheckResponseMetadata metadata for the PermissionCheckResponse. message PermissionCheckResponseMetadata { // The count of the checks performed. @@ -194,11 +531,11 @@ message PermissionCheckResponseMetadata { message PermissionExpandRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern : "[a-zA-Z0-9-,]+", - max_bytes : 64, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionExpandRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -220,14 +557,17 @@ message PermissionExpandRequest { repeated Argument arguments = 6 [json_name = "arguments"]; } -// PermissionExpandRequestMetadata is the metadata associated with a PermissionExpandRequest. +// PermissionExpandRequestMetadata metadata for the PermissionExpandRequest. message PermissionExpandRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; } // PermissionExpandResponse is the response message for the Expand method in the Permission service. @@ -243,11 +583,11 @@ message PermissionExpandResponse { message PermissionLookupEntityRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern : "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes : 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionLookupEntityRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -273,17 +613,24 @@ message PermissionLookupEntityRequest { Context context = 6 [json_name = "context"]; } -// PermissionLookupEntityRequestMetadata is the metadata associated with a PermissionLookupEntityRequest. +// PermissionLookupEntityRequestMetadata metadata for the PermissionLookupEntityRequest. message PermissionLookupEntityRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; // Depth of lookup, required, must be greater or equal to 3. - int32 depth = 3 [json_name = "depth", (validate.rules).int32.gte = 3]; + int32 depth = 3 [ + json_name = "depth", + (validate.rules).int32.gte = 3, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Query limit when if recursive database queries got in loop."} + ]; } // PermissionLookupEntityResponse is the response message for the LookupEntity method in the Permission service. @@ -306,11 +653,11 @@ message PermissionLookupEntityStreamResponse { message PermissionEntityFilterRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern : "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes : 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionEntityFilterRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -325,17 +672,24 @@ message PermissionEntityFilterRequest { Context context = 5 [json_name = "context"]; } -// PermissionEntityFilterRequestMetadata is the metadata associated with a PermissionEntityFilterRequest. +// PermissionEntityFilterRequestMetadata metadata for the PermissionEntityFilterRequest. message PermissionEntityFilterRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; // Depth of lookup, required, must be greater or equal to 3. - int32 depth = 3 [json_name = "depth", (validate.rules).int32.gte = 3]; + int32 depth = 3 [ + json_name = "depth", + (validate.rules).int32.gte = 3, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Query limit when if recursive database queries got in loop."} + ]; } // LOOKUP SUBJECT @@ -344,11 +698,11 @@ message PermissionEntityFilterRequestMetadata { message PermissionLookupSubjectRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionLookupSubjectRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -370,17 +724,24 @@ message PermissionLookupSubjectRequest { Context context = 6 [json_name = "context"]; } -// PermissionLookupSubjectRequestMetadata is the metadata associated with a PermissionLookupSubjectRequest. +// PermissionLookupSubjectRequestMetadata metadata for the PermissionLookupSubjectRequest. message PermissionLookupSubjectRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; // Depth of the check, must be greater than or equal to 3. - int32 depth = 3 [json_name = "depth", (validate.rules).int32.gte = 3]; + int32 depth = 3 [ + json_name = "depth", + (validate.rules).int32.gte = 3, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Query limit when if recursive database queries got in loop."} + ]; } // PermissionLookupSubjectResponse is the response message for the LookupSubject method in the Permission service. @@ -396,11 +757,11 @@ message PermissionLookupSubjectResponse { message PermissionSubjectPermissionRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata associated with this request, required. PermissionSubjectPermissionRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -415,20 +776,27 @@ message PermissionSubjectPermissionRequest { Context context = 5 [json_name = "context"]; } -// PermissionSubjectPermissionRequestMetadata is the metadata associated with a PermissionSubjectPermissionRequest. +// PermissionSubjectPermissionRequestMetadata metadata for the PermissionSubjectPermissionRequest. message PermissionSubjectPermissionRequestMetadata { // Version of the schema. string schema_version = 1 [json_name = "schema_version"]; // Token associated with the snap. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; // Whether to only check permissions. bool only_permission = 3 [json_name = "only_permission"]; // Depth of the check, must be greater than or equal to 3. - int32 depth = 4 [json_name = "depth", (validate.rules).int32.gte = 3]; + int32 depth = 4 [ + json_name = "depth", + (validate.rules).int32.gte = 3, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Query limit when if recursive database queries got in loop."} + ]; } // PermissionSubjectPermissionResponse is the response message for the SubjectPermission method in the Permission service. @@ -453,11 +821,51 @@ service Watch { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "" // Short summary of what the operation does. + summary: "watch changes" // Short summary of what the operation does. tags: [ "Watch" // Adds an additional categorization for the operation. ] operation_id: "watch.watch" // Unique string used to identify the operation. + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err := client.Watch.Watch(context.Background(), &v1.WatchRequest{\n TenantId: \"t1\",\n SnapToken: \"\",\n})\n// handle stream response\nfor {\n res, err := cr.Recv()\n\n if err == io.EOF {\n break\n }\n\n // res.Changes\n}\n"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "const permify = require(\"@permify/permify-node\");\nconst {WatchResponse} = require(\"@permify/permify-node/dist/src/grpc/generated/base/v1/service\");\n\nfunction main() {\n const client = new permify.grpc.newClient({\n endpoint: \"localhost:3478\",\n })\n\n let res = client.watch.watch({\n tenantId: \"t1\",\n snapToken: \"\"\n })\n\n handle(res)\n}\n\nasync function handle(res: AsyncIterable) {\n for await (const response of res) {\n // response.changes\n }\n}\n"}, + } + } + } + } + } + } }; } } @@ -467,14 +875,17 @@ service Watch { message WatchRequest { // Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern : "[a-zA-Z0-9-,]+", - max_bytes : 64, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Snap token to be used for watching. - string snap_token = 2 [json_name = "snap_token"]; + string snap_token = 2 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; } // WatchResponse is the response message for the Watch RPC. It contains the @@ -505,9 +916,65 @@ service Schema { // OpenAPI specific annotation option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "write your authorization model" // Short summary of what the operation does. + summary: "write schema" // Short summary of what the operation does. tags: ["Schema"] // Adds an additional categorization for the operation. operation_id: "schemas.write" // Unique string used to identify the operation. + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "sr, err: = client.Schema.Write(context.Background(), &v1.SchemaWriteRequest {\n TenantId: \"t1\",\n Schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `,\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.schema.write({\n tenantId: \"t1\",\n schema: `\n \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n `\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"schema\": \"entity user {}\\n\\n entity organization {\\n\\n relation admin @user\\n relation member @user\\n\\n action create_repository = (admin or member)\\n action delete = admin\\n }\\n\\n entity repository {\\n\\n relation owner @user\\n relation parent @organization\\n\\n action push = owner\\n action read = (owner and (parent.admin and parent.member))\\n action delete = (parent.member and (parent.admin or owner))\\n }\"\n}'"}, + } + } + } + } + } + } }; } @@ -523,9 +990,65 @@ service Schema { // OpenAPI specific annotation option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "read your authorization model" // Short summary of what the operation does. + summary: "read schema" // Short summary of what the operation does. tags: ["Schema"] // Adds an additional categorization for the operation. operation_id: "schemas.read" // Unique string used to identify the operation. + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "sr, err: = client.Schema.Read(context.Background(), &v1.SchemaReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.SchemaReadRequestMetadata{\n SchemaVersion: \"cnbe6se5fmal18gpc66g\",\n },\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "let res = client.schema.read({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: swResponse.schemaVersion,\n },\n })"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"metadata\": {\n \"schema_version\": \"cnbe6se5fmal18gpc66g\"\n }\n}'"}, + } + } + } + } + } + } }; } @@ -541,9 +1064,65 @@ service Schema { // OpenAPI specific annotation. option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "list all authorization models" // Short summary of what the operation does. + summary: "list schema" // Short summary of what the operation does. tags: ["Schema"] // Adds an additional categorization for the operation. operation_id: "schemas.list" // Unique string used to identify the operation. + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "sr, err: = client.Schema.List(context.Background(), &v1.SchemaListRequest {\n TenantId: \"t1\",\n PageSize: \"10\",\n ContinuousToken: \"\",\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "let res = client.schema.list({\n tenantId: \"t1\",\n continuousToken: \"\"\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/schemas/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'"}, + } + } + } + } + } + } }; } } @@ -556,11 +1135,11 @@ message SchemaWriteRequest { // tenant_id is a string that identifies the tenant. It must match the pattern "[a-zA-Z0-9-,]+", // be a maximum of 64 bytes, and must not be empty. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // schema is the string representation of the schema to be written. string schema = 2 [json_name = "schema"]; @@ -582,11 +1161,11 @@ message SchemaReadRequest { // tenant_id is a string that identifies the tenant. It must match the pattern "[a-zA-Z0-9-,]+", // be a maximum of 64 bytes, and must not be empty. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // metadata is the additional information needed for the Read request. SchemaReadRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -608,7 +1187,7 @@ message SchemaReadResponse { SchemaDefinition schema = 1 [json_name = "schema"]; } -// LIST +// LIST // SchemaListRequest is the request message for the List method in the Schema service. // It contains tenant_id for which the schemas are to be listed. @@ -616,11 +1195,11 @@ message SchemaListRequest { // tenant_id is a string that identifies the tenant. It must match the pattern "[a-zA-Z0-9-,]+", // be a maximum of 64 bytes, and must not be empty. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // page_size is the number of tenants to be returned in the response. // The value should be between 1 and 100. @@ -665,11 +1244,67 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "create data" + summary: "write data" tags: [ "Data" ] operation_id: "data.write" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "// Convert the wrapped attribute value into Any proto message\nvalue, err := anypb.New(&v1.BooleanValue{\n Data: true,\n})\nif err != nil {\n // Handle error\n}\n\ncr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{\n TenantId: \"t1\",,\n Metadata: &v1.DataWriteRequestMetadata{\n SchemaVersion: \"\",\n },\n Tuples: []*v1.Attribute{\n {\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Relation: \"editor\",\n Subject: &v1.Subject{\n Type: \"user\",\n Id: \"1\",\n Relation: \"\",\n },\n },\n },\n Attributes: []*v1.Attribute{\n {\n Entity: &v1.Entity{\n Type: \"document\",\n Id: \"1\",\n },\n Attribute: \"is_private\",\n Value: value,\n },\n },\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "const booleanValue = BooleanValue.fromJSON({ data: true });\n\nconst value = Any.fromJSON({\n typeUrl: 'type.googleapis.com/base.v1.BooleanValue',\n value: BooleanValue.encode(booleanValue).finish()\n});\n\nclient.data.write({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: \"\"\n },\n tuples: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n relation: \"editor\",\n subject: {\n type: \"user\",\n id: \"1\"\n }\n }],\n attributes: [{\n entity: {\n type: \"document\",\n id: \"1\"\n },\n attribute: \"is_private\",\n value: value,\n }]\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n{\n \"metadata\": {\n \"schema_version\": \"\"\n },\n \"tuples\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"relation\": \"editor\",\n \"subject\": {\n \"type\": \"user\",\n \"id\": \"1\"\n }\n }\n ],\n \"attributes\": [\n {\n \"entity\": {\n \"type\": \"document\",\n \"id\": \"1\"\n },\n \"attribute\": \"is_private\",\n \"value\": {\n \"@type\": \"type.googleapis.com/base.v1.BooleanValue\",\n \"data\": true\n }\n }\n ]\n}\n}'"}, + } + } + } + } + } + } }; } @@ -681,7 +1316,7 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "create new relationships" + summary: "write relationships" tags: [ "Data" ] @@ -697,11 +1332,67 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "read relation tuple(s)" + summary: "read relationships" tags: [ "Data" ] operation_id: "data.relationships.read" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Data.ReadRelationships(context.Background(), & v1.Data.RelationshipReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.Data.RelationshipReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: &v1.TupleFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"member\",\n Subject: &v1.SubjectFilter {\n Type: \"\",\n Id: []string {\"\"},\n Relation: \"\"\n }}\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.data.readRelationships({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/relationships/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"member\",\n subject: {\n type: \"\",\n ids: [],\n relation: \"\"\n }\n }\n}'"}, + } + } + } + } + } + } }; } @@ -713,11 +1404,67 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "read attribute(s)" + summary: "read attributes" tags: [ "Data" ] operation_id: "data.attributes.read" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Data.ReadAttributes(context.Background(), & v1.Data.AttributeReadRequest {\n TenantId: \"t1\",\n Metadata: &v1.Data.AttributeReadRequestMetadata {\n SnapToken: \"\"\n },\n Filter: &v1.AttributeFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Attributes: []string {\"private\"},\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.data.readAttributes({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/attributes/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n metadata: {\n snap_token: \"\",\n },\n filter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n attributes: [\n \"private\"\n ],\n }\n}'"}, + } + } + } + } + } + } }; } @@ -734,6 +1481,62 @@ service Data { "Data" ] operation_id: "data.delete" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Data.Delete(context.Background(), & v1.DataDeleteRequest {\n TenantId: \"t1\",\n Metadata: &v1.DataDeleteRequestMetadata {\n SnapToken: \"\"\n },\n TupleFilter: &v1.TupleFilter {\n Entity: &v1.EntityFilter {\n Type: \"organization\",\n Ids: []string {\"1\"} ,\n },\n Relation: \"admin\",\n Subject: &v1.SubjectFilter {\n Type: \"user\",\n Id: []string {\"1\"},\n Relation: \"\"\n }}\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.data.delete({\n tenantId: \"t1\",\n metadata: {\n snap_token: \"\",\n },\n tupleFilter: {\n entity: {\n type: \"organization\",\n ids: [\n \"1\"\n ]\n },\n relation: \"admin\",\n subject: {\n type: \"user\",\n ids: [\n \"1\"\n ],\n relation: \"\"\n }\n }\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"tupleFilter\": {\n \"entity\": {\n \"type\": \"organization\",\n \"ids\": [\n \"1\"\n ]\n },\n \"relation\": \"admin\",\n \"subject\": {\n \"type\": \"user\",\n \"ids\": [\n \"1\"\n ],\n \"relation\": \"\"\n }\n },\n}'"}, + } + } + } + } + } + } }; } @@ -766,6 +1569,62 @@ service Data { "Data" ] operation_id: "bundle.run" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Data.RunBundle(context.Background(), &v1.BundleRunRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n Arguments: map[string]string{\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n },\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.data.runBundle({\n tenantId: \"t1\",\n name: \"organization_created\",\n arguments: {\n creatorID: \"564\",\n organizationID: \"789\",\n }\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/run-bundle' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n \"arguments\": {\n \"creatorID\": \"564\",\n \"organizationID\": \"789\",\n }\n}'"}, + } + } + } + } + } + } }; } } @@ -775,11 +1634,11 @@ service Data { // tuples and attributes for the write operation. message DataWriteRequest { // tenant_id represents the unique identifier of the tenant for which data is written. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // metadata holds additional data related to the request. DataWriteRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -818,18 +1677,21 @@ message DataWriteRequestMetadata { // It contains the snap_token generated after the write operation. message DataWriteResponse { // snap_token is the token generated after the data write operation, representing a snapshot of the data. - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)."} + ]; } // Represents a request to write relationship data. message RelationshipWriteRequest { // Unique identifier for the tenant with specific constraints. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Metadata for the request. It's required. RelationshipWriteRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -853,18 +1715,21 @@ message RelationshipWriteRequestMetadata { // RelationshipWriteResponse message RelationshipWriteResponse { - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // RelationshipReadRequest defines the structure of a request for reading relationships. // It contains the necessary information such as tenant_id, metadata, and filter for the read operation. message RelationshipReadRequest { // tenant_id represents the unique identifier of the tenant for which relationships are read. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // metadata holds additional data related to the request. RelationshipReadRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -887,7 +1752,10 @@ message RelationshipReadRequest { // It includes the snap_token associated with a particular state of the database. message RelationshipReadRequestMetadata { // snap_token represents a specific state or "snapshot" of the database. - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // RelationshipReadResponse defines the structure of the response after reading relationships. @@ -904,11 +1772,11 @@ message RelationshipReadResponse { // It includes the tenant_id, metadata, attribute filter, page size for pagination, and a continuous token for multi-page results. message AttributeReadRequest { // tenant_id represents the unique identifier of the tenant from which the attributes are being read. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // metadata holds additional information related to the request. AttributeReadRequestMetadata metadata = 2 [json_name = "metadata", (validate.rules).message.required = true]; @@ -931,7 +1799,10 @@ message AttributeReadRequest { // It includes the snap_token associated with a particular state of the database. message AttributeReadRequestMetadata { // snap_token represents a specific state or "snapshot" of the database. - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // AttributeReadResponse defines the structure of the response to an attribute read request. @@ -948,11 +1819,11 @@ message AttributeReadResponse { // It includes the tenant_id and filters for selecting tuples and attributes to be deleted. message DataDeleteRequest { // tenant_id represents the unique identifier of the tenant from which the data will be deleted. - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // tuple_filter specifies the criteria used to select the tuples that should be deleted. TupleFilter tuple_filter = 2 [json_name = "tuple_filter", (validate.rules).message.required = true]; @@ -965,33 +1836,39 @@ message DataDeleteRequest { // It includes a snap_token representing the state of the database after the deletion. message DataDeleteResponse { // snap_token represents the state of the database after the requested deletions. - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // RelationshipDeleteRequest message RelationshipDeleteRequest { - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; TupleFilter filter = 2 [json_name = "filter"]; } // RelationshipDeleteResponse message RelationshipDeleteResponse { - string snap_token = 1 [json_name = "snap_token"]; + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // BundleRunRequest is used to request the execution of a bundle. // It includes tenant_id, the name of the bundle, and additional arguments for execution. message BundleRunRequest { - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; // Name of the bundle to be executed. string name = 2 [json_name = "name"]; @@ -1003,7 +1880,10 @@ message BundleRunRequest { // BundleRunResponse is the response for a BundleRunRequest. // It includes a snap_token, which may be used for tracking the execution or its results. message BundleRunResponse { - string snap_token = 1 [json_name = "snap_token"]; // Token related to the bundle execution. + string snap_token = 1 [ + json_name = "snap_token", + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)"} + ]; } // ** BUNDLE SERVICE ** @@ -1022,6 +1902,62 @@ service Bundle { "Bundle" ] operation_id: "bundle.write" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err := client.Bundle.Write(context.Background(), &v1.BundleWriteRequest{\n TenantId: \"t1\",\n Bundles: []*v1.DataBundle{\n {\n Name: \"organization_created\",\n Arguments: []string{\n \"creatorID\",\n \"organizationID\",\n },\n Operations: []*v1.Operation{\n {\n RelationshipsWrite: []string{\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n },\n AttributesWrite: []string{\n \"organization:{{.organizationID}}$public|boolean:false\",\n },\n },\n },\n },\n },\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.bundle.write({\n tenantId: \"t1\",\n bundles: [\n {\n name: \"organization_created\",\n arguments: [\n \"creatorID\",\n \"organizationID\",\n ],\n operations: [\n {\n relationships_write: [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n attributes_write: [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ]\n }\n ]\n }\n ]\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/write' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"bundles\": [\n {\n \"name\": \"organization_created\"\n \"arguments\": [\n \"creatorID\",\n \"organizationID\"\n ],\n \"operations\": [\n {\n \"relationships_write\": [\n \"organization:{{.organizationID}}#admin@user:{{.creatorID}}\",\n \"organization:{{.organizationID}}#manager@user:{{.creatorID}}\",\n ],\n \"attributes_write\": [\n \"organization:{{.organizationID}}$public|boolean:false\",\n ],\n },\n ],\n },\n ],\n}'"}, + } + } + } + } + } + } }; } @@ -1038,6 +1974,62 @@ service Bundle { "Bundle" ] operation_id: "bundle.read" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Bundle.Read(context.Background(), &v1.BundleReadRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.bundle.read({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/read' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'"}, + } + } + } + } + } + } }; } @@ -1054,6 +2046,62 @@ service Bundle { "Bundle" ] operation_id: "bundle.delete" + description: "" + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Bundle.Delete(context.Background(), &v1.BundleDeleteRequest{\n TenantId: \"t1\",\n Name: \"organization_created\",\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.bundle.delete({\n tenantId: \"t1\",\n name: \"organization_created\",\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/bundle/delete' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"name\": \"organization_created\",\n}'"}, + } + } + } + } + } + } }; } } @@ -1061,11 +2109,11 @@ service Bundle { // BundleWriteRequest is used to request the writing of a bundle. // It contains the tenant_id to identify the tenant and the Bundles object. message BundleWriteRequest { - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; repeated DataBundle bundles = 2; // Contains the bundle data to be written. } @@ -1077,11 +2125,11 @@ message BundleWriteResponse { } message BundleReadRequest { - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; string name = 2 [json_name = "name"]; } @@ -1093,11 +2141,11 @@ message BundleReadResponse { // BundleDeleteRequest is used to request the deletion of a bundle. // It contains the tenant_id to specify the tenant and the name of the bundle to be deleted. message BundleDeleteRequest { - string tenant_id = 1 [json_name = "tenant_id", (validate.rules).string = { - pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", - max_bytes: 128, - ignore_empty: false, - }]; + string tenant_id = 1 [ + json_name = "tenant_id", + (validate.rules).string = {pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$", max_bytes: 128, ignore_empty: false}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant t1 for this field. Required, and must match the pattern \\โ€œ[a-zA-Z0-9-,]+\\โ€œ, max 64 bytes."} + ]; string name = 2 [json_name = "name"]; // Name of the bundle to be deleted. } @@ -1119,11 +2167,67 @@ service Tenancy { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "create new tenant" + summary: "create tenant" tags: [ "Tenancy" ] operation_id: "tenants.create" + description: "", + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Tenancy.Create(context.Background(), &v1.TenantCreateRequest {\n Id: \"\"\n Name: \"\"\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.tenancy.create({\n id: \"\",\n name: \"\"\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'http://localhost:3476/v1/tenants/create' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"id\": \"\",\n \"name\": \"\"\n}'"}, + } + } + } + } + } + } }; } @@ -1140,6 +2244,62 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.delete" + description: "", + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "rr, err: = client.Tenancy.Delete(context.Background(), &v1.TenantDeleteRequest {\n Id: \"\"\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "client.tenancy.delete({\n id: \"\",\n}).then((response) => {\n // handle response\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request DELETE 'http://localhost:3476/v1/tenants/t1'"}, + } + } + } + } + } + } }; } @@ -1157,6 +2317,62 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.list" + description: "", + extensions: { + key: "x-codeSamples" + value: { + list_value: { + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "go" }, + } + fields: { + key: "label" + value : { string_value: "go" }, + } + fields: { + key: "source" + value : { string_value: "cr, err := client.Tenancy.List(context.Background(), &v1.TenantListRequest{\n PageSize: 20,\n ContinuousToken: \"\",\n})"}, + } + }, + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "javascript" }, + } + fields: { + key: "label" + value : { string_value: "node" }, + } + fields: { + key: "source" + value : { string_value: "let res = client.tenancy.list({\n pageSize: 20,\n continuousToken: \"\",\n})"}, + } + } + }, + values: { + struct_value: { + fields: { + key: "lang" + value : { string_value: "curl" }, + } + fields: { + key: "label" + value : { string_value: "cURL" }, + } + fields: { + key: "source" + value : { string_value: "curl --location --request POST 'localhost:3476/v1/tenants/list' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"page_size\": \"10\",\n \"continuous_token\": \"\"\n}'"}, + } + } + } + } + } + } }; } }